diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 000000000000..9eb0caba874d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,99 @@ +name: Bug Report +description: Something is not working as expected +labels: ["type=defect"] +body: + - type: markdown + attributes: + value: > + Thank you for filing a bug report. Please help us identify and resolve the bug by filling + out the following fields. Before we begin, please make sure that the bug is still present in + the [latest](https://github.com/google/guava/releases/latest) version of Guava available. + If it's already fixed in the latest version of Guava, then please just update your Guava + version instead. + + - type: input + attributes: + label: Guava Version + description: Which version of Guava are you using? + placeholder: e.g., 33.2.1-jre + validations: + required: true + + - type: textarea + attributes: + label: Description + description: Please describe the issue you encountered. + validations: + required: true + + - type: textarea + attributes: + label: Example + description: > + Please provide a [Short, Self Contained, Correct (Compilable), Example](http://sscce.org/) + demonstrating the bug. + render: java + validations: + required: true + + - type: textarea + attributes: + label: Expected Behavior + description: What did you expect to happen? + validations: + required: true + + - type: textarea + attributes: + label: Actual Behavior + description: What actually happened? + validations: + required: true + + - type: dropdown + attributes: + label: Packages + description: If this issue is package-specific, then please select the relevant packages. + multiple: true + options: + - com.google.common.annotations + - com.google.common.base + - com.google.common.cache + - com.google.common.collect + - com.google.common.escape + - com.google.common.eventbus + - com.google.common.graph + - com.google.common.hash + - com.google.common.io + - com.google.common.math + - com.google.common.net + - com.google.common.primitives + - com.google.common.reflect + - com.google.common.testing + - com.google.common.util.concurrent + + - type: dropdown + attributes: + label: Platforms + description: If this issue is platform-specific, then please select the relevant platforms. + multiple: true + options: + - Android + - GWT + - Java 8 + - Java 11 + - Java 17 + - Java 21 + + - type: checkboxes + attributes: + label: Checklist + options: + - label: > + I agree to follow the + [code of conduct](https://github.com/google/.github/blob/master/CODE_OF_CONDUCT.md). + required: true + - label: > + I can reproduce the bug with the + [latest](https://github.com/google/guava/releases/latest) version of Guava available. + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_addition_request.yaml b/.github/ISSUE_TEMPLATE/feature_addition_request.yaml new file mode 100644 index 000000000000..d86c4ca535dc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_addition_request.yaml @@ -0,0 +1,156 @@ +name: Feature Addition Request +description: I want to add a new feature +labels: ["type=addition"] +body: + - type: markdown + attributes: + value: > + Filing feature requests is one of the most popular ways to contribute to Guava. + + + Be aware, though: most feature requests are not accepted, even if they're suggested by + a full-time Guava team member. [Feedback](https://stackoverflow.com/a/4543114) from our + users indicates that they really appreciate Guava's high power-to-weight ratio. It's + important to us to keep Guava as easy to use and understand as we can. That means boiling + features down to compact but powerful abstractions, and controlling feature bloat carefully. + + + Guava's main yardstick for evaluating proposed features can be summed up as [utility times + ubiquity](https://github.com/google/guava/wiki/PhilosophyExplained#utility-times-ubiquity). + + + #### Utility: compare with alternatives + + + There is always *some* alternative to adding a new feature to Guava, even if it's just + forking Guava yourself. + + + We want to see that new features have some significant advantage over the alternatives. + These advantages can take + [many forms](https://github.com/google/guava/wiki/PhilosophyExplained#utility), but taking + the time to discuss them in detail will make it much clearer why this feature should be + added to Guava. + + + Please fill out the following fields to give us a better understanding of your proposed + feature and its potential value for other Guava users. + + - type: textarea + attributes: + label: 1. What are you trying to do? + validations: + required: true + + - type: textarea + attributes: + label: 2. What's the best code you can write to accomplish that without the new feature? + validations: + required: true + + - type: textarea + attributes: + label: 3. What would that same code look like if we added your feature? + validations: + required: true + + - type: markdown + attributes: + value: > + Comparing two approaches to a use case side by side can make it easier to examine the + differences between them. + + + Additionally, it's very useful to us if you can provide a "straw API" — what the + method signatures would look like, for example, even if the method and class names are still + in flux. This can make the feature you're suggesting much clearer to us. + + - type: textarea + attributes: + label: (Optional) What would the method signatures for your feature look like? + placeholder: | + e.g., + public static ImmutableList of(); + public static ImmutableList of(E element); + public static ImmutableList of(E e1, E e2); + ... + render: java + validations: + required: false + + - type: markdown + attributes: + value: > + #### Ubiquity: provide concrete use cases + + + Did you *actually* encounter the need for this feature in a real-world scenario, or is it + just a feature that seems like a sensible addition to Guava? + + + Before new features get added to Guava, we really want to be sure that it's for a use case + that actually comes up in the real world. We want to hear the real-world use case so the + community can discuss and debate whether this feature is actually the *best* way to address + the real use case, or whether or not a different abstraction might be more appropriate. + + + It's okay if you can't provide complete context on a use case. We understand if you are not + able to discuss the full details of what you're working on. + + + But Guava aims to provide features that are useful across boundaries of projects, companies, + or even industries — utilities useful for a sizable proportion of all Java programmers + everywhere. If you can give enough detail such that any of us can imagine coming across + a similar need in our own work, that's extremely helpful in studying how broadly useful the + feature will be. + + - type: textarea + attributes: + label: Concrete Use Cases + description: Please provide use cases that actually came up in the real world. + validations: + required: true + + - type: dropdown + attributes: + label: Packages + description: Please select all of the packages that are relevant to this feature request. + multiple: true + options: + - com.google.common.annotations + - com.google.common.base + - com.google.common.cache + - com.google.common.collect + - com.google.common.escape + - com.google.common.eventbus + - com.google.common.graph + - com.google.common.hash + - com.google.common.io + - com.google.common.math + - com.google.common.net + - com.google.common.primitives + - com.google.common.reflect + - com.google.common.testing + - com.google.common.util.concurrent + + - type: checkboxes + attributes: + label: Checklist + options: + - label: > + I agree to follow the + [code of conduct](https://github.com/google/.github/blob/master/CODE_OF_CONDUCT.md). + required: true + - label: > + I have read and understood the [contribution + guidelines](https://github.com/google/guava/wiki/HowToContribute#feature-requests). + required: true + - label: > + I have read and understood + [Guava's philosophy](https://github.com/google/guava/wiki/PhilosophyExplained), and + I strongly believe that this proposal aligns with it. + required: true + - label: > + I have visited the [idea graveyard](https://github.com/google/guava/wiki/IdeaGraveyard), + and did not see anything similar to this idea. + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_enhancement_request.yaml b/.github/ISSUE_TEMPLATE/feature_enhancement_request.yaml new file mode 100644 index 000000000000..93be4411227a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_enhancement_request.yaml @@ -0,0 +1,108 @@ +name: Feature Enhancement Request +description: I want to make an existing feature better +labels: ["type=enhancement"] +body: + - type: markdown + attributes: + value: > + Filing feature requests is one of the most popular ways to contribute to Guava. + + + Be aware, though: most feature requests are not accepted, even if they're suggested by + a full-time Guava team member. [Feedback](https://stackoverflow.com/a/4543114) from our + users indicates that they really appreciate Guava's high power-to-weight ratio. It's + important to us to keep Guava as easy to use and understand as we can. That means boiling + features down to compact but powerful abstractions, and controlling feature bloat carefully. + + - type: textarea + attributes: + label: API(s) + description: Which existing classes or methods do you want to improve? + placeholder: e.g., `com.google.common.collect.ImmutableList::of` + render: java + validations: + required: true + + - type: textarea + attributes: + label: How do you want it to be improved? + validations: + required: true + + - type: textarea + attributes: + label: Why do we need it to be improved? + validations: + required: true + + - type: textarea + attributes: + label: Example + description: > + Please provide an example usage of the feature that would be different with the improvement. + render: java + validations: + required: true + + - type: textarea + attributes: + label: Current Behavior + description: What does the feature currently do? + validations: + required: true + + - type: textarea + attributes: + label: Desired Behavior + description: What do you want it to do instead? + validations: + required: true + + - type: markdown + attributes: + value: > + Did you *actually* encounter the need for this enhancement in a real-world scenario, or does + it just seem like a sensible behavior for the feature to have? + + + Before we make significant changes to existing features in Guava, we really want to be sure + that it's for a use case that actually comes up in the real world. We want to hear the + real-world use case so the community can discuss and debate whether this feature is actually + the *best* way to address the real use case, or whether or not a different approach might be + more appropriate. + + + It's okay if you can't provide complete context on a use case. We understand if you are not + able to discuss the full details of what you're working on. + + + But Guava aims to provide functionality that is useful across boundaries of projects, + companies, or even industries — utilities useful for a sizable proportion of all Java + programmers everywhere. If you can give enough detail such that any of us can imagine coming + across a similar need in our own work, that's extremely helpful in studying how broadly + useful the proposed change will be. + + - type: textarea + attributes: + label: Concrete Use Cases + description: Please provide use cases that actually came up in the real world. + validations: + required: true + + - type: checkboxes + attributes: + label: Checklist + options: + - label: > + I agree to follow the + [code of conduct](https://github.com/google/.github/blob/master/CODE_OF_CONDUCT.md). + required: true + - label: > + I have read and understood the [contribution + guidelines](https://github.com/google/guava/wiki/HowToContribute#feature-requests). + required: true + - label: > + I have read and understood + [Guava's philosophy](https://github.com/google/guava/wiki/PhilosophyExplained), and + I strongly believe that this proposal aligns with it. + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1e956d2faa00..3ce8b9a3bd1e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,12 +5,27 @@ updates: # - package-ecosystem: "maven" # directory: "/" # schedule: -# interval: "daily" +# interval: "weekly" +# groups: +# dependencies: +# applies-to: version-updates +# patterns: +# - "*" # - package-ecosystem: "maven" # directory: "/android" # schedule: -# interval: "daily" +# interval: "weekly" +# groups: +# dependencies: +# applies-to: version-updates +# patterns: +# - "*" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "monthly" + groups: + github-actions: + applies-to: version-updates + patterns: + - "*" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..a41327e07ba3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,10 @@ + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69767a291aef..b4783e978f52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,47 +8,65 @@ on: branches: - master +permissions: + contents: read + jobs: test: - name: "${{ matrix.root-pom }} on JDK ${{ matrix.java }}" + permissions: + actions: write # for styfle/cancel-workflow-action to cancel/stop running workflows + contents: read # for actions/checkout to fetch code + name: "${{ matrix.root-pom }} on JDK ${{ matrix.java }} on ${{ matrix.os }}" strategy: matrix: - java: [ 8, 11 ] + os: [ ubuntu-latest ] + java: [ 8, 11, 17, 21 ] root-pom: [ 'pom.xml', 'android/pom.xml' ] - runs-on: ubuntu-latest + include: + - os: windows-latest + java: 21 + root-pom: pom.xml + runs-on: ${{ matrix.os }} env: ROOT_POM: ${{ matrix.root-pom }} steps: # Cancel any previous runs for the same branch that are still running. - name: 'Cancel previous runs' - uses: styfle/cancel-workflow-action@0.9.0 + uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1 with: access_token: ${{ github.token }} - name: 'Check out repository' - uses: actions/checkout@v2 - - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 - with: - path: ~/.m2/repository - key: maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - maven- - - name: 'Set up JDK ${{ matrix.java }}' - uses: actions/setup-java@v2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + # When we specify multiple JDKs, the final one becomes the default, which is used to execute Maven itself. + # Our Maven configuration then specifies different JDKs to use for some of the steps: + # - 11 (sometimes) to *download* to support anyone who runs JDiff or our Gradle integration tests (including our doc snapshots and our Java 11 CI test run) but not to use directly + # - 25 for running Javadoc and javac (to help people who build Guava locally and might not use a recent JDK to run Maven) + - name: 'Set up JDKs' + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: - java-version: ${{ matrix.java }} - distribution: 'zulu' + java-version: | + ${{ matrix.java }} + 25 + distribution: 'temurin' + cache: 'maven' - name: 'Install' shell: bash - run: mvn -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn install -U -DskipTests=true -f $ROOT_POM + run: ./mvnw -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -Dtoolchain.skip install -U -DskipTests=true -f $ROOT_POM - name: 'Test' shell: bash - run: mvn -B -P!standard-with-extra-repos verify -U -Dmaven.javadoc.skip=true -f $ROOT_POM + run: ./mvnw -B -P!standard-with-extra-repos -Dtoolchain.skip verify -U -Dmaven.javadoc.skip=true -Dsurefire.toolchain.version=${{ matrix.java }} -f $ROOT_POM - name: 'Print Surefire reports' # Note: Normally a step won't run if the job has failed, but this causes it to if: ${{ failure() }} shell: bash run: ./util/print_surefire_reports.sh + - name: 'Set up Gradle' + if: matrix.java == 11 # used only by the integration tests below + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 + - name: 'Integration Test' + if: matrix.java == 11 + shell: bash + run: util/gradle_integration_tests.sh publish_snapshot: name: 'Publish snapshot' @@ -57,20 +75,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v2 - - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 - with: - path: ~/.m2/repository - key: maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - maven- - - name: 'Set up JDK 11' - uses: actions/setup-java@v2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: 'Set up JDKs' + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: - java-version: 11 - distribution: 'zulu' - server-id: sonatype-nexus-snapshots + # For discussion, see the first setup-java block. + # The publish-snapshot workflow doesn't run tests, so we don't have to care which version Maven would select for that step. + java-version: 25 + distribution: 'temurin' + cache: 'maven' + server-id: central server-username: CI_DEPLOY_USERNAME server-password: CI_DEPLOY_PASSWORD - name: 'Publish' @@ -80,25 +94,26 @@ jobs: run: ./util/deploy_snapshot.sh generate_docs: + permissions: + contents: write name: 'Generate latest docs' needs: test if: github.event_name == 'push' && github.repository == 'google/guava' runs-on: ubuntu-latest steps: - name: 'Check out repository' - uses: actions/checkout@v2 - - name: 'Cache local Maven repository' - uses: actions/cache@v2.1.5 - with: - path: ~/.m2/repository - key: maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - maven- - - name: 'Set up JDK 11' - uses: actions/setup-java@v2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: 'Set up JDKs' + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: - java-version: 11 - distribution: 'zulu' + # For discussion, see the first setup-java block. + # The generate-docs workflow doesn't run tests, so we don't have to care which version Maven would select for that step. + # But we need Java 11 for JDiff. + java-version: | + 11 + 25 + distribution: 'temurin' + cache: 'maven' - name: 'Generate latest docs' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000000..e529641034ca --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,72 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '45 9 * * 0' + push: + branches: [ "master" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 942c3986a9b7..c6c875cd186a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ target/ *.ser *.ec +.mvn/wrapper/maven-wrapper.jar # IntelliJ Idea .idea/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000000..2c1966721354 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar +distributionSha256Sum=4ec3f26fb1a692473aea0235c300bd20f0f9fe741947c82c1234cefd76ac3a3c diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e54c6f9aada..1c1bd8fba349 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,10 +4,10 @@ How to contribute Thank you so much for wanting to contribute to Guava! Here are a few important things you should know about contributing: - 1. API changes require discussion, use cases, etc. Code comes later. - 2. Pull requests are great for small fixes for bugs, documentation, etc. - 3. Pull requests are not merged directly into the master branch. - 3. Code contributions require signing a Google CLA. +1. API changes require discussion, use cases, etc. Code comes later. +2. Pull requests are great for small fixes for bugs, documentation, etc. +3. Pull requests are not merged directly into the master branch. +4. Code contributions require signing a Google CLA. API changes ----------- @@ -21,7 +21,7 @@ for it. If the feature has merit, it will go through a thorough process of API design and review. Any code should come after this. -[APIs]: http://en.wikipedia.org/wiki/Application_programming_interface +[APIs]: https://en.wikipedia.org/wiki/Application_programming_interface [issue]: https://github.com/google/guava/issues Pull requests @@ -53,7 +53,7 @@ Guidelines for any code contributions: [well-formed commit message][] for the change. [Java style guide]: https://google.github.io/styleguide/javaguide.html -[well-formed commit message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[well-formed commit message]: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html #### Merging pull requests #### diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 88ecb640d1a2..fb64dd47aa2e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1 +1,27 @@ -Doug Lea +Guava has mostly not used the CONTRIBUTORS file, instead relying on Git +history. You can see a summary of contributions at +https://github.com/google/guava/graphs/contributors. However, Git history +over-counts some people's contributions because they were responsible for +mirroring out changes from our internal repo. + +This files serves mainly to credit people who have not received proper credit +in the Git history. + +Doug Lea, author of some concurrency libraries + +Joshua O'Madadhain (@jrtom), author of some common.graph commits beyond those +that are already attributed to him in the Git history: +https://github.com/google/guava/commit/909c593c61a656c2d70f0f9bd1cb0e5cdf43a556 +https://github.com/google/guava/commit/d333afeffe474c9d93ec13cb92c59f469986edaf +https://github.com/google/guava/commit/a56d68aef71375acf39fcfb1cd535f2afcab4389 +https://github.com/google/guava/commit/2e13df25b2e7c3d59e29cc238b10541139fd2509 +https://github.com/google/guava/commit/f827e52534ba44f6796b1c0e69313c757bc0701e +https://github.com/google/guava/commit/6a5ca217af46eff28bd9703aeba62f57c5a3693a +https://github.com/google/guava/commit/91fee0605ca75cf4f7d0b07ea3068f8beaedda46 +https://github.com/google/guava/commit/8dca77634125cbaf53365000d1c9f7fb32f6dcea +https://github.com/google/guava/commit/dd5f97bf678b13564ffa1b4a45e00284fb24f178 +https://github.com/google/guava/commit/d812e15c120fea9c7cf9cd33be21c9cd0e424f81 +https://github.com/google/guava/commit/0a30cc3551bfcfc44c08e7358226d53e215519f7 +https://github.com/google/guava/commit/76260d9b3c6acbabf9a8ddae11d4fff3985b6272 +https://github.com/google/guava/commit/9303eb2115f3be584c1efba78b39055f098be0f8 +https://github.com/google/guava/commit/b0be21d46c1312bce1947ad6b6db33ab386dbeaf diff --git a/COPYING b/LICENSE similarity index 100% rename from COPYING rename to LICENSE diff --git a/README.md b/README.md index 57a8e455cd7e..9b19e05298f0 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,24 @@ # Guava: Google Core Libraries for Java -[![Latest release](https://img.shields.io/github/release/google/guava.svg)](https://github.com/google/guava/releases/latest) -[![Build Status](https://github.com/google/guava/workflows/CI/badge.svg?branch=master)](https://github.com/google/guava/actions) +[![GitHub Release](https://img.shields.io/github/v/release/google/guava)](https://github.com/google/guava/releases/latest) +[![CI](https://github.com/google/guava/actions/workflows/ci.yml/badge.svg)](https://github.com/google/guava/actions/workflows/ci.yml) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7197/badge)](https://www.bestpractices.dev/projects/7197) -Guava is a set of core Java libraries from Google that includes new collection types -(such as multimap and multiset), immutable collections, a graph library, and -utilities for concurrency, I/O, hashing, caching, primitives, strings, and more! It + + +Guava is a set of core Java libraries from Google that includes new collection +types (such as multimap and multiset), immutable collections, a graph library, +and utilities for concurrency, I/O, hashing, primitives, strings, and more! It is widely used on most Java projects within Google, and widely used by many other companies as well. -Guava comes in two flavors. + + +Guava comes in two flavors: * The JRE flavor requires JDK 1.8 or higher. -* If you need support for JDK 1.7 or Android, use the Android flavor. You can +* If you need support for Android, use + [the Android flavor](https://github.com/google/guava/wiki/Android). You can find the Android Guava source in the [`android` directory]. [`android` directory]: https://github.com/google/guava/tree/master/android @@ -21,9 +27,9 @@ Guava comes in two flavors. Guava's Maven group ID is `com.google.guava`, and its artifact ID is `guava`. Guava provides two different "flavors": one for use on a (Java 8+) JRE and one -for use on Android or Java 7 or by any library that wants to be compatible with -either of those. These flavors are specified in the Maven version field as -either `30.1.1-jre` or `30.1.1-android`. For more about depending on Guava, see +for use on Android or by any library that wants to be compatible with Android. +These flavors are specified in the Maven version field as either `33.5.0-jre` or +`33.5.0-android`. For more about depending on Guava, see [using Guava in your build]. To add a dependency on Guava using Maven, use the following: @@ -32,9 +38,9 @@ To add a dependency on Guava using Maven, use the following: com.google.guava guava - 30.1.1-jre + 33.5.0-jre - 30.1.1-android + 33.5.0-android ``` @@ -45,16 +51,16 @@ dependencies { // Pick one: // 1. Use Guava in your implementation only: - implementation("com.google.guava:guava:30.1.1-jre") + implementation("com.google.guava:guava:33.5.0-jre") // 2. Use Guava types in your public API: - api("com.google.guava:guava:30.1.1-jre") + api("com.google.guava:guava:33.5.0-jre") // 3. Android - Use Guava in your implementation only: - implementation("com.google.guava:guava:30.1.1-android") + implementation("com.google.guava:guava:33.5.0-android") // 4. Android - Use Guava types in your public API: - api("com.google.guava:guava:30.1.1-android") + api("com.google.guava:guava:33.5.0-android") } ``` @@ -65,16 +71,21 @@ consult the ## Snapshots and Documentation Snapshots of Guava built from the `master` branch are available through Maven -using version `HEAD-jre-SNAPSHOT`, or `HEAD-android-SNAPSHOT` for the Android -flavor. +using version `999.0.0-HEAD-jre-SNAPSHOT`, or `999.0.0-HEAD-android-SNAPSHOT` +for the Android flavor. + +[Snapshot API Javadoc][guava-snapshot-api-docs] as well as +[Snapshot API Diffs][guava-snapshot-api-diffs] are also available. -- Snapshot API Docs: [guava][guava-snapshot-api-docs] -- Snapshot API Diffs: [guava][guava-snapshot-api-diffs] +Another easy way to get to the Javadoc is to open +[guava.dev/api](https://guava.dev/api). You can also jump right to a specific +class by appending the class name to guava.dev. For example, +[guava.dev/ImmutableList](https://guava.dev/ImmutableList)! ## Learn about Guava - Our users' guide, [Guava Explained] -- [A nice collection](http://www.tfnico.com/presentations/google-guava) of +- [A nice collection](https://www.tfnico.com/presentations/google-guava) of other helpful links ## Links @@ -82,8 +93,8 @@ flavor. - [GitHub project](https://github.com/google/guava) - [Issue tracker: Report a defect or feature request](https://github.com/google/guava/issues/new) - [StackOverflow: Ask "how-to" and "why-didn't-it-work" questions](https://stackoverflow.com/questions/ask?tags=guava+java) -- [guava-announce: Announcements of releases and upcoming significant changes](http://groups.google.com/group/guava-announce) -- [guava-discuss: For open-ended questions and discussion](http://groups.google.com/group/guava-discuss) +- [guava-announce: Announcements of releases and upcoming significant changes](https://groups.google.com/group/guava-announce) +- [guava-discuss: For open-ended questions and discussion](https://groups.google.com/group/guava-discuss) ## IMPORTANT WARNINGS @@ -102,7 +113,7 @@ flavor. options open in case of surprises (like, say, a serious security problem). 3. Guava has one dependency that is needed for linkage at runtime: - `com.google.guava:failureaccess:1.0.1`. It also has + `com.google.guava:failureaccess:1.0.3`. It also has [some annotation-only dependencies][guava-deps], which we discuss in more detail at that link. @@ -113,10 +124,11 @@ flavor. 5. Our classes are not designed to protect against a malicious caller. You should not use them for communication between trusted and untrusted code. -6. For the mainline flavor, we test the libraries using only OpenJDK 8 and - OpenJDK 11 on Linux. Some features, especially in `com.google.common.io`, - may not work correctly in other environments. For the Android flavor, our - unit tests also run on API level 15 (Ice Cream Sandwich). +6. For the mainline flavor, we test the libraries using OpenJDK 8, 11, and 17 + on Linux, with some additional testing on newer JDKs and on Windows. Some + features, especially in `com.google.common.io`, may not work correctly in + non-Linux environments. For the Android flavor, our unit tests also run on + API level 23 (Marshmallow). [guava-snapshot-api-docs]: https://guava.dev/releases/snapshot-jre/api/docs/ [guava-snapshot-api-diffs]: https://guava.dev/releases/snapshot-jre/api/diffs/ diff --git a/android/guava-bom/pom.xml b/android/guava-bom/pom.xml index 8fde93d1a75a..148cb698e5bf 100644 --- a/android/guava-bom/pom.xml +++ b/android/guava-bom/pom.xml @@ -8,26 +8,14 @@ com.google.guava guava-bom - HEAD-android-SNAPSHOT + 999.0.0-HEAD-android-SNAPSHOT pom - - - org.sonatype.oss - oss-parent - 9 - - Guava BOM BOM for Guava artifacts https://github.com/google/guava 2010 - - GitHub Issues - https://github.com/google/guava/issues - - Apache License, Version 2.0 @@ -36,6 +24,31 @@ + + + + cpovirk + Chris Povirk + cpovirk@google.com + + + + + scm:git:https://github.com/google/guava.git + scm:git:git@github.com:google/guava.git + https://github.com/google/guava + + + + GitHub Issues + https://github.com/google/guava/issues + + + + 0.9.0 + 3.0.1 + + @@ -50,4 +63,38 @@ + + + + + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} + true + + + + + + + sonatype-oss-release + + + + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + + + diff --git a/android/guava-testlib/pom.xml b/android/guava-testlib/pom.xml index 79b05190ce85..bfad37eaca34 100644 --- a/android/guava-testlib/pom.xml +++ b/android/guava-testlib/pom.xml @@ -5,7 +5,7 @@ com.google.guava guava-parent - HEAD-android-SNAPSHOT + 999.0.0-HEAD-android-SNAPSHOT guava-testlib Guava Testing Library @@ -15,12 +15,14 @@ - com.google.code.findbugs - jsr305 + org.jspecify + jspecify - org.checkerframework - checker-compat-qual + com.google.code.findbugs + jsr305 + ${jsr305.version} + test com.google.errorprone @@ -38,7 +40,8 @@ junit junit - compile + + ${junit.version} com.google.truth truth + ${truth.version} test + + + + com.google.guava + guava + + + + org.mvnsearch + toolchains-maven-plugin + + + maven-toolchains-plugin + maven-compiler-plugin + + + default-compile + + + -XDignore.symbol.file + + + + + compile-java9 + compile + + compile + + + 9 + + ${project.basedir}/src + + + + + -sourcepath + ${project.basedir}/src + --add-reads=com.google.common=ALL-UNNAMED + --add-reads=com.google.common.testlib=ALL-UNNAMED + + -XDcompilePolicy=simple + -Xlint:-removal + -Xlint:-options + + true + + + maven-source-plugin diff --git a/android/guava-testlib/src/com/google/common/collect/testing/AbstractCollectionTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/AbstractCollectionTestSuiteBuilder.java index ef7917b93122..5c20e3fcff3e 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/AbstractCollectionTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/AbstractCollectionTestSuiteBuilder.java @@ -46,11 +46,10 @@ public abstract class AbstractCollectionTestSuiteBuilder< B extends AbstractCollectionTestSuiteBuilder, E> extends PerCollectionSizeTestSuiteBuilder, Collection, E> { - // Class parameters must be raw. - @SuppressWarnings("unchecked") + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - return Arrays.>asList( + return Arrays.asList( CollectionAddAllTester.class, CollectionAddTester.class, CollectionClearTester.class, diff --git a/android/guava-testlib/src/com/google/common/collect/testing/AbstractCollectionTester.java b/android/guava-testlib/src/com/google/common/collect/testing/AbstractCollectionTester.java index c5191be960d4..7ebb133e3cb4 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/AbstractCollectionTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/AbstractCollectionTester.java @@ -17,7 +17,10 @@ package com.google.common.collect.testing; import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -27,8 +30,11 @@ * @author Kevin Bourrillion */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public abstract class AbstractCollectionTester +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public abstract class AbstractCollectionTester extends AbstractContainerTester, E> { // TODO: replace this with an accessor. @@ -41,17 +47,22 @@ protected Collection actualContents() { // TODO: dispose of this once collection is encapsulated. @Override + @CanIgnoreReturnValue protected Collection resetContainer(Collection newContents) { collection = super.resetContainer(newContents); return collection; } - /** @see AbstractContainerTester#resetContainer() */ + /** + * @see AbstractContainerTester#resetContainer() + */ protected void resetCollection() { resetContainer(); } - /** @return an array of the proper size with {@code null} inserted into the middle element. */ + /** + * @return an array of the proper size with {@code null} inserted into the middle element. + */ protected E[] createArrayWithNullElement() { E[] array = createSamplesArray(); array[getNullLocation()] = null; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/AbstractContainerTester.java b/android/guava-testlib/src/com/google/common/collect/testing/AbstractContainerTester.java index a5674d3db4d0..5bf3a0975b04 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/AbstractContainerTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/AbstractContainerTester.java @@ -16,13 +16,19 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.copyToList; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; + import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -34,8 +40,11 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public abstract class AbstractContainerTester +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public abstract class AbstractContainerTester extends AbstractTester> { protected SampleElements samples; protected C container; @@ -61,6 +70,7 @@ public void setUp() throws Exception { * @see #resetContainer(Object) resetContainer(C) * @return the new container instance. */ + @CanIgnoreReturnValue protected C resetContainer() { return resetContainer(getSubjectGenerator().createTestSubject()); } @@ -75,6 +85,7 @@ protected C resetContainer() { * @return the new container instance * @param newValue the new container instance */ + @CanIgnoreReturnValue protected C resetContainer(C newValue) { container = newValue; return container; @@ -85,7 +96,7 @@ protected C resetContainer(C newValue) { * @param elements expected contents of {@link #container} */ protected final void expectContents(E... elements) { - expectContents(Arrays.asList(elements)); + expectContents(asList(elements)); } /** @@ -105,7 +116,7 @@ protected final void expectContents(E... elements) { * examining whether the features include KNOWN_ORDER? */ protected void expectContents(Collection expected) { - Helpers.assertEqualIgnoringOrder(expected, actualContents()); + assertEqualIgnoringOrder(expected, actualContents()); } protected void expectUnchanged() { @@ -132,17 +143,17 @@ protected void expectUnchanged() { * @param elements expected additional contents of {@link #container} */ protected final void expectAdded(E... elements) { - List expected = Helpers.copyToList(getSampleElements()); - expected.addAll(Arrays.asList(elements)); + List expected = copyToList(getSampleElements()); + expected.addAll(asList(elements)); expectContents(expected); } protected final void expectAdded(int index, E... elements) { - expectAdded(index, Arrays.asList(elements)); + expectAdded(index, asList(elements)); } protected final void expectAdded(int index, Collection elements) { - List expected = Helpers.copyToList(getSampleElements()); + List expected = copyToList(getSampleElements()); expected.addAll(index, elements); expectContents(expected); } @@ -171,7 +182,7 @@ protected E[] createOrderedArray() { return array; } - public static class ArrayWithDuplicate { + public static class ArrayWithDuplicate { public final E[] elements; public final E duplicate; @@ -188,7 +199,7 @@ protected ArrayWithDuplicate createArrayWithDuplicateElement() { E[] elements = createSamplesArray(); E duplicate = elements[(elements.length / 2) - 1]; elements[(elements.length / 2) + 1] = duplicate; - return new ArrayWithDuplicate(elements, duplicate); + return new ArrayWithDuplicate<>(elements, duplicate); } // Helper methods to improve readability of derived classes @@ -207,15 +218,15 @@ protected Collection getSampleElements() { /** * Returns the {@linkplain #getSampleElements() sample elements} as ordered by {@link - * TestContainerGenerator#order(List)}. Tests should used this method only if they declare + * TestContainerGenerator#order(List)}. Tests should use this method only if they declare * requirement {@link com.google.common.collect.testing.features.CollectionFeature#KNOWN_ORDER}. */ protected List getOrderedElements() { - List list = new ArrayList(); + List list = new ArrayList<>(); for (E e : getSubjectGenerator().order(new ArrayList(getSampleElements()))) { list.add(e); } - return Collections.unmodifiableList(list); + return unmodifiableList(list); } /** @@ -226,14 +237,12 @@ protected int getNullLocation() { return getNumElements() / 2; } - @SuppressWarnings("unchecked") protected MinimalCollection createDisjointCollection() { return MinimalCollection.of(e3(), e4()); } - @SuppressWarnings("unchecked") protected MinimalCollection emptyCollection() { - return MinimalCollection.of(); + return MinimalCollection.of(); } protected final E e0() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/AbstractIteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/AbstractIteratorTester.java index 5e6a583c43de..4fce64c98e68 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/AbstractIteratorTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/AbstractIteratorTester.java @@ -16,21 +16,28 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.copyToSet; +import static java.lang.System.arraycopy; +import static java.util.Arrays.asList; +import static java.util.Collections.frequency; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.Stack; -import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Most of the logic for {@link IteratorTester} and {@link ListIteratorTester}. @@ -41,8 +48,9 @@ * @author Chris Povirk */ @GwtCompatible -abstract class AbstractIteratorTester> { - private Stimulus[] stimuli; +@NullMarked +abstract class AbstractIteratorTester> { + private final Stimulus[] stimuli; private final Iterator elementsToInsert; private final Set features; private final List expectedElements; @@ -57,7 +65,7 @@ private abstract static class PermittedMetaException extends RuntimeException { static final PermittedMetaException UOE_OR_ISE = new PermittedMetaException("UnsupportedOperationException or IllegalStateException") { @Override - boolean isPermitted(RuntimeException exception) { + boolean isPermitted(Exception exception) { return exception instanceof UnsupportedOperationException || exception instanceof IllegalStateException; } @@ -65,21 +73,21 @@ boolean isPermitted(RuntimeException exception) { static final PermittedMetaException UOE = new PermittedMetaException("UnsupportedOperationException") { @Override - boolean isPermitted(RuntimeException exception) { + boolean isPermitted(Exception exception) { return exception instanceof UnsupportedOperationException; } }; static final PermittedMetaException ISE = new PermittedMetaException("IllegalStateException") { @Override - boolean isPermitted(RuntimeException exception) { + boolean isPermitted(Exception exception) { return exception instanceof IllegalStateException; } }; static final PermittedMetaException NSEE = new PermittedMetaException("NoSuchElementException") { @Override - boolean isPermitted(RuntimeException exception) { + boolean isPermitted(Exception exception) { return exception instanceof NoSuchElementException; } }; @@ -88,20 +96,20 @@ private PermittedMetaException(String message) { super(message); } - abstract boolean isPermitted(RuntimeException exception); + abstract boolean isPermitted(Exception exception); - void assertPermitted(RuntimeException exception) { + void assertPermitted(Exception exception) { if (!isPermitted(exception)) { String message = "Exception " + exception.getClass().getSimpleName() + " was thrown; expected " + getMessage(); - Helpers.fail(exception, message); + throw new AssertionError(message, exception); } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } private static final class UnknownElementException extends RuntimeException { @@ -109,7 +117,7 @@ private UnknownElementException(Collection expected, Object actual) { super("Returned value '" + actual + "' not found. Remaining elements: " + expected); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -135,20 +143,22 @@ protected final class MultiExceptionListIterator implements ListIterator { * The elements to be returned by future calls to {@code next()}, with the first at the top of * the stack. */ - final Stack nextElements = new Stack(); + final Stack nextElements = new Stack<>(); + /** * The elements to be returned by future calls to {@code previous()}, with the first at the top * of the stack. */ - final Stack previousElements = new Stack(); + final Stack previousElements = new Stack<>(); + /** * {@link #nextElements} if {@code next()} was called more recently then {@code previous}, * {@link #previousElements} if the reverse is true, or -- overriding both of these -- {@code * null} if {@code remove()} or {@code add()} has been called more recently than either. We use * this to determine which stack to pop from on a call to {@code remove()} (or to pop from and - * push to on a call to {@code set()}. + * push to on a call to {@code set()}). */ - Stack stackWithLastReturnedElementAtTop = null; + @Nullable Stack stackWithLastReturnedElementAtTop = null; MultiExceptionListIterator(List expectedElements) { Helpers.addAll(nextElements, Helpers.reverse(expectedElements)); @@ -254,7 +264,7 @@ private void throwIfInvalid(IteratorFeature methodFeature) { } private List getElements() { - List elements = new ArrayList(); + List elements = new ArrayList<>(); Helpers.addAll(elements, previousElements); Helpers.addAll(elements, Helpers.reverse(nextElements)); return elements; @@ -266,7 +276,7 @@ public enum KnownOrder { UNKNOWN_ORDER } - @SuppressWarnings("unchecked") // creating array of generic class Stimulus + @SuppressWarnings("unchecked") // TODO(cpovirk): Stop using arrays. AbstractIteratorTester( int steps, Iterable elementsToInsertIterable, @@ -276,13 +286,13 @@ public enum KnownOrder { int startIndex) { // periodically we should manually try (steps * 3 / 2) here; all tests but // one should still pass (testVerifyGetsCalled()). - stimuli = new Stimulus[steps]; + stimuli = (Stimulus[]) new Stimulus[steps]; if (!elementsToInsertIterable.iterator().hasNext()) { throw new IllegalArgumentException(); } elementsToInsert = Helpers.cycle(elementsToInsertIterable); - this.features = Helpers.copyToSet(features); - this.expectedElements = Helpers.copyToList(expectedElements); + this.features = copyToSet(features); + this.expectedElements = copyToList(expectedElements); this.knownOrder = knownOrder; this.startIndex = startIndex; } @@ -312,10 +322,11 @@ public enum KnownOrder { protected void verify(List elements) {} /** Executes the test. */ + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception public final void test() { try { recurse(0); - } catch (RuntimeException e) { + } catch (Exception e) { // sneaky checked exception throw new RuntimeException(Arrays.toString(stimuli), e); } } @@ -336,7 +347,7 @@ private void recurse(int level) { } private void compareResultsForThisListOfStimuli() { - int removes = Collections.frequency(Arrays.asList(stimuli), remove); + int removes = frequency(asList(stimuli), remove); if ((!features.contains(IteratorFeature.SUPPORTS_REMOVE) && removes > 1) || (stimuli.length >= 5 && removes > 2)) { // removes are the most expensive thing to test, since they often throw exceptions with stack @@ -350,20 +361,20 @@ private void compareResultsForThisListOfStimuli() { try { stimuli[i].executeAndCompare(reference, target); verify(reference.getElements()); - } catch (AssertionFailedError cause) { - Helpers.fail(cause, "failed with stimuli " + subListCopy(stimuli, i + 1)); + } catch (AssertionError cause) { + throw new AssertionError("failed with stimuli " + subListCopy(stimuli, i + 1), cause); } } } private static List subListCopy(Object[] source, int size) { - final Object[] copy = new Object[size]; - System.arraycopy(source, 0, copy, 0, size); - return Arrays.asList(copy); + Object[] copy = new Object[size]; + arraycopy(source, 0, copy, 0, size); + return asList(copy); } private interface IteratorOperation { - Object execute(Iterator iterator); + @Nullable Object execute(Iterator iterator); } /** @@ -371,16 +382,17 @@ private interface IteratorOperation { * * @see Stimulus#executeAndCompare(ListIterator, Iterator) */ + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception private > void internalExecuteAndCompare( T reference, T target, IteratorOperation method) { Object referenceReturnValue = null; PermittedMetaException referenceException = null; Object targetReturnValue = null; - RuntimeException targetException = null; + Exception targetException = null; try { targetReturnValue = method.execute(target); - } catch (RuntimeException e) { + } catch (Exception e) { // sneaky checked exception targetException = e; } @@ -395,20 +407,15 @@ private > void internalExecuteAndCompare( @SuppressWarnings("unchecked") E targetReturnValueFromNext = (E) targetReturnValue; /* - * We have an Iterator and want to cast it to - * MultiExceptionListIterator. Because we're inside an - * AbstractIteratorTester, that's implicitly a cast to - * AbstractIteratorTester.MultiExceptionListIterator. The runtime - * won't be able to verify the AbstractIteratorTester part, so it's - * an unchecked cast. We know, however, that the only possible value for - * the type parameter is , since otherwise the - * MultiExceptionListIterator wouldn't be an Iterator. The cast is - * safe, even though javac can't tell. - * - * Sun bug 6665356 is an additional complication. Until OpenJDK 7, javac - * doesn't recognize this kind of cast as unchecked cast. Neither does - * Eclipse 3.4. Right now, this suppression is mostly unnecessary. + * We have an Iterator and want to cast it to MultiExceptionListIterator. Because we're + * inside an AbstractIteratorTester, that's implicitly a cast to + * AbstractIteratorTester.MultiExceptionListIterator. The runtime won't be able to verify + * the AbstractIteratorTester part, so it's an unchecked cast. We know, however, that the + * only possible value for the type parameter is , since otherwise the + * MultiExceptionListIterator wouldn't be an Iterator. The cast is safe, even though + * javac can't tell. */ + @SuppressWarnings("unchecked") MultiExceptionListIterator multiExceptionListIterator = (MultiExceptionListIterator) reference; multiExceptionListIterator.promoteToNext(targetReturnValueFromNext); @@ -418,12 +425,12 @@ private > void internalExecuteAndCompare( } catch (PermittedMetaException e) { referenceException = e; } catch (UnknownElementException e) { - Helpers.fail(e, e.getMessage()); + throw new AssertionError(e); } if (referenceException == null) { if (targetException != null) { - Helpers.fail(targetException, "Target threw exception when reference did not"); + throw new AssertionError("Target threw exception when reference did not", targetException); } /* @@ -449,7 +456,7 @@ private > void internalExecuteAndCompare( private static final IteratorOperation REMOVE_METHOD = new IteratorOperation() { @Override - public Object execute(Iterator iterator) { + public @Nullable Object execute(Iterator iterator) { iterator.remove(); return null; } @@ -458,7 +465,7 @@ public Object execute(Iterator iterator) { private static final IteratorOperation NEXT_METHOD = new IteratorOperation() { @Override - public Object execute(Iterator iterator) { + public @Nullable Object execute(Iterator iterator) { return iterator.next(); } }; @@ -466,16 +473,16 @@ public Object execute(Iterator iterator) { private static final IteratorOperation PREVIOUS_METHOD = new IteratorOperation() { @Override - public Object execute(Iterator iterator) { + public @Nullable Object execute(Iterator iterator) { return ((ListIterator) iterator).previous(); } }; private final IteratorOperation newAddMethod() { - final Object toInsert = elementsToInsert.next(); + Object toInsert = elementsToInsert.next(); return new IteratorOperation() { @Override - public Object execute(Iterator iterator) { + public @Nullable Object execute(Iterator iterator) { @SuppressWarnings("unchecked") ListIterator rawIterator = (ListIterator) iterator; rawIterator.add(toInsert); @@ -485,10 +492,10 @@ public Object execute(Iterator iterator) { } private final IteratorOperation newSetMethod() { - final E toInsert = elementsToInsert.next(); + E toInsert = elementsToInsert.next(); return new IteratorOperation() { @Override - public Object execute(Iterator iterator) { + public @Nullable Object execute(Iterator iterator) { @SuppressWarnings("unchecked") ListIterator li = (ListIterator) iterator; li.set(toInsert); @@ -497,7 +504,7 @@ public Object execute(Iterator iterator) { }; } - abstract static class Stimulus> { + abstract static class Stimulus> { private final String toString; protected Stimulus(String toString) { @@ -538,9 +545,8 @@ void executeAndCompare(ListIterator reference, Iterator target) { } }; - @SuppressWarnings("unchecked") List>> iteratorStimuli() { - return Arrays.asList(hasNext, next, remove); + return asList(hasNext, next, remove); } Stimulus> hasPrevious = @@ -586,8 +592,7 @@ void executeAndCompare(ListIterator reference, ListIterator target) { } }; - @SuppressWarnings("unchecked") List>> listIteratorStimuli() { - return Arrays.asList(hasPrevious, nextIndex, previousIndex, previous, add, set); + return asList(hasPrevious, nextIndex, previousIndex, previous, add, set); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/AbstractMapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/AbstractMapTester.java index 090442edc4da..23e5584ba72b 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/AbstractMapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/AbstractMapTester.java @@ -16,6 +16,9 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.mapEntry; + import com.google.common.annotations.GwtCompatible; import java.util.Collection; import java.util.Iterator; @@ -23,6 +26,8 @@ import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -36,8 +41,11 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public abstract class AbstractMapTester +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public abstract class AbstractMapTester extends AbstractContainerTester, Entry> { protected Map getMap() { return container; @@ -48,7 +56,9 @@ protected Collection> actualContents() { return getMap().entrySet(); } - /** @see AbstractContainerTester#resetContainer() */ + /** + * @see AbstractContainerTester#resetContainer() + */ protected final void resetMap() { resetContainer(); } @@ -69,11 +79,13 @@ protected void expectMissingValues(V... elements) { } } - /** @return an array of the proper size with {@code null} as the key of the middle element. */ + /** + * @return an array of the proper size with {@code null} as the key of the middle element. + */ protected Entry[] createArrayWithNullKey() { Entry[] array = createSamplesArray(); - final int nullKeyLocation = getNullLocation(); - final Entry oldEntry = array[nullKeyLocation]; + int nullKeyLocation = getNullLocation(); + Entry oldEntry = array[nullKeyLocation]; array[nullKeyLocation] = entry(null, oldEntry.getValue()); return array; } @@ -94,11 +106,13 @@ private Entry getEntryNullReplaces() { return entries.next(); } - /** @return an array of the proper size with {@code null} as the value of the middle element. */ + /** + * @return an array of the proper size with {@code null} as the value of the middle element. + */ protected Entry[] createArrayWithNullValue() { Entry[] array = createSamplesArray(); - final int nullValueLocation = getNullLocation(); - final Entry oldEntry = array[nullValueLocation]; + int nullValueLocation = getNullLocation(); + Entry oldEntry = array[nullValueLocation]; array[nullValueLocation] = entry(oldEntry.getKey(), null); return array; } @@ -139,7 +153,6 @@ protected void expectNullValueMissingWhenNullValuesUnsupported(String message) { } } - @SuppressWarnings("unchecked") @Override protected MinimalCollection> createDisjointCollection() { return MinimalCollection.of(e3(), e4()); @@ -167,13 +180,13 @@ protected void expectMissing(Entry... entries) { } } - private static boolean equal(Object a, Object b) { + private static boolean equal(@Nullable Object a, @Nullable Object b) { return a == b || (a != null && a.equals(b)); } // This one-liner saves us from some ugly casts protected Entry entry(K key, V value) { - return Helpers.mapEntry(key, value); + return mapEntry(key, value); } @Override @@ -187,7 +200,7 @@ protected void expectContents(Collection> expected) { } protected final void expectReplacement(Entry newEntry) { - List> expected = Helpers.copyToList(getSampleElements()); + List> expected = copyToList(getSampleElements()); replaceValue(expected, newEntry); expectContents(expected); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/AbstractTester.java b/android/guava-testlib/src/com/google/common/collect/testing/AbstractTester.java index dc1e1a0674fc..1fba49a94ebb 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/AbstractTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/AbstractTester.java @@ -17,7 +17,11 @@ package com.google.common.collect.testing; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * This abstract base class for testers allows the framework to inject needed information after @@ -31,11 +35,12 @@ * @author George van den Driessche */ @GwtCompatible +@NullMarked public class AbstractTester extends TestCase { private G subjectGenerator; private String suiteName; - private Runnable setUp; - private Runnable tearDown; + private @Nullable Runnable setUp; + private @Nullable Runnable tearDown; // public so that it can be referenced in generated GWT tests. @Override @@ -54,7 +59,8 @@ public void tearDown() throws Exception { } // public so that it can be referenced in generated GWT tests. - public final void init(G subjectGenerator, String suiteName, Runnable setUp, Runnable tearDown) { + public final void init( + G subjectGenerator, String suiteName, @Nullable Runnable setUp, @Nullable Runnable tearDown) { this.subjectGenerator = subjectGenerator; this.suiteName = suiteName; this.setUp = setUp; @@ -71,12 +77,29 @@ public G getSubjectGenerator() { } /** Returns the name of the test method invoked by this test instance. */ + @J2ktIncompatible + @GwtIncompatible // not used under GWT, and super.getName() is not available under J2CL public final String getTestMethodName() { return super.getName(); } + @J2ktIncompatible + @GwtIncompatible // not used under GWT, and super.getName() is not available under J2CL @Override public String getName() { - return Platform.format("%s[%s]", super.getName(), suiteName); + return super.getName() + '[' + suiteName + ']'; + } + + /** + * Asserts that the given object is non-null, with a better failure message than {@link + * TestCase#assertNull(String, Object)}. + * + *

The {@link TestCase} version (which is from JUnit 3) produces a failure message that does + * not include the value of the object. + * + * @since 33.4.0 + */ + public static void assertNull(String message, Object object) { + assertEquals(message, null, object); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/BaseComparable.java b/android/guava-testlib/src/com/google/common/collect/testing/BaseComparable.java index 3847709894cd..13e1b9e032cf 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/BaseComparable.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/BaseComparable.java @@ -17,7 +17,10 @@ package com.google.common.collect.testing; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; +import org.jspecify.annotations.Nullable; /** * Simple base class to verify that we handle generics correctly. @@ -38,7 +41,7 @@ public int hashCode() { // delegate to 's' } @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { if (other == null) { return false; } else if (other instanceof BaseComparable) { @@ -53,5 +56,5 @@ public int compareTo(BaseComparable o) { return s.compareTo(o.s); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/CollectionTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/CollectionTestSuiteBuilder.java index cf1ed23aa41e..0b8044cee14e 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/CollectionTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/CollectionTestSuiteBuilder.java @@ -57,12 +57,15 @@ protected List createDerivedSuites( .named(getName() + " reserialized") .withFeatures(computeReserializedCollectionFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } return derivedSuites; } - static class ReserializedCollectionGenerator implements TestCollectionGenerator { + private static final class ReserializedCollectionGenerator + implements TestCollectionGenerator { final OneSizeTestContainerGenerator, E> gen; private ReserializedCollectionGenerator(OneSizeTestContainerGenerator, E> gen) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/ConcurrentMapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/ConcurrentMapTestSuiteBuilder.java index 47220b0f1f8d..048723e40385 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/ConcurrentMapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/ConcurrentMapTestSuiteBuilder.java @@ -14,12 +14,14 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.testers.ConcurrentMapPutIfAbsentTester; import com.google.common.collect.testing.testers.ConcurrentMapRemoveTester; import com.google.common.collect.testing.testers.ConcurrentMapReplaceEntryTester; import com.google.common.collect.testing.testers.ConcurrentMapReplaceTester; -import java.util.Arrays; import java.util.List; /** @@ -36,16 +38,18 @@ public static ConcurrentMapTestSuiteBuilder using(TestMapGenerator< return result; } + @SuppressWarnings("rawtypes") // class literals static final List> TESTERS = - Arrays.asList( + asList( ConcurrentMapPutIfAbsentTester.class, ConcurrentMapRemoveTester.class, ConcurrentMapReplaceTester.class, ConcurrentMapReplaceEntryTester.class); + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.addAll(TESTERS); return testers; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/ConcurrentNavigableMapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/ConcurrentNavigableMapTestSuiteBuilder.java index cccd06ef6bf2..d0f71b61db40 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/ConcurrentNavigableMapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/ConcurrentNavigableMapTestSuiteBuilder.java @@ -16,6 +16,8 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; + import com.google.common.annotations.GwtIncompatible; import java.util.List; @@ -37,9 +39,10 @@ public static ConcurrentNavigableMapTestSuiteBuilder using( return result; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.addAll(ConcurrentMapTestSuiteBuilder.TESTERS); return testers; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/DerivedCollectionGenerators.java b/android/guava-testlib/src/com/google/common/collect/testing/DerivedCollectionGenerators.java index 3588e856fa23..85a6e8a7913e 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/DerivedCollectionGenerators.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/DerivedCollectionGenerators.java @@ -17,15 +17,15 @@ package com.google.common.collect.testing; import static com.google.common.collect.testing.Helpers.castOrCopyToList; +import static com.google.common.collect.testing.Helpers.entryComparator; import static com.google.common.collect.testing.Helpers.equal; import static com.google.common.collect.testing.Helpers.mapEntry; +import static java.util.Arrays.asList; import static java.util.Collections.sort; import com.google.common.annotations.GwtCompatible; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -33,6 +33,8 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Derived suite generators, split out of the suite builders so that they are available to GWT. @@ -40,8 +42,9 @@ * @author George van den Driessche */ @GwtCompatible +@NullMarked public final class DerivedCollectionGenerators { - public static class MapEntrySetGenerator + public static class MapEntrySetGenerator implements TestSetGenerator>, DerivedGenerator { private final OneSizeTestContainerGenerator, Entry> mapGenerator; @@ -79,8 +82,9 @@ public OneSizeTestContainerGenerator, Entry> getInnerGenerator() // TODO: investigate some API changes to SampleElements that would tidy up // parts of the following classes. - static TestSetGenerator keySetGenerator( - OneSizeTestContainerGenerator, Entry> mapGenerator) { + static + TestSetGenerator keySetGenerator( + OneSizeTestContainerGenerator, Entry> mapGenerator) { TestContainerGenerator, Entry> generator = mapGenerator.getInnerGenerator(); if (generator instanceof TestSortedMapGenerator && ((TestSortedMapGenerator) generator).create().keySet() instanceof SortedSet) { @@ -90,15 +94,16 @@ static TestSetGenerator keySetGenerator( } } - public static class MapKeySetGenerator implements TestSetGenerator, DerivedGenerator { + public static class MapKeySetGenerator + implements TestSetGenerator, DerivedGenerator { private final OneSizeTestContainerGenerator, Entry> mapGenerator; private final SampleElements samples; public MapKeySetGenerator(OneSizeTestContainerGenerator, Entry> mapGenerator) { this.mapGenerator = mapGenerator; - final SampleElements> mapSamples = this.mapGenerator.samples(); + SampleElements> mapSamples = this.mapGenerator.samples(); this.samples = - new SampleElements( + new SampleElements<>( mapSamples.e0().getKey(), mapSamples.e1().getKey(), mapSamples.e2().getKey(), @@ -123,7 +128,7 @@ public Set create(Object... elements) { Collection> entries = new ArrayList<>(elements.length); int i = 0; for (Entry entry : originalEntries) { - entries.add(Helpers.mapEntry(keysArray[i++], entry.getValue())); + entries.add(mapEntry(keysArray[i++], entry.getValue())); } return mapGenerator.create(entries.toArray()).keySet(); @@ -159,8 +164,9 @@ public OneSizeTestContainerGenerator, Entry> getInnerGenerator() } } - public static class MapSortedKeySetGenerator extends MapKeySetGenerator - implements TestSortedSetGenerator, DerivedGenerator { + public static class MapSortedKeySetGenerator< + K extends @Nullable Object, V extends @Nullable Object> + extends MapKeySetGenerator implements TestSortedSetGenerator { private final TestSortedMapGenerator delegate; public MapSortedKeySetGenerator( @@ -195,7 +201,8 @@ public K aboveSamplesGreater() { } } - public static class MapValueCollectionGenerator + public static class MapValueCollectionGenerator< + K extends @Nullable Object, V extends @Nullable Object> implements TestCollectionGenerator, DerivedGenerator { private final OneSizeTestContainerGenerator, Entry> mapGenerator; private final SampleElements samples; @@ -203,9 +210,9 @@ public static class MapValueCollectionGenerator public MapValueCollectionGenerator( OneSizeTestContainerGenerator, Entry> mapGenerator) { this.mapGenerator = mapGenerator; - final SampleElements> mapSamples = this.mapGenerator.samples(); + SampleElements> mapSamples = this.mapGenerator.samples(); this.samples = - new SampleElements( + new SampleElements<>( mapSamples.e0().getValue(), mapSamples.e1().getValue(), mapSamples.e2().getValue(), @@ -230,7 +237,7 @@ public Collection create(Object... elements) { Collection> entries = new ArrayList<>(elements.length); int i = 0; for (Entry entry : originalEntries) { - entries.add(Helpers.mapEntry(entry.getKey(), valuesArray[i++])); + entries.add(mapEntry(entry.getKey(), valuesArray[i++])); } return mapGenerator.create(entries.toArray()).values(); @@ -239,14 +246,13 @@ public Collection create(Object... elements) { @Override public V[] createArray(int length) { // noinspection UnnecessaryLocalVariable - final V[] vs = - ((TestMapGenerator) mapGenerator.getInnerGenerator()).createValueArray(length); + V[] vs = ((TestMapGenerator) mapGenerator.getInnerGenerator()).createValueArray(length); return vs; } @Override public Iterable order(List insertionOrder) { - final List> orderedEntries = + List> orderedEntries = castOrCopyToList(mapGenerator.order(castOrCopyToList(mapGenerator.getSampleElements(5)))); sort( insertionOrder, @@ -277,7 +283,8 @@ public OneSizeTestContainerGenerator, Entry> getInnerGenerator() } // TODO(cpovirk): could something like this be used elsewhere, e.g., ReserializedListGenerator? - static class ForwardingTestMapGenerator implements TestMapGenerator { + static class ForwardingTestMapGenerator + implements TestMapGenerator { TestMapGenerator delegate; ForwardingTestMapGenerator(TestMapGenerator delegate) { @@ -322,7 +329,8 @@ public enum Bound { NO_BOUND; } - public static class SortedSetSubsetTestSetGenerator implements TestSortedSetGenerator { + public static class SortedSetSubsetTestSetGenerator + implements TestSortedSetGenerator { final Bound to; final Bound from; final E firstInclusive; @@ -341,7 +349,7 @@ public SortedSetSubsetTestSetGenerator( SampleElements samples = delegate.samples(); List samplesList = new ArrayList<>(samples.asList()); - Collections.sort(samplesList, comparator); + sort(samplesList, comparator); this.firstInclusive = samplesList.get(0); this.lastInclusive = samplesList.get(samplesList.size() - 1); } @@ -375,7 +383,7 @@ public Iterable order(List insertionOrder) { @Override public SortedSet create(Object... elements) { - List normalValues = (List) Arrays.asList(elements); + List normalValues = (List) asList(elements); List extremeValues = new ArrayList<>(); // nulls are usually out of bounds for a subset, so ban them altogether @@ -398,7 +406,7 @@ public SortedSet create(Object... elements) { } // the regular values should be visible after filtering - List allEntries = new ArrayList<>(); + List<@Nullable Object> allEntries = new ArrayList<>(); allEntries.addAll(extremeValues); allEntries.addAll(normalValues); SortedSet set = delegate.create(allEntries.toArray()); @@ -444,8 +452,9 @@ public E aboveSamplesGreater() { * TODO(cpovirk): surely we can find a less ugly solution than a class that accepts 3 parameters, * exposes as many getters, does work in the constructor, and has both a superclass and a subclass */ - public static class SortedMapSubmapTestMapGenerator extends ForwardingTestMapGenerator - implements TestSortedMapGenerator { + public static class SortedMapSubmapTestMapGenerator< + K extends @Nullable Object, V extends @Nullable Object> + extends ForwardingTestMapGenerator implements TestSortedMapGenerator { final Bound to; final Bound from; final K firstInclusive; @@ -459,14 +468,13 @@ public SortedMapSubmapTestMapGenerator( this.from = from; SortedMap emptyMap = delegate.create(); - this.entryComparator = Helpers.entryComparator(emptyMap.comparator()); + this.entryComparator = entryComparator(emptyMap.comparator()); // derive values for inclusive filtering from the input samples SampleElements> samples = delegate.samples(); - @SuppressWarnings("unchecked") // no elements are inserted into the array List> samplesList = - Arrays.asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4()); - Collections.sort(samplesList, entryComparator); + asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4()); + sort(samplesList, entryComparator); this.firstInclusive = samplesList.get(0).getKey(); this.lastInclusive = samplesList.get(samplesList.size() - 1).getKey(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/DerivedComparable.java b/android/guava-testlib/src/com/google/common/collect/testing/DerivedComparable.java index 5b0d16f32657..fd9ba97da9b9 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/DerivedComparable.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/DerivedComparable.java @@ -17,6 +17,8 @@ package com.google.common.collect.testing; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; /** * Simple derived class to verify that we handle generics correctly. @@ -29,5 +31,5 @@ public DerivedComparable(String s) { super(s); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/DerivedGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/DerivedGenerator.java index d80f5a93c917..b96e1d07b63f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/DerivedGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/DerivedGenerator.java @@ -24,7 +24,7 @@ * collection generator. * *

{@code GwtTestSuiteGenerator} expects every {@code DerivedIterator} implementation to provide - * a one-arg constructor accepting its inner generator as an argument). This requirement enables it + * a one-arg constructor accepting its inner generator as an argument. This requirement enables it * to generate source code (since GWT cannot use reflection to generate the suites). * * @author Chris Povirk diff --git a/android/guava-testlib/src/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilder.java index 0d921a580896..4481d7619587 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilder.java @@ -16,7 +16,12 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToSet; +import static com.google.common.collect.testing.Helpers.getMethod; +import static com.google.common.collect.testing.features.FeatureUtil.addImpliedFeatures; +import static java.util.Arrays.asList; import static java.util.Collections.disjoint; +import static java.util.Collections.unmodifiableSet; import static java.util.logging.Level.FINER; import com.google.common.annotations.GwtIncompatible; @@ -24,11 +29,10 @@ import com.google.common.collect.testing.features.Feature; import com.google.common.collect.testing.features.FeatureUtil; import com.google.common.collect.testing.features.TesterRequirements; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedHashSet; @@ -38,6 +42,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.Nullable; /** * Creates, based on your criteria, a JUnit test suite that exhaustively tests the object generated @@ -61,12 +66,13 @@ protected B self() { // Test Data - private G subjectGenerator; + private @Nullable G subjectGenerator; // Gets run before every test. private Runnable setUp; // Gets run at the conclusion of every test. private Runnable tearDown; + @CanIgnoreReturnValue protected B usingGenerator(G subjectGenerator) { this.subjectGenerator = subjectGenerator; return self(); @@ -76,36 +82,40 @@ public G getSubjectGenerator() { return subjectGenerator; } + @CanIgnoreReturnValue public B withSetUp(Runnable setUp) { this.setUp = setUp; return self(); } - protected Runnable getSetUp() { + public Runnable getSetUp() { return setUp; } + @CanIgnoreReturnValue public B withTearDown(Runnable tearDown) { this.tearDown = tearDown; return self(); } - protected Runnable getTearDown() { + public Runnable getTearDown() { return tearDown; } // Features - private Set> features = new LinkedHashSet<>(); + private final Set> features = new LinkedHashSet<>(); /** * Configures this builder to produce tests appropriate for the given features. This method may be * called more than once to add features in multiple groups. */ + @CanIgnoreReturnValue public B withFeatures(Feature... features) { - return withFeatures(Arrays.asList(features)); + return withFeatures(asList(features)); } + @CanIgnoreReturnValue public B withFeatures(Iterable> features) { for (Feature feature : features) { this.features.add(feature); @@ -114,14 +124,15 @@ public B withFeatures(Iterable> features) { } public Set> getFeatures() { - return Collections.unmodifiableSet(features); + return unmodifiableSet(features); } // Name - private String name; + private @Nullable String name; /** Configures this builder produce a TestSuite with the given name. */ + @CanIgnoreReturnValue public B named(String name) { if (name.contains("(")) { throw new IllegalArgumentException( @@ -138,7 +149,7 @@ public String getName() { // Test suppression - private Set suppressedTests = new HashSet<>(); + private final Set suppressedTests = new HashSet<>(); /** * Prevents the given methods from being run as part of the test suite. @@ -147,10 +158,12 @@ public String getName() { * semantics of an implementation disagree in unforeseen ways with the semantics expected by a * test, or to keep dependent builds clean in spite of an erroneous test. */ + @CanIgnoreReturnValue public B suppressing(Method... methods) { - return suppressing(Arrays.asList(methods)); + return suppressing(asList(methods)); } + @CanIgnoreReturnValue public B suppressing(Collection methods) { suppressedTests.addAll(methods); return self(); @@ -164,28 +177,24 @@ public Set getSuppressedTests() { Logger.getLogger(FeatureSpecificTestSuiteBuilder.class.getName()); /** Creates a runnable JUnit test suite based on the criteria already given. */ - /* - * Class parameters must be raw. This annotation should go on testerClass in - * the for loop, but the 1.5 javac crashes on annotations in for loops: - * - */ - @SuppressWarnings("unchecked") public TestSuite createTestSuite() { checkCanCreate(); logger.fine(" Testing: " + name); logger.fine("Features: " + formatFeatureSet(features)); - FeatureUtil.addImpliedFeatures(features); + addImpliedFeatures(features); logger.fine("Expanded: " + formatFeatureSet(features)); - // Class parameters must be raw. + @SuppressWarnings("rawtypes") // class literals List> testers = getTesters(); TestSuite suite = new TestSuite(name); - for (Class testerClass : testers) { - final TestSuite testerSuite = + for (@SuppressWarnings("rawtypes") // class literals + Class testerClass : testers) { + @SuppressWarnings("unchecked") // getting rid of the raw type, for better or for worse + TestSuite testerSuite = makeSuiteForTesterClass((Class>) testerClass); if (testerSuite.countTestCases() > 0) { suite.addTest(testerSuite); @@ -207,11 +216,11 @@ protected void checkCanCreate() { } } - // Class parameters must be raw. + @SuppressWarnings("rawtypes") // class literals protected abstract List> getTesters(); private boolean matches(Test test) { - final Method method; + Method method; try { method = extractMethod(test); } catch (IllegalArgumentException e) { @@ -222,7 +231,7 @@ private boolean matches(Test test) { logger.finer(Platform.format("%s: excluding because it was explicitly suppressed.", test)); return false; } - final TesterRequirements requirements; + TesterRequirements requirements; try { requirements = FeatureUtil.getTesterRequirements(method); } catch (ConflictingRequirementsException e) { @@ -230,7 +239,7 @@ private boolean matches(Test test) { } if (!features.containsAll(requirements.getPresentFeatures())) { if (logger.isLoggable(FINER)) { - Set> missingFeatures = Helpers.copyToSet(requirements.getPresentFeatures()); + Set> missingFeatures = copyToSet(requirements.getPresentFeatures()); missingFeatures.removeAll(features); logger.finer( Platform.format( @@ -240,7 +249,7 @@ private boolean matches(Test test) { } if (intersect(features, requirements.getAbsentFeatures())) { if (logger.isLoggable(FINER)) { - Set> unwantedFeatures = Helpers.copyToSet(requirements.getAbsentFeatures()); + Set> unwantedFeatures = copyToSet(requirements.getAbsentFeatures()); unwantedFeatures.retainAll(features); logger.finer( Platform.format( @@ -258,18 +267,18 @@ private static boolean intersect(Set a, Set b) { private static Method extractMethod(Test test) { if (test instanceof AbstractTester) { AbstractTester tester = (AbstractTester) test; - return Helpers.getMethod(tester.getClass(), tester.getTestMethodName()); + return getMethod(tester.getClass(), tester.getTestMethodName()); } else if (test instanceof TestCase) { TestCase testCase = (TestCase) test; - return Helpers.getMethod(testCase.getClass(), testCase.getName()); + return getMethod(testCase.getClass(), testCase.getName()); } else { throw new IllegalArgumentException("unable to extract method from test: not a TestCase."); } } protected TestSuite makeSuiteForTesterClass(Class> testerClass) { - final TestSuite candidateTests = new TestSuite(testerClass); - final TestSuite suite = filterSuite(candidateTests); + TestSuite candidateTests = new TestSuite(testerClass); + TestSuite suite = filterSuite(candidateTests); Enumeration allTests = suite.tests(); while (allTests.hasMoreElements()) { @@ -286,7 +295,7 @@ protected TestSuite makeSuiteForTesterClass(Class> t private TestSuite filterSuite(TestSuite suite) { TestSuite filtered = new TestSuite(suite.getName()); - final Enumeration tests = suite.tests(); + Enumeration tests = suite.tests(); while (tests.hasMoreElements()) { Test test = (Test) tests.nextElement(); if (matches(test)) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/Helpers.java b/android/guava-testlib/src/com/google/common/collect/testing/Helpers.java index 8efafc373525..8bdc01b6b2a2 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/Helpers.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/Helpers.java @@ -16,17 +16,23 @@ package com.google.common.collect.testing; +import static java.lang.Math.max; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; import static java.util.Collections.sort; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.AbstractList; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -37,41 +43,43 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import junit.framework.Assert; -import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class Helpers { - // Clone of Objects.equal - static boolean equal(Object a, Object b) { + // Clone of Objects.equals + static boolean equal(@Nullable Object a, @Nullable Object b) { return a == b || (a != null && a.equals(b)); } // Clone of Lists.newArrayList - public static List copyToList(Iterable elements) { - List list = new ArrayList(); + public static List copyToList(Iterable elements) { + List list = new ArrayList<>(); addAll(list, elements); return list; } - public static List copyToList(E[] elements) { - return copyToList(Arrays.asList(elements)); + public static List copyToList(E[] elements) { + return copyToList(asList(elements)); } // Clone of Sets.newLinkedHashSet - public static Set copyToSet(Iterable elements) { - Set set = new LinkedHashSet(); + public static Set copyToSet(Iterable elements) { + Set set = new LinkedHashSet<>(); addAll(set, elements); return set; } - public static Set copyToSet(E[] elements) { - return copyToSet(Arrays.asList(elements)); + public static Set copyToSet(E[] elements) { + return copyToSet(asList(elements)); } // Would use Maps.immutableEntry - public static Entry mapEntry(K key, V value) { - return Collections.singletonMap(key, value).entrySet().iterator().next(); + public static Entry mapEntry( + K key, V value) { + return singletonMap(key, value).entrySet().iterator().next(); } private static boolean isEmpty(Iterable iterable) { @@ -82,13 +90,13 @@ private static boolean isEmpty(Iterable iterable) { public static void assertEmpty(Iterable iterable) { if (!isEmpty(iterable)) { - Assert.fail("Not true that " + iterable + " is empty"); + fail("Not true that " + iterable + " is empty"); } } public static void assertEmpty(Map map) { if (!map.isEmpty()) { - Assert.fail("Not true that " + map + " is empty"); + fail("Not true that " + map + " is empty"); } } @@ -98,7 +106,7 @@ public static void assertEqualInOrder(Iterable expected, Iterable actual) while (expectedIter.hasNext() && actualIter.hasNext()) { if (!equal(expectedIter.next(), actualIter.next())) { - Assert.fail( + fail( "contents were not equal and in the same order: " + "expected = " + expected @@ -109,7 +117,7 @@ public static void assertEqualInOrder(Iterable expected, Iterable actual) if (expectedIter.hasNext() || actualIter.hasNext()) { // actual either had too few or too many elements - Assert.fail( + fail( "contents were not equal and in the same order: " + "expected = " + expected @@ -119,7 +127,7 @@ public static void assertEqualInOrder(Iterable expected, Iterable actual) } public static void assertContentsInOrder(Iterable actual, Object... expected) { - assertEqualInOrder(Arrays.asList(expected), actual); + assertEqualInOrder(asList(expected), actual); } public static void assertEqualIgnoringOrder(Iterable expected, Iterable actual) { @@ -133,7 +141,7 @@ public static void assertEqualIgnoringOrder(Iterable expected, Iterable ac // Yeah it's n^2. for (Object object : exp) { if (!act.remove(object)) { - Assert.fail( + fail( "did not contain expected element " + object + ", " @@ -147,7 +155,7 @@ public static void assertEqualIgnoringOrder(Iterable expected, Iterable ac } public static void assertContentsAnyOrder(Iterable actual, Object... expected) { - assertEqualIgnoringOrder(Arrays.asList(expected), actual); + assertEqualIgnoringOrder(asList(expected), actual); } public static void assertContains(Iterable actual, Object expected) { @@ -164,23 +172,25 @@ public static void assertContains(Iterable actual, Object expected) { } if (!contained) { - Assert.fail("Not true that " + actual + " contains " + expected); + fail("Not true that " + actual + " contains " + expected); } } public static void assertContainsAllOf(Iterable actual, Object... expected) { - List expectedList = new ArrayList<>(Arrays.asList(expected)); + List expectedList = new ArrayList<>(asList(expected)); for (Object o : actual) { expectedList.remove(o); } if (!expectedList.isEmpty()) { - Assert.fail("Not true that " + actual + " contains all of " + Arrays.asList(expected)); + fail("Not true that " + actual + " contains all of " + asList(expected)); } } - public static boolean addAll(Collection addTo, Iterable elementsToAdd) { + @CanIgnoreReturnValue + public static boolean addAll( + Collection addTo, Iterable elementsToAdd) { boolean modified = false; for (E e : elementsToAdd) { modified |= addTo.add(e); @@ -188,12 +198,11 @@ public static boolean addAll(Collection addTo, Iterable elem return modified; } - static Iterable reverse(final List list) { - return new Iterable() { - @Override - public Iterator iterator() { - final ListIterator listIter = list.listIterator(list.size()); - return new Iterator() { + static Iterable reverse(List list) { + return () -> + new Iterator() { + private final ListIterator listIter = list.listIterator(list.size()); + @Override public boolean hasNext() { return listIter.hasPrevious(); @@ -209,11 +218,9 @@ public void remove() { listIter.remove(); } }; - } - }; } - static Iterator cycle(final Iterable iterable) { + static Iterator cycle(Iterable iterable) { return new Iterator() { Iterator iterator = Collections.emptySet().iterator(); @@ -237,30 +244,33 @@ public void remove() { }; } - static T get(Iterator iterator, int position) { + static T get(Iterator iterator, int position) { for (int i = 0; i < position; i++) { iterator.next(); } return iterator.next(); } - static void fail(Throwable cause, Object message) { - AssertionFailedError assertionFailedError = new AssertionFailedError(String.valueOf(message)); - assertionFailedError.initCause(cause); - throw assertionFailedError; + private static final class EntryComparator + implements Comparator> { + final @Nullable Comparator keyComparator; + + EntryComparator(@Nullable Comparator keyComparator) { + this.keyComparator = keyComparator; + } + + @Override + @SuppressWarnings("unchecked") // no less safe than putting it in the map! + public int compare(Entry a, Entry b) { + return (keyComparator == null) + ? ((Comparable) a.getKey()).compareTo(b.getKey()) + : keyComparator.compare(a.getKey(), b.getKey()); + } } - public static Comparator> entryComparator( - final Comparator keyComparator) { - return new Comparator>() { - @Override - @SuppressWarnings("unchecked") // no less safe than putting it in the map! - public int compare(Entry a, Entry b) { - return (keyComparator == null) - ? ((Comparable) a.getKey()).compareTo(b.getKey()) - : keyComparator.compare(a.getKey(), b.getKey()); - } - }; + public static + Comparator> entryComparator(@Nullable Comparator keyComparator) { + return new EntryComparator(keyComparator); } /** @@ -270,9 +280,9 @@ public int compare(Entry a, Entry b) { * * @see #testComparator(Comparator, List) */ - public static void testComparator( + public static void testComparator( Comparator comparator, T... valuesInExpectedOrder) { - testComparator(comparator, Arrays.asList(valuesInExpectedOrder)); + testComparator(comparator, asList(valuesInExpectedOrder)); } /** @@ -290,7 +300,7 @@ public static void testComparator( * valuesInExpectedOrder.get(i)} and {@code tj = valuesInExpectedOrder.get(j)}. * */ - public static void testComparator( + public static void testComparator( Comparator comparator, List valuesInExpectedOrder) { // This does an O(n^2) test of all pairs of values in both orders for (int i = 0; i < valuesInExpectedOrder.size(); i++) { @@ -345,14 +355,47 @@ public static > void testCompareToAndEquals( * @param delta the difference between the true size of the collection and the values returned by * the size method */ - public static Collection misleadingSizeCollection(final int delta) { + public static Collection misleadingSizeCollection(int delta) { // It would be nice to be able to return a real concurrent // collection like ConcurrentLinkedQueue, so that e.g. concurrent // iteration would work, but that would not be GWT-compatible. - return new ArrayList() { + // We are not "just" inheriting from ArrayList here as this doesn't work for J2kt. + return new AbstractList() { + final ArrayList data = new ArrayList<>(); + @Override public int size() { - return Math.max(0, super.size() + delta); + return max(0, data.size() + delta); + } + + @Override + public T get(int index) { + return data.get(index); + } + + @Override + public T set(int index, T element) { + return data.set(index, element); + } + + @Override + public boolean add(T element) { + return data.add(element); + } + + @Override + public void add(int index, T element) { + data.add(index, element); + } + + @Override + public T remove(int index) { + return data.remove(index); + } + + @Override + public @Nullable Object[] toArray() { + return data.toArray(); } }; } @@ -363,7 +406,8 @@ public int size() { * equals. This is used for testing unmodifiable collections of map entries; for example, it * should not be possible to access the raw (modifiable) map entry via a nefarious equals method. */ - public static Entry nefariousMapEntry(final K key, final V value) { + public static + Entry nefariousMapEntry(K key, V value) { return new Entry() { @Override public K getKey() { @@ -382,7 +426,7 @@ public V setValue(V value) { @SuppressWarnings("unchecked") @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof Entry) { Entry e = (Entry) o; e.setValue(value); // muhahaha! @@ -406,50 +450,34 @@ public String toString() { }; } - static List castOrCopyToList(Iterable iterable) { + static List castOrCopyToList(Iterable iterable) { if (iterable instanceof List) { return (List) iterable; } - List list = new ArrayList(); + List list = new ArrayList<>(); for (E e : iterable) { list.add(e); } return list; } - private static final Comparator NATURAL_ORDER = - new Comparator() { - @SuppressWarnings("unchecked") // assume any Comparable is Comparable - @Override - public int compare(Comparable left, Comparable right) { - return left.compareTo(right); - } - }; - - public static Iterable> orderEntriesByKey( - List> insertionOrder) { - sort(insertionOrder, Helpers.entryComparator(NATURAL_ORDER)); + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 + public static + Iterable> orderEntriesByKey(List> insertionOrder) { + @SuppressWarnings("unchecked") // assume any Comparable is Comparable + Comparator keyComparator = (Comparator) (o1, o2) -> o1.compareTo(o2); + sort(insertionOrder, entryComparator(keyComparator)); return insertionOrder; } - /** - * Private replacement for {@link com.google.gwt.user.client.rpc.GwtTransient} to work around - * build-system quirks. - */ - private @interface GwtTransient {} - /** * Compares strings in natural order except that null comes immediately before a given value. This * works better than Ordering.natural().nullsFirst() because, if null comes before all other * values, it lies outside the submap/submultiset ranges we test, and the variety of tests that * exercise null handling fail on those subcollections. */ - public abstract static class NullsBefore implements Comparator, Serializable { - /* - * We don't serialize this class in GWT, so we don't care about whether GWT will serialize this - * field. - */ - @GwtTransient private final String justAfterNull; + public abstract static class NullsBefore implements Comparator<@Nullable String>, Serializable { + private final String justAfterNull; protected NullsBefore(String justAfterNull) { if (justAfterNull == null) { @@ -460,7 +488,7 @@ protected NullsBefore(String justAfterNull) { } @Override - public int compare(String lhs, String rhs) { + public int compare(@Nullable String lhs, @Nullable String rhs) { if (lhs == rhs) { return 0; } @@ -484,7 +512,7 @@ public int compare(String lhs, String rhs) { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof NullsBefore) { NullsBefore other = (NullsBefore) obj; return justAfterNull.equals(other.justAfterNull); @@ -514,6 +542,7 @@ private NullsBeforeTwo() { } } + @J2ktIncompatible @GwtIncompatible // reflection public static Method getMethod(Class clazz, String name) { try { @@ -522,4 +551,12 @@ public static Method getMethod(Class clazz, String name) { throw new IllegalArgumentException(e); } } + + /** + * Useless constructor for a class of static utility methods. + * + * @deprecated Do not instantiate this utility class. + */ + @Deprecated + public Helpers() {} } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java b/android/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java new file mode 100644 index 000000000000..330dbf7f3c28 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect.testing; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; +import org.jspecify.annotations.NullMarked; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@NullMarked +@interface IgnoreJRERequirement {} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/IteratorFeature.java b/android/guava-testlib/src/com/google/common/collect/testing/IteratorFeature.java index c447e2922e31..c1e502272dea 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/IteratorFeature.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/IteratorFeature.java @@ -16,10 +16,13 @@ package com.google.common.collect.testing; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableSet; + import com.google.common.annotations.GwtCompatible; -import java.util.Collections; -import java.util.EnumSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.ListIterator; import java.util.Set; @@ -49,12 +52,12 @@ public enum IteratorFeature { * A set containing none of the optional features of the {@link Iterator} or {@link ListIterator} * interfaces. */ - public static final Set UNMODIFIABLE = Collections.emptySet(); + public static final Set UNMODIFIABLE = emptySet(); /** * A set containing all of the optional features of the {@link Iterator} and {@link ListIterator} * interfaces. */ public static final Set MODIFIABLE = - Collections.unmodifiableSet(EnumSet.allOf(IteratorFeature.class)); + unmodifiableSet(new LinkedHashSet<>(asList(values()))); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/IteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/IteratorTester.java index fdc418a73745..416f934cb4e4 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/IteratorTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/IteratorTester.java @@ -19,6 +19,8 @@ import com.google.common.annotations.GwtCompatible; import java.util.Collections; import java.util.Iterator; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A utility for testing an Iterator implementation by comparing its behavior to that of a "known @@ -56,7 +58,7 @@ *

For example, to test {@link java.util.Collections#unmodifiableList(java.util.List) * Collections.unmodifiableList}'s iterator: * - *

{@code
+ * {@snippet :
  * List expectedElements =
  *     Arrays.asList("a", "b", "c", "d", "e");
  * List actualElements =
@@ -75,7 +77,7 @@
  *     };
  * iteratorTester.test();
  * iteratorTester.testForEachRemaining();
- * }
+ * } * *

Note: It is necessary to use {@code IteratorTester.KnownOrder} as shown above, rather * than {@code KnownOrder} directly, because otherwise the code cannot be compiled. @@ -84,7 +86,9 @@ * @author Chris Povirk */ @GwtCompatible -public abstract class IteratorTester extends AbstractIteratorTester> { +@NullMarked +public abstract class IteratorTester + extends AbstractIteratorTester> { /** * Creates an IteratorTester. * @@ -96,7 +100,7 @@ protected IteratorTester( Iterable features, Iterable expectedElements, KnownOrder knownOrder) { - super(steps, Collections.singleton(null), features, expectedElements, knownOrder, 0); + super(steps, Collections.singleton(null), features, expectedElements, knownOrder, 0); } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/ListIteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/ListIteratorTester.java index 126eb860780d..96c920f4a2ec 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/ListIteratorTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/ListIteratorTester.java @@ -20,6 +20,8 @@ import java.util.ArrayList; import java.util.List; import java.util.ListIterator; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A utility similar to {@link IteratorTester} for testing a {@link ListIterator} against a known @@ -35,7 +37,9 @@ * @author Chris Povirk */ @GwtCompatible -public abstract class ListIteratorTester extends AbstractIteratorTester> { +@NullMarked +public abstract class ListIteratorTester + extends AbstractIteratorTester> { protected ListIteratorTester( int steps, Iterable elementsToInsert, diff --git a/android/guava-testlib/src/com/google/common/collect/testing/ListTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/ListTestSuiteBuilder.java index e01056459338..e20f96b5857c 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/ListTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/ListTestSuiteBuilder.java @@ -16,6 +16,7 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS; @@ -63,9 +64,10 @@ public static ListTestSuiteBuilder using(TestListGenerator generator) return new ListTestSuiteBuilder().usingGenerator(generator); } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(CollectionSerializationEqualTester.class); testers.add(ListAddAllAtIndexTester.class); @@ -112,12 +114,14 @@ protected List createDerivedSuites( .named(getName() + " reserialized") .withFeatures(computeReserializedCollectionFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } return derivedSuites; } - static class ReserializedListGenerator implements TestListGenerator { + private static final class ReserializedListGenerator implements TestListGenerator { final OneSizeTestContainerGenerator, E> gen; private ReserializedListGenerator(OneSizeTestContainerGenerator, E> gen) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/MapInterfaceTest.java b/android/guava-testlib/src/com/google/common/collect/testing/MapInterfaceTest.java index b8b5c28fb936..f62bcadc668a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/MapInterfaceTest.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/MapInterfaceTest.java @@ -16,18 +16,25 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.mapEntry; +import static com.google.common.collect.testing.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; import com.google.common.annotations.GwtCompatible; -import java.util.Arrays; +import com.google.common.annotations.J2ktIncompatible; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests representing the contract of {@link Map}. Concrete subclasses of this base class test @@ -42,7 +49,9 @@ // check the order if so. // TODO: Refactor to share code with SetTestBuilder etc. @GwtCompatible -public abstract class MapInterfaceTest extends TestCase { +@NullMarked +public abstract class MapInterfaceTest + extends TestCase { /** A key type that is not assignable to any classes but Object. */ private static final class IncompatibleKeyType { @@ -143,14 +152,15 @@ protected Map makeEitherMap() { } } + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception protected final boolean supportsValuesHashCode(Map map) { // get the first non-null value Collection values = map.values(); for (V value : values) { if (value != null) { try { - value.hashCode(); - } catch (Exception e) { + int unused = value.hashCode(); + } catch (Exception e) { // sneaky checked exception return false; } return true; @@ -183,7 +193,7 @@ protected final void assertInvariants(Map map) { assertTrue(map.containsKey(key)); assertTrue(map.containsValue(value)); assertTrue(valueCollection.contains(value)); - assertTrue(valueCollection.containsAll(Collections.singleton(value))); + assertTrue(valueCollection.containsAll(singleton(value))); assertTrue(entrySet.contains(mapEntry(key, value))); assertTrue(allowsNullKeys || (key != null)); } @@ -221,23 +231,23 @@ protected final void assertInvariants(Map map) { Object[] entrySetToArray1 = entrySet.toArray(); assertEquals(map.size(), entrySetToArray1.length); - assertTrue(Arrays.asList(entrySetToArray1).containsAll(entrySet)); + assertTrue(asList(entrySetToArray1).containsAll(entrySet)); Entry[] entrySetToArray2 = new Entry[map.size() + 2]; entrySetToArray2[map.size()] = mapEntry("foo", 1); assertSame(entrySetToArray2, entrySet.toArray(entrySetToArray2)); assertNull(entrySetToArray2[map.size()]); - assertTrue(Arrays.asList(entrySetToArray2).containsAll(entrySet)); + assertTrue(asList(entrySetToArray2).containsAll(entrySet)); Object[] valuesToArray1 = valueCollection.toArray(); assertEquals(map.size(), valuesToArray1.length); - assertTrue(Arrays.asList(valuesToArray1).containsAll(valueCollection)); + assertTrue(asList(valuesToArray1).containsAll(valueCollection)); Object[] valuesToArray2 = new Object[map.size() + 2]; valuesToArray2[map.size()] = "foo"; assertSame(valuesToArray2, valueCollection.toArray(valuesToArray2)); assertNull(valuesToArray2[map.size()]); - assertTrue(Arrays.asList(valuesToArray2).containsAll(valueCollection)); + assertTrue(asList(valuesToArray2).containsAll(valueCollection)); if (supportsValuesHashCode) { int expectedHash = 0; @@ -265,7 +275,7 @@ private void assertEntrySetNotContainsString(Set> entrySet) { protected void assertMoreInvariants(Map map) {} public void testClear() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -276,18 +286,15 @@ public void testClear() { map.clear(); assertTrue(map.isEmpty()); } else { - try { - map.clear(); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, map::clear); } assertInvariants(map); } + @J2ktIncompatible // https://youtrack.jetbrains.com/issue/KT-58242/ undefined behavior (crash) public void testContainsKey() { - final Map map; - final K unmappedKey; + Map map; + K unmappedKey; try { map = makePopulatedMap(); unmappedKey = getKeyNotInPopulatedMap(); @@ -312,8 +319,8 @@ public void testContainsKey() { } public void testContainsValue() { - final Map map; - final V unmappedValue; + Map map; + V unmappedValue; try { map = makePopulatedMap(); unmappedValue = getValueNotInPopulatedMap(); @@ -334,8 +341,7 @@ public void testContainsValue() { } public void testEntrySet() { - final Map map; - final Set> entrySet; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -343,9 +349,9 @@ public void testEntrySet() { } assertInvariants(map); - entrySet = map.entrySet(); - final K unmappedKey; - final V unmappedValue; + Set> entrySet = map.entrySet(); + K unmappedKey; + V unmappedValue; try { unmappedKey = getKeyNotInPopulatedMap(); unmappedValue = getValueNotInPopulatedMap(); @@ -359,7 +365,7 @@ public void testEntrySet() { } public void testEntrySetForEmptyMap() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -368,9 +374,9 @@ public void testEntrySetForEmptyMap() { assertInvariants(map); } + @J2ktIncompatible // https://youtrack.jetbrains.com/issue/KT-58242/ undefined behavior (crash) public void testEntrySetContainsEntryIncompatibleKey() { - final Map map; - final Set> entrySet; + Map map; try { map = makeEitherMap(); } catch (UnsupportedOperationException e) { @@ -378,8 +384,8 @@ public void testEntrySetContainsEntryIncompatibleKey() { } assertInvariants(map); - entrySet = map.entrySet(); - final V unmappedValue; + Set> entrySet = map.entrySet(); + V unmappedValue; try { unmappedValue = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { @@ -396,8 +402,7 @@ public void testEntrySetContainsEntryNullKeyPresent() { if (!allowsNullKeys || !supportsPut) { return; } - final Map map; - final Set> entrySet; + Map map; try { map = makeEitherMap(); } catch (UnsupportedOperationException e) { @@ -405,8 +410,8 @@ public void testEntrySetContainsEntryNullKeyPresent() { } assertInvariants(map); - entrySet = map.entrySet(); - final V unmappedValue; + Set> entrySet = map.entrySet(); + V unmappedValue; try { unmappedValue = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { @@ -414,14 +419,14 @@ public void testEntrySetContainsEntryNullKeyPresent() { } map.put(null, unmappedValue); - Entry entry = mapEntry(null, unmappedValue); + Entry<@Nullable K, V> entry = mapEntry(null, unmappedValue); assertTrue(entrySet.contains(entry)); - assertFalse(entrySet.contains(mapEntry(null, null))); + Entry<@Nullable K, @Nullable V> nonEntry = mapEntry(null, null); + assertFalse(entrySet.contains(nonEntry)); } public void testEntrySetContainsEntryNullKeyMissing() { - final Map map; - final Set> entrySet; + Map map; try { map = makeEitherMap(); } catch (UnsupportedOperationException e) { @@ -429,28 +434,29 @@ public void testEntrySetContainsEntryNullKeyMissing() { } assertInvariants(map); - entrySet = map.entrySet(); - final V unmappedValue; + Set> entrySet = map.entrySet(); + V unmappedValue; try { unmappedValue = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { return; } - Entry entry = mapEntry(null, unmappedValue); + Entry<@Nullable K, V> nullKeyEntry = mapEntry(null, unmappedValue); try { - assertFalse(entrySet.contains(entry)); + assertFalse(entrySet.contains(nullKeyEntry)); } catch (NullPointerException e) { assertFalse(allowsNullKeys); } + Entry<@Nullable K, @Nullable V> nullKeyValueEntry = mapEntry(null, null); try { - assertFalse(entrySet.contains(mapEntry(null, null))); + assertFalse(entrySet.contains(nullKeyValueEntry)); } catch (NullPointerException e) { assertFalse(allowsNullKeys && allowsNullValues); } } public void testEntrySetIteratorRemove() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -462,7 +468,7 @@ public void testEntrySetIteratorRemove() { if (supportsIteratorRemove) { int initialSize = map.size(); Entry entry = iterator.next(); - Entry entryCopy = Helpers.mapEntry(entry.getKey(), entry.getValue()); + Entry entryCopy = mapEntry(entry.getKey(), entry.getValue()); iterator.remove(); assertEquals(initialSize - 1, map.size()); @@ -471,24 +477,16 @@ public void testEntrySetIteratorRemove() { // iterator.remove(). assertFalse(entrySet.contains(entryCopy)); assertInvariants(map); - try { - iterator.remove(); - fail("Expected IllegalStateException."); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, iterator::remove); } else { - try { - iterator.next(); - iterator.remove(); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + iterator.next(); + assertThrows(UnsupportedOperationException.class, iterator::remove); } assertInvariants(map); } public void testEntrySetRemove() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -502,18 +500,15 @@ public void testEntrySetRemove() { assertTrue(didRemove); assertEquals(initialSize - 1, map.size()); } else { - try { - entrySet.remove(entrySet.iterator().next()); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> entrySet.remove(entrySet.iterator().next())); } assertInvariants(map); } public void testEntrySetRemoveMissingKey() { - final Map map; - final K key; + Map map; + K key; try { map = makeEitherMap(); key = getKeyNotInPopulatedMap(); @@ -540,7 +535,7 @@ public void testEntrySetRemoveMissingKey() { } public void testEntrySetRemoveDifferentValue() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -570,8 +565,7 @@ public void testEntrySetRemoveNullKeyPresent() { if (!allowsNullKeys || !supportsPut || !supportsRemove) { return; } - final Map map; - final Set> entrySet; + Map map; try { map = makeEitherMap(); } catch (UnsupportedOperationException e) { @@ -579,8 +573,8 @@ public void testEntrySetRemoveNullKeyPresent() { } assertInvariants(map); - entrySet = map.entrySet(); - final V unmappedValue; + Set> entrySet = map.entrySet(); + V unmappedValue; try { unmappedValue = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { @@ -590,14 +584,14 @@ public void testEntrySetRemoveNullKeyPresent() { map.put(null, unmappedValue); assertEquals(unmappedValue, map.get(null)); assertTrue(map.containsKey(null)); - Entry entry = mapEntry(null, unmappedValue); + Entry<@Nullable K, V> entry = mapEntry(null, unmappedValue); assertTrue(entrySet.remove(entry)); assertNull(map.get(null)); assertFalse(map.containsKey(null)); } public void testEntrySetRemoveNullKeyMissing() { - final Map map; + Map map; try { map = makeEitherMap(); } catch (UnsupportedOperationException e) { @@ -605,7 +599,7 @@ public void testEntrySetRemoveNullKeyMissing() { } Set> entrySet = map.entrySet(); - Entry entry = mapEntry(null, getValueNotInPopulatedMap()); + Entry<@Nullable K, V> entry = mapEntry(null, getValueNotInPopulatedMap()); int initialSize = map.size(); if (supportsRemove) { try { @@ -626,7 +620,7 @@ public void testEntrySetRemoveNullKeyMissing() { } public void testEntrySetRemoveAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -641,8 +635,7 @@ public void testEntrySetRemoveAll() { // We use a copy of "entryToRemove" in the assertion because "entryToRemove" might be // invalidated and have undefined behavior after entrySet.removeAll(entriesToRemove), // for example entryToRemove.getValue() might be null. - Entry entryToRemoveCopy = - Helpers.mapEntry(entryToRemove.getKey(), entryToRemove.getValue()); + Entry entryToRemoveCopy = mapEntry(entryToRemove.getKey(), entryToRemove.getValue()); int initialSize = map.size(); boolean didRemove = entrySet.removeAll(entriesToRemove); @@ -653,17 +646,13 @@ public void testEntrySetRemoveAll() { // have undefined behavior after entrySet.removeAll(entriesToRemove), assertFalse(entrySet.contains(entryToRemoveCopy)); } else { - try { - entrySet.removeAll(entriesToRemove); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> entrySet.removeAll(entriesToRemove)); } assertInvariants(map); } public void testEntrySetRemoveAllNullFromEmpty() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -672,11 +661,7 @@ public void testEntrySetRemoveAllNullFromEmpty() { Set> entrySet = map.entrySet(); if (supportsRemove) { - try { - entrySet.removeAll(null); - fail("Expected NullPointerException."); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> entrySet.removeAll(null)); } else { try { entrySet.removeAll(null); @@ -689,7 +674,7 @@ public void testEntrySetRemoveAllNullFromEmpty() { } public void testEntrySetRetainAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -697,9 +682,12 @@ public void testEntrySetRetainAll() { } Set> entrySet = map.entrySet(); - Set> entriesToRetain = singleton(entrySet.iterator().next()); + Entry originalEntry = entrySet.iterator().next(); + // Copy the Entry, as discussed in testEntrySetRemoveAll. + Set> entriesToRetain = + singleton(mapEntry(originalEntry.getKey(), originalEntry.getValue())); if (supportsRemove) { - boolean shouldRemove = (entrySet.size() > entriesToRetain.size()); + boolean shouldRemove = entrySet.size() > entriesToRetain.size(); boolean didRemove = entrySet.retainAll(entriesToRetain); assertEquals(shouldRemove, didRemove); assertEquals(entriesToRetain.size(), map.size()); @@ -707,17 +695,13 @@ public void testEntrySetRetainAll() { assertTrue(entrySet.contains(entry)); } } else { - try { - entrySet.retainAll(entriesToRetain); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> entrySet.retainAll(entriesToRetain)); } assertInvariants(map); } public void testEntrySetRetainAllNullFromEmpty() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -729,7 +713,7 @@ public void testEntrySetRetainAllNullFromEmpty() { try { entrySet.retainAll(null); // Returning successfully is not ideal, but tolerated. - } catch (NullPointerException expected) { + } catch (NullPointerException tolerated) { } } else { try { @@ -743,7 +727,7 @@ public void testEntrySetRetainAllNullFromEmpty() { } public void testEntrySetClear() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -755,22 +739,18 @@ public void testEntrySetClear() { entrySet.clear(); assertTrue(entrySet.isEmpty()); } else { - try { - entrySet.clear(); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, entrySet::clear); } assertInvariants(map); } public void testEntrySetAddAndAddAll() { - final Map map = makeEitherMap(); + Map map = makeEitherMap(); Set> entrySet = map.entrySet(); - final Entry entryToAdd = mapEntry(null, null); + Entry<@Nullable K, @Nullable V> entryToAdd = mapEntry(null, null); try { - entrySet.add(entryToAdd); + entrySet.add((Entry) entryToAdd); fail("Expected UnsupportedOperationException or NullPointerException."); } catch (UnsupportedOperationException | NullPointerException e) { // Expected. @@ -778,7 +758,7 @@ public void testEntrySetAddAndAddAll() { assertInvariants(map); try { - entrySet.addAll(singleton(entryToAdd)); + entrySet.addAll(singleton((Entry) entryToAdd)); fail("Expected UnsupportedOperationException or NullPointerException."); } catch (UnsupportedOperationException | NullPointerException e) { // Expected. @@ -793,8 +773,8 @@ public void testEntrySetSetValue() { return; } - final Map map; - final V valueToSet; + Map map; + V valueToSet; try { map = makePopulatedMap(); valueToSet = getValueNotInPopulatedMap(); @@ -804,8 +784,8 @@ public void testEntrySetSetValue() { Set> entrySet = map.entrySet(); Entry entry = entrySet.iterator().next(); - final V oldValue = entry.getValue(); - final V returnedValue = entry.setValue(valueToSet); + V oldValue = entry.getValue(); + V returnedValue = entry.setValue(valueToSet); assertEquals(oldValue, returnedValue); assertTrue(entrySet.contains(mapEntry(entry.getKey(), valueToSet))); assertEquals(valueToSet, map.get(entry.getKey())); @@ -819,7 +799,7 @@ public void testEntrySetSetValueSameValue() { return; } - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -828,8 +808,8 @@ public void testEntrySetSetValueSameValue() { Set> entrySet = map.entrySet(); Entry entry = entrySet.iterator().next(); - final V oldValue = entry.getValue(); - final V returnedValue = entry.setValue(oldValue); + V oldValue = entry.getValue(); + V returnedValue = entry.setValue(oldValue); assertEquals(oldValue, returnedValue); assertTrue(entrySet.contains(mapEntry(entry.getKey(), oldValue))); assertEquals(oldValue, map.get(entry.getKey())); @@ -837,16 +817,17 @@ public void testEntrySetSetValueSameValue() { } public void testEqualsForEqualMap() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { return; } - assertEquals(map, map); - assertEquals(makePopulatedMap(), map); - assertFalse(map.equals(Collections.emptyMap())); + // Explicitly call `equals`; `assertEquals` might return fast + assertTrue(map.equals(map)); + assertTrue(makePopulatedMap().equals(map)); + assertFalse(map.equals(emptyMap())); // no-inspection ObjectEqualsNull assertFalse(map.equals(null)); } @@ -856,8 +837,8 @@ public void testEqualsForLargerMap() { return; } - final Map map; - final Map largerMap; + Map map; + Map largerMap; try { map = makePopulatedMap(); largerMap = makePopulatedMap(); @@ -874,8 +855,8 @@ public void testEqualsForSmallerMap() { return; } - final Map map; - final Map smallerMap; + Map map; + Map smallerMap; try { map = makePopulatedMap(); smallerMap = makePopulatedMap(); @@ -888,23 +869,24 @@ public void testEqualsForSmallerMap() { } public void testEqualsForEmptyMap() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { return; } - assertEquals(map, map); - assertEquals(makeEmptyMap(), map); - assertEquals(Collections.emptyMap(), map); - assertFalse(map.equals(Collections.emptySet())); + // Explicitly call `equals`; `assertEquals` might return fast + assertTrue(map.equals(map)); + assertTrue(makeEmptyMap().equals(map)); + assertEquals(emptyMap(), map); + assertFalse(map.equals(emptySet())); // noinspection ObjectEqualsNull assertFalse(map.equals(null)); } public void testGet() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -925,7 +907,7 @@ public void testGet() { } public void testGetForEmptyMap() { - final Map map; + Map map; K unmappedKey = null; try { map = makeEmptyMap(); @@ -946,7 +928,7 @@ public void testGetNull() { } } else { try { - map.get(null); + V unused = map.get(null); } catch (NullPointerException optional) { } } @@ -954,7 +936,7 @@ public void testGetNull() { } public void testHashCode() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -964,7 +946,7 @@ public void testHashCode() { } public void testHashCodeForEmptyMap() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -974,9 +956,9 @@ public void testHashCodeForEmptyMap() { } public void testPutNewKey() { - final Map map = makeEitherMap(); - final K keyToPut; - final V valueToPut; + Map map = makeEitherMap(); + K keyToPut; + V valueToPut; try { keyToPut = getKeyNotInPopulatedMap(); valueToPut = getValueNotInPopulatedMap(); @@ -992,26 +974,21 @@ public void testPutNewKey() { assertEquals(initialSize + 1, map.size()); assertNull(oldValue); } else { - try { - map.put(keyToPut, valueToPut); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.put(keyToPut, valueToPut)); } assertInvariants(map); } public void testPutExistingKey() { - final Map map; - final K keyToPut; - final V valueToPut; + Map map; + V valueToPut; try { map = makePopulatedMap(); valueToPut = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { return; } - keyToPut = map.keySet().iterator().next(); + K keyToPut = map.keySet().iterator().next(); if (supportsPut) { int initialSize = map.size(); map.put(keyToPut, valueToPut); @@ -1020,11 +997,7 @@ public void testPutExistingKey() { assertTrue(map.containsValue(valueToPut)); assertEquals(initialSize, map.size()); } else { - try { - map.put(keyToPut, valueToPut); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.put(keyToPut, valueToPut)); } assertInvariants(map); } @@ -1033,26 +1006,22 @@ public void testPutNullKey() { if (!supportsPut) { return; } - final Map map = makeEitherMap(); - final V valueToPut; + Map map = makeEitherMap(); + V valueToPut; try { valueToPut = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { return; } if (allowsNullKeys) { - final V oldValue = map.get(null); - final V returnedValue = map.put(null, valueToPut); + V oldValue = map.get(null); + V returnedValue = map.put(null, valueToPut); assertEquals(oldValue, returnedValue); assertEquals(valueToPut, map.get(null)); assertTrue(map.containsKey(null)); assertTrue(map.containsValue(valueToPut)); } else { - try { - map.put(null, valueToPut); - fail("Expected RuntimeException"); - } catch (RuntimeException expected) { - } + assertThrows(RuntimeException.class, () -> map.put(null, valueToPut)); } assertInvariants(map); } @@ -1061,8 +1030,8 @@ public void testPutNullValue() { if (!supportsPut) { return; } - final Map map = makeEitherMap(); - final K keyToPut; + Map map = makeEitherMap(); + K keyToPut; try { keyToPut = getKeyNotInPopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1070,19 +1039,15 @@ public void testPutNullValue() { } if (allowsNullValues) { int initialSize = map.size(); - final V oldValue = map.get(keyToPut); - final V returnedValue = map.put(keyToPut, null); + V oldValue = map.get(keyToPut); + V returnedValue = map.put(keyToPut, null); assertEquals(oldValue, returnedValue); assertNull(map.get(keyToPut)); assertTrue(map.containsKey(keyToPut)); assertTrue(map.containsValue(null)); assertEquals(initialSize + 1, map.size()); } else { - try { - map.put(keyToPut, null); - fail("Expected RuntimeException"); - } catch (RuntimeException expected) { - } + assertThrows(RuntimeException.class, () -> map.put(keyToPut, null)); } assertInvariants(map); } @@ -1091,8 +1056,8 @@ public void testPutNullValueForExistingKey() { if (!supportsPut) { return; } - final Map map; - final K keyToPut; + Map map; + K keyToPut; try { map = makePopulatedMap(); keyToPut = map.keySet().iterator().next(); @@ -1101,34 +1066,30 @@ public void testPutNullValueForExistingKey() { } if (allowsNullValues) { int initialSize = map.size(); - final V oldValue = map.get(keyToPut); - final V returnedValue = map.put(keyToPut, null); + V oldValue = map.get(keyToPut); + V returnedValue = map.put(keyToPut, null); assertEquals(oldValue, returnedValue); assertNull(map.get(keyToPut)); assertTrue(map.containsKey(keyToPut)); assertTrue(map.containsValue(null)); assertEquals(initialSize, map.size()); } else { - try { - map.put(keyToPut, null); - fail("Expected RuntimeException"); - } catch (RuntimeException expected) { - } + assertThrows(RuntimeException.class, () -> map.put(keyToPut, null)); } assertInvariants(map); } public void testPutAllNewKey() { - final Map map = makeEitherMap(); - final K keyToPut; - final V valueToPut; + Map map = makeEitherMap(); + K keyToPut; + V valueToPut; try { keyToPut = getKeyNotInPopulatedMap(); valueToPut = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { return; } - final Map mapToPut = Collections.singletonMap(keyToPut, valueToPut); + Map mapToPut = singletonMap(keyToPut, valueToPut); if (supportsPut) { int initialSize = map.size(); map.putAll(mapToPut); @@ -1137,27 +1098,22 @@ public void testPutAllNewKey() { assertTrue(map.containsValue(valueToPut)); assertEquals(initialSize + 1, map.size()); } else { - try { - map.putAll(mapToPut); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.putAll(mapToPut)); } assertInvariants(map); } public void testPutAllExistingKey() { - final Map map; - final K keyToPut; - final V valueToPut; + Map map; + V valueToPut; try { map = makePopulatedMap(); valueToPut = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { return; } - keyToPut = map.keySet().iterator().next(); - final Map mapToPut = Collections.singletonMap(keyToPut, valueToPut); + K keyToPut = map.keySet().iterator().next(); + Map mapToPut = singletonMap(keyToPut, valueToPut); int initialSize = map.size(); if (supportsPut) { map.putAll(mapToPut); @@ -1165,25 +1121,20 @@ public void testPutAllExistingKey() { assertTrue(map.containsKey(keyToPut)); assertTrue(map.containsValue(valueToPut)); } else { - try { - map.putAll(mapToPut); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.putAll(mapToPut)); } assertEquals(initialSize, map.size()); assertInvariants(map); } public void testRemove() { - final Map map; - final K keyToRemove; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { return; } - keyToRemove = map.keySet().iterator().next(); + K keyToRemove = map.keySet().iterator().next(); if (supportsRemove) { int initialSize = map.size(); V expectedValue = map.get(keyToRemove); @@ -1192,18 +1143,14 @@ public void testRemove() { assertFalse(map.containsKey(keyToRemove)); assertEquals(initialSize - 1, map.size()); } else { - try { - map.remove(keyToRemove); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.remove(keyToRemove)); } assertInvariants(map); } public void testRemoveMissingKey() { - final Map map; - final K keyToRemove; + Map map; + K keyToRemove; try { map = makePopulatedMap(); keyToRemove = getKeyNotInPopulatedMap(); @@ -1215,11 +1162,7 @@ public void testRemoveMissingKey() { assertNull(map.remove(keyToRemove)); assertEquals(initialSize, map.size()); } else { - try { - map.remove(keyToRemove); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.remove(keyToRemove)); } assertInvariants(map); } @@ -1229,7 +1172,7 @@ public void testSize() { } public void testKeySetRemove() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1244,17 +1187,13 @@ public void testKeySetRemove() { assertEquals(initialSize - 1, map.size()); assertFalse(map.containsKey(key)); } else { - try { - keys.remove(key); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> keys.remove(key)); } assertInvariants(map); } public void testKeySetRemoveAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1265,21 +1204,17 @@ public void testKeySetRemoveAll() { K key = keys.iterator().next(); if (supportsRemove) { int initialSize = map.size(); - assertTrue(keys.removeAll(Collections.singleton(key))); + assertTrue(keys.removeAll(singleton(key))); assertEquals(initialSize - 1, map.size()); assertFalse(map.containsKey(key)); } else { - try { - keys.removeAll(Collections.singleton(key)); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> keys.removeAll(singleton(key))); } assertInvariants(map); } public void testKeySetRetainAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1289,21 +1224,17 @@ public void testKeySetRetainAll() { Set keys = map.keySet(); K key = keys.iterator().next(); if (supportsRemove) { - keys.retainAll(Collections.singleton(key)); + keys.retainAll(singleton(key)); assertEquals(1, map.size()); assertTrue(map.containsKey(key)); } else { - try { - keys.retainAll(Collections.singleton(key)); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> keys.retainAll(singleton(key))); } assertInvariants(map); } public void testKeySetClear() { - final Map map; + Map map; try { map = makeEitherMap(); } catch (UnsupportedOperationException e) { @@ -1315,17 +1246,13 @@ public void testKeySetClear() { keySet.clear(); assertTrue(keySet.isEmpty()); } else { - try { - keySet.clear(); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, keySet::clear); } assertInvariants(map); } public void testKeySetRemoveAllNullFromEmpty() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -1334,11 +1261,7 @@ public void testKeySetRemoveAllNullFromEmpty() { Set keySet = map.keySet(); if (supportsRemove) { - try { - keySet.removeAll(null); - fail("Expected NullPointerException."); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> keySet.removeAll(null)); } else { try { keySet.removeAll(null); @@ -1351,7 +1274,7 @@ public void testKeySetRemoveAllNullFromEmpty() { } public void testKeySetRetainAllNullFromEmpty() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -1363,7 +1286,7 @@ public void testKeySetRetainAllNullFromEmpty() { try { keySet.retainAll(null); // Returning successfully is not ideal, but tolerated. - } catch (NullPointerException expected) { + } catch (NullPointerException tolerated) { } } else { try { @@ -1377,8 +1300,7 @@ public void testKeySetRetainAllNullFromEmpty() { } public void testValues() { - final Map map; - final Collection valueCollection; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1386,8 +1308,8 @@ public void testValues() { } assertInvariants(map); - valueCollection = map.values(); - final V unmappedValue; + Collection valueCollection = map.values(); + V unmappedValue; try { unmappedValue = getValueNotInPopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1399,7 +1321,7 @@ public void testValues() { } public void testValuesIteratorRemove() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1417,24 +1339,16 @@ public void testValuesIteratorRemove() { // removed value, because the underlying map can have multiple mappings // to the same value.) assertInvariants(map); - try { - iterator.remove(); - fail("Expected IllegalStateException."); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, iterator::remove); } else { - try { - iterator.next(); - iterator.remove(); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + iterator.next(); + assertThrows(UnsupportedOperationException.class, iterator::remove); } assertInvariants(map); } public void testValuesRemove() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1450,18 +1364,16 @@ public void testValuesRemove() { // removed value, because the underlying map can have multiple mappings // to the same value.) } else { - try { - valueCollection.remove(valueCollection.iterator().next()); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, + () -> valueCollection.remove(valueCollection.iterator().next())); } assertInvariants(map); } public void testValuesRemoveMissing() { - final Map map; - final V valueToRemove; + Map map; + V valueToRemove; try { map = makeEitherMap(); valueToRemove = getValueNotInPopulatedMap(); @@ -1485,7 +1397,7 @@ public void testValuesRemoveMissing() { } public void testValuesRemoveAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1503,17 +1415,14 @@ public void testValuesRemoveAll() { assertFalse(valuesToRemove.contains(value)); } } else { - try { - valueCollection.removeAll(valuesToRemove); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> valueCollection.removeAll(valuesToRemove)); } assertInvariants(map); } public void testValuesRemoveAllNullFromEmpty() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -1525,7 +1434,7 @@ public void testValuesRemoveAllNullFromEmpty() { try { values.removeAll(null); // Returning successfully is not ideal, but tolerated. - } catch (NullPointerException expected) { + } catch (NullPointerException tolerated) { } } else { try { @@ -1539,7 +1448,7 @@ public void testValuesRemoveAllNullFromEmpty() { } public void testValuesRetainAll() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1557,17 +1466,14 @@ public void testValuesRetainAll() { assertTrue(valuesToRetain.contains(value)); } } else { - try { - valueCollection.retainAll(valuesToRetain); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> valueCollection.retainAll(valuesToRetain)); } assertInvariants(map); } public void testValuesRetainAllNullFromEmpty() { - final Map map; + Map map; try { map = makeEmptyMap(); } catch (UnsupportedOperationException e) { @@ -1579,7 +1485,7 @@ public void testValuesRetainAllNullFromEmpty() { try { values.retainAll(null); // Returning successfully is not ideal, but tolerated. - } catch (NullPointerException expected) { + } catch (NullPointerException tolerated) { } } else { try { @@ -1593,7 +1499,7 @@ public void testValuesRetainAllNullFromEmpty() { } public void testValuesClear() { - final Map map; + Map map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -1605,16 +1511,8 @@ public void testValuesClear() { valueCollection.clear(); assertTrue(valueCollection.isEmpty()); } else { - try { - valueCollection.clear(); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, valueCollection::clear); } assertInvariants(map); } - - static Entry mapEntry(K key, V value) { - return Collections.singletonMap(key, value).entrySet().iterator().next(); - } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/MapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/MapTestSuiteBuilder.java index 7bb418f5a97b..7ebe6f489579 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/MapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/MapTestSuiteBuilder.java @@ -17,6 +17,7 @@ package com.google.common.collect.testing; import static com.google.common.collect.testing.DerivedCollectionGenerators.keySetGenerator; +import static com.google.common.collect.testing.Helpers.copyToSet; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.DerivedCollectionGenerators.MapEntrySetGenerator; @@ -62,10 +63,10 @@ public static MapTestSuiteBuilder using(TestMapGenerator gene return new MapTestSuiteBuilder().usingGenerator(generator); } - @SuppressWarnings("unchecked") // Class parameters must be raw. + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - return Arrays.>asList( + return Arrays.asList( MapClearTester.class, MapContainsKeyTester.class, MapContainsValueTester.class, @@ -101,6 +102,8 @@ protected List createDerivedSuites( .withFeatures(computeReserializedMapFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + " reserialized") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } @@ -110,6 +113,8 @@ protected List createDerivedSuites( .withFeatures(computeEntrySetFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + " entrySet") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); derivedSuites.add( @@ -117,6 +122,8 @@ protected List createDerivedSuites( .withFeatures(computeKeySetFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + " keys") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); derivedSuites.add( @@ -125,6 +132,8 @@ protected List createDerivedSuites( .named(parentBuilder.getName() + " values") .withFeatures(computeValuesCollectionFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); return derivedSuites; @@ -145,7 +154,7 @@ protected CollectionTestSuiteBuilder createDerivedValueCollectionSuite( } private static Set> computeReserializedMapFeatures(Set> mapFeatures) { - Set> derivedFeatures = Helpers.copyToSet(mapFeatures); + Set> derivedFeatures = copyToSet(mapFeatures); derivedFeatures.remove(CollectionFeature.SERIALIZABLE); derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS); return derivedFeatures; @@ -218,11 +227,10 @@ public static Set> computeCommonDerivedCollectionFeatures( return derivedFeatures; } - private static class ReserializedMapGenerator implements TestMapGenerator { + private static final class ReserializedMapGenerator implements TestMapGenerator { private final OneSizeTestContainerGenerator, Entry> mapGenerator; - public ReserializedMapGenerator( - OneSizeTestContainerGenerator, Entry> mapGenerator) { + ReserializedMapGenerator(OneSizeTestContainerGenerator, Entry> mapGenerator) { this.mapGenerator = mapGenerator; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/MinimalCollection.java b/android/guava-testlib/src/com/google/common/collect/testing/MinimalCollection.java index a6ec93d1eca3..93645d9ea7d2 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/MinimalCollection.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/MinimalCollection.java @@ -16,11 +16,16 @@ package com.google.common.collect.testing; +import static java.lang.System.arraycopy; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtCompatible; import java.util.AbstractCollection; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A simplistic collection which implements only the bare minimum allowed by the spec, and throws @@ -29,24 +34,26 @@ * @author Kevin Bourrillion */ @GwtCompatible -public class MinimalCollection extends AbstractCollection { +@NullMarked +public class MinimalCollection extends AbstractCollection { // TODO: expose allow nulls parameter? - public static MinimalCollection of(E... contents) { - return new MinimalCollection(Object.class, true, contents); + public static MinimalCollection of(E... contents) { + return new MinimalCollection<>(Object.class, true, contents); } // TODO: use this - public static MinimalCollection ofClassAndContents(Class type, E... contents) { - return new MinimalCollection(type, true, contents); + public static MinimalCollection ofClassAndContents( + Class type, E... contents) { + return new MinimalCollection<>(type, true, contents); } private final E[] contents; - private final Class type; + private final Class type; private final boolean allowNulls; // Package-private so that it can be extended. - MinimalCollection(Class type, boolean allowNulls, E... contents) { + MinimalCollection(Class type, boolean allowNulls, E... contents) { // TODO: consider making it shuffle the contents to test iteration order. this.contents = Platform.clone(contents); this.type = type; @@ -67,7 +74,7 @@ public int size() { } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { if (!allowNulls) { // behave badly if (object == null) { @@ -75,7 +82,7 @@ public boolean contains(Object object) { } } Platform.checkCast(type, object); // behave badly - return Arrays.asList(contents).contains(object); + return asList(contents).contains(object); } @Override @@ -93,13 +100,13 @@ public boolean containsAll(Collection collection) { @Override public Iterator iterator() { - return Arrays.asList(contents).iterator(); + return asList(contents).iterator(); } @Override - public Object[] toArray() { - Object[] result = new Object[contents.length]; - System.arraycopy(contents, 0, result, 0, contents.length); + public @Nullable Object[] toArray() { + @Nullable Object[] result = new @Nullable Object[contents.length]; + arraycopy(contents, 0, result, 0, contents.length); return result; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/MinimalIterable.java b/android/guava-testlib/src/com/google/common/collect/testing/MinimalIterable.java index c151c15e1319..1cc49efa3dcc 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/MinimalIterable.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/MinimalIterable.java @@ -16,10 +16,12 @@ package com.google.common.collect.testing; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtCompatible; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import org.jspecify.annotations.Nullable; /** * An implementation of {@code Iterable} which throws an exception on all invocations of the {@link @@ -47,11 +49,11 @@ * @author Kevin Bourrillion */ @GwtCompatible -public final class MinimalIterable implements Iterable { +public final class MinimalIterable implements Iterable { /** Returns an iterable whose iterator returns the given elements in order. */ - public static MinimalIterable of(E... elements) { + public static MinimalIterable of(E... elements) { // Make sure to get an unmodifiable iterator - return new MinimalIterable(Arrays.asList(elements).iterator()); + return new MinimalIterable<>(asList(elements).iterator()); } /** @@ -59,11 +61,11 @@ public static MinimalIterable of(E... elements) { * out of the source collection at the time this method is called. */ @SuppressWarnings("unchecked") // Es come in, Es go out - public static MinimalIterable from(final Collection elements) { + public static MinimalIterable from(Collection elements) { return (MinimalIterable) of(elements.toArray()); } - private Iterator iterator; + private @Nullable Iterator iterator; private MinimalIterable(Iterator iterator) { this.iterator = iterator; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/MinimalSet.java b/android/guava-testlib/src/com/google/common/collect/testing/MinimalSet.java index 988dd3f95511..db09a4edd544 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/MinimalSet.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/MinimalSet.java @@ -16,12 +16,16 @@ package com.google.common.collect.testing; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtCompatible; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A simplistic set which implements the bare minimum so that it can be used in tests without @@ -31,30 +35,31 @@ * @author Regina O'Dell */ @GwtCompatible -public class MinimalSet extends MinimalCollection implements Set { +@NullMarked +public class MinimalSet extends MinimalCollection implements Set { @SuppressWarnings("unchecked") // empty Object[] as E[] - public static MinimalSet of(E... contents) { - return ofClassAndContents(Object.class, (E[]) new Object[0], Arrays.asList(contents)); + public static MinimalSet of(E... contents) { + return ofClassAndContents(Object.class, (E[]) new Object[0], asList(contents)); } @SuppressWarnings("unchecked") // empty Object[] as E[] - public static MinimalSet from(Collection contents) { + public static MinimalSet from(Collection contents) { return ofClassAndContents(Object.class, (E[]) new Object[0], contents); } - public static MinimalSet ofClassAndContents( - Class type, E[] emptyArrayForContents, Iterable contents) { - List setContents = new ArrayList(); + public static MinimalSet ofClassAndContents( + Class type, E[] emptyArrayForContents, Iterable contents) { + List setContents = new ArrayList<>(); for (E e : contents) { if (!setContents.contains(e)) { setContents.add(e); } } - return new MinimalSet(type, setContents.toArray(emptyArrayForContents)); + return new MinimalSet<>(type, setContents.toArray(emptyArrayForContents)); } - private MinimalSet(Class type, E... contents) { + private MinimalSet(Class type, E... contents) { super(type, true, contents); } @@ -63,7 +68,7 @@ private MinimalSet(Class type, E... contents) { */ @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof Set) { Set that = (Set) object; return (this.size() == that.size()) && this.containsAll(that); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/NavigableMapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/NavigableMapTestSuiteBuilder.java index 77a198454bd8..40bfecc36252 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/NavigableMapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/NavigableMapTestSuiteBuilder.java @@ -17,6 +17,7 @@ package com.google.common.collect.testing; import static com.google.common.collect.testing.Helpers.castOrCopyToList; +import static com.google.common.collect.testing.Helpers.copyToList; import static java.util.Collections.reverse; import com.google.common.annotations.GwtIncompatible; @@ -46,9 +47,10 @@ public static NavigableMapTestSuiteBuilder using( return result; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(NavigableMapNavigationTester.class); return testers; } @@ -116,10 +118,10 @@ public NavigableMapTestSuiteBuilder newBuilderUsing( /** Create a suite whose maps are descending views of other maps. */ private TestSuite createDescendingSuite( - final FeatureSpecificTestSuiteBuilder< + FeatureSpecificTestSuiteBuilder< ?, ? extends OneSizeTestContainerGenerator, Entry>> parentBuilder) { - final TestSortedMapGenerator delegate = + TestSortedMapGenerator delegate = (TestSortedMapGenerator) parentBuilder.getSubjectGenerator().getInnerGenerator(); List> features = new ArrayList<>(); @@ -137,8 +139,8 @@ NavigableMapTestSuiteBuilder subSuiteUsing(TestSortedMapGenerator ge return using(generator); } - static class DescendingTestMapGenerator extends ForwardingTestMapGenerator - implements TestSortedMapGenerator { + private static final class DescendingTestMapGenerator + extends ForwardingTestMapGenerator implements TestSortedMapGenerator { DescendingTestMapGenerator(TestSortedMapGenerator delegate) { super(delegate); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/NavigableSetTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/NavigableSetTestSuiteBuilder.java index 38eb56e09dee..31a1f1858f15 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/NavigableSetTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/NavigableSetTestSuiteBuilder.java @@ -16,6 +16,7 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionFeature.DESCENDING_VIEW; import static com.google.common.collect.testing.features.CollectionFeature.SUBSET_VIEW; @@ -40,7 +41,7 @@ @GwtIncompatible public final class NavigableSetTestSuiteBuilder extends SortedSetTestSuiteBuilder { public static NavigableSetTestSuiteBuilder using(TestSortedSetGenerator generator) { - NavigableSetTestSuiteBuilder builder = new NavigableSetTestSuiteBuilder(); + NavigableSetTestSuiteBuilder builder = new NavigableSetTestSuiteBuilder<>(); builder.usingGenerator(generator); return builder; } @@ -99,10 +100,9 @@ public NavigableSetTestSuiteBuilder newBuilderUsing( /** Create a suite whose maps are descending views of other maps. */ private TestSuite createDescendingSuite( - final FeatureSpecificTestSuiteBuilder< - ?, ? extends OneSizeTestContainerGenerator, E>> + FeatureSpecificTestSuiteBuilder, E>> parentBuilder) { - final TestSetGenerator delegate = + TestSetGenerator delegate = (TestSetGenerator) parentBuilder.getSubjectGenerator().getInnerGenerator(); List> features = new ArrayList<>(); @@ -124,7 +124,7 @@ public E[] createArray(int length) { @Override public Iterable order(List insertionOrder) { - List list = new ArrayList(); + List list = new ArrayList<>(); for (E e : delegate.order(insertionOrder)) { list.add(e); } @@ -144,9 +144,10 @@ public Set create(Object... elements) { .createTestSuite(); } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(NavigableSetNavigationTester.class); return testers; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/OneSizeGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/OneSizeGenerator.java index 1b8924c07606..4baec8023be7 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/OneSizeGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/OneSizeGenerator.java @@ -16,12 +16,15 @@ package com.google.common.collect.testing; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionSize; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Generator for collection of a particular size. @@ -29,7 +32,9 @@ * @author George van den Driessche */ @GwtCompatible -public final class OneSizeGenerator implements OneSizeTestContainerGenerator { +@NullMarked +public final class OneSizeGenerator + implements OneSizeTestContainerGenerator { private final TestContainerGenerator generator; private final CollectionSize collectionSize; @@ -67,10 +72,9 @@ public T createTestSubject() { @Override public Collection getSampleElements(int howMany) { SampleElements samples = samples(); - @SuppressWarnings("unchecked") List allSampleElements = - Arrays.asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4()); - return new ArrayList(allSampleElements.subList(0, howMany)); + asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4()); + return new ArrayList<>(allSampleElements.subList(0, howMany)); } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/OneSizeTestContainerGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/OneSizeTestContainerGenerator.java index 7e727cd8242b..85feb9d3edfc 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/OneSizeTestContainerGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/OneSizeTestContainerGenerator.java @@ -19,6 +19,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionSize; import java.util.Collection; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * The subject-generator interface accepted by Collection testers, for testing a Collection at one @@ -31,7 +33,8 @@ * @author George van den Driessche */ @GwtCompatible -public interface OneSizeTestContainerGenerator +@NullMarked +public interface OneSizeTestContainerGenerator extends TestSubjectGenerator, TestContainerGenerator { TestContainerGenerator getInnerGenerator(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/PerCollectionSizeTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/PerCollectionSizeTestSuiteBuilder.java index c0068b4f9274..34dfd3c67ba4 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/PerCollectionSizeTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/PerCollectionSizeTestSuiteBuilder.java @@ -16,13 +16,15 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToSet; +import static com.google.common.collect.testing.features.FeatureUtil.addImpliedFeatures; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.Feature; -import com.google.common.collect.testing.features.FeatureUtil; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.logging.Logger; @@ -58,19 +60,19 @@ public TestSuite createTestSuite() { String name = getName(); // Copy this set, so we can modify it. - Set> features = Helpers.copyToSet(getFeatures()); + Set> features = copyToSet(getFeatures()); + @SuppressWarnings("rawtypes") // class literals List> testers = getTesters(); logger.fine(" Testing: " + name); // Split out all the specified sizes. - Set> sizesToTest = Helpers.>copyToSet(CollectionSize.values()); + Set> sizesToTest = Helpers.copyToSet(CollectionSize.values()); sizesToTest.retainAll(features); features.removeAll(sizesToTest); - FeatureUtil.addImpliedFeatures(sizesToTest); - sizesToTest.retainAll( - Arrays.asList(CollectionSize.ZERO, CollectionSize.ONE, CollectionSize.SEVERAL)); + addImpliedFeatures(sizesToTest); + sizesToTest.retainAll(asList(CollectionSize.ZERO, CollectionSize.ONE, CollectionSize.SEVERAL)); logger.fine(" Sizes: " + formatFeatureSet(sizesToTest)); @@ -88,7 +90,7 @@ public TestSuite createTestSuite() { "%s [collection size: %s]", name, collectionSize.toString().toLowerCase()); OneSizeGenerator oneSizeGenerator = new OneSizeGenerator<>(getSubjectGenerator(), (CollectionSize) collectionSize); - Set> oneSizeFeatures = Helpers.copyToSet(features); + Set> oneSizeFeatures = copyToSet(features); oneSizeFeatures.add(collectionSize); Set oneSizeSuppressedTests = getSuppressedTests(); @@ -120,12 +122,15 @@ protected List createDerivedSuites( private static final class OneSizeTestSuiteBuilder extends FeatureSpecificTestSuiteBuilder< OneSizeTestSuiteBuilder, OneSizeGenerator> { + @SuppressWarnings("rawtypes") // class literals private final List> testers; - public OneSizeTestSuiteBuilder(List> testers) { + @SuppressWarnings("rawtypes") // class literals + OneSizeTestSuiteBuilder(List> testers) { this.testers = testers; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { return testers; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/Platform.java b/android/guava-testlib/src/com/google/common/collect/testing/Platform.java index a2c020c915d4..cfc7ac3b284e 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/Platform.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/Platform.java @@ -34,7 +34,7 @@ static T[] clone(T[] array) { // Class.cast is not supported in GWT. This method is a no-op in GWT. static void checkCast(Class clazz, Object obj) { - clazz.cast(obj); + Object unused = clazz.cast(obj); } static String format(String template, Object... args) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/QueueTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/QueueTestSuiteBuilder.java index ac62d44b6999..304cc2caf41a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/QueueTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/QueueTestSuiteBuilder.java @@ -22,6 +22,7 @@ import com.google.common.collect.testing.testers.QueuePeekTester; import com.google.common.collect.testing.testers.QueuePollTester; import com.google.common.collect.testing.testers.QueueRemoveTester; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.List; @@ -45,11 +46,13 @@ public static QueueTestSuiteBuilder using(TestQueueGenerator generator * collection that's both a queue and a list, to avoid running the common collection tests twice. * By default, collection tests do run. */ + @CanIgnoreReturnValue public QueueTestSuiteBuilder skipCollectionTests() { runCollectionTests = false; return this; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { List> testers = new ArrayList<>(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/ReflectionFreeAssertThrows.java b/android/guava-testlib/src/com/google/common/collect/testing/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..60f3144dd116 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/ReflectionFreeAssertThrows.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect.testing; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/ReserializingTestCollectionGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/ReserializingTestCollectionGenerator.java index 28d6f7917dd9..b1e58aaac677 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/ReserializingTestCollectionGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/ReserializingTestCollectionGenerator.java @@ -42,7 +42,7 @@ public class ReserializingTestCollectionGenerator implements TestCollectionGe public static ReserializingTestCollectionGenerator newInstance( TestCollectionGenerator delegate) { - return new ReserializingTestCollectionGenerator(delegate); + return new ReserializingTestCollectionGenerator<>(delegate); } @Override @@ -59,9 +59,8 @@ static T reserialize(T object) { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())); return (T) in.readObject(); } catch (IOException | ClassNotFoundException e) { - Helpers.fail(e, e.getMessage()); + throw new AssertionError(e); } - throw new AssertionError("not reachable"); } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/ReserializingTestSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/ReserializingTestSetGenerator.java index c92a3ff20aca..555440adc172 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/ReserializingTestSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/ReserializingTestSetGenerator.java @@ -35,7 +35,7 @@ public class ReserializingTestSetGenerator extends ReserializingTestCollectio } public static TestSetGenerator newInstance(TestSetGenerator delegate) { - return new ReserializingTestSetGenerator(delegate); + return new ReserializingTestSetGenerator<>(delegate); } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SafeTreeMap.java b/android/guava-testlib/src/com/google/common/collect/testing/SafeTreeMap.java index 5856e3b543f9..b9fc32b941d9 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/SafeTreeMap.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/SafeTreeMap.java @@ -17,6 +17,7 @@ package com.google.common.collect.testing; import com.google.common.annotations.GwtIncompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; import java.util.AbstractSet; import java.util.Collection; @@ -28,6 +29,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import org.jspecify.annotations.Nullable; /** * A wrapper around {@code TreeMap} that aggressively checks to see if keys are mutually comparable. @@ -75,12 +77,12 @@ private SafeTreeMap(NavigableMap delegate) { } @Override - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(K key) { return delegate.ceilingEntry(checkValid(key)); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(K key) { return delegate.ceilingKey(checkValid(key)); } @@ -89,7 +91,6 @@ public void clear() { delegate.clear(); } - @SuppressWarnings("unchecked") @Override public Comparator comparator() { Comparator comparator = delegate.comparator(); @@ -162,7 +163,7 @@ public void clear() { } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return delegate.firstEntry(); } @@ -172,17 +173,17 @@ public K firstKey() { } @Override - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(K key) { return delegate.floorEntry(checkValid(key)); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(K key) { return delegate.floorKey(checkValid(key)); } @Override - public V get(Object key) { + public @Nullable V get(Object key) { return delegate.get(checkValid(key)); } @@ -197,12 +198,12 @@ public NavigableMap headMap(K toKey, boolean inclusive) { } @Override - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(K key) { return delegate.higherEntry(checkValid(key)); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(K key) { return delegate.higherKey(checkValid(key)); } @@ -217,7 +218,7 @@ public NavigableSet keySet() { } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return delegate.lastEntry(); } @@ -227,12 +228,12 @@ public K lastKey() { } @Override - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(K key) { return delegate.lowerEntry(checkValid(key)); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(K key) { return delegate.lowerKey(checkValid(key)); } @@ -242,17 +243,17 @@ public NavigableSet navigableKeySet() { } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return delegate.pollFirstEntry(); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return delegate.pollLastEntry(); } @Override - public V put(K key, V value) { + public @Nullable V put(K key, V value) { return delegate.put(checkValid(key), value); } @@ -265,7 +266,7 @@ public void putAll(Map map) { } @Override - public V remove(Object key) { + public @Nullable V remove(Object key) { return delegate.remove(checkValid(key)); } @@ -300,16 +301,17 @@ public Collection values() { return delegate.values(); } + @CanIgnoreReturnValue private T checkValid(T t) { // a ClassCastException is what's supposed to happen! @SuppressWarnings("unchecked") K k = (K) t; - comparator().compare(k, k); + int unused = comparator().compare(k, k); return t; } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return delegate.equals(obj); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SafeTreeSet.java b/android/guava-testlib/src/com/google/common/collect/testing/SafeTreeSet.java index 8ed48107347c..cbb8b1421aab 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/SafeTreeSet.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/SafeTreeSet.java @@ -17,6 +17,7 @@ package com.google.common.collect.testing; import com.google.common.annotations.GwtIncompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; import java.util.Collection; import java.util.Comparator; @@ -24,6 +25,7 @@ import java.util.NavigableSet; import java.util.SortedSet; import java.util.TreeSet; +import org.jspecify.annotations.Nullable; /** * A wrapper around {@code TreeSet} that aggressively checks to see if elements are mutually @@ -81,7 +83,7 @@ public boolean addAll(Collection collection) { } @Override - public E ceiling(E e) { + public @Nullable E ceiling(E e) { return delegate.ceiling(checkValid(e)); } @@ -90,7 +92,6 @@ public void clear() { delegate.clear(); } - @SuppressWarnings("unchecked") @Override public Comparator comparator() { Comparator comparator = delegate.comparator(); @@ -117,7 +118,7 @@ public Iterator descendingIterator() { @Override public NavigableSet descendingSet() { - return new SafeTreeSet(delegate.descendingSet()); + return new SafeTreeSet<>(delegate.descendingSet()); } @Override @@ -126,7 +127,7 @@ public E first() { } @Override - public E floor(E e) { + public @Nullable E floor(E e) { return delegate.floor(checkValid(e)); } @@ -137,11 +138,11 @@ public SortedSet headSet(E toElement) { @Override public NavigableSet headSet(E toElement, boolean inclusive) { - return new SafeTreeSet(delegate.headSet(checkValid(toElement), inclusive)); + return new SafeTreeSet<>(delegate.headSet(checkValid(toElement), inclusive)); } @Override - public E higher(E e) { + public @Nullable E higher(E e) { return delegate.higher(checkValid(e)); } @@ -161,17 +162,17 @@ public E last() { } @Override - public E lower(E e) { + public @Nullable E lower(E e) { return delegate.lower(checkValid(e)); } @Override - public E pollFirst() { + public @Nullable E pollFirst() { return delegate.pollFirst(); } @Override - public E pollLast() { + public @Nullable E pollLast() { return delegate.pollLast(); } @@ -198,7 +199,7 @@ public int size() { @Override public NavigableSet subSet( E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { - return new SafeTreeSet( + return new SafeTreeSet<>( delegate.subSet( checkValid(fromElement), fromInclusive, checkValid(toElement), toInclusive)); } @@ -215,7 +216,7 @@ public SortedSet tailSet(E fromElement) { @Override public NavigableSet tailSet(E fromElement, boolean inclusive) { - return new SafeTreeSet(delegate.tailSet(checkValid(fromElement), inclusive)); + return new SafeTreeSet<>(delegate.tailSet(checkValid(fromElement), inclusive)); } @Override @@ -228,16 +229,17 @@ public T[] toArray(T[] a) { return delegate.toArray(a); } + @CanIgnoreReturnValue private T checkValid(T t) { // a ClassCastException is what's supposed to happen! @SuppressWarnings("unchecked") E e = (E) t; - comparator().compare(e, e); + int unused = comparator().compare(e, e); return t; } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return delegate.equals(obj); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SampleElements.java b/android/guava-testlib/src/com/google/common/collect/testing/SampleElements.java index 400107dc58fc..d05a684c33b5 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/SampleElements.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/SampleElements.java @@ -16,11 +16,15 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.mapEntry; + import com.google.common.annotations.GwtCompatible; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A container class for the five sample elements we need for testing. @@ -28,7 +32,8 @@ * @author Kevin Bourrillion */ @GwtCompatible -public class SampleElements implements Iterable { +@NullMarked +public class SampleElements implements Iterable { // TODO: rename e3, e4 => missing1, missing2 private final E e0; private final E e1; @@ -88,14 +93,14 @@ public Ints() { } } - public static SampleElements> mapEntries( - SampleElements keys, SampleElements values) { + public static + SampleElements> mapEntries(SampleElements keys, SampleElements values) { return new SampleElements<>( - Helpers.mapEntry(keys.e0(), values.e0()), - Helpers.mapEntry(keys.e1(), values.e1()), - Helpers.mapEntry(keys.e2(), values.e2()), - Helpers.mapEntry(keys.e3(), values.e3()), - Helpers.mapEntry(keys.e4(), values.e4())); + mapEntry(keys.e0(), values.e0()), + mapEntry(keys.e1(), values.e1()), + mapEntry(keys.e2(), values.e2()), + mapEntry(keys.e3(), values.e3()), + mapEntry(keys.e4(), values.e4())); } public E e0() { @@ -135,7 +140,7 @@ public Colliders() { } } - private static class Collider { + private static final class Collider { final int value; Collider(int value) { @@ -143,7 +148,7 @@ private static class Collider { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return obj instanceof Collider && ((Collider) obj).value == value; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SetTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/SetTestSuiteBuilder.java index 26a2870ca9d5..fe076dabb33f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/SetTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/SetTestSuiteBuilder.java @@ -16,6 +16,7 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS; @@ -48,9 +49,10 @@ public static SetTestSuiteBuilder using(TestSetGenerator generator) { return new SetTestSuiteBuilder().usingGenerator(generator); } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(CollectionSerializationEqualTester.class); testers.add(SetAddAllTester.class); @@ -78,12 +80,14 @@ protected List createDerivedSuites( .named(getName() + " reserialized") .withFeatures(computeReserializedCollectionFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } return derivedSuites; } - static class ReserializedSetGenerator implements TestSetGenerator { + private static final class ReserializedSetGenerator implements TestSetGenerator { final OneSizeTestContainerGenerator, E> gen; private ReserializedSetGenerator(OneSizeTestContainerGenerator, E> gen) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SortedMapInterfaceTest.java b/android/guava-testlib/src/com/google/common/collect/testing/SortedMapInterfaceTest.java index 829c4cc27874..38f06daab4a7 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/SortedMapInterfaceTest.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/SortedMapInterfaceTest.java @@ -16,6 +16,8 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import java.util.Iterator; import java.util.Map.Entry; @@ -56,7 +58,7 @@ protected SortedMap makeEitherMap() { } public void testTailMapWriteThrough() { - final SortedMap map; + SortedMap map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -74,15 +76,11 @@ public void testTailMapWriteThrough() { subMap.put(key, value); assertEquals(secondEntry.getValue(), value); assertEquals(map.get(key), value); - try { - subMap.put(firstEntry.getKey(), value); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> subMap.put(firstEntry.getKey(), value)); } public void testTailMapRemoveThrough() { - final SortedMap map; + SortedMap map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { @@ -105,7 +103,7 @@ public void testTailMapRemoveThrough() { } public void testTailMapClearThrough() { - final SortedMap map; + SortedMap map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SortedMapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/SortedMapTestSuiteBuilder.java index e95383af64a6..e8c46d51d222 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/SortedMapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/SortedMapTestSuiteBuilder.java @@ -16,7 +16,9 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; +import static java.util.Collections.emptySet; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.DerivedCollectionGenerators.Bound; @@ -24,12 +26,12 @@ import com.google.common.collect.testing.features.Feature; import com.google.common.collect.testing.testers.SortedMapNavigationTester; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import junit.framework.TestSuite; +import org.jspecify.annotations.Nullable; /** * Creates, based on your criteria, a JUnit test suite that exhaustively tests a SortedMap @@ -44,9 +46,10 @@ public static SortedMapTestSuiteBuilder using( return result; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(SortedMapNavigationTester.class); return testers; } @@ -54,7 +57,7 @@ protected List> getTesters() { @Override public TestSuite createTestSuite() { if (!getFeatures().contains(KNOWN_ORDER)) { - List> features = Helpers.copyToList(getFeatures()); + List> features = copyToList(getFeatures()); features.add(KNOWN_ORDER); withFeatures(features); } @@ -88,13 +91,13 @@ protected SetTestSuiteBuilder createDerivedKeySetSuite(TestSetGenerator ke * To avoid infinite recursion, test suites with these marker features won't have derived suites * created for them. */ - enum NoRecurse implements Feature { + enum NoRecurse implements Feature<@Nullable Void> { SUBMAP, DESCENDING; @Override - public Set> getImpliedFeatures() { - return Collections.emptySet(); + public Set> getImpliedFeatures() { + return emptySet(); } } @@ -105,12 +108,12 @@ public Set> getImpliedFeatures() { * these extreme values rather than relying on their regular sort ordering. */ final TestSuite createSubmapSuite( - final FeatureSpecificTestSuiteBuilder< + FeatureSpecificTestSuiteBuilder< ?, ? extends OneSizeTestContainerGenerator, Entry>> parentBuilder, - final Bound from, - final Bound to) { - final TestSortedMapGenerator delegate = + Bound from, + Bound to) { + TestSortedMapGenerator delegate = (TestSortedMapGenerator) parentBuilder.getSubjectGenerator().getInnerGenerator(); List> features = new ArrayList<>(); @@ -121,6 +124,8 @@ final TestSuite createSubmapSuite( .named(parentBuilder.getName() + " subMap " + from + "-" + to) .withFeatures(features) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SortedSetTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/SortedSetTestSuiteBuilder.java index e97b08fc5537..7505392fdf7e 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/SortedSetTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/SortedSetTestSuiteBuilder.java @@ -16,15 +16,22 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.copyToList; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.DerivedCollectionGenerators.Bound; import com.google.common.collect.testing.DerivedCollectionGenerators.SortedSetSubsetTestSetGenerator; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.Feature; +import com.google.common.collect.testing.testers.CollectionAddAllTester; +import com.google.common.collect.testing.testers.CollectionAddTester; import com.google.common.collect.testing.testers.SortedSetNavigationTester; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import junit.framework.TestSuite; /** @@ -34,14 +41,15 @@ @GwtIncompatible public class SortedSetTestSuiteBuilder extends SetTestSuiteBuilder { public static SortedSetTestSuiteBuilder using(TestSortedSetGenerator generator) { - SortedSetTestSuiteBuilder builder = new SortedSetTestSuiteBuilder(); + SortedSetTestSuiteBuilder builder = new SortedSetTestSuiteBuilder<>(); builder.usingGenerator(generator); return builder; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(SortedSetNavigationTester.class); return testers; } @@ -49,7 +57,7 @@ protected List> getTesters() { @Override public TestSuite createTestSuite() { if (!getFeatures().contains(CollectionFeature.KNOWN_ORDER)) { - List> features = Helpers.copyToList(getFeatures()); + List> features = copyToList(getFeatures()); features.add(CollectionFeature.KNOWN_ORDER); withFeatures(features); } @@ -78,22 +86,30 @@ protected List createDerivedSuites( * these extreme values rather than relying on their regular sort ordering. */ final TestSuite createSubsetSuite( - final FeatureSpecificTestSuiteBuilder< - ?, ? extends OneSizeTestContainerGenerator, E>> + FeatureSpecificTestSuiteBuilder, E>> parentBuilder, - final Bound from, - final Bound to) { - final TestSortedSetGenerator delegate = + Bound from, + Bound to) { + TestSortedSetGenerator delegate = (TestSortedSetGenerator) parentBuilder.getSubjectGenerator().getInnerGenerator(); List> features = new ArrayList<>(parentBuilder.getFeatures()); - features.remove(CollectionFeature.ALLOWS_NULL_VALUES); + Set suppressing = new HashSet<>(parentBuilder.getSuppressedTests()); features.add(CollectionFeature.SUBSET_VIEW); + if (features.remove(CollectionFeature.ALLOWS_NULL_VALUES)) { + // the null value might be out of bounds, so we can't always construct a subset with nulls + features.add(CollectionFeature.ALLOWS_NULL_QUERIES); + // but add null might still be supported if it happens to be within range of the subset + suppressing.add(CollectionAddTester.getAddNullUnsupportedMethod()); + suppressing.add(CollectionAddAllTester.getAddAllNullUnsupportedMethod()); + } return newBuilderUsing(delegate, to, from) .named(parentBuilder.getName() + " subSet " + from + "-" + to) .withFeatures(features) - .suppressing(parentBuilder.getSuppressedTests()) + .suppressing(suppressing) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java new file mode 100644 index 000000000000..93d455c2e624 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.assertEqualInOrder; +import static com.google.common.collect.testing.Platform.format; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableSet; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Ordering; +import com.google.common.primitives.Ints; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterator.OfPrimitive; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Tester for {@code Spliterator} implementations. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ +@GwtCompatible +@NullMarked +@IgnoreJRERequirement // Users will use this only if they're already using Spliterator. +public final class SpliteratorTester { + /** Return type from "contains the following elements" assertions. */ + public interface Ordered { + /** + * Attests that the expected values must not just be present but must be present in the order + * they were given. + */ + void inOrder(); + } + + @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester + private abstract static class GeneralSpliterator { + final Spliterator spliterator; + + GeneralSpliterator(Spliterator spliterator) { + this.spliterator = checkNotNull(spliterator); + } + + abstract void forEachRemaining(Consumer action); + + abstract boolean tryAdvance(Consumer action); + + abstract @Nullable GeneralSpliterator trySplit(); + + final int characteristics() { + return spliterator.characteristics(); + } + + final long estimateSize() { + return spliterator.estimateSize(); + } + + final @Nullable Comparator getComparator() { + return spliterator.getComparator(); + } + + final long getExactSizeIfKnown() { + return spliterator.getExactSizeIfKnown(); + } + + final boolean hasCharacteristics(int characteristics) { + return spliterator.hasCharacteristics(characteristics); + } + } + + @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester + private static final class GeneralSpliteratorOfObject + extends GeneralSpliterator { + GeneralSpliteratorOfObject(Spliterator spliterator) { + super(spliterator); + } + + @Override + void forEachRemaining(Consumer action) { + spliterator.forEachRemaining(action); + } + + @Override + boolean tryAdvance(Consumer action) { + return spliterator.tryAdvance(action); + } + + @Override + @Nullable GeneralSpliterator trySplit() { + Spliterator split = spliterator.trySplit(); + return split == null ? null : new GeneralSpliteratorOfObject<>(split); + } + } + + @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester + private static final class GeneralSpliteratorOfPrimitive< + E extends @Nullable Object, C, S extends Spliterator.OfPrimitive> + extends GeneralSpliterator { + final OfPrimitive spliteratorOfPrimitive; + final Function, C> consumerizer; + + GeneralSpliteratorOfPrimitive( + Spliterator.OfPrimitive spliterator, + Function, C> consumerizer) { + super(spliterator); + this.spliteratorOfPrimitive = spliterator; + this.consumerizer = consumerizer; + } + + @Override + void forEachRemaining(Consumer action) { + spliteratorOfPrimitive.forEachRemaining(consumerizer.apply(action)); + } + + @Override + boolean tryAdvance(Consumer action) { + return spliteratorOfPrimitive.tryAdvance(consumerizer.apply(action)); + } + + @Override + @Nullable GeneralSpliterator trySplit() { + Spliterator.OfPrimitive split = spliteratorOfPrimitive.trySplit(); + return split == null ? null : new GeneralSpliteratorOfPrimitive<>(split, consumerizer); + } + } + + /** + * Different ways of decomposing a Spliterator, all of which must produce the same elements (up to + * ordering, if Spliterator.ORDERED is not present). + */ + @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester + enum SpliteratorDecompositionStrategy { + NO_SPLIT_FOR_EACH_REMAINING { + @Override + void forEach( + GeneralSpliterator spliterator, Consumer consumer) { + spliterator.forEachRemaining(consumer); + } + }, + NO_SPLIT_TRY_ADVANCE { + @Override + void forEach( + GeneralSpliterator spliterator, Consumer consumer) { + while (spliterator.tryAdvance(consumer)) { + // do nothing + } + } + }, + MAXIMUM_SPLIT { + @Override + void forEach( + GeneralSpliterator spliterator, Consumer consumer) { + for (GeneralSpliterator prefix = trySplitTestingSize(spliterator); + prefix != null; + prefix = trySplitTestingSize(spliterator)) { + forEach(prefix, consumer); + } + long size = spliterator.getExactSizeIfKnown(); + long[] counter = {0}; + spliterator.forEachRemaining( + e -> { + consumer.accept(e); + counter[0]++; + }); + if (size >= 0) { + assertEquals(size, counter[0]); + } + } + }, + ALTERNATE_ADVANCE_AND_SPLIT { + @Override + void forEach( + GeneralSpliterator spliterator, Consumer consumer) { + while (spliterator.tryAdvance(consumer)) { + GeneralSpliterator prefix = trySplitTestingSize(spliterator); + if (prefix != null) { + forEach(prefix, consumer); + } + } + } + }; + + abstract void forEach( + GeneralSpliterator spliterator, Consumer consumer); + + static final Set ALL_STRATEGIES = + unmodifiableSet(new LinkedHashSet<>(asList(values()))); + } + + private static @Nullable GeneralSpliterator trySplitTestingSize( + GeneralSpliterator spliterator) { + boolean subsized = spliterator.hasCharacteristics(Spliterator.SUBSIZED); + long originalSize = spliterator.estimateSize(); + GeneralSpliterator trySplit = spliterator.trySplit(); + if (spliterator.estimateSize() > originalSize) { + fail( + format( + "estimated size of spliterator after trySplit (%s) is larger than original size (%s)", + spliterator.estimateSize(), originalSize)); + } + if (trySplit != null) { + if (trySplit.estimateSize() > originalSize) { + fail( + format( + "estimated size of trySplit result (%s) is larger than original size (%s)", + trySplit.estimateSize(), originalSize)); + } + } + if (subsized) { + if (trySplit != null) { + assertEquals( + "sum of estimated sizes of trySplit and original spliterator after trySplit", + originalSize, + trySplit.estimateSize() + spliterator.estimateSize()); + } else { + assertEquals( + "estimated size of spliterator after failed trySplit", + originalSize, + spliterator.estimateSize()); + } + } + return trySplit; + } + + public static SpliteratorTester of( + Supplier> spliteratorSupplier) { + return new SpliteratorTester<>( + ImmutableSet.of(() -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()))); + } + + /** + * @since 33.4.0 (but since 28.1 in the JRE flavor) + */ + public static SpliteratorTester ofInt(Supplier spliteratorSupplier) { + return new SpliteratorTester<>( + ImmutableSet.of( + () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), + () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); + } + + /** + * @since 33.4.0 (but since 28.1 in the JRE flavor) + */ + public static SpliteratorTester ofLong(Supplier spliteratorSupplier) { + return new SpliteratorTester<>( + ImmutableSet.of( + () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), + () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); + } + + /** + * @since 33.4.0 (but since 28.1 in the JRE flavor) + */ + public static SpliteratorTester ofDouble( + Supplier spliteratorSupplier) { + return new SpliteratorTester<>( + ImmutableSet.of( + () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), + () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); + } + + private final ImmutableSet>> spliteratorSuppliers; + + private SpliteratorTester(ImmutableSet>> spliteratorSuppliers) { + this.spliteratorSuppliers = checkNotNull(spliteratorSuppliers); + } + + @SafeVarargs + @CanIgnoreReturnValue + public final Ordered expect(Object... elements) { + return expect(asList(elements)); + } + + @CanIgnoreReturnValue + public final Ordered expect(Iterable elements) { + List> resultsForAllStrategies = new ArrayList<>(); + for (Supplier> spliteratorSupplier : spliteratorSuppliers) { + GeneralSpliterator spliterator = spliteratorSupplier.get(); + int characteristics = spliterator.characteristics(); + long estimatedSize = spliterator.estimateSize(); + for (SpliteratorDecompositionStrategy strategy : + SpliteratorDecompositionStrategy.ALL_STRATEGIES) { + List resultsForStrategy = new ArrayList<>(); + strategy.forEach(spliteratorSupplier.get(), resultsForStrategy::add); + + // TODO(cpovirk): better failure messages + if ((characteristics & Spliterator.NONNULL) != 0) { + assertFalse(resultsForStrategy.contains(null)); + } + if ((characteristics & Spliterator.SORTED) != 0) { + Comparator comparator = spliterator.getComparator(); + if (comparator == null) { + // A sorted spliterator with no comparator is already using natural order. + // (We could probably find a way to avoid rawtypes here if we wanted.) + @SuppressWarnings({"unchecked", "rawtypes"}) + Comparator naturalOrder = + (Comparator) Comparator.naturalOrder(); + comparator = naturalOrder; + } + assertTrue(Ordering.from(comparator).isOrdered(resultsForStrategy)); + } + if ((characteristics & Spliterator.SIZED) != 0) { + assertEquals(Ints.checkedCast(estimatedSize), resultsForStrategy.size()); + } + + assertEqualIgnoringOrder(elements, resultsForStrategy); + resultsForAllStrategies.add(resultsForStrategy); + } + } + return new Ordered() { + @Override + public void inOrder() { + for (List resultsForStrategy : resultsForAllStrategies) { + assertEqualInOrder(elements, resultsForStrategy); + } + } + }; + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestCharacterListGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestCharacterListGenerator.java index b7542b55b654..acf6ec52e5ab 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestCharacterListGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestCharacterListGenerator.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.SampleElements.Chars; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * Generates {@code List} instances for test suites. @@ -27,6 +28,7 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public abstract class TestCharacterListGenerator implements TestListGenerator { @Override public SampleElements samples() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestCollectionGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestCollectionGenerator.java index f1df8c9da0d8..704ec159172a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestCollectionGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestCollectionGenerator.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import java.util.Collection; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates collections, containing sample elements, to be tested. @@ -25,4 +27,6 @@ * @author Kevin Bourrillion */ @GwtCompatible -public interface TestCollectionGenerator extends TestContainerGenerator, E> {} +@NullMarked +public interface TestCollectionGenerator + extends TestContainerGenerator, E> {} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestCollidingSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestCollidingSetGenerator.java index 6aa0c33d3852..836f4bf971e7 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestCollidingSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestCollidingSetGenerator.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.SampleElements.Colliders; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * A generator using sample elements whose hash codes all collide badly. @@ -26,6 +27,7 @@ * @author Kevin Bourrillion */ @GwtCompatible +@NullMarked public abstract class TestCollidingSetGenerator implements TestSetGenerator { @Override public SampleElements samples() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestContainerGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestContainerGenerator.java index d08cf90c27d2..23d702b266f4 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestContainerGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestContainerGenerator.java @@ -20,6 +20,8 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * To be implemented by test generators of things that can contain elements. Such things include @@ -29,7 +31,8 @@ * @author George van den Driessche */ @GwtCompatible -public interface TestContainerGenerator { +@NullMarked +public interface TestContainerGenerator { /** Returns the sample elements that this generate populates its container with. */ SampleElements samples(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestEnumMapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestEnumMapGenerator.java index c117fcda749a..a6f1c610faf1 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestEnumMapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestEnumMapGenerator.java @@ -16,12 +16,14 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.Helpers.orderEntriesByKey; import com.google.common.annotations.GwtCompatible; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; /** * Implementation helper for {@link TestMapGenerator} for use with enum maps. @@ -29,22 +31,23 @@ * @author Kevin Bourrillion */ @GwtCompatible +@NullMarked public abstract class TestEnumMapGenerator implements TestMapGenerator { @Override public SampleElements> samples() { return new SampleElements<>( - Helpers.mapEntry(AnEnum.A, "January"), - Helpers.mapEntry(AnEnum.B, "February"), - Helpers.mapEntry(AnEnum.C, "March"), - Helpers.mapEntry(AnEnum.D, "April"), - Helpers.mapEntry(AnEnum.E, "May")); + mapEntry(AnEnum.A, "January"), + mapEntry(AnEnum.B, "February"), + mapEntry(AnEnum.C, "March"), + mapEntry(AnEnum.D, "April"), + mapEntry(AnEnum.E, "May")); } @Override public final Map create(Object... entries) { @SuppressWarnings("unchecked") - Entry[] array = new Entry[entries.length]; + Entry[] array = (Entry[]) new Entry[entries.length]; int i = 0; for (Object o : entries) { @SuppressWarnings("unchecked") @@ -59,7 +62,7 @@ public final Map create(Object... entries) { @Override @SuppressWarnings("unchecked") public final Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestEnumSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestEnumSetGenerator.java index 88391ef03be7..7629836a5e6c 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestEnumSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestEnumSetGenerator.java @@ -16,11 +16,13 @@ package com.google.common.collect.testing; +import static java.util.Collections.sort; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.SampleElements.Enums; -import java.util.Collections; import java.util.List; import java.util.Set; +import org.jspecify.annotations.NullMarked; /** * An abstract TestSetGenerator for generating sets containing enum values. @@ -28,6 +30,7 @@ * @author Kevin Bourrillion */ @GwtCompatible +@NullMarked public abstract class TestEnumSetGenerator implements TestSetGenerator { @Override public SampleElements samples() { @@ -52,9 +55,15 @@ public AnEnum[] createArray(int length) { } /** Sorts the enums according to their natural ordering. */ + /* + * While the current implementation returns `this`, that's not something we mean to guarantee. + * Callers of TestContainerGenerator.order need to be prepared for implementations to return a new + * collection. + */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestIntegerSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestIntegerSetGenerator.java index 9cd9ecb43d6b..b8e8b595d846 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestIntegerSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestIntegerSetGenerator.java @@ -20,6 +20,7 @@ import com.google.common.collect.testing.SampleElements.Ints; import java.util.List; import java.util.Set; +import org.jspecify.annotations.NullMarked; /** * Create integer sets for collection tests. @@ -27,6 +28,7 @@ * @author Gregory Kick */ @GwtCompatible +@NullMarked public abstract class TestIntegerSetGenerator implements TestSetGenerator { @Override public SampleElements samples() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestIntegerSortedSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestIntegerSortedSetGenerator.java index babac37bcd79..c02008d59a9b 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestIntegerSortedSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestIntegerSortedSetGenerator.java @@ -16,10 +16,12 @@ package com.google.common.collect.testing; +import static java.util.Collections.sort; + import com.google.common.annotations.GwtCompatible; -import java.util.Collections; import java.util.List; import java.util.SortedSet; +import org.jspecify.annotations.NullMarked; /** * Create integer sets for testing collections that are sorted by natural ordering. @@ -28,14 +30,21 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public abstract class TestIntegerSortedSetGenerator extends TestIntegerSetGenerator { @Override protected abstract SortedSet create(Integer[] elements); /** Sorts the elements by their natural ordering. */ + /* + * While the current implementation returns `this`, that's not something we mean to guarantee. + * Callers of TestContainerGenerator.order need to be prepared for implementations to return a new + * collection. + */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestListGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestListGenerator.java index 99f95d0af0be..6dcffa779f4d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestListGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestListGenerator.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import java.util.List; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates sets, containing sample elements, to be tested. @@ -25,7 +27,8 @@ * @author Kevin Bourrillion */ @GwtCompatible -public interface TestListGenerator extends TestCollectionGenerator { +@NullMarked +public interface TestListGenerator extends TestCollectionGenerator { @Override List create(Object... elements); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestMapEntrySetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestMapEntrySetGenerator.java index 0c298cc1226b..95319d10e056 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestMapEntrySetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestMapEntrySetGenerator.java @@ -16,11 +16,15 @@ package com.google.common.collect.testing; +import static java.lang.System.arraycopy; + import com.google.common.annotations.GwtCompatible; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates map entries using sample keys and sample values. @@ -28,7 +32,10 @@ * @author Jesse Wilson */ @GwtCompatible -public abstract class TestMapEntrySetGenerator implements TestSetGenerator> { +@NullMarked +public abstract class TestMapEntrySetGenerator< + K extends @Nullable Object, V extends @Nullable Object> + implements TestSetGenerator> { private final SampleElements keys; private final SampleElements values; @@ -45,7 +52,7 @@ public SampleElements> samples() { @Override public Set> create(Object... elements) { Entry[] entries = createArray(elements.length); - System.arraycopy(elements, 0, entries, 0, elements.length); + arraycopy(elements, 0, entries, 0, elements.length); return createFromEntries(entries); } @@ -54,7 +61,7 @@ public Set> create(Object... elements) { @Override @SuppressWarnings("unchecked") // generic arrays make typesafety sad public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } /** Returns the original element list, unchanged. */ diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestMapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestMapGenerator.java index 2267a04f5250..50530ba16449 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestMapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestMapGenerator.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import java.util.Map; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates maps, containing sample elements, to be tested. @@ -25,7 +27,9 @@ * @author George van den Driessche */ @GwtCompatible -public interface TestMapGenerator extends TestContainerGenerator, Map.Entry> { +@NullMarked +public interface TestMapGenerator + extends TestContainerGenerator, Map.Entry> { K[] createKeyArray(int length); V[] createValueArray(int length); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestQueueGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestQueueGenerator.java index 939b48583d4b..25863e224538 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestQueueGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestQueueGenerator.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import java.util.Queue; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates queues, containing sample elements, to be tested. @@ -25,7 +27,8 @@ * @author Jared Levy */ @GwtCompatible -public interface TestQueueGenerator extends TestCollectionGenerator { +@NullMarked +public interface TestQueueGenerator extends TestCollectionGenerator { @Override Queue create(Object... elements); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestSetGenerator.java index 680ff44eee16..319feb449a2d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestSetGenerator.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates sets, containing sample elements, to be tested. @@ -25,7 +27,8 @@ * @author Kevin Bourrillion */ @GwtCompatible -public interface TestSetGenerator extends TestCollectionGenerator { +@NullMarked +public interface TestSetGenerator extends TestCollectionGenerator { @Override Set create(Object... elements); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestSortedMapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestSortedMapGenerator.java index 5d902189cd76..3fc2e5b8ceca 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestSortedMapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestSortedMapGenerator.java @@ -19,6 +19,8 @@ import com.google.common.annotations.GwtCompatible; import java.util.Map.Entry; import java.util.SortedMap; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates sorted maps, containing sample elements, to be tested. @@ -26,7 +28,9 @@ * @author Louis Wasserman */ @GwtCompatible -public interface TestSortedMapGenerator extends TestMapGenerator { +@NullMarked +public interface TestSortedMapGenerator + extends TestMapGenerator { @Override SortedMap create(Object... elements); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestSortedSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestSortedSetGenerator.java index 8f7ee5c76f7a..6218fe5aee93 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestSortedSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestSortedSetGenerator.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import java.util.SortedSet; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates sorted sets, containing sample elements, to be tested. @@ -25,7 +27,8 @@ * @author Louis Wasserman */ @GwtCompatible -public interface TestSortedSetGenerator extends TestSetGenerator { +@NullMarked +public interface TestSortedSetGenerator extends TestSetGenerator { @Override SortedSet create(Object... elements); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestStringCollectionGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestStringCollectionGenerator.java index f9c58cd7f333..c637c223dafc 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestStringCollectionGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestStringCollectionGenerator.java @@ -20,6 +20,7 @@ import com.google.common.collect.testing.SampleElements.Strings; import java.util.Collection; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * String creation for testing arbitrary collections. @@ -27,6 +28,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public abstract class TestStringCollectionGenerator implements TestCollectionGenerator { @Override public SampleElements samples() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestStringListGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestStringListGenerator.java index 7e22ab143059..9b2bddec9305 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestStringListGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestStringListGenerator.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.SampleElements.Strings; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * TODO: javadoc. @@ -26,6 +27,7 @@ * @author Kevin Bourrillion */ @GwtCompatible +@NullMarked public abstract class TestStringListGenerator implements TestListGenerator { @Override public SampleElements samples() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestStringMapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestStringMapGenerator.java index 3449d3a9c3f4..2edf70fff26d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestStringMapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestStringMapGenerator.java @@ -16,10 +16,13 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.mapEntry; + import com.google.common.annotations.GwtCompatible; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; /** * Implementation helper for {@link TestMapGenerator} for use with maps of strings. @@ -29,22 +32,23 @@ * @author George van den Driessche */ @GwtCompatible +@NullMarked public abstract class TestStringMapGenerator implements TestMapGenerator { @Override public SampleElements> samples() { return new SampleElements<>( - Helpers.mapEntry("one", "January"), - Helpers.mapEntry("two", "February"), - Helpers.mapEntry("three", "March"), - Helpers.mapEntry("four", "April"), - Helpers.mapEntry("five", "May")); + mapEntry("one", "January"), + mapEntry("two", "February"), + mapEntry("three", "March"), + mapEntry("four", "April"), + mapEntry("five", "May")); } @Override public Map create(Object... entries) { @SuppressWarnings("unchecked") - Entry[] array = new Entry[entries.length]; + Entry[] array = (Entry[]) new Entry[entries.length]; int i = 0; for (Object o : entries) { @SuppressWarnings("unchecked") @@ -59,7 +63,7 @@ public Map create(Object... entries) { @Override @SuppressWarnings("unchecked") public final Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestStringQueueGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestStringQueueGenerator.java index 16cb2539d1f3..1d2d3f4af55a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestStringQueueGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestStringQueueGenerator.java @@ -20,6 +20,7 @@ import com.google.common.collect.testing.SampleElements.Strings; import java.util.List; import java.util.Queue; +import org.jspecify.annotations.NullMarked; /** * Create queue of strings for tests. @@ -27,6 +28,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public abstract class TestStringQueueGenerator implements TestQueueGenerator { @Override public SampleElements samples() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestStringSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestStringSetGenerator.java index 1724bb2c8dc6..d8a517f6c991 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestStringSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestStringSetGenerator.java @@ -20,6 +20,7 @@ import com.google.common.collect.testing.SampleElements.Strings; import java.util.List; import java.util.Set; +import org.jspecify.annotations.NullMarked; /** * Create string sets for collection tests. @@ -27,6 +28,7 @@ * @author Kevin Bourrillion */ @GwtCompatible +@NullMarked public abstract class TestStringSetGenerator implements TestSetGenerator { @Override public SampleElements samples() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestStringSortedMapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestStringSortedMapGenerator.java index c1878bea2214..7f2738c2236f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestStringSortedMapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestStringSortedMapGenerator.java @@ -16,12 +16,14 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.Helpers.orderEntriesByKey; import com.google.common.annotations.GwtCompatible; import java.util.List; import java.util.Map.Entry; import java.util.SortedMap; +import org.jspecify.annotations.NullMarked; /** * Implementation helper for {@link TestMapGenerator} for use with sorted maps of strings. @@ -29,26 +31,27 @@ * @author Chris Povirk */ @GwtCompatible +@NullMarked public abstract class TestStringSortedMapGenerator extends TestStringMapGenerator implements TestSortedMapGenerator { @Override public Entry belowSamplesLesser() { - return Helpers.mapEntry("!! a", "below view"); + return mapEntry("!! a", "below view"); } @Override public Entry belowSamplesGreater() { - return Helpers.mapEntry("!! b", "below view"); + return mapEntry("!! b", "below view"); } @Override public Entry aboveSamplesLesser() { - return Helpers.mapEntry("~~ a", "above view"); + return mapEntry("~~ a", "above view"); } @Override public Entry aboveSamplesGreater() { - return Helpers.mapEntry("~~ b", "above view"); + return mapEntry("~~ b", "above view"); } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestStringSortedSetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestStringSortedSetGenerator.java index 7f5453d63cd9..a1548ed837f7 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestStringSortedSetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestStringSortedSetGenerator.java @@ -16,10 +16,12 @@ package com.google.common.collect.testing; +import static java.util.Collections.sort; + import com.google.common.annotations.GwtCompatible; -import java.util.Collections; import java.util.List; import java.util.SortedSet; +import org.jspecify.annotations.NullMarked; /** * Create string sets for testing collections that are sorted by natural ordering. @@ -27,6 +29,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public abstract class TestStringSortedSetGenerator extends TestStringSetGenerator implements TestSortedSetGenerator { @@ -39,9 +42,15 @@ public SortedSet create(Object... elements) { protected abstract SortedSet create(String[] elements); /** Sorts the elements by their natural ordering. */ + /* + * While the current implementation returns `this`, that's not something we mean to guarantee. + * Callers of TestContainerGenerator.order need to be prepared for implementations to return a new + * collection. + */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestSubjectGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestSubjectGenerator.java index 4a8ae76a8f40..2cf33c668909 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestSubjectGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestSubjectGenerator.java @@ -17,6 +17,8 @@ package com.google.common.collect.testing; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * To be implemented by test generators that can produce test subjects without requiring any @@ -26,6 +28,7 @@ * @author George van den Driessche */ @GwtCompatible -public interface TestSubjectGenerator { +@NullMarked +public interface TestSubjectGenerator { T createTestSubject(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestUnhashableCollectionGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/TestUnhashableCollectionGenerator.java index 9b4602d045d8..94719ef52a0a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestUnhashableCollectionGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestUnhashableCollectionGenerator.java @@ -20,6 +20,7 @@ import com.google.common.collect.testing.SampleElements.Unhashables; import java.util.Collection; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * Creates collections containing unhashable sample elements, to be tested. @@ -27,6 +28,7 @@ * @author Regina O'Dell */ @GwtCompatible +@NullMarked public abstract class TestUnhashableCollectionGenerator> implements TestCollectionGenerator { @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestsForListsInJavaUtil.java b/android/guava-testlib/src/com/google/common/collect/testing/TestsForListsInJavaUtil.java index ac0d15fa9b47..558f9517daa4 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestsForListsInJavaUtil.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestsForListsInJavaUtil.java @@ -20,6 +20,11 @@ import static com.google.common.collect.testing.testers.ListSubListTester.getSubListOriginalListSetAffectsSubListLargeListMethod; import static com.google.common.collect.testing.testers.ListSubListTester.getSubListOriginalListSetAffectsSubListMethod; import static com.google.common.collect.testing.testers.ListSubListTester.getSubListSubListRemoveAffectsOriginalLargeListMethod; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.features.CollectionFeature; @@ -29,7 +34,6 @@ import java.util.AbstractList; import java.util.AbstractSequentialList; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; @@ -69,27 +73,27 @@ public Test allTests() { } protected Collection suppressForEmptyList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForSingletonList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForArraysAsList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForArrayList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForLinkedList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForCopyOnWriteArrayList() { - return Arrays.asList( + return asList( getSubListOriginalListSetAffectsSubListMethod(), getSubListOriginalListSetAffectsSubListLargeListMethod(), getSubListSubListRemoveAffectsOriginalLargeListMethod(), @@ -97,31 +101,32 @@ protected Collection suppressForCopyOnWriteArrayList() { } protected Collection suppressForUnmodifiableList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForCheckedList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForAbstractList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForAbstractSequentialList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForVector() { - return Collections.emptySet(); + return emptySet(); } + @SuppressWarnings("EmptyList") // We specifically want to test emptyList() public Test testsForEmptyList() { return ListTestSuiteBuilder.using( new TestStringListGenerator() { @Override public List create(String[] elements) { - return Collections.emptyList(); + return emptyList(); } }) .named("emptyList") @@ -135,7 +140,7 @@ public Test testsForSingletonList() { new TestStringListGenerator() { @Override public List create(String[] elements) { - return Collections.singletonList(elements[0]); + return singletonList(elements[0]); } }) .named("singletonList") @@ -152,7 +157,7 @@ public Test testsForArraysAsList() { new TestStringListGenerator() { @Override public List create(String[] elements) { - return Arrays.asList(elements.clone()); + return asList(elements.clone()); } }) .named("Arrays.asList") @@ -184,6 +189,8 @@ public List create(String[] elements) { .createTestSuite(); } + // We are testing LinkedList / testing our tests on LinkedList. + @SuppressWarnings("JdkObsolete") public Test testsForLinkedList() { return ListTestSuiteBuilder.using( new TestStringListGenerator() { @@ -232,7 +239,7 @@ public Test testsForUnmodifiableList() { public List create(String[] elements) { List innerList = new ArrayList<>(); Collections.addAll(innerList, elements); - return Collections.unmodifiableList(innerList); + return unmodifiableList(innerList); } }) .named("unmodifiableList/ArrayList") @@ -269,7 +276,7 @@ public Test testsForAbstractList() { return ListTestSuiteBuilder.using( new TestStringListGenerator() { @Override - protected List create(final String[] elements) { + protected List create(String[] elements) { return new AbstractList() { @Override public int size() { @@ -294,9 +301,9 @@ public Test testsForAbstractSequentialList() { return ListTestSuiteBuilder.using( new TestStringListGenerator() { @Override - protected List create(final String[] elements) { + protected List create(String[] elements) { // For this test we trust ArrayList works - final List list = new ArrayList<>(); + List list = new ArrayList<>(); Collections.addAll(list, elements); return new AbstractSequentialList() { @Override @@ -318,6 +325,8 @@ public ListIterator listIterator(int index) { .createTestSuite(); } + // We are testing Vector / testing our tests on Vector. + @SuppressWarnings("JdkObsolete") private Test testsForVector() { return ListTestSuiteBuilder.using( new TestStringListGenerator() { @@ -333,6 +342,7 @@ protected List create(String[] elements) { CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION, CollectionFeature.SERIALIZABLE, CollectionSize.ANY) + .suppressing(suppressForVector()) .createTestSuite(); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestsForMapsInJavaUtil.java b/android/guava-testlib/src/com/google/common/collect/testing/TestsForMapsInJavaUtil.java index e9014fbfa1bf..593878d75ce7 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestsForMapsInJavaUtil.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestsForMapsInJavaUtil.java @@ -17,12 +17,17 @@ package com.google.common.collect.testing; import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonMap; +import static java.util.Collections.unmodifiableMap; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import com.google.common.collect.testing.testers.MapEntrySetTester; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; @@ -75,55 +80,55 @@ public Test allTests() { } protected Collection suppressForCheckedMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForCheckedSortedMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForEmptyMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForSingletonMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForHashMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForHashtable() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForLinkedHashMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForTreeMapNatural() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForTreeMapWithComparator() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForUnmodifiableMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForUnmodifiableSortedMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForEnumMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForConcurrentHashMap() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForConcurrentSkipListMap() { @@ -187,7 +192,7 @@ public Test testsForEmptyMap() { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - return Collections.emptyMap(); + return emptyMap(); } }) .named("emptyMap") @@ -201,7 +206,7 @@ public Test testsForSingletonMap() { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - return Collections.singletonMap(entries[0].getKey(), entries[0].getValue()); + return singletonMap(entries[0].getKey(), entries[0].getValue()); } }) .named("singletonMap") @@ -241,6 +246,8 @@ public Test testsForHashtable() { return MapTestSuiteBuilder.using( new TestStringMapGenerator() { @Override + // We are testing Hashtable / testing our tests on Hashtable. + @SuppressWarnings("JdkObsolete") protected Map create(Entry[] entries) { return populate(new Hashtable(), entries); } @@ -337,7 +344,7 @@ public Test testsForUnmodifiableMap() { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - return Collections.unmodifiableMap(toHashMap(entries)); + return unmodifiableMap(toHashMap(entries)); } }) .named("unmodifiableMap/HashMap") @@ -457,6 +464,7 @@ private static Map toHashMap(Entry[] entries) { // TODO: call conversion constructors or factory methods instead of using // populate() on an empty map + @CanIgnoreReturnValue private static > M populate(M map, Entry[] entries) { for (Entry entry : entries) { map.put(entry.getKey(), entry.getValue()); @@ -465,7 +473,7 @@ private static > M populate(M map, Entry[ } static Comparator arbitraryNullFriendlyComparator() { - return new NullFriendlyComparator(); + return new NullFriendlyComparator<>(); } private static final class NullFriendlyComparator implements Comparator, Serializable { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestsForQueuesInJavaUtil.java b/android/guava-testlib/src/com/google/common/collect/testing/TestsForQueuesInJavaUtil.java index b10a5d9022e2..0f26097ed2fd 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestsForQueuesInJavaUtil.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestsForQueuesInJavaUtil.java @@ -16,13 +16,14 @@ package com.google.common.collect.testing; +import static java.util.Collections.emptySet; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.Collection; -import java.util.Collections; import java.util.LinkedList; import java.util.PriorityQueue; import java.util.Queue; @@ -60,35 +61,35 @@ public Test allTests() { } protected Collection suppressForArrayDeque() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForLinkedList() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForArrayBlockingQueue() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForConcurrentLinkedQueue() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForLinkedBlockingDeque() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForLinkedBlockingQueue() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForPriorityBlockingQueue() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForPriorityQueue() { - return Collections.emptySet(); + return emptySet(); } public Test testsForArrayDeque() { @@ -110,6 +111,8 @@ public Test testsForLinkedList() { return QueueTestSuiteBuilder.using( new TestStringQueueGenerator() { @Override + // We are testing LinkedList / testing our tests on LinkedList. + @SuppressWarnings("JdkObsolete") public Queue create(String[] elements) { return new LinkedList<>(MinimalCollection.of(elements)); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/TestsForSetsInJavaUtil.java b/android/guava-testlib/src/com/google/common/collect/testing/TestsForSetsInJavaUtil.java index 3324ace0fc3e..9179df9cbc9b 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/TestsForSetsInJavaUtil.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/TestsForSetsInJavaUtil.java @@ -16,6 +16,10 @@ package com.google.common.collect.testing; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.unmodifiableSet; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -72,31 +76,31 @@ public Test allTests() { } protected Collection suppressForEmptySet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForSingletonSet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForHashSet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForLinkedHashSet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForEnumSet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForTreeSetNatural() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForTreeSetWithComparator() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForCopyOnWriteArraySet() { @@ -104,27 +108,27 @@ protected Collection suppressForCopyOnWriteArraySet() { } protected Collection suppressForUnmodifiableSet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForCheckedSet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForCheckedSortedSet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForAbstractSet() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForConcurrentSkipListSetNatural() { - return Collections.emptySet(); + return emptySet(); } protected Collection suppressForConcurrentSkipListSetWithComparator() { - return Collections.emptySet(); + return emptySet(); } public Test testsForEmptySet() { @@ -132,7 +136,7 @@ public Test testsForEmptySet() { new TestStringSetGenerator() { @Override public Set create(String[] elements) { - return Collections.emptySet(); + return emptySet(); } }) .named("emptySet") @@ -146,7 +150,7 @@ public Test testsForSingletonSet() { new TestStringSetGenerator() { @Override public Set create(String[] elements) { - return Collections.singleton(elements[0]); + return singleton(elements[0]); } }) .named("singleton") @@ -286,7 +290,7 @@ public Test testsForUnmodifiableSet() { public Set create(String[] elements) { Set innerSet = new HashSet<>(); Collections.addAll(innerSet, elements); - return Collections.unmodifiableSet(innerSet); + return unmodifiableSet(innerSet); } }) .named("unmodifiableSet/HashSet") @@ -347,7 +351,7 @@ public Test testsForAbstractSet() { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - final String[] deduped = dedupe(elements); + String[] deduped = dedupe(elements); return new AbstractSet() { @Override public int size() { @@ -434,7 +438,7 @@ private static String[] dedupe(String[] elements) { } static Comparator arbitraryNullFriendlyComparator() { - return new NullFriendlyComparator(); + return new NullFriendlyComparator<>(); } private static final class NullFriendlyComparator implements Comparator, Serializable { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/UnhashableObject.java b/android/guava-testlib/src/com/google/common/collect/testing/UnhashableObject.java index 69d263bda096..fe021dfdda7d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/UnhashableObject.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/UnhashableObject.java @@ -17,6 +17,7 @@ package com.google.common.collect.testing; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.Nullable; /** * An unhashable object to be used in testing as values in our collections. @@ -32,7 +33,7 @@ public UnhashableObject(int value) { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof UnhashableObject) { UnhashableObject that = (UnhashableObject) object; return this.value == that.value; @@ -53,6 +54,6 @@ public String toString() { @Override public int compareTo(UnhashableObject o) { - return (this.value < o.value) ? -1 : (this.value > o.value) ? 1 : 0; + return Integer.compare(this.value, o.value); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/CollectionFeature.java b/android/guava-testlib/src/com/google/common/collect/testing/features/CollectionFeature.java index 0f0a1235f018..db1851306650 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/CollectionFeature.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/CollectionFeature.java @@ -16,8 +16,9 @@ package com.google.common.collect.testing.features; +import static com.google.common.collect.testing.Helpers.copyToSet; + import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,8 +32,7 @@ * * @author George van den Driessche */ -// Enum values use constructors with generic varargs. -@SuppressWarnings("unchecked") +@SuppressWarnings("rawtypes") // maybe avoidable if we rework the whole package? @GwtCompatible public enum CollectionFeature implements Feature { /** @@ -109,7 +109,7 @@ public enum CollectionFeature implements Feature { private final Set> implied; CollectionFeature(Feature... implied) { - this.implied = Helpers.copyToSet(implied); + this.implied = copyToSet(implied); } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/CollectionSize.java b/android/guava-testlib/src/com/google/common/collect/testing/features/CollectionSize.java index 6203e514862e..1ca44740a01a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/CollectionSize.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/CollectionSize.java @@ -16,14 +16,16 @@ package com.google.common.collect.testing.features; +import static com.google.common.collect.testing.Helpers.copyToSet; +import static java.util.Collections.emptySet; + import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collection; -import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * When describing the features of the collection produced by a given generator (i.e. in a call to @@ -41,8 +43,7 @@ * * @author George van den Driessche */ -// Enum values use constructors with generic varargs. -@SuppressWarnings("unchecked") +@SuppressWarnings("rawtypes") // maybe avoidable if we rework the whole package? @GwtCompatible public enum CollectionSize implements Feature, Comparable { /** Test an empty collection. */ @@ -59,17 +60,17 @@ public enum CollectionSize implements Feature, Comparable> implied; - private final Integer numElements; + private final @Nullable Integer numElements; CollectionSize(int numElements) { - this.implied = Collections.emptySet(); + this.implied = emptySet(); this.numElements = numElements; } CollectionSize(Feature... implied) { // Keep the order here, so that PerCollectionSizeTestSuiteBuilder // gives a predictable order of test suites. - this.implied = Helpers.copyToSet(implied); + this.implied = copyToSet(implied); this.numElements = null; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/ConflictingRequirementsException.java b/android/guava-testlib/src/com/google/common/collect/testing/features/ConflictingRequirementsException.java index 9096a118173f..e3d208af5067 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/ConflictingRequirementsException.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/ConflictingRequirementsException.java @@ -17,6 +17,8 @@ package com.google.common.collect.testing.features; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.Set; /** @@ -26,8 +28,8 @@ */ @GwtCompatible public class ConflictingRequirementsException extends Exception { - private Set> conflicts; - private Object source; + private final Set> conflicts; + private final Object source; public ConflictingRequirementsException( String message, Set> conflicts, Object source) { @@ -49,5 +51,5 @@ public String getMessage() { return super.getMessage() + " (source: " + source + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/FeatureUtil.java b/android/guava-testlib/src/com/google/common/collect/testing/features/FeatureUtil.java index 95cbc0b3d8be..6c1fb6451598 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/FeatureUtil.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/FeatureUtil.java @@ -16,14 +16,17 @@ package com.google.common.collect.testing.features; +import static com.google.common.collect.testing.Helpers.copyToSet; +import static java.util.Collections.disjoint; +import static java.util.Collections.unmodifiableList; + import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -31,6 +34,7 @@ import java.util.Map; import java.util.Queue; import java.util.Set; +import org.jspecify.annotations.NullMarked; /** * Utilities for collecting and validating tester requirements from annotations. @@ -38,9 +42,9 @@ * @author George van den Driessche */ @GwtIncompatible -public class FeatureUtil { +public final class FeatureUtil { /** A cache of annotated objects (typically a Class or Method) to its set of annotations. */ - private static Map> annotationCache = new HashMap<>(); + private static final Map> annotationCache = new HashMap<>(); private static final Map, TesterRequirements> classTesterRequirementsCache = new HashMap<>(); @@ -55,6 +59,7 @@ public class FeatureUtil { * @param features the set of features to expand * @return the same set of features, expanded with all implied features */ + @CanIgnoreReturnValue public static Set> addImpliedFeatures(Set> features) { Queue> queue = new ArrayDeque<>(features); while (!queue.isEmpty()) { @@ -139,12 +144,12 @@ public static TesterRequirements getTesterRequirements(Method testerMethod) */ static TesterRequirements buildTesterRequirements(Class testerClass) throws ConflictingRequirementsException { - final TesterRequirements declaredRequirements = buildDeclaredTesterRequirements(testerClass); + TesterRequirements declaredRequirements = buildDeclaredTesterRequirements(testerClass); Class baseClass = testerClass.getSuperclass(); if (baseClass == null) { return declaredRequirements; } else { - final TesterRequirements clonedBaseRequirements = + TesterRequirements clonedBaseRequirements = new TesterRequirements(getTesterRequirements(baseClass)); return incorporateRequirements(clonedBaseRequirements, declaredRequirements, testerClass); } @@ -176,19 +181,17 @@ static TesterRequirements buildTesterRequirements(Method testerMethod) private static TesterRequirements buildTesterRequirements(Annotation testerAnnotation) throws ConflictingRequirementsException { Class annotationClass = testerAnnotation.annotationType(); - final Feature[] presentFeatures; - final Feature[] absentFeatures; + Feature[] presentFeatures; + Feature[] absentFeatures; try { - presentFeatures = (Feature[]) annotationClass.getMethod("value").invoke(testerAnnotation); - absentFeatures = (Feature[]) annotationClass.getMethod("absent").invoke(testerAnnotation); + presentFeatures = (Feature[]) annotationClass.getMethod("value").invoke(testerAnnotation); + absentFeatures = (Feature[]) annotationClass.getMethod("absent").invoke(testerAnnotation); } catch (Exception e) { throw new IllegalArgumentException("Error extracting features from tester annotation.", e); } - Set> allPresentFeatures = - addImpliedFeatures(Helpers.>copyToSet(presentFeatures)); - Set> allAbsentFeatures = - addImpliedFeatures(Helpers.>copyToSet(absentFeatures)); - if (!Collections.disjoint(allPresentFeatures, allAbsentFeatures)) { + Set> allPresentFeatures = addImpliedFeatures(copyToSet(presentFeatures)); + Set> allAbsentFeatures = copyToSet(absentFeatures); + if (!disjoint(allPresentFeatures, allAbsentFeatures)) { throw new ConflictingRequirementsException( "Annotation explicitly or " + "implicitly requires one or more features to be both present " @@ -233,11 +236,16 @@ public static Iterable getTesterAnnotations(AnnotatedElement classOr if (annotations == null) { annotations = new ArrayList<>(); for (Annotation a : classOrMethod.getDeclaredAnnotations()) { - if (a.annotationType().isAnnotationPresent(TesterAnnotation.class)) { + /* + * We avoid reflecting on NullMarked because its @Target(..., MODULE) causes problems + * under JDK 8. + */ + if (!(a instanceof NullMarked) + && a.annotationType().isAnnotationPresent(TesterAnnotation.class)) { annotations.add(a); } } - annotations = Collections.unmodifiableList(annotations); + annotations = unmodifiableList(annotations); annotationCache.put(classOrMethod, annotations); } return annotations; @@ -254,6 +262,7 @@ public static Iterable getTesterAnnotations(AnnotatedElement classOr * @throws ConflictingRequirementsException if the additional requirements are inconsistent with * the existing requirements */ + @CanIgnoreReturnValue private static TesterRequirements incorporateRequirements( TesterRequirements requirements, TesterRequirements moreRequirements, Object source) throws ConflictingRequirementsException { @@ -276,7 +285,7 @@ private static void checkConflict( Set> newFeatures, Object source) throws ConflictingRequirementsException { - if (!Collections.disjoint(newFeatures, earlierFeatures)) { + if (!disjoint(newFeatures, earlierFeatures)) { throw new ConflictingRequirementsException( String.format( Locale.ROOT, @@ -289,10 +298,17 @@ private static void checkConflict( } } - /** Construct a new {@link java.util.Set} that is the intersection of the given sets. */ + /** + * Construct a new {@link java.util.Set} that is the intersection of the given sets. + * + * @deprecated Use {@link com.google.common.collect.Sets#intersection(Set, Set)} instead. + */ + @Deprecated public static Set intersection(Set set1, Set set2) { - Set result = Helpers.copyToSet(set1); + Set result = copyToSet(set1); result.retainAll(set2); return result; } + + private FeatureUtil() {} } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/ListFeature.java b/android/guava-testlib/src/com/google/common/collect/testing/features/ListFeature.java index 1427efaf0d37..da0d1a4cbce5 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/ListFeature.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/ListFeature.java @@ -16,8 +16,9 @@ package com.google.common.collect.testing.features; +import static com.google.common.collect.testing.Helpers.copyToSet; + import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -29,8 +30,7 @@ * * @author George van den Driessche */ -// Enum values use constructors with generic varargs. -@SuppressWarnings("unchecked") +@SuppressWarnings("rawtypes") // maybe avoidable if we rework the whole package? @GwtCompatible public enum ListFeature implements Feature { SUPPORTS_SET, @@ -49,7 +49,7 @@ public enum ListFeature implements Feature { private final Set> implied; ListFeature(Feature... implied) { - this.implied = Helpers.copyToSet(implied); + this.implied = copyToSet(implied); } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/MapFeature.java b/android/guava-testlib/src/com/google/common/collect/testing/features/MapFeature.java index dff7b124f1ef..14b78b5eb316 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/MapFeature.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/MapFeature.java @@ -16,8 +16,9 @@ package com.google.common.collect.testing.features; +import static com.google.common.collect.testing.Helpers.copyToSet; + import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -29,8 +30,7 @@ * * @author George van den Driessche */ -// Enum values use constructors with generic varargs. -@SuppressWarnings("unchecked") +@SuppressWarnings("rawtypes") // maybe avoidable if we rework the whole package? @GwtCompatible public enum MapFeature implements Feature { /** @@ -76,7 +76,7 @@ public enum MapFeature implements Feature { private final Set> implied; MapFeature(Feature... implied) { - this.implied = Helpers.copyToSet(implied); + this.implied = copyToSet(implied); } @Override @@ -88,8 +88,8 @@ public Set> getImpliedFeatures() { @Inherited @TesterAnnotation public @interface Require { - public abstract MapFeature[] value() default {}; + MapFeature[] value() default {}; - public abstract MapFeature[] absent() default {}; + MapFeature[] absent() default {}; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/SetFeature.java b/android/guava-testlib/src/com/google/common/collect/testing/features/SetFeature.java index 7b5dcd959ff5..0b01eb7919cb 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/SetFeature.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/SetFeature.java @@ -16,8 +16,9 @@ package com.google.common.collect.testing.features; +import static com.google.common.collect.testing.Helpers.copyToSet; + import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,8 +29,7 @@ * * @author George van den Driessche */ -// Enum values use constructors with generic varargs. -@SuppressWarnings("unchecked") +@SuppressWarnings("rawtypes") // maybe avoidable if we rework the whole package? @GwtCompatible public enum SetFeature implements Feature { GENERAL_PURPOSE(CollectionFeature.GENERAL_PURPOSE); @@ -37,7 +37,7 @@ public enum SetFeature implements Feature { private final Set> implied; SetFeature(Feature... implied) { - this.implied = Helpers.copyToSet(implied); + this.implied = copyToSet(implied); } @Override @@ -49,8 +49,8 @@ public Set> getImpliedFeatures() { @Inherited @TesterAnnotation public @interface Require { - public abstract SetFeature[] value() default {}; + SetFeature[] value() default {}; - public abstract SetFeature[] absent() default {}; + SetFeature[] absent() default {}; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/TesterAnnotation.java b/android/guava-testlib/src/com/google/common/collect/testing/features/TesterAnnotation.java index 1831e417f08e..247c62a7067a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/TesterAnnotation.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/TesterAnnotation.java @@ -16,6 +16,7 @@ import com.google.common.annotations.GwtCompatible; import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -29,7 +30,7 @@ * * @author George van den Driessche */ -@Target(value = {java.lang.annotation.ElementType.ANNOTATION_TYPE}) +@Target(value = {ElementType.ANNOTATION_TYPE}) @Retention(value = RetentionPolicy.RUNTIME) @Documented @GwtCompatible diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/TesterRequirements.java b/android/guava-testlib/src/com/google/common/collect/testing/features/TesterRequirements.java index 4780b1bf50ff..260c2b6feb14 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/features/TesterRequirements.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/TesterRequirements.java @@ -16,10 +16,14 @@ package com.google.common.collect.testing.features; +import static com.google.common.collect.testing.Helpers.copyToSet; + import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * Encapsulates the constraints that a class under test must satisfy in order for a tester method to @@ -33,8 +37,8 @@ public final class TesterRequirements { private final Set> absentFeatures; public TesterRequirements(Set> presentFeatures, Set> absentFeatures) { - this.presentFeatures = Helpers.copyToSet(presentFeatures); - this.absentFeatures = Helpers.copyToSet(absentFeatures); + this.presentFeatures = copyToSet(presentFeatures); + this.absentFeatures = copyToSet(absentFeatures); } public TesterRequirements(TesterRequirements tr) { @@ -42,7 +46,7 @@ public TesterRequirements(TesterRequirements tr) { } public TesterRequirements() { - this(Collections.>emptySet(), Collections.>emptySet()); + this(Collections.emptySet(), Collections.emptySet()); } public final Set> getPresentFeatures() { @@ -54,7 +58,7 @@ public final Set> getAbsentFeatures() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -76,5 +80,5 @@ public String toString() { return "{TesterRequirements: present=" + presentFeatures + ", absent=" + absentFeatures + "}"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/features/package-info.java b/android/guava-testlib/src/com/google/common/collect/testing/features/package-info.java new file mode 100644 index 000000000000..88b611ca42a9 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/features/package-info.java @@ -0,0 +1,2 @@ +@com.google.errorprone.annotations.CheckReturnValue +package com.google.common.collect.testing.features; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractBiMapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractBiMapTester.java index 85ddb447648b..a1e6ca4ae8e2 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractBiMapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractBiMapTester.java @@ -16,28 +16,37 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.mapEntry; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.BiMap; import com.google.common.collect.testing.AbstractMapTester; -import com.google.common.collect.testing.Helpers; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** Skeleton for a tester of a {@code BiMap}. */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public abstract class AbstractBiMapTester extends AbstractMapTester { +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public abstract class AbstractBiMapTester + extends AbstractMapTester { @Override protected BiMap getMap() { return (BiMap) super.getMap(); } - static Entry reverseEntry(Entry entry) { - return Helpers.mapEntry(entry.getValue(), entry.getKey()); + static Entry reverseEntry( + Entry entry) { + return mapEntry(entry.getValue(), entry.getKey()); } @Override @@ -47,7 +56,7 @@ protected void expectContents(Collection> expected) { for (Entry entry : expected) { reversedEntries.add(reverseEntry(entry)); } - Helpers.assertEqualIgnoringOrder(getMap().inverse().entrySet(), reversedEntries); + assertEqualIgnoringOrder(getMap().inverse().entrySet(), reversedEntries); for (Entry entry : expected) { assertEquals( diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractListMultimapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractListMultimapTester.java index 7bce8b012072..0c4534e55468 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractListMultimapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractListMultimapTester.java @@ -15,11 +15,13 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.Helpers.assertEqualInOrder; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ListMultimap; -import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -28,17 +30,20 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class AbstractListMultimapTester +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class AbstractListMultimapTester extends AbstractMultimapTester> { @Override protected void assertGet(K key, V... values) { - assertGet(key, Arrays.asList(values)); + assertGet(key, asList(values)); } @Override - protected void assertGet(K key, Collection values) { + protected void assertGet(K key, Collection values) { assertEqualInOrder(values, multimap().get(key)); if (!values.isEmpty()) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultimapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultimapTester.java index 7b7801db2633..c12835009f9a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultimapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultimapTester.java @@ -17,16 +17,19 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.mapEntry; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; import com.google.common.collect.testing.AbstractContainerTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.SampleElements; -import java.util.Arrays; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; import java.util.Iterator; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -35,8 +38,12 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public abstract class AbstractMultimapTester> +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public abstract class AbstractMultimapTester< + K extends @Nullable Object, V extends @Nullable Object, M extends Multimap> extends AbstractContainerTester> { private M multimap; @@ -45,21 +52,25 @@ protected M multimap() { return multimap; } - /** @return an array of the proper size with {@code null} as the key of the middle element. */ + /** + * @return an array of the proper size with {@code null} as the key of the middle element. + */ protected Entry[] createArrayWithNullKey() { Entry[] array = createSamplesArray(); - final int nullKeyLocation = getNullLocation(); - final Entry oldEntry = array[nullKeyLocation]; - array[nullKeyLocation] = Helpers.mapEntry(null, oldEntry.getValue()); + int nullKeyLocation = getNullLocation(); + Entry oldEntry = array[nullKeyLocation]; + array[nullKeyLocation] = mapEntry(null, oldEntry.getValue()); return array; } - /** @return an array of the proper size with {@code null} as the value of the middle element. */ + /** + * @return an array of the proper size with {@code null} as the value of the middle element. + */ protected Entry[] createArrayWithNullValue() { Entry[] array = createSamplesArray(); - final int nullValueLocation = getNullLocation(); - final Entry oldEntry = array[nullValueLocation]; - array[nullValueLocation] = Helpers.mapEntry(oldEntry.getKey(), null); + int nullValueLocation = getNullLocation(); + Entry oldEntry = array[nullValueLocation]; + array[nullValueLocation] = mapEntry(oldEntry.getKey(), null); return array; } @@ -69,8 +80,8 @@ protected Entry[] createArrayWithNullValue() { */ protected Entry[] createArrayWithNullKeyAndValue() { Entry[] array = createSamplesArray(); - final int nullValueLocation = getNullLocation(); - array[nullValueLocation] = Helpers.mapEntry(null, null); + int nullValueLocation = getNullLocation(); + array[nullValueLocation] = mapEntry(null, null); return array; } @@ -121,26 +132,30 @@ protected Collection> actualContents() { // TODO: dispose of this once collection is encapsulated. @Override + @CanIgnoreReturnValue protected M resetContainer(M newContents) { multimap = super.resetContainer(newContents); return multimap; } + @CanIgnoreReturnValue protected Multimap resetContainer(Entry... newContents) { multimap = super.resetContainer(getSubjectGenerator().create((Object[]) newContents)); return multimap; } - /** @see AbstractContainerTester#resetContainer() */ + /** + * @see AbstractContainerTester#resetContainer() + */ protected void resetCollection() { resetContainer(); } protected void assertGet(K key, V... values) { - assertGet(key, Arrays.asList(values)); + assertGet(key, asList(values)); } - protected void assertGet(K key, Collection values) { + protected void assertGet(K key, Collection values) { assertEqualIgnoringOrder(values, multimap().get(key)); if (!values.isEmpty()) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultisetSetCountTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultisetSetCountTester.java index a72fd9fba5e8..b2c7d175f785 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultisetSetCountTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultisetSetCountTester.java @@ -23,16 +23,18 @@ import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Multiset; import com.google.common.collect.Multiset.Entry; import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; @@ -46,8 +48,10 @@ * * @author Chris Povirk */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public abstract class AbstractMultisetSetCountTester extends AbstractMultisetTester { /* * TODO: consider adding MultisetFeatures.SUPPORTS_SET_COUNT. Currently we @@ -188,26 +192,16 @@ public void testSetCount_zeroToOne_supported() { @CollectionFeature.Require({SUPPORTS_ADD, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) public void testSetCountZeroToOneConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - assertSetCount(e3(), 1); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + Iterator iterator = collection.iterator(); + assertSetCount(e3(), 1); + assertThrows(ConcurrentModificationException.class, iterator::next); } @CollectionFeature.Require({SUPPORTS_ADD, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) public void testSetCountZeroToOneConcurrentWithEntrySetIteration() { - try { - Iterator> iterator = getMultiset().entrySet().iterator(); - assertSetCount(e3(), 1); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + Iterator> iterator = getMultiset().entrySet().iterator(); + assertSetCount(e3(), 1); + assertThrows(ConcurrentModificationException.class, iterator::next); } @CollectionFeature.Require(SUPPORTS_ADD) @@ -248,27 +242,17 @@ public void testSetCount_oneToZero_supported() { @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) @CollectionSize.Require(absent = ZERO) public void testSetCountOneToZeroConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - assertSetCount(e0(), 0); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + Iterator iterator = collection.iterator(); + assertSetCount(e0(), 0); + assertThrows(ConcurrentModificationException.class, iterator::next); } @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) @CollectionSize.Require(absent = ZERO) public void testSetCountOneToZeroConcurrentWithEntrySetIteration() { - try { - Iterator> iterator = getMultiset().entrySet().iterator(); - assertSetCount(e0(), 0); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + Iterator> iterator = getMultiset().entrySet().iterator(); + assertSetCount(e0(), 0); + assertThrows(ConcurrentModificationException.class, iterator::next); } @CollectionSize.Require(SEVERAL) @@ -323,11 +307,7 @@ public void testSetCount_addNull_nullSupported() { @CollectionFeature.Require(value = SUPPORTS_ADD, absent = ALLOWS_NULL_VALUES) public void testSetCount_addNull_nullUnsupported() { - try { - setCountNoCheckReturnValue(null, 1); - fail("adding null with setCount() should throw NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> setCountNoCheckReturnValue(null, 1)); } @CollectionFeature.Require(ALLOWS_NULL_VALUES) @@ -360,11 +340,7 @@ public void testSetCount_existingNoNopNull_nullSupported() { @CollectionFeature.Require(SUPPORTS_REMOVE) public void testSetCount_negative_removeSupported() { - try { - setCountNoCheckReturnValue(e3(), -1); - fail("calling setCount() with a negative count should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> setCountNoCheckReturnValue(e3(), -1)); } @CollectionFeature.Require(absent = SUPPORTS_REMOVE) @@ -384,14 +360,16 @@ public void testSetCount_negative_removeUnsupported() { * Returns {@link Method} instances for the {@code setCount()} tests that assume multisets support * duplicates so that the test of {@code Multisets.forSet()} can suppress them. */ + @J2ktIncompatible @GwtIncompatible // reflection public static List getSetCountDuplicateInitializingMethods() { - return Arrays.asList( + return asList( getMethod("testSetCount_threeToThree_removeSupported"), getMethod("testSetCount_threeToZero_supported"), getMethod("testSetCount_threeToOne_supported")); } + @J2ktIncompatible @GwtIncompatible // reflection private static Method getMethod(String methodName) { return Helpers.getMethod(AbstractMultisetSetCountTester.class, methodName); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultisetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultisetTester.java index 18ffcbca5126..5e0279ae1099 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultisetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/AbstractMultisetTester.java @@ -27,7 +27,9 @@ * @author Jared Levy */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class AbstractMultisetTester extends AbstractCollectionTester { protected final Multiset getMultiset() { return (Multiset) collection; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapClearTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapClearTester.java index 4319a85068b9..fa6242ae0c6d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapClearTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapClearTester.java @@ -29,7 +29,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class BiMapClearTester extends AbstractBiMapTester { @MapFeature.Require(SUPPORTS_REMOVE) public void testClearClearsInverse() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapEntrySetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapEntrySetTester.java index 5c9a47989bf4..5d97e11636d8 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapEntrySetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapEntrySetTester.java @@ -20,6 +20,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionSize; @@ -29,7 +30,9 @@ /** Tester for {@code BiMap.entrySet} and methods on the entries in the set. */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class BiMapEntrySetTester extends AbstractBiMapTester { @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(absent = ZERO) @@ -47,11 +50,7 @@ public void testSetValue_valueAbsent() { public void testSetValue_valuePresent() { for (Entry entry : getMap().entrySet()) { if (entry.getKey().equals(k0())) { - try { - entry.setValue(v1()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> entry.setValue(v1())); } } expectUnchanged(); @@ -61,11 +60,7 @@ public void testSetValue_valuePresent() { @CollectionSize.Require(absent = ZERO) public void testSetValueNullUnsupported() { for (Entry entry : getMap().entrySet()) { - try { - entry.setValue(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> entry.setValue(null)); expectUnchanged(); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapGenerators.java b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapGenerators.java index 13efdcd6d1be..e669f41ccabc 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapGenerators.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapGenerators.java @@ -16,13 +16,16 @@ package com.google.common.collect.testing.google; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; -import com.google.common.collect.Maps; -import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; /** * Generators of various {@link com.google.common.collect.BiMap}s and derived collections. @@ -31,12 +34,14 @@ * @author Hayward Chan */ @GwtCompatible +@NullMarked public class BiMapGenerators { public static class ImmutableBiMapGenerator extends TestStringBiMapGenerator { @Override protected BiMap create(Entry[] entries) { ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); for (Entry entry : entries) { + checkNotNull(entry); builder.put(entry.getKey(), entry.getValue()); } return builder.build(); @@ -46,7 +51,7 @@ protected BiMap create(Entry[] entries) { public static class ImmutableBiMapCopyOfGenerator extends TestStringBiMapGenerator { @Override protected BiMap create(Entry[] entries) { - Map builder = Maps.newLinkedHashMap(); + Map builder = new LinkedHashMap<>(); for (Entry entry : entries) { builder.put(entry.getKey(), entry.getValue()); } @@ -57,7 +62,15 @@ protected BiMap create(Entry[] entries) { public static class ImmutableBiMapCopyOfEntriesGenerator extends TestStringBiMapGenerator { @Override protected BiMap create(Entry[] entries) { - return ImmutableBiMap.copyOf(Arrays.asList(entries)); + return ImmutableBiMap.copyOf(asList(entries)); } } + + /** + * Useless constructor for a class of static utility methods. + * + * @deprecated Do not instantiate this utility class. + */ + @Deprecated + public BiMapGenerators() {} } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapInverseTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapInverseTester.java index 984558e2b72d..217548505ce5 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapInverseTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapInverseTester.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.BiMap; import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; @@ -38,8 +39,10 @@ * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class BiMapInverseTester extends AbstractBiMapTester { public void testInverseSame() { @@ -56,7 +59,7 @@ public void testInverseSerialization() { assertSame(copy.forward, copy.backward.inverse()); } - private static class BiMapPair implements Serializable { + private static final class BiMapPair implements Serializable { final BiMap forward; final BiMap backward; @@ -65,18 +68,20 @@ private static class BiMapPair implements Serializable { this.backward = original.inverse(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns {@link Method} instances for the tests that assume that the inverse will be the same * after serialization. */ + @J2ktIncompatible @GwtIncompatible // reflection public static List getInverseSameAfterSerializingMethods() { return Collections.singletonList(getMethod("testInverseSerialization")); } + @J2ktIncompatible @GwtIncompatible // reflection private static Method getMethod(String methodName) { return Helpers.getMethod(BiMapInverseTester.class, methodName); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapPutTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapPutTester.java index 4bb72a2f3888..5106c538e005 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapPutTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapPutTester.java @@ -16,40 +16,36 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import org.junit.Ignore; /** Tester for {@code BiMap.put} and {@code BiMap.forcePut}. */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class BiMapPutTester extends AbstractBiMapTester { - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(ZERO) public void testPutWithSameValueFails() { getMap().put(k0(), v0()); - try { - getMap().put(k1(), v0()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // success - } + assertThrows(IllegalArgumentException.class, () -> getMap().put(k1(), v0())); // verify that the bimap is unchanged expectAdded(e0()); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(ZERO) public void testPutPresentKeyDifferentValue() { @@ -57,10 +53,9 @@ public void testPutPresentKeyDifferentValue() { getMap().put(k0(), v1()); // verify that the bimap is changed, and that the old inverse mapping // from v1 -> v0 is deleted - expectContents(Helpers.mapEntry(k0(), v1())); + expectContents(mapEntry(k0(), v1())); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(ZERO) public void putDistinctKeysDistinctValues() { @@ -69,41 +64,37 @@ public void putDistinctKeysDistinctValues() { expectAdded(e0(), e1()); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(ONE) public void testForcePutKeyPresent() { getMap().forcePut(k0(), v1()); - expectContents(Helpers.mapEntry(k0(), v1())); + expectContents(mapEntry(k0(), v1())); assertFalse(getMap().containsValue(v0())); assertNull(getMap().inverse().get(v0())); assertEquals(1, getMap().size()); assertTrue(getMap().containsKey(k0())); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(ONE) public void testForcePutValuePresent() { getMap().forcePut(k1(), v0()); - expectContents(Helpers.mapEntry(k1(), v0())); + expectContents(mapEntry(k1(), v0())); assertEquals(k1(), getMap().inverse().get(v0())); assertEquals(1, getMap().size()); assertFalse(getMap().containsKey(k0())); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(SEVERAL) public void testForcePutKeyAndValuePresent() { getMap().forcePut(k0(), v1()); - expectContents(Helpers.mapEntry(k0(), v1()), Helpers.mapEntry(k2(), v2())); + expectContents(mapEntry(k0(), v1()), mapEntry(k2(), v2())); assertEquals(2, getMap().size()); assertFalse(getMap().containsKey(k1())); assertFalse(getMap().containsValue(v0())); } - @SuppressWarnings("unchecked") @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS}) @CollectionSize.Require(ONE) public void testForcePutNullKeyPresent() { @@ -111,7 +102,7 @@ public void testForcePutNullKeyPresent() { getMap().forcePut(null, v1()); - expectContents(Helpers.mapEntry((K) null, v1())); + expectContents(mapEntry((K) null, v1())); assertFalse(getMap().containsValue(v0())); @@ -122,7 +113,6 @@ public void testForcePutNullKeyPresent() { assertEquals(1, getMap().size()); } - @SuppressWarnings("unchecked") @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) @CollectionSize.Require(ONE) public void testForcePutNullValuePresent() { @@ -130,7 +120,7 @@ public void testForcePutNullValuePresent() { getMap().forcePut(k1(), null); - expectContents(Helpers.mapEntry(k1(), (V) null)); + expectContents(mapEntry(k1(), (V) null)); assertFalse(getMap().containsKey(k0())); @@ -143,7 +133,6 @@ public void testForcePutNullValuePresent() { // nb: inverse is run through its own entire suite - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(ZERO) public void testInversePut() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapRemoveTester.java index e54256ad864b..9e8dee23d16a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapRemoveTester.java @@ -33,9 +33,10 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class BiMapRemoveTester extends AbstractBiMapTester { - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveKeyRemovesFromInverse() { @@ -43,7 +44,6 @@ public void testRemoveKeyRemovesFromInverse() { expectMissing(e0()); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveKeyFromKeySetRemovesFromInverse() { @@ -51,7 +51,6 @@ public void testRemoveKeyFromKeySetRemovesFromInverse() { expectMissing(e0()); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveFromValuesRemovesFromInverse() { @@ -59,7 +58,6 @@ public void testRemoveFromValuesRemovesFromInverse() { expectMissing(e0()); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveFromInverseRemovesFromForward() { @@ -67,7 +65,6 @@ public void testRemoveFromInverseRemovesFromForward() { expectMissing(e0()); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveFromInverseKeySetRemovesFromForward() { @@ -75,7 +72,6 @@ public void testRemoveFromInverseKeySetRemovesFromForward() { expectMissing(e0()); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveFromInverseValuesRemovesFromInverse() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapTestSuiteBuilder.java index fa21a71a536d..28b5572e974f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/BiMapTestSuiteBuilder.java @@ -16,6 +16,8 @@ package com.google.common.collect.testing.google; +import static java.util.Collections.emptySet; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.BiMap; import com.google.common.collect.testing.AbstractTester; @@ -32,7 +34,6 @@ import com.google.common.collect.testing.google.DerivedGoogleCollectionGenerators.MapGenerator; import com.google.common.collect.testing.testers.SetCreationTester; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; @@ -53,6 +54,7 @@ public static BiMapTestSuiteBuilder using(TestBiMapGenerator return new BiMapTestSuiteBuilder().usingGenerator(generator); } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { List> testers = new ArrayList<>(); @@ -69,7 +71,7 @@ enum NoRecurse implements Feature { @Override public Set> getImpliedFeatures() { - return Collections.emptySet(); + return emptySet(); } } @@ -88,6 +90,8 @@ protected List createDerivedSuites( .suppressing(parentBuilder.getSuppressedTests()) .suppressing(SetCreationTester.class.getMethods()) // BiMap.entrySet() duplicate-handling behavior is too confusing for SetCreationTester + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); /* * TODO(cpovirk): the Map tests duplicate most of this effort by using a @@ -101,6 +105,8 @@ protected List createDerivedSuites( .suppressing(parentBuilder.getSuppressedTests()) .suppressing(SetCreationTester.class.getMethods()) // BiMap.values() duplicate-handling behavior is too confusing for SetCreationTester + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); if (!parentBuilder.getFeatures().contains(NoRecurse.INVERSE)) { derived.add( @@ -109,6 +115,8 @@ protected List createDerivedSuites( .withFeatures(computeInverseFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + " inverse") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/DerivedGoogleCollectionGenerators.java b/android/guava-testlib/src/com/google/common/collect/testing/google/DerivedGoogleCollectionGenerators.java index bbeefd278e00..45376acba99b 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/DerivedGoogleCollectionGenerators.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/DerivedGoogleCollectionGenerators.java @@ -16,10 +16,12 @@ package com.google.common.collect.testing.google; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.testing.Helpers.mapEntry; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.BiMap; import com.google.common.collect.testing.DerivedGenerator; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.OneSizeTestContainerGenerator; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.TestMapGenerator; @@ -31,6 +33,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Derived suite generators for Guava collection interfaces, split out of the suite builders so that @@ -39,8 +43,10 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public final class DerivedGoogleCollectionGenerators { - public static class MapGenerator implements TestMapGenerator, DerivedGenerator { + public static class MapGenerator + implements TestMapGenerator, DerivedGenerator { private final OneSizeTestContainerGenerator, Entry> generator; @@ -87,7 +93,7 @@ public TestSubjectGenerator getInnerGenerator() { } } - public static class InverseBiMapGenerator + public static class InverseBiMapGenerator implements TestBiMapGenerator, DerivedGenerator { private final OneSizeTestContainerGenerator, Entry> generator; @@ -109,7 +115,8 @@ public SampleElements> samples() { } private Entry reverse(Entry entry) { - return Helpers.mapEntry(entry.getValue(), entry.getKey()); + checkNotNull(entry); + return mapEntry(entry.getValue(), entry.getKey()); } @SuppressWarnings("unchecked") @@ -125,7 +132,7 @@ public BiMap create(Object... elements) { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -151,7 +158,7 @@ public TestSubjectGenerator getInnerGenerator() { } } - public static class BiMapValueSetGenerator + public static class BiMapValueSetGenerator implements TestSetGenerator, DerivedGenerator { private final OneSizeTestContainerGenerator, Entry> mapGenerator; private final SampleElements samples; @@ -159,9 +166,9 @@ public static class BiMapValueSetGenerator public BiMapValueSetGenerator( OneSizeTestContainerGenerator, Entry> mapGenerator) { this.mapGenerator = mapGenerator; - final SampleElements> mapSamples = this.mapGenerator.samples(); + SampleElements> mapSamples = this.mapGenerator.samples(); this.samples = - new SampleElements( + new SampleElements<>( mapSamples.e0().getValue(), mapSamples.e1().getValue(), mapSamples.e2().getValue(), @@ -186,7 +193,7 @@ public Set create(Object... elements) { Collection> entries = new ArrayList<>(elements.length); int i = 0; for (Entry entry : originalEntries) { - entries.add(Helpers.mapEntry(entry.getKey(), valuesArray[i++])); + entries.add(mapEntry(entry.getKey(), valuesArray[i++])); } return mapGenerator.create(entries.toArray()).values(); @@ -194,7 +201,7 @@ public Set create(Object... elements) { @Override public V[] createArray(int length) { - final V[] vs = + V[] vs = ((TestBiMapGenerator) mapGenerator.getInnerGenerator()).createValueArray(length); return vs; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ListGenerators.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ListGenerators.java index 0839f09188ae..e6e18a93e1fc 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/ListGenerators.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ListGenerators.java @@ -16,20 +16,21 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.Lists.charactersOf; +import static java.lang.System.arraycopy; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import com.google.common.collect.testing.TestCharacterListGenerator; import com.google.common.collect.testing.TestListGenerator; import com.google.common.collect.testing.TestStringListGenerator; import com.google.common.collect.testing.TestUnhashableCollectionGenerator; import com.google.common.collect.testing.UnhashableObject; import com.google.common.primitives.Chars; -import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * Common generators of different types of lists. @@ -37,6 +38,7 @@ * @author Hayward Chan */ @GwtCompatible +@NullMarked public final class ListGenerators { private ListGenerators() {} @@ -51,7 +53,7 @@ protected List create(String[] elements) { public static class BuilderAddListGenerator extends TestStringListGenerator { @Override protected List create(String[] elements) { - ImmutableList.Builder builder = ImmutableList.builder(); + ImmutableList.Builder builder = ImmutableList.builder(); for (String element : elements) { builder.add(element); } @@ -80,8 +82,8 @@ public static class ImmutableListHeadSubListGenerator extends TestStringListGene protected List create(String[] elements) { String[] suffix = {"f", "g"}; String[] all = new String[elements.length + suffix.length]; - System.arraycopy(elements, 0, all, 0, elements.length); - System.arraycopy(suffix, 0, all, elements.length, suffix.length); + arraycopy(elements, 0, all, 0, elements.length); + arraycopy(suffix, 0, all, elements.length, suffix.length); return ImmutableList.copyOf(all).subList(0, elements.length); } } @@ -91,8 +93,8 @@ public static class ImmutableListTailSubListGenerator extends TestStringListGene protected List create(String[] elements) { String[] prefix = {"f", "g"}; String[] all = new String[elements.length + prefix.length]; - System.arraycopy(prefix, 0, all, 0, 2); - System.arraycopy(elements, 0, all, 2, elements.length); + arraycopy(prefix, 0, all, 0, 2); + arraycopy(elements, 0, all, 2, elements.length); return ImmutableList.copyOf(all).subList(2, elements.length + 2); } } @@ -104,9 +106,9 @@ protected List create(String[] elements) { String[] suffix = {"h", "i"}; String[] all = new String[2 + elements.length + 2]; - System.arraycopy(prefix, 0, all, 0, 2); - System.arraycopy(elements, 0, all, 2, elements.length); - System.arraycopy(suffix, 0, all, 2 + elements.length, 2); + arraycopy(prefix, 0, all, 0, 2); + arraycopy(elements, 0, all, 2, elements.length); + arraycopy(suffix, 0, all, 2 + elements.length, 2); return ImmutableList.copyOf(all).subList(2, elements.length + 2); } @@ -115,18 +117,18 @@ protected List create(String[] elements) { public static class CharactersOfStringGenerator extends TestCharacterListGenerator { @Override public List create(Character[] elements) { - char[] chars = Chars.toArray(Arrays.asList(elements)); - return Lists.charactersOf(String.copyValueOf(chars)); + char[] chars = Chars.toArray(asList(elements)); + return charactersOf(String.copyValueOf(chars)); } } public static class CharactersOfCharSequenceGenerator extends TestCharacterListGenerator { @Override public List create(Character[] elements) { - char[] chars = Chars.toArray(Arrays.asList(elements)); + char[] chars = Chars.toArray(asList(elements)); StringBuilder str = new StringBuilder(); str.append(chars); - return Lists.charactersOf(str); + return charactersOf(str); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapAsMapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapAsMapTester.java index ca6f21af2c55..5ec59bde6929 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapAsMapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapAsMapTester.java @@ -14,24 +14,27 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import com.google.common.testing.EqualsTester; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -42,8 +45,12 @@ * @param The value type of the tested multimap. */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class ListMultimapAsMapTester extends AbstractListMultimapTester { +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class ListMultimapAsMapTester + extends AbstractListMultimapTester { public void testAsMapValuesImplementList() { for (Collection valueCollection : multimap().asMap().values()) { assertTrue(valueCollection instanceof List); @@ -67,9 +74,8 @@ public void testAsMapRemoveImplementsList() { @CollectionSize.Require(SEVERAL) public void testEquals() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k1(), v0()), Helpers.mapEntry(k0(), v3())); - Map> expected = Maps.newHashMap(); + resetContainer(mapEntry(k0(), v0()), mapEntry(k1(), v0()), mapEntry(k0(), v3())); + Map> expected = new HashMap<>(); expected.put(k0(), Lists.newArrayList(v0(), v3())); expected.put(k1(), Lists.newArrayList(v0())); new EqualsTester().addEqualityGroup(expected, multimap().asMap()).testEquals(); @@ -77,22 +83,25 @@ public void testEquals() { @CollectionSize.Require(SEVERAL) public void testEntrySetEquals() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k1(), v0()), Helpers.mapEntry(k0(), v3())); - Set>> expected = Sets.newHashSet(); - expected.add(Helpers.mapEntry(k0(), (Collection) Lists.newArrayList(v0(), v3()))); - expected.add(Helpers.mapEntry(k1(), (Collection) Lists.newArrayList(v0()))); + resetContainer(mapEntry(k0(), v0()), mapEntry(k1(), v0()), mapEntry(k0(), v3())); + Set>> expected = new HashSet<>(); + expected.add(mapEntry(k0(), (Collection) Lists.newArrayList(v0(), v3()))); + expected.add(mapEntry(k1(), (Collection) Lists.newArrayList(v0()))); new EqualsTester().addEqualityGroup(expected, multimap().asMap().entrySet()).testEquals(); } @CollectionSize.Require(SEVERAL) @MapFeature.Require(SUPPORTS_REMOVE) + /* + * ListMultimap.asMap essentially returns a Map>; we just can't declare it that way. + * Thus, calls like asMap().values().remove(someList) are safe because they are comparing a list + * to a collection of other lists. + */ + @SuppressWarnings("CollectionUndefinedEquality") public void testValuesRemove() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k1(), v0()), Helpers.mapEntry(k0(), v3())); - assertTrue(multimap().asMap().values().remove(Collections.singletonList(v0()))); + resetContainer(mapEntry(k0(), v0()), mapEntry(k1(), v0()), mapEntry(k0(), v3())); + assertTrue(multimap().asMap().values().remove(singletonList(v0()))); assertEquals(2, multimap().size()); - assertEquals( - Collections.singletonMap(k0(), Lists.newArrayList(v0(), v3())), multimap().asMap()); + assertEquals(singletonMap(k0(), Lists.newArrayList(v0(), v3())), multimap().asMap()); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapEqualsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapEqualsTester.java index 2a9b14489b9f..01c51fa2be75 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapEqualsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapEqualsTester.java @@ -14,11 +14,11 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ListMultimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.testing.EqualsTester; import org.junit.Ignore; @@ -29,22 +29,18 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListMultimapEqualsTester extends AbstractListMultimapTester { @CollectionSize.Require(SEVERAL) public void testOrderingAffectsEqualsComparisons() { ListMultimap multimap1 = getSubjectGenerator() - .create( - Helpers.mapEntry(k0(), v0()), - Helpers.mapEntry(k0(), v1()), - Helpers.mapEntry(k0(), v0())); + .create(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k0(), v0())); ListMultimap multimap2 = getSubjectGenerator() - .create( - Helpers.mapEntry(k0(), v1()), - Helpers.mapEntry(k0(), v0()), - Helpers.mapEntry(k0(), v0())); + .create(mapEntry(k0(), v1()), mapEntry(k0(), v0()), mapEntry(k0(), v0())); new EqualsTester().addEqualityGroup(multimap1).addEqualityGroup(multimap2).testEquals(); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapPutAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapPutAllTester.java index 243361680335..046014b5e43d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapPutAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapPutAllTester.java @@ -16,11 +16,11 @@ import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ListMultimap; import com.google.common.collect.testing.features.MapFeature; -import java.util.Arrays; import java.util.List; import org.junit.Ignore; @@ -30,12 +30,13 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListMultimapPutAllTester extends AbstractListMultimapTester { @MapFeature.Require(SUPPORTS_PUT) public void testPutAllAddsAtEndInOrder() { - @SuppressWarnings("unchecked") - List values = Arrays.asList(v3(), v1(), v4()); + List values = asList(v3(), v1(), v4()); for (K k : sampleKeys()) { resetContainer(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapPutTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapPutTester.java index c459496825dd..f730826c5fbe 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapPutTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapPutTester.java @@ -20,7 +20,6 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ListMultimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.List; @@ -33,7 +32,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListMultimapPutTester extends AbstractListMultimapTester { // MultimapPutTester tests non-duplicate values, but ignores ordering @@ -44,7 +45,7 @@ public void testPutAddsValueAtEnd() { resetContainer(); List values = multimap().get(key); - List expectedValues = Helpers.copyToList(values); + List expectedValues = copyToList(values); assertTrue(multimap().put(key, value)); expectedValues.add(value); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapRemoveTester.java index 04ac0a2bc5c3..d2a5263596b4 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapRemoveTester.java @@ -19,12 +19,12 @@ import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ListMultimap; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map.Entry; @@ -36,9 +36,10 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListMultimapRemoveTester extends AbstractListMultimapTester { - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testMultimapRemoveDeletesFirstOccurrence() { @@ -49,11 +50,10 @@ public void testMultimapRemoveDeletesFirstOccurrence() { assertContentsInOrder(list, v1(), v0()); } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testRemoveAtIndexFromGetPropagates() { - List values = Arrays.asList(v0(), v1(), v0()); + List values = asList(v0(), v1(), v0()); for (int i = 0; i < 3; i++) { resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k0(), v0())); @@ -66,11 +66,10 @@ public void testRemoveAtIndexFromGetPropagates() { } } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testRemoveAtIndexFromAsMapPropagates() { - List values = Arrays.asList(v0(), v1(), v0()); + List values = asList(v0(), v1(), v0()); for (int i = 0; i < 3; i++) { resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k0(), v0())); @@ -84,11 +83,10 @@ public void testRemoveAtIndexFromAsMapPropagates() { } } - @SuppressWarnings("unchecked") @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testRemoveAtIndexFromAsMapEntrySetPropagates() { - List values = Arrays.asList(v0(), v1(), v0()); + List values = asList(v0(), v1(), v0()); for (int i = 0; i < 3; i++) { resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k0(), v0())); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapReplaceValuesTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapReplaceValuesTester.java index b0701658fcbb..1f87e69f0748 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapReplaceValuesTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapReplaceValuesTester.java @@ -16,11 +16,11 @@ import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ListMultimap; import com.google.common.collect.testing.features.MapFeature; -import java.util.Arrays; import java.util.List; import org.junit.Ignore; @@ -30,12 +30,13 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListMultimapReplaceValuesTester extends AbstractListMultimapTester { @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) public void testReplaceValuesPreservesOrder() { - @SuppressWarnings("unchecked") - List values = Arrays.asList(v3(), v1(), v4()); + List values = asList(v3(), v1(), v4()); for (K k : sampleKeys()) { resetContainer(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapTestSuiteBuilder.java index b55c7d648f10..4291876e7f1c 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ListMultimapTestSuiteBuilder.java @@ -16,11 +16,12 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.copyToList; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.ListMultimap; import com.google.common.collect.testing.AbstractTester; import com.google.common.collect.testing.FeatureSpecificTestSuiteBuilder; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.OneSizeTestContainerGenerator; import com.google.common.collect.testing.TestListGenerator; @@ -52,9 +53,10 @@ public static ListMultimapTestSuiteBuilder using( return result; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(ListMultimapAsMapTester.class); testers.add(ListMultimapEqualsTester.class); testers.add(ListMultimapPutTester.class); @@ -101,16 +103,19 @@ Set> computeMultimapGetFeatures(Set> multimapFeatures) { if (derivedFeatures.contains(CollectionFeature.SUPPORTS_ADD)) { derivedFeatures.add(ListFeature.SUPPORTS_ADD_WITH_INDEX); } + if (derivedFeatures.contains(CollectionFeature.SUPPORTS_REMOVE)) { + derivedFeatures.add(ListFeature.SUPPORTS_REMOVE_WITH_INDEX); + } if (derivedFeatures.contains(CollectionFeature.GENERAL_PURPOSE)) { derivedFeatures.add(ListFeature.GENERAL_PURPOSE); } return derivedFeatures; } - private static class MultimapGetGenerator + private static final class MultimapGetGenerator extends MultimapTestSuiteBuilder.MultimapGetGenerator> implements TestListGenerator { - public MultimapGetGenerator( + MultimapGetGenerator( OneSizeTestContainerGenerator, Entry> multimapGenerator) { super(multimapGenerator); } @@ -121,10 +126,10 @@ public List create(Object... elements) { } } - private static class MultimapAsMapGetGenerator + private static final class MultimapAsMapGetGenerator extends MultimapTestSuiteBuilder.MultimapAsMapGetGenerator> implements TestListGenerator { - public MultimapAsMapGetGenerator( + MultimapAsMapGetGenerator( OneSizeTestContainerGenerator, Entry> multimapGenerator) { super(multimapGenerator); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MapGenerators.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MapGenerators.java index bb84ea15f931..f00017c89252 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MapGenerators.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MapGenerators.java @@ -16,12 +16,14 @@ package com.google.common.collect.testing.google; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.testing.Helpers.mapEntry; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.testing.AnEnum; @@ -33,12 +35,14 @@ import com.google.common.collect.testing.TestStringMapGenerator; import com.google.common.collect.testing.TestUnhashableCollectionGenerator; import com.google.common.collect.testing.UnhashableObject; -import java.util.Arrays; import java.util.Collection; import java.util.EnumMap; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; /** * Generators of different types of map and related collections, such as keys, entries and values. @@ -46,22 +50,24 @@ * @author Hayward Chan */ @GwtCompatible +@NullMarked public class MapGenerators { public static class ImmutableMapGenerator extends TestStringMapGenerator { @Override protected Map create(Entry[] entries) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Entry entry : entries) { + checkNotNull(entry); builder.put(entry.getKey(), entry.getValue()); } - return builder.build(); + return builder.buildOrThrow(); } } public static class ImmutableMapCopyOfGenerator extends TestStringMapGenerator { @Override protected Map create(Entry[] entries) { - Map builder = Maps.newLinkedHashMap(); + Map builder = new LinkedHashMap<>(); for (Entry entry : entries) { builder.put(entry.getKey(), entry.getValue()); } @@ -72,7 +78,7 @@ protected Map create(Entry[] entries) { public static class ImmutableMapCopyOfEntriesGenerator extends TestStringMapGenerator { @Override protected Map create(Entry[] entries) { - return ImmutableMap.copyOf(Arrays.asList(entries)); + return ImmutableMap.copyOf(asList(entries)); } } @@ -86,7 +92,7 @@ public Collection create(UnhashableObject[] elements) { for (UnhashableObject value : elements) { builder.put(key++, value); } - return builder.build().values(); + return builder.buildOrThrow().values(); } } @@ -97,7 +103,7 @@ public List create(String[] elements) { for (int i = 0; i < elements.length; i++) { builder.put(elements[i], i); } - return builder.build().keySet().asList(); + return builder.buildOrThrow().keySet().asList(); } } @@ -108,7 +114,7 @@ public List create(String[] elements) { for (int i = 0; i < elements.length; i++) { builder.put(i, elements[i]); } - return builder.build().values().asList(); + return builder.buildOrThrow().values().asList(); } } @@ -128,7 +134,7 @@ public SampleElements> samples() { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -141,19 +147,19 @@ public List> create(Object... elements) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Object o : elements) { @SuppressWarnings("unchecked") - Entry entry = (Entry) o; + Entry entry = (Entry) checkNotNull(o); builder.put(entry); } - return builder.build().entrySet().asList(); + return builder.buildOrThrow().entrySet().asList(); } } public static class ImmutableEnumMapGenerator extends TestEnumMapGenerator { @Override protected Map create(Entry[] entries) { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); for (Entry entry : entries) { - // checkArgument(!map.containsKey(entry.getKey())); + checkNotNull(entry); map.put(entry.getKey(), entry.getValue()); } return Maps.immutableEnumMap(map); @@ -188,16 +194,11 @@ public static class ImmutableMapValuesAsSingletonSetGenerator @Override public SampleElements>> samples() { return new SampleElements<>( - mapEntry("one", collectionOf(10000)), - mapEntry("two", collectionOf(-2000)), - mapEntry("three", collectionOf(300)), - mapEntry("four", collectionOf(-40)), - mapEntry("five", collectionOf(5))); - } - - // javac7 can't infer the type parameters correctly in samples() - private static Collection collectionOf(int item) { - return ImmutableSet.of(item); + mapEntry("one", ImmutableSet.of(10000)), + mapEntry("two", ImmutableSet.of(-2000)), + mapEntry("three", ImmutableSet.of(300)), + mapEntry("four", ImmutableSet.of(-40)), + mapEntry("five", ImmutableSet.of(5))); } @Override @@ -207,10 +208,10 @@ public Map> create(Object... elements) { for (Object elem : elements) { @SuppressWarnings("unchecked") // safe by generator contract Entry> entry = (Entry>) elem; - Integer value = Iterables.getOnlyElement(entry.getValue()); + Integer value = getOnlyElement(entry.getValue()); builder.put(entry.getKey(), value); } - return builder.build().asMultimap().asMap(); + return builder.buildOrThrow().asMultimap().asMap(); } @Override @@ -232,8 +233,16 @@ public String[] createKeyArray(int length) { @Override @SuppressWarnings({"unchecked", "rawtypes"}) // needed for arrays - public ImmutableSet[] createValueArray(int length) { + public Collection[] createValueArray(int length) { return new ImmutableSet[length]; } } + + /** + * Useless constructor for a class of static utility methods. + * + * @deprecated Do not instantiate this utility class. + */ + @Deprecated + public MapGenerators() {} } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapAsMapGetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapAsMapGetTester.java index dadb9a324535..c9be74710cac 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapAsMapGetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapAsMapGetTester.java @@ -18,16 +18,17 @@ import static com.google.common.collect.testing.Helpers.assertContentsAnyOrder; import static com.google.common.collect.testing.Helpers.assertEmpty; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.Collection; @@ -39,14 +40,15 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapAsMapGetTester extends AbstractMultimapTester> { @CollectionSize.Require(SEVERAL) @MapFeature.Require(SUPPORTS_REMOVE) public void testPropagatesRemoveToMultimap() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v3()), Helpers.mapEntry(k0(), v2())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v3()), mapEntry(k0(), v2())); Collection result = multimap().asMap().get(k0()); assertTrue(result.remove(v0())); assertFalse(multimap().containsEntry(k0(), v0())); @@ -89,11 +91,7 @@ public void testRemoveNullValue() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) public void testAddNullValueUnsupported() { Collection result = multimap().asMap().get(k0()); - try { - result.add(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> result.add(null)); } @CollectionSize.Require(absent = ZERO) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapAsMapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapAsMapTester.java index 23b2351672e8..df8a30d91e17 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapAsMapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapAsMapTester.java @@ -14,9 +14,11 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.testing.Helpers.assertContentsAnyOrder; import static com.google.common.collect.testing.Helpers.assertContentsInOrder; import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ITERATOR_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; @@ -24,11 +26,10 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -46,7 +47,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapAsMapTester extends AbstractMultimapTester> { public void testAsMapGet() { for (K key : sampleKeys()) { @@ -80,11 +83,7 @@ public void testAsMapGetNullKeyAbsent() { @MapFeature.Require(absent = ALLOWS_NULL_KEY_QUERIES) public void testAsMapGetNullKeyUnsupported() { - try { - multimap().asMap().get(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> multimap().asMap().get(null)); } @CollectionSize.Require(absent = ZERO) @@ -98,10 +97,10 @@ public void testAsMapRemove() { @CollectionSize.Require(SEVERAL) @MapFeature.Require(SUPPORTS_PUT) public void testAsMapEntrySetReflectsPutSameKey() { - resetContainer(Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v3())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v3())); Set>> asMapEntrySet = multimap().asMap().entrySet(); - Collection valueCollection = Iterables.getOnlyElement(asMapEntrySet).getValue(); + Collection valueCollection = getOnlyElement(asMapEntrySet).getValue(); assertContentsAnyOrder(valueCollection, v0(), v3()); assertTrue(multimap().put(k0(), v4())); assertContentsAnyOrder(valueCollection, v0(), v3(), v4()); @@ -110,7 +109,7 @@ public void testAsMapEntrySetReflectsPutSameKey() { @CollectionSize.Require(SEVERAL) @MapFeature.Require(SUPPORTS_PUT) public void testAsMapEntrySetReflectsPutDifferentKey() { - resetContainer(Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v3())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v3())); Set>> asMapEntrySet = multimap().asMap().entrySet(); assertTrue(multimap().put(k1(), v4())); @@ -120,9 +119,9 @@ public void testAsMapEntrySetReflectsPutDifferentKey() { @CollectionSize.Require(SEVERAL) @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) public void testAsMapEntrySetRemovePropagatesToMultimap() { - resetContainer(Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v3())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v3())); Set>> asMapEntrySet = multimap().asMap().entrySet(); - Entry> asMapEntry0 = Iterables.getOnlyElement(asMapEntrySet); + Entry> asMapEntry0 = getOnlyElement(asMapEntrySet); assertTrue(multimap().put(k1(), v4())); assertTrue(asMapEntrySet.remove(asMapEntry0)); assertEquals(1, multimap().size()); @@ -132,7 +131,7 @@ public void testAsMapEntrySetRemovePropagatesToMultimap() { @CollectionSize.Require(SEVERAL) @CollectionFeature.Require(SUPPORTS_ITERATOR_REMOVE) public void testAsMapEntrySetIteratorRemovePropagatesToMultimap() { - resetContainer(Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v3())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v3())); Set>> asMapEntrySet = multimap().asMap().entrySet(); Iterator>> asMapEntryItr = asMapEntrySet.iterator(); asMapEntryItr.next(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapClearTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapClearTester.java index 6ce9907c14d9..907c99c66ac0 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapClearTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapClearTester.java @@ -20,6 +20,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.google.GoogleHelpers.assertEmpty; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; @@ -36,18 +37,18 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapClearTester extends AbstractMultimapTester> { @CollectionSize.Require(absent = ZERO) @MapFeature.Require(absent = SUPPORTS_REMOVE) public void testClearUnsupported() { - try { - multimap().clear(); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> multimap().clear()); } + // Empty multimaps *do* have defined equals semantics. + @SuppressWarnings("UndefinedEquals") private void assertCleared() { assertEquals(0, multimap().size()); assertEmpty(multimap()); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsEntryTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsEntryTester.java index 9da7fe3f2ac8..b302f3aa3410 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsEntryTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsEntryTester.java @@ -21,6 +21,7 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; @@ -34,7 +35,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapContainsEntryTester extends AbstractMultimapTester> { @CollectionSize.Require(absent = ZERO) @@ -68,21 +71,11 @@ public void testContainsEntryNullNo() { @MapFeature.Require(absent = ALLOWS_NULL_KEY_QUERIES) public void testContainsEntryNullDisallowedBecauseKeyQueriesDisallowed() { - try { - multimap().containsEntry(null, v3()); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - // success - } + assertThrows(NullPointerException.class, () -> multimap().containsEntry(null, v3())); } @MapFeature.Require(absent = ALLOWS_NULL_VALUE_QUERIES) public void testContainsEntryNullDisallowedBecauseValueQueriesDisallowed() { - try { - multimap().containsEntry(k3(), null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - // success - } + assertThrows(NullPointerException.class, () -> multimap().containsEntry(k3(), null)); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsKeyTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsKeyTester.java index a7dcd6626e0f..dcd4d4e3c251 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsKeyTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsKeyTester.java @@ -19,6 +19,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; @@ -32,7 +33,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapContainsKeyTester extends AbstractMultimapTester> { @CollectionSize.Require(absent = ZERO) public void testContainsKeyYes() { @@ -81,11 +84,6 @@ public void testContainsKeyNullAbsent() { @MapFeature.Require(absent = ALLOWS_NULL_KEY_QUERIES) public void testContainsKeyNullDisallowed() { - try { - multimap().containsKey(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - // success - } + assertThrows(NullPointerException.class, () -> multimap().containsKey(null)); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsValueTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsValueTester.java index 00ca12ad514b..fcb601364e4b 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsValueTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapContainsValueTester.java @@ -19,6 +19,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; @@ -32,7 +33,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapContainsValueTester extends AbstractMultimapTester> { @CollectionSize.Require(absent = ZERO) @@ -58,11 +61,6 @@ public void testContainsNullValueNo() { @MapFeature.Require(absent = ALLOWS_NULL_VALUE_QUERIES) public void testContainsNullValueFails() { - try { - multimap().containsValue(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - // success - } + assertThrows(NullPointerException.class, () -> multimap().containsValue(null)); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapEntriesTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapEntriesTester.java index 9874b884cc4b..2fc19974a5a6 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapEntriesTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapEntriesTester.java @@ -16,6 +16,7 @@ import static com.google.common.collect.testing.Helpers.assertContains; import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ITERATOR_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; @@ -24,14 +25,13 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; -import java.util.Collections; import java.util.Iterator; import java.util.Map.Entry; import org.junit.Ignore; @@ -42,7 +42,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapEntriesTester extends AbstractMultimapTester> { public void testEntries() { assertEqualIgnoringOrder(getSampleElements(), multimap().entries()); @@ -52,31 +54,31 @@ public void testEntries() { @MapFeature.Require(ALLOWS_NULL_KEYS) public void testContainsEntryWithNullKeyPresent() { initMultimapWithNullKey(); - assertContains(multimap().entries(), Helpers.mapEntry((K) null, getValueForNullKey())); + assertContains(multimap().entries(), mapEntry((K) null, getValueForNullKey())); } @MapFeature.Require(ALLOWS_NULL_KEY_QUERIES) public void testContainsEntryWithNullKeyAbsent() { - assertFalse(multimap().entries().contains(Helpers.mapEntry(null, v0()))); + assertFalse(multimap().entries().contains(mapEntry(null, v0()))); } @CollectionSize.Require(absent = ZERO) @MapFeature.Require(ALLOWS_NULL_VALUES) public void testContainsEntryWithNullValuePresent() { initMultimapWithNullValue(); - assertContains(multimap().entries(), Helpers.mapEntry(getKeyForNullValue(), (V) null)); + assertContains(multimap().entries(), mapEntry(getKeyForNullValue(), (V) null)); } @MapFeature.Require(ALLOWS_NULL_VALUE_QUERIES) public void testContainsEntryWithNullValueAbsent() { - assertFalse(multimap().entries().contains(Helpers.mapEntry(k0(), null))); + assertFalse(multimap().entries().contains(mapEntry(k0(), null))); } @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_REMOVE) public void testRemovePropagatesToMultimap() { - assertTrue(multimap().entries().remove(Helpers.mapEntry(k0(), v0()))); - expectMissing(Helpers.mapEntry(k0(), v0())); + assertTrue(multimap().entries().remove(mapEntry(k0(), v0()))); + expectMissing(mapEntry(k0(), v0())); assertEquals(getNumElements() - 1, multimap().size()); assertFalse(multimap().containsEntry(k0(), v0())); } @@ -84,17 +86,23 @@ public void testRemovePropagatesToMultimap() { @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_REMOVE) public void testRemoveAllPropagatesToMultimap() { - assertTrue(multimap().entries().removeAll(Collections.singleton(Helpers.mapEntry(k0(), v0())))); - expectMissing(Helpers.mapEntry(k0(), v0())); + assertTrue(multimap().entries().removeAll(singleton(mapEntry(k0(), v0())))); + expectMissing(mapEntry(k0(), v0())); assertEquals(getNumElements() - 1, multimap().size()); assertFalse(multimap().containsEntry(k0(), v0())); } @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_REMOVE) + /* + * We are comparing Multimaps of the same type, so as long as they have value collections that + * implement equals() (as with ListMultimap or SetMultimap, as opposed to a QueueMultimap or + * something), our equality check is value-based. + */ + @SuppressWarnings("UndefinedEquals") public void testRetainAllPropagatesToMultimap() { - multimap().entries().retainAll(Collections.singleton(Helpers.mapEntry(k0(), v0()))); - assertEquals(getSubjectGenerator().create(Helpers.mapEntry(k0(), v0())), multimap()); + multimap().entries().retainAll(singleton(mapEntry(k0(), v0()))); + assertEquals(getSubjectGenerator().create(mapEntry(k0(), v0())), multimap()); assertEquals(1, multimap().size()); assertTrue(multimap().containsEntry(k0(), v0())); } @@ -103,7 +111,7 @@ public void testRetainAllPropagatesToMultimap() { @CollectionFeature.Require(SUPPORTS_ITERATOR_REMOVE) public void testIteratorRemovePropagatesToMultimap() { Iterator> iterator = multimap().entries().iterator(); - assertEquals(Helpers.mapEntry(k0(), v0()), iterator.next()); + assertEquals(mapEntry(k0(), v0()), iterator.next()); iterator.remove(); assertTrue(multimap().isEmpty()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapEqualsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapEqualsTester.java index 21163602eecc..9a56b0d56a2d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapEqualsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapEqualsTester.java @@ -14,19 +14,21 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import com.google.common.testing.EqualsTester; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -35,8 +37,12 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class MultimapEqualsTester extends AbstractMultimapTester> { +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class MultimapEqualsTester + extends AbstractMultimapTester> { public void testEqualsTrue() { new EqualsTester() .addEqualityGroup(multimap(), getSubjectGenerator().create(getSampleElements().toArray())) @@ -45,7 +51,7 @@ public void testEqualsTrue() { public void testEqualsFalse() { List> targetEntries = new ArrayList<>(getSampleElements()); - targetEntries.add(Helpers.mapEntry(k0(), v3())); + targetEntries.add(mapEntry(k0(), v3())); new EqualsTester() .addEqualityGroup(multimap()) .addEqualityGroup(getSubjectGenerator().create(targetEntries.toArray())) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapFeature.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapFeature.java index 8c1bdaad6694..86db577c061f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapFeature.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapFeature.java @@ -16,9 +16,10 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.copyToSet; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.Feature; import com.google.common.collect.testing.features.TesterAnnotation; import java.lang.annotation.Inherited; @@ -31,8 +32,7 @@ * * @author Louis Wasserman */ -// Enum values use constructors with generic varargs. -@SuppressWarnings("unchecked") +@SuppressWarnings("rawtypes") // maybe avoidable if we rework the whole package? @GwtCompatible public enum MultimapFeature implements Feature { VALUE_COLLECTIONS_SUPPORT_ITERATOR_REMOVE; @@ -40,7 +40,7 @@ public enum MultimapFeature implements Feature { private final Set> implied; MultimapFeature(Feature... implied) { - this.implied = Helpers.copyToSet(implied); + this.implied = copyToSet(implied); } @Override @@ -52,8 +52,8 @@ public Set> getImpliedFeatures() { @Inherited @TesterAnnotation public @interface Require { - public abstract MultimapFeature[] value() default {}; + MultimapFeature[] value() default {}; - public abstract MultimapFeature[] absent() default {}; + MultimapFeature[] absent() default {}; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapGetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapGetTester.java index 6978473232fa..2e524f2b1016 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapGetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapGetTester.java @@ -18,6 +18,7 @@ import static com.google.common.collect.testing.Helpers.assertContains; import static com.google.common.collect.testing.Helpers.assertContentsAnyOrder; import static com.google.common.collect.testing.Helpers.assertEmpty; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; @@ -25,14 +26,14 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.Collection; -import java.util.Collections; import org.junit.Ignore; /** @@ -41,7 +42,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapGetTester extends AbstractMultimapTester> { public void testGetEmpty() { Collection result = multimap().get(k3()); @@ -58,8 +61,7 @@ public void testGetNonEmpty() { @CollectionSize.Require(SEVERAL) public void testGetMultiple() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v1()), Helpers.mapEntry(k0(), v2())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k0(), v2())); assertGet(k0(), v0(), v1(), v2()); } @@ -70,8 +72,7 @@ public void testGetAbsentKey() { @CollectionSize.Require(SEVERAL) @MapFeature.Require(SUPPORTS_REMOVE) public void testPropagatesRemoveToMultimap() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v3()), Helpers.mapEntry(k0(), v2())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v3()), mapEntry(k0(), v2())); Collection result = multimap().get(k0()); assertTrue(result.remove(v0())); assertFalse(multimap().containsEntry(k0(), v0())); @@ -98,7 +99,7 @@ public void testPropagatesAddToMultimap() { @MapFeature.Require(SUPPORTS_PUT) public void testPropagatesAddAllToMultimap() { Collection result = multimap().get(k0()); - assertTrue(result.addAll(Collections.singletonList(v3()))); + assertTrue(result.addAll(singletonList(v3()))); assertTrue(multimap().containsKey(k0())); assertEquals(getNumElements() + 1, multimap().size()); assertTrue(multimap().containsEntry(k0(), v3())); @@ -141,12 +142,7 @@ public void testGetNullAbsent() { @MapFeature.Require(absent = ALLOWS_NULL_KEY_QUERIES) public void testGetNullForbidden() { - try { - multimap().get(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - // success - } + assertThrows(NullPointerException.class, () -> multimap().get(null)); } @MapFeature.Require(ALLOWS_NULL_VALUES) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapKeySetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapKeySetTester.java index 100b15bb760c..0e9905eafb85 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapKeySetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapKeySetTester.java @@ -35,7 +35,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapKeySetTester extends AbstractMultimapTester> { public void testKeySet() { for (Entry entry : getSampleElements()) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapKeysTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapKeysTester.java index 6b2a93ca3d02..56c18000faa6 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapKeysTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapKeysTester.java @@ -15,18 +15,19 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.Helpers.assertContainsAllOf; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ITERATOR_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static java.lang.Math.max; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -39,12 +40,13 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapKeysTester extends AbstractMultimapTester> { @CollectionSize.Require(SEVERAL) public void testKeys() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v1()), Helpers.mapEntry(k1(), v0())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k1(), v0())); Multiset keys = multimap().keys(); assertEquals(2, keys.count(k0())); assertEquals(1, keys.count(k1())); @@ -62,10 +64,7 @@ public void testKeysCountAbsentNullKey() { @CollectionSize.Require(SEVERAL) @MapFeature.Require(ALLOWS_NULL_KEYS) public void testKeysWithNullKey() { - resetContainer( - Helpers.mapEntry((K) null, v0()), - Helpers.mapEntry((K) null, v1()), - Helpers.mapEntry(k1(), v0())); + resetContainer(mapEntry((K) null, v0()), mapEntry((K) null, v1()), mapEntry(k1(), v0())); Multiset keys = multimap().keys(); assertEquals(2, keys.count(null)); assertEquals(1, keys.count(k1())); @@ -82,7 +81,7 @@ public void testKeysElementSet() { @MapFeature.Require(SUPPORTS_REMOVE) public void testKeysRemove() { int original = multimap().keys().remove(k0(), 1); - assertEquals(Math.max(original - 1, 0), multimap().get(k0()).size()); + assertEquals(max(original - 1, 0), multimap().get(k0()).size()); } @CollectionSize.Require(ONE) @@ -98,8 +97,7 @@ public void testKeysEntrySetIteratorRemove() { @CollectionSize.Require(SEVERAL) @MapFeature.Require(SUPPORTS_REMOVE) public void testKeysEntrySetRemove() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v1()), Helpers.mapEntry(k1(), v0())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k1(), v0())); assertTrue(multimap().keys().entrySet().remove(Multisets.immutableEntry(k0(), 2))); assertEquals(1, multimap().size()); assertTrue(multimap().containsEntry(k1(), v0())); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutAllMultimapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutAllMultimapTester.java index 92622933f8b7..2f7cddef1ec4 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutAllMultimapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutAllMultimapTester.java @@ -17,13 +17,14 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.Helpers.assertContains; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.MapFeature; import java.util.Collection; import org.junit.Ignore; @@ -34,19 +35,21 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapPutAllMultimapTester extends AbstractMultimapTester> { @MapFeature.Require(absent = SUPPORTS_PUT) public void testPutUnsupported() { - try { - multimap().putAll(getSubjectGenerator().create(Helpers.mapEntry(k3(), v3()))); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, + () -> multimap().putAll(getSubjectGenerator().create(mapEntry(k3(), v3())))); } @MapFeature.Require(SUPPORTS_PUT) + // Empty multimaps *do* have defined equals semantics. + @SuppressWarnings("UndefinedEquals") public void testPutAllIntoEmpty() { Multimap target = getSubjectGenerator().create(); assertEquals(!multimap().isEmpty(), target.putAll(multimap())); @@ -56,7 +59,7 @@ public void testPutAllIntoEmpty() { @MapFeature.Require(SUPPORTS_PUT) public void testPutAll() { Multimap source = - getSubjectGenerator().create(Helpers.mapEntry(k0(), v3()), Helpers.mapEntry(k3(), v3())); + getSubjectGenerator().create(mapEntry(k0(), v3()), mapEntry(k3(), v3())); assertTrue(multimap().putAll(source)); assertTrue(multimap().containsEntry(k0(), v3())); assertTrue(multimap().containsEntry(k3(), v3())); @@ -64,44 +67,36 @@ public void testPutAll() { @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) public void testPutAllWithNullValue() { - Multimap source = getSubjectGenerator().create(Helpers.mapEntry(k0(), null)); + Multimap source = getSubjectGenerator().create(mapEntry(k0(), null)); assertTrue(multimap().putAll(source)); assertTrue(multimap().containsEntry(k0(), null)); } @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS}) public void testPutAllWithNullKey() { - Multimap source = getSubjectGenerator().create(Helpers.mapEntry(null, v0())); + Multimap source = getSubjectGenerator().create(mapEntry(null, v0())); assertTrue(multimap().putAll(source)); assertTrue(multimap().containsEntry(null, v0())); } @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) public void testPutAllRejectsNullValue() { - Multimap source = getSubjectGenerator().create(Helpers.mapEntry(k0(), null)); - try { - multimap().putAll(source); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + Multimap source = getSubjectGenerator().create(mapEntry(k0(), null)); + assertThrows(NullPointerException.class, () -> multimap().putAll(source)); expectUnchanged(); } @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS) public void testPutAllRejectsNullKey() { - Multimap source = getSubjectGenerator().create(Helpers.mapEntry(null, v0())); - try { - multimap().putAll(source); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + Multimap source = getSubjectGenerator().create(mapEntry(null, v0())); + assertThrows(NullPointerException.class, () -> multimap().putAll(source)); expectUnchanged(); } @MapFeature.Require(SUPPORTS_PUT) public void testPutAllPropagatesToGet() { Multimap source = - getSubjectGenerator().create(Helpers.mapEntry(k0(), v3()), Helpers.mapEntry(k3(), v3())); + getSubjectGenerator().create(mapEntry(k0(), v3()), mapEntry(k3(), v3())); Collection getCollection = multimap().get(k0()); int getCollectionSize = getCollection.size(); assertTrue(multimap().putAll(source)); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutIterableTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutIterableTester.java index b36037877a32..207fd54dd87e 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutIterableTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutIterableTester.java @@ -16,19 +16,21 @@ package com.google.common.collect.testing.google; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.testing.Helpers.assertContainsAllOf; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; -import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -40,68 +42,56 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +@SuppressWarnings({ + // @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. + "JUnit4ClassUsedInJUnit3", + // We use ::iterator so that we test passing a plain Iterable, not a Collection. + "UnnecessaryMethodReference", +}) public class MultimapPutIterableTester extends AbstractMultimapTester> { @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_PUT) public void testPutAllNonEmptyIterableOnPresentKey() { - assertTrue( - multimap() - .putAll( - k0(), - new Iterable() { - @Override - public Iterator iterator() { - return Lists.newArrayList(v3(), v4()).iterator(); - } - })); + assertTrue(multimap().putAll(k0(), newArrayList(v3(), v4())::iterator)); assertGet(k0(), v0(), v3(), v4()); } @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_PUT) public void testPutAllNonEmptyCollectionOnPresentKey() { - assertTrue(multimap().putAll(k0(), Lists.newArrayList(v3(), v4()))); + assertTrue(multimap().putAll(k0(), newArrayList(v3(), v4()))); assertGet(k0(), v0(), v3(), v4()); } @MapFeature.Require(SUPPORTS_PUT) public void testPutAllNonEmptyIterableOnAbsentKey() { - assertTrue( - multimap() - .putAll( - k3(), - new Iterable() { - @Override - public Iterator iterator() { - return Lists.newArrayList(v3(), v4()).iterator(); - } - })); + assertTrue(multimap().putAll(k3(), newArrayList(v3(), v4())::iterator)); assertGet(k3(), v3(), v4()); } @MapFeature.Require(SUPPORTS_PUT) public void testPutAllNonEmptyCollectionOnAbsentKey() { - assertTrue(multimap().putAll(k3(), Lists.newArrayList(v3(), v4()))); + assertTrue(multimap().putAll(k3(), newArrayList(v3(), v4()))); assertGet(k3(), v3(), v4()); } @CollectionSize.Require(absent = ZERO) @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) public void testPutAllNullValueOnPresentKey_supported() { - assertTrue(multimap().putAll(k0(), Lists.newArrayList(v3(), null))); + assertTrue(multimap().putAll(k0(), newArrayList(v3(), null))); assertGet(k0(), v0(), v3(), null); } @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) public void testPutAllNullValueOnAbsentKey_supported() { - assertTrue(multimap().putAll(k3(), Lists.newArrayList(v3(), null))); + assertTrue(multimap().putAll(k3(), newArrayList(v3(), null))); assertGet(k3(), v3(), null); } @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) public void testPutAllNullValueSingle_unsupported() { - multimap().putAll(k1(), Lists.newArrayList((V) null)); + multimap().putAll(k1(), newArrayList((V) null)); expectUnchanged(); } @@ -111,20 +101,17 @@ public void testPutAllNullValueSingle_unsupported() { public void testPutAllNullValueNullLast_unsupported() { int size = getNumElements(); - try { - multimap().putAll(k3(), Lists.newArrayList(v3(), null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> multimap().putAll(k3(), newArrayList(v3(), null))); Collection values = multimap().get(k3()); if (values.size() == 0) { expectUnchanged(); // Be extra thorough in case internal state was corrupted by the expected null. - assertEquals(Lists.newArrayList(), Lists.newArrayList(values)); + assertEquals(new ArrayList<>(), new ArrayList<>(values)); assertEquals(size, multimap().size()); } else { - assertEquals(Lists.newArrayList(v3()), Lists.newArrayList(values)); + assertEquals(newArrayList(v3()), new ArrayList<>(values)); assertEquals(size + 1, multimap().size()); } } @@ -133,11 +120,8 @@ public void testPutAllNullValueNullLast_unsupported() { public void testPutAllNullValueNullFirst_unsupported() { int size = getNumElements(); - try { - multimap().putAll(k3(), Lists.newArrayList(null, v3())); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> multimap().putAll(k3(), newArrayList(null, v3()))); /* * In principle, a Multimap implementation could add e3 first before failing on the null. But @@ -146,50 +130,39 @@ public void testPutAllNullValueNullFirst_unsupported() { */ expectUnchanged(); // Be extra thorough in case internal state was corrupted by the expected null. - assertEquals(Lists.newArrayList(), Lists.newArrayList(multimap().get(k3()))); + assertEquals(new ArrayList<>(), new ArrayList<>(multimap().get(k3()))); assertEquals(size, multimap().size()); } @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS}) public void testPutAllOnPresentNullKey() { - assertTrue(multimap().putAll(null, Lists.newArrayList(v3(), v4()))); + assertTrue(multimap().putAll(null, newArrayList(v3(), v4()))); assertGet(null, v3(), v4()); } - @MapFeature.Require(absent = ALLOWS_NULL_KEYS) + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS) public void testPutAllNullForbidden() { - try { - multimap().putAll(null, Collections.singletonList(v3())); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - // success - } + assertThrows(NullPointerException.class, () -> multimap().putAll(null, singletonList(v3()))); } + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types @MapFeature.Require(SUPPORTS_PUT) public void testPutAllEmptyCollectionOnAbsentKey() { - assertFalse(multimap().putAll(k3(), Collections.emptyList())); + assertFalse(multimap().putAll(k3(), Collections.emptyList())); expectUnchanged(); } @MapFeature.Require(SUPPORTS_PUT) public void testPutAllEmptyIterableOnAbsentKey() { - Iterable iterable = - new Iterable() { - @Override - public Iterator iterator() { - return ImmutableSet.of().iterator(); - } - }; - - assertFalse(multimap().putAll(k3(), iterable)); + assertFalse(multimap().putAll(k3(), Collections::emptyIterator)); expectUnchanged(); } + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_PUT) public void testPutAllEmptyIterableOnPresentKey() { - multimap().putAll(k0(), Collections.emptyList()); + multimap().putAll(k0(), Collections.emptyList()); expectUnchanged(); } @@ -214,7 +187,7 @@ public Iterator iterator() { public void testPutAllPropagatesToGet() { Collection getCollection = multimap().get(k0()); int getCollectionSize = getCollection.size(); - assertTrue(multimap().putAll(k0(), Lists.newArrayList(v3(), v4()))); + assertTrue(multimap().putAll(k0(), newArrayList(v3(), v4()))); assertEquals(getCollectionSize + 2, getCollection.size()); assertContainsAllOf(getCollection, v3(), v4()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutTester.java index c108e8525ca4..7702a167cb70 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapPutTester.java @@ -19,22 +19,26 @@ import static com.google.common.collect.testing.Helpers.assertContains; import static com.google.common.collect.testing.Helpers.assertEmpty; import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -43,22 +47,22 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class MultimapPutTester extends AbstractMultimapTester> { +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class MultimapPutTester + extends AbstractMultimapTester> { @MapFeature.Require(absent = SUPPORTS_PUT) public void testPutUnsupported() { - try { - multimap().put(k3(), v3()); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> multimap().put(k3(), v3())); } @MapFeature.Require(SUPPORTS_PUT) public void testPutEmpty() { int size = getNumElements(); - assertGet(k3(), ImmutableList.of()); + assertGet(k3(), ImmutableList.of()); assertTrue(multimap().put(k3(), v3())); @@ -83,7 +87,7 @@ public void testPutPresent() { public void testPutTwoElements() { int size = getNumElements(); - List values = Helpers.copyToList(multimap().get(k0())); + List values = copyToList(multimap().get(k0())); assertTrue(multimap().put(k0(), v1())); assertTrue(multimap().put(k0(), v2())); @@ -107,11 +111,7 @@ public void testPutNullValue_supported() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) public void testPutNullValue_unsupported() { - try { - multimap().put(k1(), null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> multimap().put(k1(), null)); expectUnchanged(); } @@ -139,31 +139,31 @@ public void testPutNotPresentKeyPropagatesToGet() { @MapFeature.Require(SUPPORTS_PUT) public void testPutNotPresentKeyPropagatesToEntries() { Collection> entries = multimap().entries(); - assertFalse(entries.contains(Helpers.mapEntry(k3(), v3()))); + assertFalse(entries.contains(mapEntry(k3(), v3()))); multimap().put(k3(), v3()); - assertContains(entries, Helpers.mapEntry(k3(), v3())); + assertContains(entries, mapEntry(k3(), v3())); } @CollectionSize.Require(absent = ZERO) @MapFeature.Require(SUPPORTS_PUT) public void testPutPresentKeyPropagatesToEntries() { Collection> entries = multimap().entries(); - assertFalse(entries.contains(Helpers.mapEntry(k0(), v3()))); + assertFalse(entries.contains(mapEntry(k0(), v3()))); multimap().put(k0(), v3()); - assertContains(entries, Helpers.mapEntry(k0(), v3())); + assertContains(entries, mapEntry(k0(), v3())); } @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(absent = ZERO) public void testPutPresentKeyPropagatesToGet() { - List keys = Helpers.copyToList(multimap().keySet()); + List keys = copyToList(multimap().keySet()); for (K key : keys) { resetContainer(); int size = getNumElements(); Collection collection = multimap().get(key); - Collection expectedCollection = Helpers.copyToList(collection); + Collection expectedCollection = copyToList(collection); multimap().put(key, v3()); expectedCollection.add(v3()); @@ -175,7 +175,7 @@ public void testPutPresentKeyPropagatesToGet() { @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(absent = ZERO) public void testPutPresentKeyPropagatesToAsMapGet() { - List keys = Helpers.copyToList(multimap().keySet()); + List keys = copyToList(multimap().keySet()); for (K key : keys) { resetContainer(); @@ -183,7 +183,7 @@ public void testPutPresentKeyPropagatesToAsMapGet() { Collection collection = multimap().asMap().get(key); assertNotNull(collection); - Collection expectedCollection = Helpers.copyToList(collection); + Collection expectedCollection = copyToList(collection); multimap().put(key, v3()); expectedCollection.add(v3()); @@ -195,7 +195,7 @@ public void testPutPresentKeyPropagatesToAsMapGet() { @MapFeature.Require(SUPPORTS_PUT) @CollectionSize.Require(absent = ZERO) public void testPutPresentKeyPropagatesToAsMapEntrySet() { - List keys = Helpers.copyToList(multimap().keySet()); + List keys = copyToList(multimap().keySet()); for (K key : keys) { resetContainer(); @@ -211,7 +211,7 @@ public void testPutPresentKeyPropagatesToAsMapEntrySet() { } } assertNotNull(collection); - Collection expectedCollection = Helpers.copyToList(collection); + Collection expectedCollection = copyToList(collection); multimap().put(key, v3()); expectedCollection.add(v3()); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapRemoveAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapRemoveAllTester.java index 185ba2c206cd..afce6a6e7027 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapRemoveAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapRemoveAllTester.java @@ -18,6 +18,7 @@ import static com.google.common.collect.testing.Helpers.assertContentsAnyOrder; import static com.google.common.collect.testing.Helpers.assertEmpty; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_ANY_NULL_QUERIES; @@ -27,7 +28,6 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.Collection; @@ -39,7 +39,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapRemoveAllTester extends AbstractMultimapTester> { @MapFeature.Require(SUPPORTS_REMOVE) public void testRemoveAllAbsentKey() { @@ -68,8 +70,7 @@ public void testRemoveAllPropagatesToGet() { @CollectionSize.Require(SEVERAL) @MapFeature.Require(SUPPORTS_REMOVE) public void testRemoveAllMultipleValues() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k0(), v1()), Helpers.mapEntry(k0(), v2())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k0(), v2())); assertContentsAnyOrder(multimap().removeAll(k0()), v0(), v1(), v2()); assertEmpty(multimap()); @@ -82,7 +83,7 @@ public void testRemoveAllNullKeyPresent() { assertContentsAnyOrder(multimap().removeAll(null), getValueForNullKey()); - expectMissing(Helpers.mapEntry((K) null, getValueForNullKey())); + expectMissing(mapEntry((K) null, getValueForNullKey())); } @MapFeature.Require({SUPPORTS_REMOVE, ALLOWS_ANY_NULL_QUERIES}) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapRemoveEntryTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapRemoveEntryTester.java index 250a691f3f5a..69f86c16ad80 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapRemoveEntryTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapRemoveEntryTester.java @@ -17,17 +17,19 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.Collection; @@ -42,7 +44,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapRemoveEntryTester extends AbstractMultimapTester> { @MapFeature.Require(SUPPORTS_REMOVE) public void testRemoveAbsent() { @@ -58,7 +62,7 @@ public void testRemovePresent() { assertFalse(multimap().containsEntry(k0(), v0())); expectMissing(e0()); assertEquals(getNumElements() - 1, multimap().size()); - assertGet(k0(), ImmutableList.of()); + assertGet(k0(), ImmutableList.of()); } @CollectionSize.Require(absent = ZERO) @@ -68,8 +72,8 @@ public void testRemoveNullKeyPresent() { assertTrue(multimap().remove(null, getValueForNullKey())); - expectMissing(Helpers.mapEntry((K) null, getValueForNullKey())); - assertGet(getKeyForNullValue(), ImmutableList.of()); + expectMissing(mapEntry((K) null, getValueForNullKey())); + assertGet(getKeyForNullValue(), ImmutableList.of()); } @CollectionSize.Require(absent = ZERO) @@ -79,8 +83,8 @@ public void testRemoveNullValuePresent() { assertTrue(multimap().remove(getKeyForNullValue(), null)); - expectMissing(Helpers.mapEntry(getKeyForNullValue(), (V) null)); - assertGet(getKeyForNullValue(), ImmutableList.of()); + expectMissing(mapEntry(getKeyForNullValue(), (V) null)); + assertGet(getKeyForNullValue(), ImmutableList.of()); } @MapFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_KEY_QUERIES}) @@ -97,30 +101,20 @@ public void testRemoveNullValueAbsent() { @MapFeature.Require(value = SUPPORTS_REMOVE, absent = ALLOWS_NULL_VALUE_QUERIES) public void testRemoveNullValueForbidden() { - try { - multimap().remove(k0(), null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - // success - } + assertThrows(NullPointerException.class, () -> multimap().remove(k0(), null)); expectUnchanged(); } @MapFeature.Require(value = SUPPORTS_REMOVE, absent = ALLOWS_NULL_KEY_QUERIES) public void testRemoveNullKeyForbidden() { - try { - multimap().remove(null, v0()); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - // success - } + assertThrows(NullPointerException.class, () -> multimap().remove(null, v0())); expectUnchanged(); } @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemovePropagatesToGet() { - List> entries = Helpers.copyToList(multimap().entries()); + List> entries = copyToList(multimap().entries()); for (Entry entry : entries) { resetContainer(); @@ -128,7 +122,7 @@ public void testRemovePropagatesToGet() { V value = entry.getValue(); Collection collection = multimap().get(key); assertNotNull(collection); - Collection expectedCollection = Helpers.copyToList(collection); + Collection expectedCollection = copyToList(collection); multimap().remove(key, value); expectedCollection.remove(value); @@ -141,7 +135,7 @@ public void testRemovePropagatesToGet() { @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemovePropagatesToAsMap() { - List> entries = Helpers.copyToList(multimap().entries()); + List> entries = copyToList(multimap().entries()); for (Entry entry : entries) { resetContainer(); @@ -149,7 +143,7 @@ public void testRemovePropagatesToAsMap() { V value = entry.getValue(); Collection collection = multimap().asMap().get(key); assertNotNull(collection); - Collection expectedCollection = Helpers.copyToList(collection); + Collection expectedCollection = copyToList(collection); multimap().remove(key, value); expectedCollection.remove(value); @@ -162,7 +156,7 @@ public void testRemovePropagatesToAsMap() { @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemovePropagatesToAsMapEntrySet() { - List> entries = Helpers.copyToList(multimap().entries()); + List> entries = copyToList(multimap().entries()); for (Entry entry : entries) { resetContainer(); @@ -179,7 +173,7 @@ public void testRemovePropagatesToAsMapEntrySet() { } } assertNotNull(collection); - Collection expectedCollection = Helpers.copyToList(collection); + Collection expectedCollection = copyToList(collection); multimap().remove(key, value); expectedCollection.remove(value); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapReplaceValuesTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapReplaceValuesTester.java index 3e2597d8dbf0..0127bc8cc704 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapReplaceValuesTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapReplaceValuesTester.java @@ -17,21 +17,23 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.Helpers.assertContentsAnyOrder; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import org.junit.Ignore; @@ -41,22 +43,22 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapReplaceValuesTester extends AbstractMultimapTester> { @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE, ALLOWS_NULL_VALUES}) public void testReplaceValuesWithNullValue() { - @SuppressWarnings("unchecked") - List values = Arrays.asList(v0(), null, v3()); + List values = asList(v0(), null, v3()); multimap().replaceValues(k0(), values); assertGet(k0(), values); } @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE, ALLOWS_NULL_KEYS}) public void testReplaceValuesWithNullKey() { - @SuppressWarnings("unchecked") - List values = Arrays.asList(v0(), v2(), v3()); + List values = asList(v0(), v2(), v3()); multimap().replaceValues(null, values); assertGet(null, values); } @@ -64,19 +66,18 @@ public void testReplaceValuesWithNullKey() { @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) public void testReplaceEmptyValues() { int size = multimap().size(); - @SuppressWarnings("unchecked") - List values = Arrays.asList(v0(), v2(), v3()); + List values = asList(v0(), v2(), v3()); multimap().replaceValues(k3(), values); assertGet(k3(), values); assertEquals(size + values.size(), multimap().size()); } + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) public void testReplaceValuesWithEmpty() { int size = multimap().size(); List oldValues = new ArrayList<>(multimap().get(k0())); - @SuppressWarnings("unchecked") - List values = Collections.emptyList(); + List values = emptyList(); assertEquals(oldValues, new ArrayList(multimap().replaceValues(k0(), values))); assertGet(k0()); assertEquals(size - oldValues.size(), multimap().size()); @@ -86,7 +87,7 @@ public void testReplaceValuesWithEmpty() { public void testReplaceValuesWithDuplicates() { int size = multimap().size(); List oldValues = new ArrayList<>(multimap().get(k0())); - List values = Arrays.asList(v0(), v3(), v0()); + List values = asList(v0(), v3(), v0()); assertEquals(oldValues, new ArrayList(multimap().replaceValues(k0(), values))); assertEquals(size - oldValues.size() + multimap().get(k0()).size(), multimap().size()); assertTrue(multimap().get(k0()).containsAll(values)); @@ -95,15 +96,14 @@ public void testReplaceValuesWithDuplicates() { @CollectionSize.Require(absent = ZERO) @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) public void testReplaceNonEmptyValues() { - List keys = Helpers.copyToList(multimap().keySet()); - @SuppressWarnings("unchecked") - List values = Arrays.asList(v0(), v2(), v3()); + List keys = copyToList(multimap().keySet()); + List values = asList(v0(), v2(), v3()); for (K k : keys) { resetContainer(); int size = multimap().size(); - Collection oldKeyValues = Helpers.copyToList(multimap().get(k)); + Collection oldKeyValues = copyToList(multimap().get(k)); multimap().replaceValues(k, values); assertGet(k, values); assertEquals(size + values.size() - oldKeyValues.size(), multimap().size()); @@ -113,8 +113,7 @@ public void testReplaceNonEmptyValues() { @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) public void testReplaceValuesPropagatesToGet() { Collection getCollection = multimap().get(k0()); - @SuppressWarnings("unchecked") - List values = Arrays.asList(v0(), v2(), v3()); + List values = asList(v0(), v2(), v3()); multimap().replaceValues(k0(), values); assertContentsAnyOrder(getCollection, v0(), v2(), v3()); } @@ -122,23 +121,13 @@ public void testReplaceValuesPropagatesToGet() { @MapFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testReplaceValuesRemoveNotSupported() { - List values = Collections.singletonList(v3()); - try { - multimap().replaceValues(k0(), values); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - // success - } + List values = singletonList(v3()); + assertThrows(UnsupportedOperationException.class, () -> multimap().replaceValues(k0(), values)); } @MapFeature.Require(absent = SUPPORTS_PUT) public void testReplaceValuesPutNotSupported() { - List values = Collections.singletonList(v3()); - try { - multimap().replaceValues(k0(), values); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - // success - } + List values = singletonList(v3()); + assertThrows(UnsupportedOperationException.class, () -> multimap().replaceValues(k0(), values)); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapSizeTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapSizeTester.java index 23d6bdf043e3..03d5e07d2f87 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapSizeTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapSizeTester.java @@ -28,6 +28,8 @@ import com.google.common.collect.testing.features.MapFeature; import java.util.Collection; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -36,8 +38,12 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class MultimapSizeTester extends AbstractMultimapTester> { +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class MultimapSizeTester + extends AbstractMultimapTester> { public void testSize() { int expectedSize = getNumElements(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapTestSuiteBuilder.java index 8504940bb6fb..e6cf6e8e3d5c 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapTestSuiteBuilder.java @@ -17,7 +17,9 @@ package com.google.common.collect.testing.google; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.testing.Helpers.copyToSet; import static com.google.common.collect.testing.Helpers.mapEntry; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.ImmutableList; @@ -28,7 +30,6 @@ import com.google.common.collect.testing.CollectionTestSuiteBuilder; import com.google.common.collect.testing.DerivedGenerator; import com.google.common.collect.testing.FeatureSpecificTestSuiteBuilder; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.OneSizeTestContainerGenerator; import com.google.common.collect.testing.PerCollectionSizeTestSuiteBuilder; @@ -73,9 +74,10 @@ public static > MultimapTestSuiteBuilder } // Class parameters must be raw. + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - return ImmutableList.>of( + return ImmutableList.of( MultimapAsMapGetTester.class, MultimapAsMapTester.class, MultimapSizeTester.class, @@ -115,6 +117,8 @@ protected List createDerivedSuites( .withFeatures(computeReserializedMultimapFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + " reserialized") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } @@ -123,6 +127,8 @@ protected List createDerivedSuites( .withFeatures(computeAsMapFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".asMap") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); derivedSuites.add(computeEntriesTestSuite(parentBuilder)); @@ -153,6 +159,8 @@ TestSuite computeEntriesTestSuite( .withFeatures(computeEntriesFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".entries") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite(); } @@ -164,6 +172,8 @@ TestSuite computeMultimapGetTestSuite( .withFeatures(computeMultimapGetFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".get[key]") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite(); } @@ -179,6 +189,8 @@ TestSuite computeMultimapAsMapGetTestSuite( .withFeatures(features) .named(parentBuilder.getName() + ".asMap[].get[key]") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite(); } } @@ -191,11 +203,13 @@ TestSuite computeKeysTestSuite( .withFeatures(computeKeysFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".keys") .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite(); } static Set> computeDerivedCollectionFeatures(Set> multimapFeatures) { - Set> derivedFeatures = Helpers.copyToSet(multimapFeatures); + Set> derivedFeatures = copyToSet(multimapFeatures); if (!derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS)) { derivedFeatures.remove(CollectionFeature.SERIALIZABLE); } @@ -237,14 +251,14 @@ static Set> computeKeysFeatures(Set> multimapFeatures) { private static Set> computeReserializedMultimapFeatures( Set> multimapFeatures) { - Set> derivedFeatures = Helpers.copyToSet(multimapFeatures); + Set> derivedFeatures = copyToSet(multimapFeatures); derivedFeatures.remove(CollectionFeature.SERIALIZABLE); derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS); return derivedFeatures; } private static Set> computeAsMapFeatures(Set> multimapFeatures) { - Set> derivedFeatures = Helpers.copyToSet(multimapFeatures); + Set> derivedFeatures = copyToSet(multimapFeatures); derivedFeatures.remove(MapFeature.GENERAL_PURPOSE); derivedFeatures.remove(MapFeature.SUPPORTS_PUT); derivedFeatures.remove(MapFeature.ALLOWS_NULL_VALUES); @@ -271,7 +285,7 @@ private static Set> computeAsMapFeatures(Set> multimapFeat .build(); Set> computeMultimapGetFeatures(Set> multimapFeatures) { - Set> derivedFeatures = Helpers.copyToSet(multimapFeatures); + Set> derivedFeatures = copyToSet(multimapFeatures); for (Entry, Feature> entry : GET_FEATURE_MAP.entries()) { if (derivedFeatures.contains(entry.getKey())) { derivedFeatures.add(entry.getValue()); @@ -288,8 +302,7 @@ Set> computeMultimapGetFeatures(Set> multimapFeatures) { } Set> computeMultimapAsMapGetFeatures(Set> multimapFeatures) { - Set> derivedFeatures = - Helpers.copyToSet(computeMultimapGetFeatures(multimapFeatures)); + Set> derivedFeatures = copyToSet(computeMultimapGetFeatures(multimapFeatures)); if (derivedFeatures.remove(CollectionSize.ANY)) { derivedFeatures.addAll(CollectionSize.ANY.getImpliedFeatures()); } @@ -297,11 +310,11 @@ Set> computeMultimapAsMapGetFeatures(Set> multimapFeatures return derivedFeatures; } - private static class AsMapGenerator> + private static final class AsMapGenerator> implements TestMapGenerator>, DerivedGenerator { private final OneSizeTestContainerGenerator> multimapGenerator; - public AsMapGenerator(OneSizeTestContainerGenerator> multimapGenerator) { + AsMapGenerator(OneSizeTestContainerGenerator> multimapGenerator) { this.multimapGenerator = multimapGenerator; } @@ -312,7 +325,7 @@ public TestSubjectGenerator getInnerGenerator() { private Collection createCollection(V v) { return ((TestMultimapGenerator) multimapGenerator.getInnerGenerator()) - .createCollection(Collections.singleton(v)); + .createCollection(singleton(v)); } @Override @@ -334,10 +347,16 @@ public Map> create(Object... elements) { Set keySet = new HashSet<>(); List> builder = new ArrayList<>(); for (Object o : elements) { - Entry> entry = (Entry>) o; - keySet.add(entry.getKey()); - for (V v : entry.getValue()) { - builder.add(mapEntry(entry.getKey(), v)); + Entry entry = (Entry) o; + // These come from Entry>> objects somewhere. + @SuppressWarnings("unchecked") + K key = (K) entry.getKey(); + keySet.add(key); + for (Object v : (Collection) entry.getValue()) { + // These come from Entry>> objects somewhere. + @SuppressWarnings("unchecked") + V value = (V) v; + builder.add(mapEntry(key, value)); } } checkArgument(keySet.size() == elements.length, "Duplicate keys"); @@ -347,7 +366,7 @@ public Map> create(Object... elements) { @SuppressWarnings("unchecked") @Override public Entry>[] createArray(int length) { - return new Entry[length]; + return (Entry>[]) new Entry[length]; } @Override @@ -377,7 +396,7 @@ public K[] createKeyArray(int length) { @SuppressWarnings("unchecked") @Override public Collection[] createValueArray(int length) { - return new Collection[length]; + return (Collection[]) new Collection[length]; } } @@ -407,7 +426,7 @@ public Collection> create(Object... elements) { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -416,7 +435,7 @@ public Iterable> order(List> insertionOrder) { } } - static class ValuesGenerator> + private static final class ValuesGenerator> implements TestCollectionGenerator { private final OneSizeTestContainerGenerator> multimapGenerator; @@ -436,14 +455,15 @@ public Collection create(Object... elements) { ((TestMultimapGenerator) multimapGenerator.getInnerGenerator()) .sampleKeys() .e0(); - Entry[] entries = new Entry[elements.length]; + Object[] entries = new Object[elements.length]; for (int i = 0; i < elements.length; i++) { - entries[i] = mapEntry(k, (V) elements[i]); + @SuppressWarnings("unchecked") // These come from Entry objects somewhere. + V value = (V) elements[i]; + entries[i] = mapEntry(k, value); } - return multimapGenerator.create((Object[]) entries).values(); + return multimapGenerator.create(entries).values(); } - @SuppressWarnings("unchecked") @Override public V[] createArray(int length) { return ((TestMultimapGenerator) multimapGenerator.getInnerGenerator()) @@ -469,7 +489,7 @@ public Iterable order(List insertionOrder) { } } - static class KeysGenerator> + private static final class KeysGenerator> implements TestMultisetGenerator, DerivedGenerator { private final OneSizeTestContainerGenerator> multimapGenerator; @@ -493,17 +513,17 @@ public Multiset create(Object... elements) { * This is nasty and complicated, but it's the only way to make sure keys get mapped to enough * distinct values. */ - Entry[] entries = new Entry[elements.length]; + Entry[] entries = new Entry[elements.length]; Map> valueIterators = new HashMap<>(); for (int i = 0; i < elements.length; i++) { - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") // These come from Entry objects somewhere. K key = (K) elements[i]; Iterator valueItr = valueIterators.get(key); if (valueItr == null) { valueIterators.put(key, valueItr = sampleValuesIterator()); } - entries[i] = mapEntry((K) elements[i], valueItr.next()); + entries[i] = mapEntry(key, valueItr.next()); } return multimapGenerator.create((Object[]) entries).keys(); } @@ -514,7 +534,6 @@ private Iterator sampleValuesIterator() { .iterator(); } - @SuppressWarnings("unchecked") @Override public K[] createArray(int length) { return ((TestMultimapGenerator) multimapGenerator.getInnerGenerator()) @@ -583,7 +602,9 @@ public Collection create(Object... elements) { .sampleKeys() .e0(); for (int i = 0; i < elements.length; i++) { - array[i] = mapEntry(k, (V) elements[i]); + @SuppressWarnings("unchecked") // These come from Entry objects somewhere. + V value = (V) elements[i]; + array[i] = mapEntry(k, value); } return multimapGenerator.create((Object[]) array).get(k); } @@ -605,18 +626,19 @@ public Collection create(Object... elements) { .sampleKeys() .e0(); for (int i = 0; i < elements.length; i++) { - array[i] = mapEntry(k, (V) elements[i]); + @SuppressWarnings("unchecked") // These come from Entry objects somewhere. + V value = (V) elements[i]; + array[i] = mapEntry(k, value); } return multimapGenerator.create((Object[]) array).asMap().get(k); } } - private static class ReserializedMultimapGenerator> + private static final class ReserializedMultimapGenerator> implements TestMultimapGenerator { private final OneSizeTestContainerGenerator> multimapGenerator; - public ReserializedMultimapGenerator( - OneSizeTestContainerGenerator> multimapGenerator) { + ReserializedMultimapGenerator(OneSizeTestContainerGenerator> multimapGenerator) { this.multimapGenerator = multimapGenerator; } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapToStringTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapToStringTester.java index 203f278b7f93..fb07a37ae478 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapToStringTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapToStringTester.java @@ -33,7 +33,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapToStringTester extends AbstractMultimapTester> { @CollectionSize.Require(ZERO) @CollectionFeature.Require(absent = NON_STANDARD_TOSTRING) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapValuesTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapValuesTester.java index ab7afce862d4..384ac5804403 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapValuesTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultimapValuesTester.java @@ -21,10 +21,10 @@ import static com.google.common.collect.testing.features.CollectionSize.ONE; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; @@ -36,10 +36,12 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultimapValuesTester extends AbstractMultimapTester> { public void testValues() { - List expected = Lists.newArrayList(); + List expected = new ArrayList<>(); for (Entry entry : getSampleElements()) { expected.add(entry.getValue()); } @@ -48,7 +50,7 @@ public void testValues() { @CollectionFeature.Require(KNOWN_ORDER) public void testValuesInOrder() { - List expected = Lists.newArrayList(); + List expected = new ArrayList<>(); for (Entry entry : getOrderedElements()) { expected.add(entry.getValue()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetAddTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetAddTester.java index fa8874dba760..d0f702fae9d8 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetAddTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetAddTester.java @@ -17,10 +17,11 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionFeature; -import java.util.Arrays; import java.util.Collections; import org.junit.Ignore; @@ -30,15 +31,13 @@ * @author Jared Levy */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetAddTester extends AbstractMultisetTester { @CollectionFeature.Require(absent = SUPPORTS_ADD) public void testAddUnsupported() { - try { - getMultiset().add(e0()); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getMultiset().add(e0())); } @CollectionFeature.Require(SUPPORTS_ADD) @@ -73,37 +72,25 @@ public void testAddSeveralTimes() { @CollectionFeature.Require(absent = SUPPORTS_ADD) public void testAddOccurrences_unsupported() { - try { - getMultiset().add(e0(), 2); - fail("unsupported multiset.add(E, int) didn't throw exception"); - } catch (UnsupportedOperationException required) { - } + assertThrows(UnsupportedOperationException.class, () -> getMultiset().add(e0(), 2)); } @CollectionFeature.Require(SUPPORTS_ADD) public void testAddOccurrencesNegative() { - try { - getMultiset().add(e0(), -1); - fail("multiset.add(E, -1) didn't throw an exception"); - } catch (IllegalArgumentException required) { - } + assertThrows(IllegalArgumentException.class, () -> getMultiset().add(e0(), -1)); } @CollectionFeature.Require(SUPPORTS_ADD) public void testAddTooMany() { getMultiset().add(e3(), Integer.MAX_VALUE); - try { - getMultiset().add(e3()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> getMultiset().add(e3())); assertEquals(Integer.MAX_VALUE, getMultiset().count(e3())); assertEquals(Integer.MAX_VALUE, getMultiset().size()); } @CollectionFeature.Require(SUPPORTS_ADD) public void testAddAll_emptySet() { - assertFalse(getMultiset().addAll(Collections.emptySet())); + assertFalse(getMultiset().addAll(Collections.emptySet())); expectUnchanged(); } @@ -115,7 +102,7 @@ public void testAddAll_emptyMultiset() { @CollectionFeature.Require(SUPPORTS_ADD) public void testAddAll_nonEmptyList() { - assertTrue(getMultiset().addAll(Arrays.asList(e3(), e4(), e3()))); + assertTrue(getMultiset().addAll(asList(e3(), e4(), e3()))); expectAdded(e3(), e4(), e3()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetContainsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetContainsTester.java index bdbd090cc8ff..7cad976c2faf 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetContainsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetContainsTester.java @@ -15,10 +15,10 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Arrays; import org.junit.Ignore; /** @@ -27,7 +27,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetContainsTester extends AbstractMultisetTester { @CollectionSize.Require(absent = ZERO) public void testContainsAllMultisetIgnoresFrequency() { @@ -36,6 +38,6 @@ public void testContainsAllMultisetIgnoresFrequency() { @CollectionSize.Require(absent = ZERO) public void testContainsAllListIgnoresFrequency() { - assertTrue(getMultiset().containsAll(Arrays.asList(e0(), e0(), e0()))); + assertTrue(getMultiset().containsAll(asList(e0(), e0(), e0()))); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetCountTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetCountTester.java index 7c07cd33d06c..8a24bf205ba8 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetCountTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetCountTester.java @@ -16,19 +16,21 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_QUERIES; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.WrongType; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.List; import org.junit.Ignore; @@ -37,8 +39,10 @@ * * @author Jared Levy */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetCountTester extends AbstractMultisetTester { public void testCount_0() { @@ -63,11 +67,7 @@ public void testCount_nullAbsent() { @CollectionFeature.Require(absent = ALLOWS_NULL_QUERIES) public void testCount_null_forbidden() { - try { - getMultiset().count(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getMultiset().count(null)); } @CollectionSize.Require(absent = ZERO) @@ -86,8 +86,9 @@ public void testCount_wrongType() { * Returns {@link Method} instances for the read tests that assume multisets support duplicates so * that the test of {@code Multisets.forSet()} can suppress them. */ + @J2ktIncompatible @GwtIncompatible // reflection public static List getCountDuplicateInitializingMethods() { - return Arrays.asList(Helpers.getMethod(MultisetCountTester.class, "testCount_3")); + return asList(getMethod(MultisetCountTester.class, "testCount_3")); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetElementSetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetElementSetTester.java index baa6071f84bf..872d3dc8936f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetElementSetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetElementSetTester.java @@ -17,19 +17,20 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.Helpers.assertEmpty; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Set; import org.junit.Ignore; @@ -40,7 +41,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetElementSetTester extends AbstractMultisetTester { @CollectionFeature.Require(SUPPORTS_ADD) public void testElementSetReflectsAddAbsent() { @@ -55,7 +58,7 @@ public void testElementSetReflectsAddAbsent() { public void testElementSetReflectsRemove() { Set elementSet = getMultiset().elementSet(); assertTrue(elementSet.contains(e0())); - getMultiset().removeAll(Collections.singleton(e0())); + getMultiset().removeAll(singleton(e0())); assertFalse(elementSet.contains(e0())); } @@ -99,10 +102,11 @@ public void testElementSetClear() { * Returns {@link Method} instances for the read tests that assume multisets support duplicates so * that the test of {@code Multisets.forSet()} can suppress them. */ + @J2ktIncompatible @GwtIncompatible // reflection public static List getElementSetDuplicateInitializingMethods() { - return Arrays.asList( - Helpers.getMethod( + return asList( + getMethod( MultisetElementSetTester.class, "testElementSetRemoveDuplicatePropagatesToMultiset")); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetEntrySetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetEntrySetTester.java index 3bec616aae11..a82efa8568f7 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetEntrySetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetEntrySetTester.java @@ -16,6 +16,7 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ITERATOR_REMOVE; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; @@ -23,14 +24,13 @@ import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.google.MultisetFeature.ENTRIES_ARE_VIEWS; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.Iterables; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Collections; import java.util.Iterator; import org.junit.Ignore; @@ -40,7 +40,9 @@ * @author Jared Levy */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetEntrySetTester extends AbstractMultisetTester { @CollectionFeature.Require(SUPPORTS_REMOVE) @@ -93,9 +95,7 @@ public void testEntrySet_removeAbsent() { public void testEntrySet_removeAllPresent() { assertTrue( "multiset.entrySet.removeAll(presentEntry) returned false", - getMultiset() - .entrySet() - .removeAll(Collections.singleton(Multisets.immutableEntry(e0(), 1)))); + getMultiset().entrySet().removeAll(singleton(Multisets.immutableEntry(e0(), 1)))); assertFalse("multiset contains element after removing its entry", getMultiset().contains(e0())); } @@ -104,9 +104,7 @@ public void testEntrySet_removeAllPresent() { public void testEntrySet_removeAllAbsent() { assertFalse( "multiset.entrySet.remove(missingEntry) returned true", - getMultiset() - .entrySet() - .removeAll(Collections.singleton(Multisets.immutableEntry(e0(), 2)))); + getMultiset().entrySet().removeAll(singleton(Multisets.immutableEntry(e0(), 2)))); assertTrue( "multiset didn't contain element after removing a missing entry", getMultiset().contains(e0())); @@ -117,9 +115,7 @@ public void testEntrySet_removeAllAbsent() { public void testEntrySet_retainAllPresent() { assertFalse( "multiset.entrySet.retainAll(presentEntry) returned false", - getMultiset() - .entrySet() - .retainAll(Collections.singleton(Multisets.immutableEntry(e0(), 1)))); + getMultiset().entrySet().retainAll(singleton(Multisets.immutableEntry(e0(), 1)))); assertTrue( "multiset doesn't contains element after retaining its entry", getMultiset().contains(e0())); @@ -130,9 +126,7 @@ public void testEntrySet_retainAllPresent() { public void testEntrySet_retainAllAbsent() { assertTrue( "multiset.entrySet.retainAll(missingEntry) returned true", - getMultiset() - .entrySet() - .retainAll(Collections.singleton(Multisets.immutableEntry(e0(), 2)))); + getMultiset().entrySet().retainAll(singleton(Multisets.immutableEntry(e0(), 2)))); assertFalse( "multiset contains element after retaining a different entry", getMultiset().contains(e0())); @@ -144,7 +138,7 @@ public void testEntrySet_retainAllAbsent() { public void testEntryViewReflectsRemove() { initThreeCopies(); assertEquals(3, getMultiset().count(e0())); - Multiset.Entry entry = Iterables.getOnlyElement(getMultiset().entrySet()); + Multiset.Entry entry = getOnlyElement(getMultiset().entrySet()); assertEquals(3, entry.getCount()); assertTrue(getMultiset().remove(e0())); assertEquals(2, entry.getCount()); @@ -158,7 +152,7 @@ public void testEntryViewReflectsRemove() { public void testEntryReflectsIteratorRemove() { initThreeCopies(); assertEquals(3, getMultiset().count(e0())); - Multiset.Entry entry = Iterables.getOnlyElement(getMultiset().entrySet()); + Multiset.Entry entry = getOnlyElement(getMultiset().entrySet()); assertEquals(3, entry.getCount()); Iterator itr = getMultiset().iterator(); itr.next(); @@ -177,7 +171,7 @@ public void testEntryReflectsIteratorRemove() { public void testEntryReflectsClear() { initThreeCopies(); assertEquals(3, getMultiset().count(e0())); - Multiset.Entry entry = Iterables.getOnlyElement(getMultiset().entrySet()); + Multiset.Entry entry = getOnlyElement(getMultiset().entrySet()); assertEquals(3, entry.getCount()); getMultiset().clear(); assertEquals(0, entry.getCount()); @@ -189,7 +183,7 @@ public void testEntryReflectsClear() { public void testEntryReflectsEntrySetClear() { initThreeCopies(); assertEquals(3, getMultiset().count(e0())); - Multiset.Entry entry = Iterables.getOnlyElement(getMultiset().entrySet()); + Multiset.Entry entry = getOnlyElement(getMultiset().entrySet()); assertEquals(3, entry.getCount()); getMultiset().entrySet().clear(); assertEquals(0, entry.getCount()); @@ -213,7 +207,7 @@ public void testEntryReflectsEntrySetIteratorRemove() { public void testEntryReflectsElementSetClear() { initThreeCopies(); assertEquals(3, getMultiset().count(e0())); - Multiset.Entry entry = Iterables.getOnlyElement(getMultiset().entrySet()); + Multiset.Entry entry = getOnlyElement(getMultiset().entrySet()); assertEquals(3, entry.getCount()); getMultiset().elementSet().clear(); assertEquals(0, entry.getCount()); @@ -225,7 +219,7 @@ public void testEntryReflectsElementSetClear() { public void testEntryReflectsElementSetIteratorRemove() { initThreeCopies(); assertEquals(3, getMultiset().count(e0())); - Multiset.Entry entry = Iterables.getOnlyElement(getMultiset().entrySet()); + Multiset.Entry entry = getOnlyElement(getMultiset().entrySet()); assertEquals(3, entry.getCount()); Iterator elementItr = getMultiset().elementSet().iterator(); elementItr.next(); @@ -239,7 +233,7 @@ public void testEntryReflectsElementSetIteratorRemove() { public void testEntryReflectsRemoveThenAdd() { initThreeCopies(); assertEquals(3, getMultiset().count(e0())); - Multiset.Entry entry = Iterables.getOnlyElement(getMultiset().entrySet()); + Multiset.Entry entry = getOnlyElement(getMultiset().entrySet()); assertEquals(3, entry.getCount()); assertTrue(getMultiset().remove(e0())); assertEquals(2, entry.getCount()); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetEqualsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetEqualsTester.java index 9d9fee0a1a15..a940e48b7f80 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetEqualsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetEqualsTester.java @@ -29,7 +29,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetEqualsTester extends AbstractMultisetTester { public void testEqualsSameContents() { new EqualsTester() diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetFeature.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetFeature.java index d05c560021a8..407a4b20db89 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetFeature.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetFeature.java @@ -16,6 +16,8 @@ package com.google.common.collect.testing.google; +import static java.util.Collections.emptySet; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multiset; import com.google.common.collect.testing.features.Feature; @@ -23,7 +25,6 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Collections; import java.util.Set; /** @@ -31,6 +32,7 @@ * * @author Louis Wasserman */ +@SuppressWarnings("rawtypes") // maybe avoidable if we rework the whole package? @GwtCompatible public enum MultisetFeature implements Feature { /** @@ -41,15 +43,15 @@ public enum MultisetFeature implements Feature { @Override public Set> getImpliedFeatures() { - return Collections.emptySet(); + return emptySet(); } @Retention(RetentionPolicy.RUNTIME) @Inherited @TesterAnnotation public @interface Require { - public abstract MultisetFeature[] value() default {}; + MultisetFeature[] value() default {}; - public abstract MultisetFeature[] absent() default {}; + MultisetFeature[] absent() default {}; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetIteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetIteratorTester.java index 34a8c725852d..f73419474a56 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetIteratorTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetIteratorTester.java @@ -14,20 +14,23 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ITERATOR_REMOVE; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.IteratorTester; import com.google.common.collect.testing.features.CollectionFeature; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Iterator; import java.util.List; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -36,16 +39,18 @@ * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class MultisetIteratorTester extends AbstractMultisetTester { - @SuppressWarnings("unchecked") +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class MultisetIteratorTester extends AbstractMultisetTester { @CollectionFeature.Require({SUPPORTS_ITERATOR_REMOVE, KNOWN_ORDER}) public void testRemovingIteratorKnownOrder() { new IteratorTester( 4, MODIFIABLE, - getSubjectGenerator().order(Arrays.asList(e0(), e1(), e1(), e2())), + getSubjectGenerator().order(asList(e0(), e1(), e1(), e2())), IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { @@ -54,14 +59,10 @@ protected Iterator newTargetIterator() { }.test(); } - @SuppressWarnings("unchecked") @CollectionFeature.Require(value = SUPPORTS_ITERATOR_REMOVE, absent = KNOWN_ORDER) public void testRemovingIteratorUnknownOrder() { new IteratorTester( - 4, - MODIFIABLE, - Arrays.asList(e0(), e1(), e1(), e2()), - IteratorTester.KnownOrder.UNKNOWN_ORDER) { + 4, MODIFIABLE, asList(e0(), e1(), e1(), e2()), IteratorTester.KnownOrder.UNKNOWN_ORDER) { @Override protected Iterator newTargetIterator() { return getSubjectGenerator().create(e0(), e1(), e1(), e2()).iterator(); @@ -69,13 +70,12 @@ protected Iterator newTargetIterator() { }.test(); } - @SuppressWarnings("unchecked") @CollectionFeature.Require(value = KNOWN_ORDER, absent = SUPPORTS_ITERATOR_REMOVE) public void testIteratorKnownOrder() { new IteratorTester( 4, UNMODIFIABLE, - getSubjectGenerator().order(Arrays.asList(e0(), e1(), e1(), e2())), + getSubjectGenerator().order(asList(e0(), e1(), e1(), e2())), IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { @@ -84,14 +84,10 @@ protected Iterator newTargetIterator() { }.test(); } - @SuppressWarnings("unchecked") @CollectionFeature.Require(absent = {SUPPORTS_ITERATOR_REMOVE, KNOWN_ORDER}) public void testIteratorUnknownOrder() { new IteratorTester( - 4, - UNMODIFIABLE, - Arrays.asList(e0(), e1(), e1(), e2()), - IteratorTester.KnownOrder.UNKNOWN_ORDER) { + 4, UNMODIFIABLE, asList(e0(), e1(), e1(), e2()), IteratorTester.KnownOrder.UNKNOWN_ORDER) { @Override protected Iterator newTargetIterator() { return getSubjectGenerator().create(e0(), e1(), e1(), e2()).iterator(); @@ -103,12 +99,13 @@ protected Iterator newTargetIterator() { * Returns {@link Method} instances for the tests that assume multisets support duplicates so that * the test of {@code Multisets.forSet()} can suppress them. */ + @J2ktIncompatible @GwtIncompatible // reflection public static List getIteratorDuplicateInitializingMethods() { - return Arrays.asList( - Helpers.getMethod(MultisetIteratorTester.class, "testIteratorKnownOrder"), - Helpers.getMethod(MultisetIteratorTester.class, "testIteratorUnknownOrder"), - Helpers.getMethod(MultisetIteratorTester.class, "testRemovingIteratorKnownOrder"), - Helpers.getMethod(MultisetIteratorTester.class, "testRemovingIteratorUnknownOrder")); + return asList( + getMethod(MultisetIteratorTester.class, "testIteratorKnownOrder"), + getMethod(MultisetIteratorTester.class, "testIteratorUnknownOrder"), + getMethod(MultisetIteratorTester.class, "testRemovingIteratorKnownOrder"), + getMethod(MultisetIteratorTester.class, "testRemovingIteratorUnknownOrder")); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetNavigationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetNavigationTester.java index b65cd967ac0d..563f1f866df3 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetNavigationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetNavigationTester.java @@ -22,18 +22,21 @@ import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.nCopies; +import static java.util.Collections.singletonList; +import static java.util.Collections.sort; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.BoundType; import com.google.common.collect.Iterators; -import com.google.common.collect.Multiset; import com.google.common.collect.Multiset.Entry; import com.google.common.collect.Multisets; import com.google.common.collect.SortedMultiset; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; @@ -45,7 +48,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetNavigationTester extends AbstractMultisetTester { private SortedMultiset sortedMultiset; private List entries; @@ -53,20 +58,15 @@ public class MultisetNavigationTester extends AbstractMultisetTester { private Entry b; private Entry c; - /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ - static SortedMultiset cast(Multiset iterable) { - return (SortedMultiset) iterable; - } - @Override public void setUp() throws Exception { super.setUp(); - sortedMultiset = cast(getMultiset()); + sortedMultiset = (SortedMultiset) getMultiset(); entries = copyToList( getSubjectGenerator() .getSampleElements(getSubjectGenerator().getCollectionSize().getNumElements())); - Collections.sort(entries, sortedMultiset.comparator()); + sort(entries, sortedMultiset.comparator()); // some tests assume SEVERAL == 3 if (entries.size() >= 1) { @@ -79,12 +79,11 @@ public void setUp() throws Exception { } /** Resets the contents of sortedMultiset to have entries a, c, for the navigation tests. */ - @SuppressWarnings("unchecked") // Needed to stop Eclipse whining private void resetWithHole() { - List container = new ArrayList(); - container.addAll(Collections.nCopies(a.getCount(), a.getElement())); - container.addAll(Collections.nCopies(c.getCount(), c.getElement())); + List container = new ArrayList<>(); + container.addAll(nCopies(a.getCount(), a.getElement())); + container.addAll(nCopies(c.getCount(), c.getElement())); super.resetContainer(getSubjectGenerator().create(container.toArray())); sortedMultiset = (SortedMultiset) getMultiset(); } @@ -92,11 +91,7 @@ private void resetWithHole() { @CollectionSize.Require(ZERO) public void testEmptyMultisetFirst() { assertNull(sortedMultiset.firstEntry()); - try { - sortedMultiset.elementSet().first(); - fail(); - } catch (NoSuchElementException e) { - } + assertThrows(NoSuchElementException.class, () -> sortedMultiset.elementSet().first()); } @CollectionFeature.Require(SUPPORTS_REMOVE) @@ -116,11 +111,8 @@ public void testEmptyMultisetNearby() { @CollectionSize.Require(ZERO) public void testEmptyMultisetLast() { assertNull(sortedMultiset.lastEntry()); - try { - assertNull(sortedMultiset.elementSet().last()); - fail(); - } catch (NoSuchElementException e) { - } + assertThrows( + NoSuchElementException.class, () -> assertNull(sortedMultiset.elementSet().last())); } @CollectionFeature.Require(SUPPORTS_REMOVE) @@ -167,21 +159,16 @@ public void testFirst() { assertEquals(a, sortedMultiset.firstEntry()); } - @SuppressWarnings("unchecked") @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testPollFirst() { assertEquals(a, sortedMultiset.pollFirstEntry()); - assertEquals(Arrays.asList(b, c), copyToList(sortedMultiset.entrySet())); + assertEquals(asList(b, c), copyToList(sortedMultiset.entrySet())); } @CollectionFeature.Require(absent = SUPPORTS_REMOVE) public void testPollFirstUnsupported() { - try { - sortedMultiset.pollFirstEntry(); - fail(); - } catch (UnsupportedOperationException e) { - } + assertThrows(UnsupportedOperationException.class, () -> sortedMultiset.pollFirstEntry()); } @CollectionSize.Require(SEVERAL) @@ -222,22 +209,17 @@ public void testLast() { assertEquals(c, sortedMultiset.lastEntry()); } - @SuppressWarnings("unchecked") @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testPollLast() { assertEquals(c, sortedMultiset.pollLastEntry()); - assertEquals(Arrays.asList(a, b), copyToList(sortedMultiset.entrySet())); + assertEquals(asList(a, b), copyToList(sortedMultiset.entrySet())); } @CollectionFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testPollLastUnsupported() { - try { - sortedMultiset.pollLastEntry(); - fail(); - } catch (UnsupportedOperationException e) { - } + assertThrows(UnsupportedOperationException.class, () -> sortedMultiset.pollLastEntry()); } @CollectionSize.Require(SEVERAL) @@ -264,7 +246,7 @@ void expectAddFailure(SortedMultiset multiset, Entry entry) { } try { - multiset.addAll(Collections.singletonList(entry.getElement())); + multiset.addAll(singletonList(entry.getElement())); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) { } @@ -453,9 +435,8 @@ public void testEmptyRangeSubMultiset(SortedMultiset multiset) { assertFalse(multiset.entrySet().iterator().hasNext()); } - @SuppressWarnings("unchecked") public void testEmptyRangeSubMultisetSupportingAdd(SortedMultiset multiset) { - for (Entry entry : Arrays.asList(a, b, c)) { + for (Entry entry : asList(a, b, c)) { expectAddFailure(multiset, entry); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetReadsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetReadsTester.java index 3a0cf59d3c50..78dfbd04af8c 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetReadsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetReadsTester.java @@ -33,7 +33,9 @@ * @author Jared Levy */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetReadsTester extends AbstractMultisetTester { @CollectionSize.Require(absent = ZERO) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetRemoveTester.java index e6594c18a2d1..7bc44eead661 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetRemoveTester.java @@ -17,21 +17,24 @@ package com.google.common.collect.testing.google; import static com.google.common.collect.testing.Helpers.assertEmpty; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_QUERIES; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.WrongType; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.junit.Ignore; @@ -41,26 +44,20 @@ * * @author Jared Levy */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetRemoveTester extends AbstractMultisetTester { @CollectionFeature.Require(SUPPORTS_REMOVE) public void testRemoveNegative() { - try { - getMultiset().remove(e0(), -1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> getMultiset().remove(e0(), -1)); expectUnchanged(); } @CollectionFeature.Require(absent = SUPPORTS_REMOVE) public void testRemoveUnsupported() { - try { - getMultiset().remove(e0(), 2); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getMultiset().remove(e0(), 2)); } @CollectionFeature.Require(SUPPORTS_REMOVE) @@ -127,11 +124,7 @@ public void testRemove_occurrences_0() { @CollectionFeature.Require(SUPPORTS_REMOVE) public void testRemove_occurrences_negative() { - try { - getMultiset().remove(e0(), -1); - fail("multiset.remove(E, -1) didn't throw an exception"); - } catch (IllegalArgumentException required) { - } + assertThrows(IllegalArgumentException.class, () -> getMultiset().remove(e0(), -1)); } @CollectionFeature.Require(SUPPORTS_REMOVE) @@ -160,18 +153,14 @@ public void testRemove_nullAbsent() { @CollectionFeature.Require(value = SUPPORTS_REMOVE, absent = ALLOWS_NULL_QUERIES) public void testRemove_nullForbidden() { - try { - getMultiset().remove(null, 2); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getMultiset().remove(null, 2)); } @CollectionSize.Require(SEVERAL) @CollectionFeature.Require(SUPPORTS_REMOVE) public void testRemoveAllIgnoresCount() { initThreeCopies(); - assertTrue(getMultiset().removeAll(Collections.singleton(e0()))); + assertTrue(getMultiset().removeAll(singleton(e0()))); assertEmpty(getMultiset()); } @@ -179,8 +168,8 @@ public void testRemoveAllIgnoresCount() { @CollectionFeature.Require(SUPPORTS_REMOVE) public void testRetainAllIgnoresCount() { initThreeCopies(); - List contents = Helpers.copyToList(getMultiset()); - assertFalse(getMultiset().retainAll(Collections.singleton(e0()))); + List contents = copyToList(getMultiset()); + assertFalse(getMultiset().retainAll(singleton(e0()))); expectContents(contents); } @@ -188,9 +177,9 @@ public void testRetainAllIgnoresCount() { * Returns {@link Method} instances for the remove tests that assume multisets support duplicates * so that the test of {@code Multisets.forSet()} can suppress them. */ + @J2ktIncompatible @GwtIncompatible // reflection public static List getRemoveDuplicateInitializingMethods() { - return Arrays.asList( - Helpers.getMethod(MultisetRemoveTester.class, "testRemove_some_occurrences_present")); + return asList(getMethod(MultisetRemoveTester.class, "testRemove_some_occurrences_present")); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSerializationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSerializationTester.java index 03039706b5a5..1ef915a45a51 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSerializationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSerializationTester.java @@ -27,12 +27,14 @@ /** * A generic JUnit test which tests multiset-specific serialization. Can't be invoked directly; - * please see {@link com.google.common.collect.testing.MultisetTestSuiteBuilder}. + * please see {@link MultisetTestSuiteBuilder}. * * @author Louis Wasserman */ @GwtCompatible // but no-op -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetSerializationTester extends AbstractMultisetTester { @CollectionFeature.Require(SERIALIZABLE_INCLUDING_VIEWS) public void testEntrySetSerialization() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSetCountConditionallyTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSetCountConditionallyTester.java index d5e69638f4f8..f3f8bd7d0e98 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSetCountConditionallyTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSetCountConditionallyTester.java @@ -24,6 +24,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.junit.Ignore; /** @@ -33,7 +34,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetSetCountConditionallyTester extends AbstractMultisetSetCountTester { @Override void setCountCheckReturnValue(E element, int count) { @@ -47,6 +50,7 @@ void setCountNoCheckReturnValue(E element, int count) { setCount(element, count); } + @CanIgnoreReturnValue private boolean setCount(E element, int count) { return getMultiset().setCount(element, getMultiset().count(element), count); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSetCountUnconditionallyTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSetCountUnconditionallyTester.java index ec5436ddac1a..9810f918be30 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSetCountUnconditionallyTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetSetCountUnconditionallyTester.java @@ -17,6 +17,7 @@ package com.google.common.collect.testing.google; import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.junit.Ignore; /** @@ -26,7 +27,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MultisetSetCountUnconditionallyTester extends AbstractMultisetSetCountTester { @Override void setCountCheckReturnValue(E element, int count) { @@ -41,6 +44,7 @@ void setCountNoCheckReturnValue(E element, int count) { setCount(element, count); } + @CanIgnoreReturnValue private int setCount(E element, int count) { return getMultiset().setCount(element, count); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetTestSuiteBuilder.java index 266daa4621b1..264c6708deaf 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/MultisetTestSuiteBuilder.java @@ -17,6 +17,8 @@ package com.google.common.collect.testing.google; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.testing.Helpers.copyToList; +import static java.util.Collections.emptySet; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.Multiset; @@ -25,7 +27,6 @@ import com.google.common.collect.testing.AbstractCollectionTestSuiteBuilder; import com.google.common.collect.testing.AbstractTester; import com.google.common.collect.testing.FeatureSpecificTestSuiteBuilder; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.OneSizeTestContainerGenerator; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.SetTestSuiteBuilder; @@ -36,7 +37,6 @@ import com.google.common.testing.SerializableTester; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -64,13 +64,14 @@ public enum NoRecurse implements Feature { @Override public Set> getImpliedFeatures() { - return Collections.emptySet(); + return emptySet(); } } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(CollectionSerializationEqualTester.class); testers.add(MultisetAddTester.class); testers.add(MultisetContainsTester.class); @@ -130,6 +131,8 @@ protected List createDerivedSuites( .named(getName() + ".entrySet") .withFeatures(computeEntrySetFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } @@ -140,6 +143,8 @@ protected List createDerivedSuites( .named(getName() + " reserialized") .withFeatures(computeReserializedMultisetFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite()); } return derivedSuites; @@ -153,10 +158,12 @@ TestSuite createElementSetTestSuite( .named(getName() + ".elementSet") .withFeatures(computeElementSetFeatures(parentBuilder.getFeatures())) .suppressing(parentBuilder.getSuppressedTests()) + .withSetUp(parentBuilder.getSetUp()) + .withTearDown(parentBuilder.getTearDown()) .createTestSuite(); } - static class ElementSetGenerator implements TestSetGenerator { + static final class ElementSetGenerator implements TestSetGenerator { final OneSizeTestContainerGenerator, E> gen; ElementSetGenerator(OneSizeTestContainerGenerator, E> gen) { @@ -189,7 +196,7 @@ public Iterable order(List insertionOrder) { } } - static class EntrySetGenerator implements TestSetGenerator> { + private static final class EntrySetGenerator implements TestSetGenerator> { final OneSizeTestContainerGenerator, E> gen; private EntrySetGenerator(OneSizeTestContainerGenerator, E> gen) { @@ -226,7 +233,7 @@ public Set> create(Object... entries) { @SuppressWarnings("unchecked") @Override public Multiset.Entry[] createArray(int length) { - return new Multiset.Entry[length]; + return (Multiset.Entry[]) new Multiset.Entry[length]; } @Override @@ -248,7 +255,7 @@ public Iterable> order(List> insertionOrder) { } } - static class ReserializedMultisetGenerator implements TestMultisetGenerator { + private static final class ReserializedMultisetGenerator implements TestMultisetGenerator { final OneSizeTestContainerGenerator, E> gen; private ReserializedMultisetGenerator(OneSizeTestContainerGenerator, E> gen) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/ReflectionFreeAssertThrows.java b/android/guava-testlib/src/com/google/common/collect/testing/google/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..05bc4b621aec --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/ReflectionFreeAssertThrows.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect.testing.google; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SetGenerators.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SetGenerators.java index 0b55a2798ae1..3426a8ce2fc9 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SetGenerators.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SetGenerators.java @@ -17,12 +17,15 @@ package com.google.common.collect.testing.google; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.Sets.newTreeSet; import static com.google.common.collect.testing.SampleElements.Strings.AFTER_LAST; import static com.google.common.collect.testing.SampleElements.Strings.AFTER_LAST_2; import static com.google.common.collect.testing.SampleElements.Strings.BEFORE_FIRST; import static com.google.common.collect.testing.SampleElements.Strings.BEFORE_FIRST_2; +import static java.lang.Math.max; +import static java.util.Arrays.asList; +import static java.util.Collections.sort; import static junit.framework.Assert.assertEquals; import com.google.common.annotations.GwtCompatible; @@ -34,8 +37,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.common.collect.Range; -import com.google.common.collect.Sets; -import com.google.common.collect.testing.TestCollectionGenerator; import com.google.common.collect.testing.TestCollidingSetGenerator; import com.google.common.collect.testing.TestIntegerSortedSetGenerator; import com.google.common.collect.testing.TestSetGenerator; @@ -44,12 +45,14 @@ import com.google.common.collect.testing.TestStringSortedSetGenerator; import com.google.common.collect.testing.TestUnhashableCollectionGenerator; import com.google.common.collect.testing.UnhashableObject; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; +import org.jspecify.annotations.NullMarked; /** * Generators of different types of sets and derived collections from sets. @@ -58,7 +61,8 @@ * @author Jared Levy * @author Hayward Chan */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class SetGenerators { public static class ImmutableSetCopyOfGenerator extends TestStringSetGenerator { @@ -83,7 +87,7 @@ public static class ImmutableSetSizedBuilderGenerator extends TestStringSetGener @Override protected Set create(String[] elements) { ImmutableSet.Builder builder = - ImmutableSet.builderWithExpectedSize(Sets.newHashSet(elements).size()); + ImmutableSet.builderWithExpectedSize(newHashSet(elements).size()); for (String e : elements) { builder.add(e); } @@ -95,7 +99,7 @@ public static class ImmutableSetTooBigBuilderGenerator extends TestStringSetGene @Override protected Set create(String[] elements) { ImmutableSet.Builder builder = - ImmutableSet.builderWithExpectedSize(Sets.newHashSet(elements).size() + 1); + ImmutableSet.builderWithExpectedSize(newHashSet(elements).size() + 1); for (String e : elements) { builder.add(e); } @@ -107,7 +111,7 @@ public static class ImmutableSetTooSmallBuilderGenerator extends TestStringSetGe @Override protected Set create(String[] elements) { ImmutableSet.Builder builder = - ImmutableSet.builderWithExpectedSize(Math.max(0, Sets.newHashSet(elements).size() - 1)); + ImmutableSet.builderWithExpectedSize(max(0, newHashSet(elements).size() - 1)); for (String e : elements) { builder.add(e); } @@ -115,11 +119,7 @@ protected Set create(String[] elements) { } } - public static class ImmutableSetWithBadHashesGenerator extends TestCollidingSetGenerator - // Work around a GWT compiler bug. Not explicitly listing this will - // cause the createArray() method missing in the generated javascript. - // TODO: Remove this once the GWT bug is fixed. - implements TestCollectionGenerator { + public static class ImmutableSetWithBadHashesGenerator extends TestCollidingSetGenerator { @Override public Set create(Object... elements) { return ImmutableSet.copyOf(elements); @@ -127,12 +127,10 @@ public Set create(Object... elements) { } public static class DegeneratedImmutableSetGenerator extends TestStringSetGenerator { - // Make sure we get what we think we're getting, or else this test - // is pointless - @SuppressWarnings("cast") + @SuppressWarnings("DistinctVarargsChecker") // deliberately testing deduplication @Override protected Set create(String[] elements) { - return (ImmutableSet) ImmutableSet.of(elements[0], elements[0]); + return ImmutableSet.of(elements[0], elements[0]); } } @@ -188,9 +186,15 @@ protected SortedSet create(String[] elements) { return ImmutableSortedSet.orderedBy(STRING_REVERSED).add(elements).build(); } + /* + * While the current implementation returns `this`, that's not something we mean to guarantee. + * Callers of TestContainerGenerator.order need to be prepared for implementations to return a new + * collection. + */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder, Collections.reverseOrder()); + sort(insertionOrder, Collections.reverseOrder()); return insertionOrder; } } @@ -205,9 +209,10 @@ protected SortedSet create(String[] elements) { return new ImmutableSortedSet.Builder(COMPARABLE_REVERSED).add(elements).build(); } + @SuppressWarnings("CanIgnoreReturnValueSuggester") // see ImmutableSortedSetExplicitComparator @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder, Collections.reverseOrder()); + sort(insertionOrder, Collections.reverseOrder()); return insertionOrder; } } @@ -216,14 +221,13 @@ public static class ImmutableSortedSetReversedOrderGenerator extends TestStringS @Override protected SortedSet create(String[] elements) { - return ImmutableSortedSet.reverseOrder() - .addAll(Arrays.asList(elements).iterator()) - .build(); + return ImmutableSortedSet.reverseOrder().addAll(asList(elements).iterator()).build(); } + @SuppressWarnings("CanIgnoreReturnValueSuggester") // see ImmutableSortedSetExplicitComparator @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder, Collections.reverseOrder()); + sort(insertionOrder, Collections.reverseOrder()); return insertionOrder; } } @@ -246,7 +250,7 @@ public static class ImmutableSortedSetAsListGenerator extends TestStringListGene @Override protected List create(String[] elements) { Comparator comparator = createExplicitComparator(elements); - ImmutableSet set = ImmutableSortedSet.copyOf(comparator, Arrays.asList(elements)); + ImmutableSet set = ImmutableSortedSet.copyOf(comparator, asList(elements)); return set.asList(); } } @@ -314,13 +318,13 @@ public abstract static class TestUnhashableSetGenerator private static Ordering createExplicitComparator(String[] elements) { // Collapse equal elements, which Ordering.explicit() doesn't support, while // maintaining the ordering by first occurrence. - Set elementsPlus = Sets.newLinkedHashSet(); + Set elementsPlus = new LinkedHashSet<>(); elementsPlus.add(BEFORE_FIRST); elementsPlus.add(BEFORE_FIRST_2); - elementsPlus.addAll(Arrays.asList(elements)); + elementsPlus.addAll(asList(elements)); elementsPlus.add(AFTER_LAST); elementsPlus.add(AFTER_LAST_2); - return Ordering.explicit(Lists.newArrayList(elementsPlus)); + return Ordering.explicit(new ArrayList<>(elementsPlus)); } /* @@ -398,17 +402,18 @@ protected SortedSet create(Integer[] elements) { } /** Sorts the elements in reverse natural order. */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") // see ImmutableSortedSetExplicitComparator @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder, Ordering.natural().reverse()); + sort(insertionOrder, Ordering.natural().reverse()); return insertionOrder; } } private abstract static class AbstractContiguousSetGenerator extends TestIntegerSortedSetGenerator { - protected final ContiguousSet checkedCreate(SortedSet elementsSet) { - List elements = newArrayList(elementsSet); + static final ContiguousSet checkedCreate(SortedSet elementsSet) { + List elements = new ArrayList<>(elementsSet); /* * A ContiguousSet can't have holes. If a test demands a hole, it should be changed so that it * doesn't need one, or it should be suppressed for ContiguousSet. @@ -421,4 +426,12 @@ protected final ContiguousSet checkedCreate(SortedSet elements return ContiguousSet.create(range, DiscreteDomain.integers()); } } + + /** + * Useless constructor for a class of static utility methods. + * + * @deprecated Do not instantiate this utility class. + */ + @Deprecated + public SetGenerators() {} } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapAsMapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapAsMapTester.java index 49187dd1dffe..3688686a2e85 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapAsMapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapAsMapTester.java @@ -14,24 +14,28 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.Sets.newHashSet; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; -import com.google.common.collect.Sets; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import com.google.common.testing.EqualsTester; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -42,8 +46,12 @@ * @param The value type of the tested multimap. */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class SetMultimapAsMapTester extends AbstractMultimapTester> { +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class SetMultimapAsMapTester + extends AbstractMultimapTester> { public void testAsMapValuesImplementSet() { for (Collection valueCollection : multimap().asMap().values()) { assertTrue(valueCollection instanceof Set); @@ -58,7 +66,7 @@ public void testAsMapGetImplementsSet() { @MapFeature.Require(SUPPORTS_REMOVE) public void testAsMapRemoveImplementsSet() { - List keys = new ArrayList(multimap().keySet()); + List keys = new ArrayList<>(multimap().keySet()); for (K key : keys) { resetCollection(); assertTrue(multimap().asMap().remove(key) instanceof Set); @@ -67,31 +75,34 @@ public void testAsMapRemoveImplementsSet() { @CollectionSize.Require(SEVERAL) public void testEquals() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k1(), v0()), Helpers.mapEntry(k0(), v3())); - Map> expected = Maps.newHashMap(); - expected.put(k0(), Sets.newHashSet(v0(), v3())); - expected.put(k1(), Sets.newHashSet(v0())); + resetContainer(mapEntry(k0(), v0()), mapEntry(k1(), v0()), mapEntry(k0(), v3())); + Map> expected = new HashMap<>(); + expected.put(k0(), newHashSet(v0(), v3())); + expected.put(k1(), newHashSet(v0())); new EqualsTester().addEqualityGroup(expected, multimap().asMap()).testEquals(); } @CollectionSize.Require(SEVERAL) public void testEntrySetEquals() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k1(), v0()), Helpers.mapEntry(k0(), v3())); - Set>> expected = Sets.newHashSet(); - expected.add(Helpers.mapEntry(k0(), (Collection) Sets.newHashSet(v0(), v3()))); - expected.add(Helpers.mapEntry(k1(), (Collection) Sets.newHashSet(v0()))); + resetContainer(mapEntry(k0(), v0()), mapEntry(k1(), v0()), mapEntry(k0(), v3())); + Set>> expected = new HashSet<>(); + expected.add(mapEntry(k0(), (Collection) newHashSet(v0(), v3()))); + expected.add(mapEntry(k1(), (Collection) newHashSet(v0()))); new EqualsTester().addEqualityGroup(expected, multimap().asMap().entrySet()).testEquals(); } @CollectionSize.Require(SEVERAL) @MapFeature.Require(SUPPORTS_REMOVE) + /* + * SetMultimap.asMap essentially returns a Map>; we just can't declare it that way. + * Thus, calls like asMap().values().remove(someSet) are safe because they are comparing a set to + * a collection of other sets. + */ + @SuppressWarnings("CollectionUndefinedEquality") public void testValuesRemove() { - resetContainer( - Helpers.mapEntry(k0(), v0()), Helpers.mapEntry(k1(), v0()), Helpers.mapEntry(k0(), v3())); - assertTrue(multimap().asMap().values().remove(Collections.singleton(v0()))); + resetContainer(mapEntry(k0(), v0()), mapEntry(k1(), v0()), mapEntry(k0(), v3())); + assertTrue(multimap().asMap().values().remove(singleton(v0()))); assertEquals(2, multimap().size()); - assertEquals(Collections.singletonMap(k0(), Sets.newHashSet(v0(), v3())), multimap().asMap()); + assertEquals(singletonMap(k0(), newHashSet(v0(), v3())), multimap().asMap()); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapEqualsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapEqualsTester.java index 18d3823eb78b..58af05033ee9 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapEqualsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapEqualsTester.java @@ -14,11 +14,11 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.SetMultimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.testing.EqualsTester; import org.junit.Ignore; @@ -29,22 +29,18 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetMultimapEqualsTester extends AbstractMultimapTester> { @CollectionSize.Require(SEVERAL) public void testOrderingDoesntAffectEqualsComparisons() { SetMultimap multimap1 = getSubjectGenerator() - .create( - Helpers.mapEntry(k0(), v0()), - Helpers.mapEntry(k0(), v1()), - Helpers.mapEntry(k0(), v4())); + .create(mapEntry(k0(), v0()), mapEntry(k0(), v1()), mapEntry(k0(), v4())); SetMultimap multimap2 = getSubjectGenerator() - .create( - Helpers.mapEntry(k0(), v1()), - Helpers.mapEntry(k0(), v0()), - Helpers.mapEntry(k0(), v4())); + .create(mapEntry(k0(), v1()), mapEntry(k0(), v0()), mapEntry(k0(), v4())); new EqualsTester().addEqualityGroup(multimap1, multimap2).testEquals(); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapPutAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapPutAllTester.java index ca02b560326f..3a74618260ce 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapPutAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapPutAllTester.java @@ -16,11 +16,11 @@ import static com.google.common.collect.testing.Helpers.copyToSet; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.SetMultimap; import com.google.common.collect.testing.features.MapFeature; -import java.util.Arrays; import java.util.List; import java.util.Set; import org.junit.Ignore; @@ -31,13 +31,14 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetMultimapPutAllTester extends AbstractMultimapTester> { @MapFeature.Require(SUPPORTS_PUT) public void testPutAllHandlesDuplicates() { - @SuppressWarnings("unchecked") - List valuesToPut = Arrays.asList(v0(), v1(), v0()); + List valuesToPut = asList(v0(), v1(), v0()); for (K k : sampleKeys()) { resetContainer(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapPutTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapPutTester.java index 7aaf9dce54ba..26a2180e249d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapPutTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapPutTester.java @@ -35,7 +35,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetMultimapPutTester extends AbstractMultimapTester> { // Tests for non-duplicate values are in MultimapPutTester diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapReplaceValuesTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapReplaceValuesTester.java index 67b6aec86590..673ae501405f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapReplaceValuesTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapReplaceValuesTester.java @@ -16,11 +16,11 @@ import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.SetMultimap; import com.google.common.collect.testing.features.MapFeature; -import java.util.Arrays; import java.util.List; import org.junit.Ignore; @@ -30,14 +30,15 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetMultimapReplaceValuesTester extends AbstractMultimapTester> { @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) public void testReplaceValuesHandlesDuplicates() { - @SuppressWarnings("unchecked") - List values = Arrays.asList(v0(), v1(), v0()); + List values = asList(v0(), v1(), v0()); for (K k : sampleKeys()) { resetContainer(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapTestSuiteBuilder.java index 4368cee9bf83..8dc65db90c44 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SetMultimapTestSuiteBuilder.java @@ -16,11 +16,12 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.copyToList; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.SetMultimap; import com.google.common.collect.testing.AbstractTester; import com.google.common.collect.testing.FeatureSpecificTestSuiteBuilder; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.OneSizeTestContainerGenerator; import com.google.common.collect.testing.SetTestSuiteBuilder; import com.google.common.collect.testing.TestSetGenerator; @@ -50,9 +51,10 @@ public static SetMultimapTestSuiteBuilder using( return result; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(SetMultimapAsMapTester.class); testers.add(SetMultimapEqualsTester.class); testers.add(SetMultimapPutTester.class); @@ -105,11 +107,11 @@ TestSuite computeEntriesTestSuite( .createTestSuite(); } - private static class EntriesGenerator + private static final class EntriesGenerator extends MultimapTestSuiteBuilder.EntriesGenerator> implements TestSetGenerator> { - public EntriesGenerator( + EntriesGenerator( OneSizeTestContainerGenerator, Entry> multimapGenerator) { super(multimapGenerator); } @@ -120,7 +122,7 @@ public Set> create(Object... elements) { } } - static class MultimapGetGenerator + static final class MultimapGetGenerator extends MultimapTestSuiteBuilder.MultimapGetGenerator> implements TestSetGenerator { public MultimapGetGenerator( @@ -134,7 +136,7 @@ public Set create(Object... elements) { } } - static class MultimapAsMapGetGenerator + static final class MultimapAsMapGetGenerator extends MultimapTestSuiteBuilder.MultimapAsMapGetGenerator> implements TestSetGenerator { public MultimapAsMapGetGenerator( diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedMapGenerators.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedMapGenerators.java index 424fbb17efe3..1eec5bcfdbc8 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedMapGenerators.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedMapGenerators.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.testing.Helpers.mapEntry; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableSortedMap; @@ -26,10 +27,10 @@ import com.google.common.collect.testing.TestListGenerator; import com.google.common.collect.testing.TestStringListGenerator; import com.google.common.collect.testing.TestStringSortedMapGenerator; -import java.util.Arrays; import java.util.List; import java.util.Map.Entry; import java.util.SortedMap; +import org.jspecify.annotations.NullMarked; /** * Generators of sorted maps and derived collections. @@ -42,6 +43,7 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public class SortedMapGenerators { public static class ImmutableSortedMapGenerator extends TestStringSortedMapGenerator { @Override @@ -59,7 +61,7 @@ public static class ImmutableSortedMapCopyOfEntriesGenerator extends TestStringSortedMapGenerator { @Override public SortedMap create(Entry[] entries) { - return ImmutableSortedMap.copyOf(Arrays.asList(entries)); + return ImmutableSortedMap.copyOf(asList(entries)); } } @@ -79,7 +81,7 @@ public SampleElements> samples() { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -97,7 +99,7 @@ public List> create(Object... elements) { ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); for (Object o : elements) { @SuppressWarnings("unchecked") - Entry entry = (Entry) o; + Entry entry = (Entry) checkNotNull(o); builder.put(entry); } return builder.build().entrySet().asList(); @@ -116,7 +118,7 @@ protected List create(String[] elements) { @Override public List order(List insertionOrder) { - return Ordering.natural().sortedCopy(insertionOrder); + return Ordering.natural().sortedCopy(insertionOrder); } } @@ -130,4 +132,12 @@ protected List create(String[] elements) { return builder.build().values().asList(); } } + + /** + * Useless constructor for a class of static utility methods. + * + * @deprecated Do not instantiate this utility class. + */ + @Deprecated + public SortedMapGenerators() {} } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedMultisetTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedMultisetTestSuiteBuilder.java index dafd52187df4..f04fc4e17660 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedMultisetTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedMultisetTestSuiteBuilder.java @@ -16,29 +16,29 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionFeature.RESTRICTS_ELEMENTS; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.sort; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.BoundType; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import com.google.common.collect.Multiset; import com.google.common.collect.SortedMultiset; import com.google.common.collect.testing.AbstractTester; import com.google.common.collect.testing.FeatureSpecificTestSuiteBuilder; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.OneSizeTestContainerGenerator; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.SetTestSuiteBuilder; import com.google.common.collect.testing.features.Feature; import com.google.common.testing.SerializableTester; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; @@ -56,7 +56,7 @@ @GwtIncompatible public class SortedMultisetTestSuiteBuilder extends MultisetTestSuiteBuilder { public static SortedMultisetTestSuiteBuilder using(TestMultisetGenerator generator) { - SortedMultisetTestSuiteBuilder result = new SortedMultisetTestSuiteBuilder(); + SortedMultisetTestSuiteBuilder result = new SortedMultisetTestSuiteBuilder<>(); result.usingGenerator(generator); return result; } @@ -71,9 +71,10 @@ public TestSuite createTestSuite() { return suite; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(MultisetNavigationTester.class); return testers; } @@ -101,7 +102,7 @@ enum NoRecurse implements Feature { @Override public Set> getImpliedFeatures() { - return Collections.emptySet(); + return emptySet(); } } @@ -113,7 +114,7 @@ enum Bound { } List createDerivedSuites(SortedMultisetTestSuiteBuilder parentBuilder) { - List derivedSuites = Lists.newArrayList(); + List derivedSuites = new ArrayList<>(); if (!parentBuilder.getFeatures().contains(NoRecurse.DESCENDING)) { derivedSuites.add(createDescendingSuite(parentBuilder)); @@ -138,8 +139,8 @@ List createDerivedSuites(SortedMultisetTestSuiteBuilder parentBuil } private TestSuite createSubMultisetSuite( - SortedMultisetTestSuiteBuilder parentBuilder, final Bound from, final Bound to) { - final TestMultisetGenerator delegate = + SortedMultisetTestSuiteBuilder parentBuilder, Bound from, Bound to) { + TestMultisetGenerator delegate = (TestMultisetGenerator) parentBuilder.getSubjectGenerator(); Set> features = new HashSet<>(); @@ -152,15 +153,14 @@ private TestSuite createSubMultisetSuite( } SortedMultiset emptyMultiset = (SortedMultiset) delegate.create(); - final Comparator comparator = emptyMultiset.comparator(); + Comparator comparator = emptyMultiset.comparator(); SampleElements samples = delegate.samples(); - @SuppressWarnings("unchecked") List samplesList = - Arrays.asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4()); + asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4()); - Collections.sort(samplesList, comparator); - final E firstInclusive = samplesList.get(0); - final E lastInclusive = samplesList.get(samplesList.size() - 1); + sort(samplesList, comparator); + E firstInclusive = samplesList.get(0); + E lastInclusive = samplesList.get(samplesList.size() - 1); return SortedMultisetTestSuiteBuilder.using( new ForwardingTestMultisetGenerator(delegate) { @@ -171,10 +171,10 @@ public SortedMultiset create(Object... entries) { List extremeValues = (List) getExtremeValues(); @SuppressWarnings("unchecked") // map generators must past entry objects - List normalValues = (List) Arrays.asList(entries); + List normalValues = (List) asList(entries); // prepare extreme values to be filtered out of view - Collections.sort(extremeValues, comparator); + sort(extremeValues, comparator); E firstExclusive = extremeValues.get(1); E lastExclusive = extremeValues.get(2); if (from == Bound.NO_BOUND) { @@ -187,7 +187,7 @@ public SortedMultiset create(Object... entries) { } // the regular values should be visible after filtering - List allEntries = new ArrayList(); + List allEntries = new ArrayList<>(); allEntries.addAll(extremeValues); allEntries.addAll(normalValues); SortedMultiset multiset = @@ -224,7 +224,7 @@ public SortedMultiset create(Object... entries) { * work for this purpose, which may cause problems for navigable maps with non-string or unicode * generators. */ - private List getExtremeValues() { + private static List getExtremeValues() { List result = new ArrayList<>(); result.add("!! a"); result.add("!! b"); @@ -234,7 +234,7 @@ private List getExtremeValues() { } private TestSuite createDescendingSuite(SortedMultisetTestSuiteBuilder parentBuilder) { - final TestMultisetGenerator delegate = + TestMultisetGenerator delegate = (TestMultisetGenerator) parentBuilder.getSubjectGenerator(); Set> features = new HashSet<>(); @@ -263,7 +263,7 @@ public Iterable order(List insertionOrder) { } private TestSuite createReserializedSuite(SortedMultisetTestSuiteBuilder parentBuilder) { - final TestMultisetGenerator delegate = + TestMultisetGenerator delegate = (TestMultisetGenerator) parentBuilder.getSubjectGenerator(); Set> features = new HashSet<>(parentBuilder.getFeatures()); @@ -274,7 +274,7 @@ private TestSuite createReserializedSuite(SortedMultisetTestSuiteBuilder pare new ForwardingTestMultisetGenerator(delegate) { @Override public SortedMultiset create(Object... entries) { - return SerializableTester.reserialize(((SortedMultiset) super.create(entries))); + return SerializableTester.reserialize((SortedMultiset) super.create(entries)); } }) .named(parentBuilder.getName() + " reserialized") diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapAsMapTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapAsMapTester.java index 1c00f0970102..4fa0263d9c2b 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapAsMapTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapAsMapTester.java @@ -33,7 +33,9 @@ * @param The value type of the tested multimap. */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SortedSetMultimapAsMapTester extends AbstractMultimapTester> { public void testAsMapValuesImplementSortedSet() { @@ -52,7 +54,7 @@ public void testAsMapGetImplementsSortedSet() { @MapFeature.Require(SUPPORTS_REMOVE) public void testAsMapRemoveImplementsSortedSet() { - List keys = new ArrayList(multimap().keySet()); + List keys = new ArrayList<>(multimap().keySet()); for (K key : keys) { resetCollection(); SortedSet valueSet = (SortedSet) multimap().asMap().remove(key); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapGetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapGetTester.java index 5244b5747caa..17cee7cea609 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapGetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapGetTester.java @@ -26,7 +26,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SortedSetMultimapGetTester extends AbstractMultimapTester> { public void testValueComparator() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapTestSuiteBuilder.java b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapTestSuiteBuilder.java index 66c5a8ed09a6..a66e3d423137 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapTestSuiteBuilder.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/SortedSetMultimapTestSuiteBuilder.java @@ -16,11 +16,12 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.copyToList; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.SetMultimap; import com.google.common.collect.testing.AbstractTester; import com.google.common.collect.testing.FeatureSpecificTestSuiteBuilder; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.OneSizeTestContainerGenerator; import com.google.common.collect.testing.SortedSetTestSuiteBuilder; import com.google.common.collect.testing.features.CollectionSize; @@ -49,9 +50,10 @@ public static SortedSetMultimapTestSuiteBuilder using( return result; } + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - List> testers = Helpers.copyToList(super.getTesters()); + List> testers = copyToList(super.getTesters()); testers.add(SetMultimapAsMapTester.class); testers.add(SetMultimapEqualsTester.class); testers.add(SetMultimapPutTester.class); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestBiMapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestBiMapGenerator.java index 11c353b6bbce..af48b03606a3 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestBiMapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestBiMapGenerator.java @@ -20,6 +20,8 @@ import com.google.common.collect.BiMap; import com.google.common.collect.testing.TestContainerGenerator; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates bimaps, containing sample entries, to be tested. @@ -27,7 +29,9 @@ * @author Louis Wasserman */ @GwtCompatible -public interface TestBiMapGenerator extends TestContainerGenerator, Entry> { +@NullMarked +public interface TestBiMapGenerator + extends TestContainerGenerator, Entry> { K[] createKeyArray(int length); V[] createValueArray(int length); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestEnumMultisetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestEnumMultisetGenerator.java index 851e221896c0..49e5b489edbb 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestEnumMultisetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestEnumMultisetGenerator.java @@ -16,13 +16,15 @@ package com.google.common.collect.testing.google; +import static java.util.Collections.sort; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multiset; import com.google.common.collect.testing.AnEnum; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.SampleElements.Enums; -import java.util.Collections; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * An abstract {@code TestMultisetGenerator} for generating multisets containing enum values. @@ -30,6 +32,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public abstract class TestEnumMultisetGenerator implements TestMultisetGenerator { @Override public SampleElements samples() { @@ -54,9 +57,15 @@ public AnEnum[] createArray(int length) { } /** Sorts the enums according to their natural ordering. */ + /* + * While the current implementation returns `this`, that's not something we mean to guarantee. + * Callers of TestContainerGenerator.order need to be prepared for implementations to return a new + * collection. + */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestListMultimapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestListMultimapGenerator.java index 1ab668fb92b0..26a1f2f57e09 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestListMultimapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestListMultimapGenerator.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ListMultimap; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A generator for {@code ListMultimap} implementations based on test data. @@ -25,5 +27,6 @@ * @author Louis Wasserman */ @GwtCompatible -public interface TestListMultimapGenerator +@NullMarked +public interface TestListMultimapGenerator extends TestMultimapGenerator> {} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestMultimapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestMultimapGenerator.java index 06ce43f306c9..d555f51c2c1d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestMultimapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestMultimapGenerator.java @@ -22,6 +22,8 @@ import com.google.common.collect.testing.TestContainerGenerator; import java.util.Collection; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates multimaps, containing sample elements, to be tested. @@ -29,7 +31,9 @@ * @author Louis Wasserman */ @GwtCompatible -public interface TestMultimapGenerator> +@NullMarked +public interface TestMultimapGenerator< + K extends @Nullable Object, V extends @Nullable Object, M extends Multimap> extends TestContainerGenerator> { K[] createKeyArray(int length); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestMultisetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestMultisetGenerator.java index d3b5acd49d7e..0a36c8946904 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestMultisetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestMultisetGenerator.java @@ -19,6 +19,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multiset; import com.google.common.collect.testing.TestCollectionGenerator; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Creates multisets, containing sample elements, to be tested. @@ -26,7 +28,9 @@ * @author Jared Levy */ @GwtCompatible -public interface TestMultisetGenerator extends TestCollectionGenerator { +@NullMarked +public interface TestMultisetGenerator + extends TestCollectionGenerator { @Override Multiset create(Object... elements); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringBiMapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringBiMapGenerator.java index d475397edd21..ee56438dac48 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringBiMapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringBiMapGenerator.java @@ -16,12 +16,14 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.mapEntry; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.BiMap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.SampleElements; import java.util.List; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; /** * Implementation helper for {@link TestBiMapGenerator} for use with bimaps of strings. @@ -32,22 +34,23 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public abstract class TestStringBiMapGenerator implements TestBiMapGenerator { @Override public SampleElements> samples() { return new SampleElements<>( - Helpers.mapEntry("one", "January"), - Helpers.mapEntry("two", "February"), - Helpers.mapEntry("three", "March"), - Helpers.mapEntry("four", "April"), - Helpers.mapEntry("five", "May")); + mapEntry("one", "January"), + mapEntry("two", "February"), + mapEntry("three", "March"), + mapEntry("four", "April"), + mapEntry("five", "May")); } @Override public final BiMap create(Object... entries) { @SuppressWarnings("unchecked") - Entry[] array = new Entry[entries.length]; + Entry[] array = (Entry[]) new Entry[entries.length]; int i = 0; for (Object o : entries) { @SuppressWarnings("unchecked") @@ -62,7 +65,7 @@ public final BiMap create(Object... entries) { @Override @SuppressWarnings("unchecked") public final Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringListMultimapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringListMultimapGenerator.java index 64b33af0a6e7..790fe46b1dfe 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringListMultimapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringListMultimapGenerator.java @@ -16,13 +16,16 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.mapEntry; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ListMultimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.SampleElements; import java.util.Collection; import java.util.List; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; /** * A skeleton generator for a {@code ListMultimap} implementation. @@ -30,17 +33,18 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public abstract class TestStringListMultimapGenerator implements TestListMultimapGenerator { @Override public SampleElements> samples() { return new SampleElements<>( - Helpers.mapEntry("one", "January"), - Helpers.mapEntry("two", "February"), - Helpers.mapEntry("three", "March"), - Helpers.mapEntry("four", "April"), - Helpers.mapEntry("five", "May")); + mapEntry("one", "January"), + mapEntry("two", "February"), + mapEntry("three", "March"), + mapEntry("four", "April"), + mapEntry("five", "May")); } @Override @@ -55,13 +59,13 @@ public SampleElements sampleValues() { @Override public Collection createCollection(Iterable values) { - return Helpers.copyToList(values); + return copyToList(values); } @Override public final ListMultimap create(Object... entries) { @SuppressWarnings("unchecked") - Entry[] array = new Entry[entries.length]; + Entry[] array = (Entry[]) new Entry[entries.length]; int i = 0; for (Object o : entries) { @SuppressWarnings("unchecked") @@ -76,7 +80,7 @@ public final ListMultimap create(Object... entries) { @Override @SuppressWarnings("unchecked") public final Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringMultisetGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringMultisetGenerator.java index eeacf5d1932c..8bdef2293382 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringMultisetGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringMultisetGenerator.java @@ -21,6 +21,7 @@ import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.SampleElements.Strings; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * Create multisets of strings for tests. @@ -28,6 +29,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public abstract class TestStringMultisetGenerator implements TestMultisetGenerator { @Override public SampleElements samples() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringSetMultimapGenerator.java b/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringSetMultimapGenerator.java index e49ccffed7a8..414860e414c5 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringSetMultimapGenerator.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/TestStringSetMultimapGenerator.java @@ -15,13 +15,16 @@ */ package com.google.common.collect.testing.google; +import static com.google.common.collect.testing.Helpers.copyToSet; +import static com.google.common.collect.testing.Helpers.mapEntry; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.SetMultimap; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.SampleElements; import java.util.Collection; import java.util.List; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; /** * A skeleton generator for a {@code SetMultimap} implementation. @@ -29,17 +32,18 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public abstract class TestStringSetMultimapGenerator implements TestSetMultimapGenerator { @Override public SampleElements> samples() { return new SampleElements<>( - Helpers.mapEntry("one", "January"), - Helpers.mapEntry("two", "February"), - Helpers.mapEntry("three", "March"), - Helpers.mapEntry("four", "April"), - Helpers.mapEntry("five", "May")); + mapEntry("one", "January"), + mapEntry("two", "February"), + mapEntry("three", "March"), + mapEntry("four", "April"), + mapEntry("five", "May")); } @Override @@ -54,13 +58,13 @@ public SampleElements sampleValues() { @Override public Collection createCollection(Iterable values) { - return Helpers.copyToSet(values); + return copyToSet(values); } @Override public final SetMultimap create(Object... entries) { @SuppressWarnings("unchecked") - Entry[] array = new Entry[entries.length]; + Entry[] array = (Entry[]) new Entry[entries.length]; int i = 0; for (Object o : entries) { @SuppressWarnings("unchecked") @@ -75,7 +79,7 @@ public final SetMultimap create(Object... entries) { @Override @SuppressWarnings("unchecked") public final Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/UnmodifiableCollectionTests.java b/android/guava-testlib/src/com/google/common/collect/testing/google/UnmodifiableCollectionTests.java index 005746f4be20..c59760073af0 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/google/UnmodifiableCollectionTests.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/UnmodifiableCollectionTests.java @@ -16,25 +16,26 @@ package com.google.common.collect.testing.google; +import static com.google.common.collect.Maps.immutableEntry; +import static java.util.Collections.singleton; +import static java.util.Collections.unmodifiableList; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Iterators; import com.google.common.collect.LinkedHashMultiset; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A series of tests that support asserting that collections cannot be modified, either through @@ -43,11 +44,15 @@ * @author Robert Konigsberg */ @GwtCompatible +@NullMarked public class UnmodifiableCollectionTests { public static void assertMapEntryIsUnmodifiable(Entry entry) { try { - entry.setValue(null); + // fine because the call is going to fail without modifying the entry + @SuppressWarnings("unchecked") + Entry nullableValueEntry = (Entry) entry; + nullableValueEntry.setValue(null); fail("setValue on unmodifiable Map.Entry succeeded"); } catch (UnsupportedOperationException expected) { } @@ -109,14 +114,13 @@ public static void assertIteratorsInOrder( * @param sampleElement an element of the same type as that contained by {@code collection}. * {@code collection} may or may not have {@code sampleElement} as a member. */ - public static void assertCollectionIsUnmodifiable(Collection collection, E sampleElement) { + public static void assertCollectionIsUnmodifiable( + Collection collection, E sampleElement) { Collection siblingCollection = new ArrayList<>(); siblingCollection.add(sampleElement); Collection copy = new ArrayList<>(); - // Avoid copy.addAll(collection), which runs afoul of an Android bug in older versions: - // http://b.android.com/72073 http://r.android.com/98929 - Iterators.addAll(copy, collection.iterator()); + copy.addAll(collection); try { collection.add(sampleElement); @@ -181,7 +185,8 @@ public static void assertCollectionIsUnmodifiable(Collection collection, * @param sampleElement an element of the same type as that contained by {@code set}. {@code set} * may or may not have {@code sampleElement} as a member. */ - public static void assertSetIsUnmodifiable(Set set, E sampleElement) { + public static void assertSetIsUnmodifiable( + Set set, E sampleElement) { assertCollectionIsUnmodifiable(set, sampleElement); } @@ -201,7 +206,8 @@ public static void assertSetIsUnmodifiable(Set set, E sampleElement) { * @param sampleElement an element of the same type as that contained by {@code multiset}. {@code * multiset} may or may not have {@code sampleElement} as a member. */ - public static void assertMultisetIsUnmodifiable(Multiset multiset, final E sampleElement) { + public static void assertMultisetIsUnmodifiable( + Multiset multiset, E sampleElement) { Multiset copy = LinkedHashMultiset.create(multiset); assertCollectionsAreEquivalent(multiset, copy); @@ -263,14 +269,13 @@ public E getElement() { * @param sampleValue a key of the same type as that contained by {@code multimap}. {@code * multimap} may or may not have {@code sampleValue} as a key. */ - public static void assertMultimapIsUnmodifiable( - Multimap multimap, final K sampleKey, final V sampleValue) { - List> originalEntries = - Collections.unmodifiableList(Lists.newArrayList(multimap.entries())); + public static + void assertMultimapIsUnmodifiable(Multimap multimap, K sampleKey, V sampleValue) { + List> originalEntries = unmodifiableList(new ArrayList<>(multimap.entries())); assertMultimapRemainsUnmodified(multimap, originalEntries); - Collection sampleValueAsCollection = Collections.singleton(sampleValue); + Collection sampleValueAsCollection = singleton(sampleValue); // Test #clear() try { @@ -283,7 +288,7 @@ public static void assertMultimapIsUnmodifiable( // Test asMap().entrySet() assertSetIsUnmodifiable( - multimap.asMap().entrySet(), Maps.immutableEntry(sampleKey, sampleValueAsCollection)); + multimap.asMap().entrySet(), immutableEntry(sampleKey, sampleValueAsCollection)); // Test #values() @@ -295,7 +300,7 @@ public static void assertMultimapIsUnmodifiable( } // Test #entries() - assertCollectionIsUnmodifiable(multimap.entries(), Maps.immutableEntry(sampleKey, sampleValue)); + assertCollectionIsUnmodifiable(multimap.entries(), immutableEntry(sampleKey, sampleValue)); assertMultimapRemainsUnmodified(multimap, originalEntries); // Iterate over every element in the entry set @@ -403,13 +408,21 @@ public static void assertMultimapIsUnmodifiable( assertMultimapRemainsUnmodified(multimap, originalEntries); } - private static void assertCollectionsAreEquivalent( + private static void assertCollectionsAreEquivalent( Collection expected, Collection actual) { assertIteratorsInOrder(expected.iterator(), actual.iterator()); } - private static void assertMultimapRemainsUnmodified( - Multimap expected, List> actual) { + private static + void assertMultimapRemainsUnmodified(Multimap expected, List> actual) { assertIteratorsInOrder(expected.entries().iterator(), actual.iterator()); } + + /** + * Useless constructor for a class of static utility methods. + * + * @deprecated Do not instantiate this utility class. + */ + @Deprecated + public UnmodifiableCollectionTests() {} } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/google/package-info.java b/android/guava-testlib/src/com/google/common/collect/testing/google/package-info.java new file mode 100644 index 000000000000..57c1a21bb0b4 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/google/package-info.java @@ -0,0 +1,2 @@ +@com.google.errorprone.annotations.CheckReturnValue +package com.google.common.collect.testing.google; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/package-info.java b/android/guava-testlib/src/com/google/common/collect/testing/package-info.java new file mode 100644 index 000000000000..15086f3ae088 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/package-info.java @@ -0,0 +1,2 @@ +@com.google.errorprone.annotations.CheckReturnValue +package com.google.common.collect.testing; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractListIndexOfTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractListIndexOfTester.java index 46bd52942657..ef0b917fe69a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractListIndexOfTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractListIndexOfTester.java @@ -23,6 +23,7 @@ import com.google.common.collect.testing.WrongType; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -31,10 +32,12 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public abstract class AbstractListIndexOfTester extends AbstractListTester { /** Override to call {@code indexOf()} or {@code lastIndexOf()}. */ - protected abstract int find(Object o); + protected abstract int find(@Nullable Object o); /** Override to return "indexOf" or "lastIndexOf()" for use in failure messages. */ protected abstract String getMethodName(); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractListTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractListTester.java index 5275acb250e0..0f8900c066ef 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractListTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractListTester.java @@ -16,11 +16,14 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractCollectionTester; -import com.google.common.collect.testing.Helpers; import java.util.Collection; import java.util.List; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -29,8 +32,11 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class AbstractListTester extends AbstractCollectionTester { +@NullMarked +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +public class AbstractListTester extends AbstractCollectionTester { /* * Previously we had a field named list that was initialized to the value of * collection in setUp(), but that caused problems when a tester changed the @@ -49,7 +55,7 @@ protected final List getList() { */ @Override protected void expectContents(Collection expectedCollection) { - List expectedList = Helpers.copyToList(expectedCollection); + List expectedList = copyToList(expectedCollection); // Avoid expectEquals() here to delay reason manufacture until necessary. if (getList().size() != expectedList.size()) { fail("size mismatch: " + reportContext(expectedList)); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractQueueTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractQueueTester.java index 7476340ea0d4..639e683d2cc8 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractQueueTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractQueueTester.java @@ -27,7 +27,9 @@ * @author Jared Levy */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class AbstractQueueTester extends AbstractCollectionTester { protected final Queue getQueue() { return (Queue) collection; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractSetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractSetTester.java index b7409af8362e..32cb5f97775d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractSetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/AbstractSetTester.java @@ -21,9 +21,13 @@ import java.util.Set; import org.junit.Ignore; -/** @author George van den Driessche */ +/** + * @author George van den Driessche + */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class AbstractSetTester extends AbstractCollectionTester { /* * Previously we had a field named set that was initialized to the value of diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionAddAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionAddAllTester.java index 5c21c9a93137..d1d94fcb143e 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionAddAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionAddAllTester.java @@ -16,17 +16,19 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; import static com.google.common.collect.testing.features.CollectionFeature.RESTRICTS_ELEMENTS; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AbstractCollectionTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.MinimalCollection; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -34,6 +36,7 @@ import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -43,10 +46,12 @@ * @author Chris Povirk * @author Kevin Bourrillion */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class CollectionAddAllTester extends AbstractCollectionTester { +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +public class CollectionAddAllTester + extends AbstractCollectionTester { @CollectionFeature.Require(SUPPORTS_ADD) public void testAddAll_supportedNothing() { assertFalse("addAll(nothing) should return false", collection.addAll(emptyCollection())); @@ -72,11 +77,8 @@ public void testAddAll_supportedNonePresent() { @CollectionFeature.Require(absent = SUPPORTS_ADD) public void testAddAll_unsupportedNonePresent() { - try { - collection.addAll(createDisjointCollection()); - fail("addAll(nonePresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> collection.addAll(createDisjointCollection())); expectUnchanged(); expectMissing(e3(), e4()); } @@ -94,25 +96,22 @@ public void testAddAll_supportedSomePresent() { @CollectionFeature.Require(absent = SUPPORTS_ADD) @CollectionSize.Require(absent = ZERO) public void testAddAll_unsupportedSomePresent() { - try { - collection.addAll(MinimalCollection.of(e3(), e0())); - fail("addAll(somePresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, + () -> collection.addAll(MinimalCollection.of(e3(), e0()))); expectUnchanged(); } @CollectionFeature.Require({SUPPORTS_ADD, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) @CollectionSize.Require(absent = ZERO) public void testAddAllConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - assertTrue(collection.addAll(MinimalCollection.of(e3(), e0()))); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = collection.iterator(); + assertTrue(collection.addAll(MinimalCollection.of(e3(), e0()))); + iterator.next(); + }); } @CollectionFeature.Require(absent = SUPPORTS_ADD) @@ -143,11 +142,7 @@ public void testAddAll_nullSupported() { @CollectionFeature.Require(value = SUPPORTS_ADD, absent = ALLOWS_NULL_VALUES) public void testAddAll_nullUnsupported() { List containsNull = singletonList(null); - try { - collection.addAll(containsNull); - fail("addAll(containsNull) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> collection.addAll(containsNull)); expectUnchanged(); expectNullMissingWhenNullUnsupported( "Should not contain null after unsupported addAll(containsNull)"); @@ -155,42 +150,43 @@ public void testAddAll_nullUnsupported() { @CollectionFeature.Require(SUPPORTS_ADD) public void testAddAll_nullCollectionReference() { - try { - collection.addAll(null); - fail("addAll(null) should throw NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> collection.addAll(null)); } /** * Returns the {@link Method} instance for {@link #testAddAll_nullUnsupported()} so that tests can * suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 5045147 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddAllNullUnsupportedMethod() { - return Helpers.getMethod(CollectionAddAllTester.class, "testAddAll_nullUnsupported"); + return getMethod(CollectionAddAllTester.class, "testAddAll_nullUnsupported"); } /** * Returns the {@link Method} instance for {@link #testAddAll_unsupportedNonePresent()} so that * tests can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()} while we - * figure out what to do with {@code ConcurrentHashMap} support for - * {@code entrySet().add()}. + * figure out what to do with {@code + * ConcurrentHashMap} support for {@code entrySet().add()}. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddAllUnsupportedNonePresentMethod() { - return Helpers.getMethod(CollectionAddAllTester.class, "testAddAll_unsupportedNonePresent"); + return getMethod(CollectionAddAllTester.class, "testAddAll_unsupportedNonePresent"); } /** * Returns the {@link Method} instance for {@link #testAddAll_unsupportedSomePresent()} so that * tests can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()} while we - * figure out what to do with {@code ConcurrentHashMap} support for - * {@code entrySet().add()}. + * figure out what to do with {@code + * ConcurrentHashMap} support for {@code entrySet().add()}. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddAllUnsupportedSomePresentMethod() { - return Helpers.getMethod(CollectionAddAllTester.class, "testAddAll_unsupportedSomePresent"); + return getMethod(CollectionAddAllTester.class, "testAddAll_unsupportedSomePresent"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionAddTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionAddTester.java index bed257c97675..23ac132853ce 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionAddTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionAddTester.java @@ -16,16 +16,18 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; import static com.google.common.collect.testing.features.CollectionFeature.RESTRICTS_ELEMENTS; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AbstractCollectionTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; @@ -40,9 +42,10 @@ * @author Chris Povirk * @author Kevin Bourrillion */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionAddTester extends AbstractCollectionTester { @CollectionFeature.Require(SUPPORTS_ADD) public void testAdd_supportedNotPresent() { @@ -52,11 +55,7 @@ public void testAdd_supportedNotPresent() { @CollectionFeature.Require(absent = SUPPORTS_ADD) public void testAdd_unsupportedNotPresent() { - try { - collection.add(e3()); - fail("add(notPresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> collection.add(e3())); expectUnchanged(); expectMissing(e3()); } @@ -81,11 +80,7 @@ public void testAdd_nullSupported() { @CollectionFeature.Require(value = SUPPORTS_ADD, absent = ALLOWS_NULL_VALUES) public void testAdd_nullUnsupported() { - try { - collection.add(null); - fail("add(null) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> collection.add(null)); expectUnchanged(); expectNullMissingWhenNullUnsupported("Should not contain null after unsupported add(null)"); } @@ -93,49 +88,52 @@ public void testAdd_nullUnsupported() { @CollectionFeature.Require({SUPPORTS_ADD, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) @CollectionSize.Require(absent = ZERO) public void testAddConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - assertTrue(collection.add(e3())); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = collection.iterator(); + assertTrue(collection.add(e3())); + iterator.next(); + }); } /** * Returns the {@link Method} instance for {@link #testAdd_nullSupported()} so that tests of * {@link java.util.Collections#checkedCollection(java.util.Collection, Class)} can suppress it * with {@code FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 6409434 is fixed. - * It's unclear whether nulls were to be permitted or forbidden, but presumably the eventual fix - * will be to permit them, as it seems more likely that code would depend on that behavior than on - * the other. Thus, we say the bug is in add(), which fails to support null. + * href="https://bugs.openjdk.org/browse/JDK-6409434">JDK-6409434 is fixed. It's unclear + * whether nulls were to be permitted or forbidden, but presumably the eventual fix will be to + * permit them, as it seems more likely that code would depend on that behavior than on the other. + * Thus, we say the bug is in add(), which fails to support null. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddNullSupportedMethod() { - return Helpers.getMethod(CollectionAddTester.class, "testAdd_nullSupported"); + return getMethod(CollectionAddTester.class, "testAdd_nullSupported"); } /** * Returns the {@link Method} instance for {@link #testAdd_nullSupported()} so that tests of * {@link java.util.TreeSet} can suppress it with {@code * FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 5045147 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddNullUnsupportedMethod() { - return Helpers.getMethod(CollectionAddTester.class, "testAdd_nullUnsupported"); + return getMethod(CollectionAddTester.class, "testAdd_nullUnsupported"); } /** * Returns the {@link Method} instance for {@link #testAdd_unsupportedNotPresent()} so that tests * can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()} while we figure out - * what to do with {@code ConcurrentHashMap} support for {@code - * entrySet().add()}. + * what to do with {@code + * ConcurrentHashMap} support for {@code entrySet().add()}. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddUnsupportedNotPresentMethod() { - return Helpers.getMethod(CollectionAddTester.class, "testAdd_unsupportedNotPresent"); + return getMethod(CollectionAddTester.class, "testAdd_unsupportedNotPresent"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionClearTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionClearTester.java index 9b97fff73ee4..1dae1b6de689 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionClearTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionClearTester.java @@ -20,6 +20,7 @@ import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractCollectionTester; @@ -36,7 +37,9 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionClearTester extends AbstractCollectionTester { @CollectionFeature.Require(SUPPORTS_REMOVE) public void testClear() { @@ -49,13 +52,7 @@ public void testClear() { @CollectionFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testClear_unsupported() { - try { - collection.clear(); - fail( - "clear() should throw UnsupportedOperation if a collection does " - + "not support it and is not empty."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> collection.clear()); expectUnchanged(); } @@ -72,17 +69,12 @@ public void testClear_unsupportedByEmptyCollection() { @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) @CollectionSize.Require(SEVERAL) public void testClearConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - collection.clear(); - iterator.next(); - /* - * We prefer for iterators to fail immediately on hasNext, but ArrayList - * and LinkedList will notably return true on hasNext here! - */ - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = collection.iterator(); + collection.clear(); + iterator.next(); + }); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionContainsAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionContainsAllTester.java index 8d03271cb7f1..53792ed4ddb2 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionContainsAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionContainsAllTester.java @@ -37,9 +37,10 @@ * @author Kevin Bourrillion * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionContainsAllTester extends AbstractCollectionTester { public void testContainsAll_empty() { assertTrue( diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionContainsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionContainsTester.java index 47e0dd6cfffa..58c9f470cf15 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionContainsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionContainsTester.java @@ -35,7 +35,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionContainsTester extends AbstractCollectionTester { @CollectionSize.Require(absent = ZERO) public void testContains_yes() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionCreationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionCreationTester.java index 848fdd663263..07ddbe88704c 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionCreationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionCreationTester.java @@ -16,13 +16,15 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AbstractCollectionTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; @@ -35,8 +37,10 @@ * * @author Chris Povirk */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionCreationTester extends AbstractCollectionTester { @CollectionFeature.Require(ALLOWS_NULL_VALUES) @CollectionSize.Require(absent = ZERO) @@ -51,20 +55,21 @@ public void testCreateWithNull_supported() { public void testCreateWithNull_unsupported() { E[] array = createArrayWithNullElement(); - try { - getSubjectGenerator().create(array); - fail("Creating a collection containing null should fail"); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> { + Object unused = getSubjectGenerator().create(array); + }); } /** * Returns the {@link Method} instance for {@link #testCreateWithNull_unsupported()} so that tests * can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 5045147 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getCreateWithNullUnsupportedMethod() { - return Helpers.getMethod(CollectionCreationTester.class, "testCreateWithNull_unsupported"); + return getMethod(CollectionCreationTester.class, "testCreateWithNull_unsupported"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionEqualsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionEqualsTester.java index e8276cb4372a..d303ff5d6366 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionEqualsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionEqualsTester.java @@ -26,20 +26,28 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionEqualsTester extends AbstractCollectionTester { - // TODO(cpovirk): Consider using EqualsTester from Guava. - @SuppressWarnings("SelfEquals") + @SuppressWarnings({ + "SelfEquals", // TODO(cpovirk): Consider using EqualsTester from Guava. + "UndefinedEquals", // Comparisons of an object to itself *are* defined. + }) public void testEquals_self() { assertTrue("An Object should be equal to itself.", collection.equals(collection)); } + // Comparisons to null *are* defined. + @SuppressWarnings("UndefinedEquals") public void testEquals_null() { // noinspection ObjectEqualsNull assertFalse("An object should not be equal to null.", collection.equals(null)); } + // A collection should essentially never be equal to a non-collection. + @SuppressWarnings("UndefinedEquals") public void testEquals_notACollection() { // noinspection EqualsBetweenInconvertibleTypes assertFalse( diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionForEachTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionForEachTester.java new file mode 100644 index 000000000000..b22fef9a4b84 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionForEachTester.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractCollectionTester; +import com.google.common.collect.testing.Helpers; +import com.google.common.collect.testing.features.CollectionFeature; +import java.util.ArrayList; +import java.util.List; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@code forEach} operations on a collection. Can't be invoked + * directly; please see {@link com.google.common.collect.testing.CollectionTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class CollectionForEachTester extends AbstractCollectionTester { + @CollectionFeature.Require(absent = KNOWN_ORDER) + public void testForEachUnknownOrder() { + List elements = new ArrayList<>(); + collection.forEach(elements::add); + Helpers.assertEqualIgnoringOrder(asList(createSamplesArray()), elements); + } + + @CollectionFeature.Require(KNOWN_ORDER) + public void testForEachKnownOrder() { + List elements = new ArrayList<>(); + collection.forEach(elements::add); + List expected = Helpers.copyToList(getOrderedElements()); + assertEquals("Different ordered iteration", expected, elements); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionIsEmptyTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionIsEmptyTester.java index 419e049d8ef9..d543645cad44 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionIsEmptyTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionIsEmptyTester.java @@ -30,7 +30,9 @@ * @author Kevin Bourrillion */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionIsEmptyTester extends AbstractCollectionTester { @CollectionSize.Require(ZERO) public void testIsEmpty_yes() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionIteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionIteratorTester.java index b17049709e81..cfb1c024a90c 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionIteratorTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionIteratorTester.java @@ -16,6 +16,8 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; @@ -23,22 +25,23 @@ import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ITERATOR_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractCollectionTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.IteratorFeature; import com.google.common.collect.testing.IteratorTester; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.util.ArrayList; -import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -47,24 +50,28 @@ * * @author Chris Povirk */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class CollectionIteratorTester extends AbstractCollectionTester { +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class CollectionIteratorTester + extends AbstractCollectionTester { public void testIterator() { - List iteratorElements = new ArrayList(); + List iteratorElements = new ArrayList<>(); for (E element : collection) { // uses iterator() iteratorElements.add(element); } - Helpers.assertEqualIgnoringOrder(Arrays.asList(createSamplesArray()), iteratorElements); + assertEqualIgnoringOrder(asList(createSamplesArray()), iteratorElements); } @CollectionFeature.Require(KNOWN_ORDER) public void testIterationOrdering() { - List iteratorElements = new ArrayList(); + List iteratorElements = new ArrayList<>(); for (E element : collection) { // uses iterator() iteratorElements.add(element); } - List expected = Helpers.copyToList(getOrderedElements()); + List expected = copyToList(getOrderedElements()); assertEquals("Different ordered iteration", expected, iteratorElements); } @@ -72,11 +79,11 @@ public void testIterationOrdering() { @CollectionSize.Require(absent = ZERO) public void testIterator_nullElement() { initCollectionWithNullElement(); - List iteratorElements = new ArrayList(); + List iteratorElements = new ArrayList<>(); for (E element : collection) { // uses iterator() iteratorElements.add(element); } - Helpers.assertEqualIgnoringOrder(asList(createArrayWithNullElement()), iteratorElements); + assertEqualIgnoringOrder(asList(createArrayWithNullElement()), iteratorElements); } @CollectionFeature.Require(SUPPORTS_ITERATOR_REMOVE) @@ -139,10 +146,6 @@ public void testIteratorNoSuchElementException() { iterator.next(); } - try { - iterator.next(); - fail("iterator.next() should throw NoSuchElementException"); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, iterator::next); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveAllTester.java index 0d59097377be..581b718235b8 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveAllTester.java @@ -22,6 +22,8 @@ import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractCollectionTester; @@ -29,7 +31,7 @@ import com.google.common.collect.testing.WrongType; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Collections; +import java.util.AbstractSet; import java.util.ConcurrentModificationException; import java.util.Iterator; import org.junit.Ignore; @@ -41,9 +43,10 @@ * @author George van den Driessche * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionRemoveAllTester extends AbstractCollectionTester { @CollectionFeature.Require(SUPPORTS_REMOVE) public void testRemoveAll_emptyCollection() { @@ -82,17 +85,16 @@ public void testRemoveAll_somePresent() { @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) @CollectionSize.Require(SEVERAL) public void testRemoveAllSomePresentConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - assertTrue(collection.removeAll(MinimalCollection.of(e0(), e3()))); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = collection.iterator(); + assertTrue(collection.removeAll(MinimalCollection.of(e0(), e3()))); + iterator.next(); + }); } - /** Trigger the {@code other.size() >= this.size()} case in {@link AbstractSet#removeAll()}. */ + /** Trigger the {@code other.size() >= this.size()} case in {@link AbstractSet#removeAll}. */ @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveAll_somePresentLargeCollectionToRemove() { @@ -129,11 +131,9 @@ public void testRemoveAll_unsupportedNonePresent() { @CollectionFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveAll_unsupportedPresent() { - try { - collection.removeAll(MinimalCollection.of(e0())); - fail("removeAll(intersectingCollection) should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, + () -> collection.removeAll(MinimalCollection.of(e0()))); expectUnchanged(); assertTrue(collection.contains(e0())); } @@ -158,11 +158,7 @@ public void testRemoveAll_nullCollectionReferenceEmptySubject() { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemoveAll_nullCollectionReferenceNonEmptySubject() { - try { - collection.removeAll(null); - fail("removeAll(null) should throw NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> collection.removeAll(null)); } @CollectionFeature.Require(value = SUPPORTS_REMOVE, absent = ALLOWS_NULL_QUERIES) @@ -188,9 +184,7 @@ public void testRemoveAll_containsNullNoButAllowed() { @CollectionSize.Require(absent = ZERO) public void testRemoveAll_containsNullYes() { initCollectionWithNullElement(); - assertTrue( - "removeAll(containsNull) should return true", - collection.removeAll(Collections.singleton(null))); + assertTrue("removeAll(containsNull) should return true", collection.removeAll(singleton(null))); // TODO: make this work with MinimalCollection } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveIfTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveIfTester.java new file mode 100644 index 000000000000..dbdbed853922 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveIfTester.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; +import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ITERATOR_REMOVE; +import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractCollectionTester; +import com.google.common.collect.testing.features.CollectionFeature; +import com.google.common.collect.testing.features.CollectionSize; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.function.Predicate; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Collection#removeIf}. Can't be invoked directly; please + * see {@link com.google.common.collect.testing.CollectionTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class CollectionRemoveIfTester extends AbstractCollectionTester { + @CollectionFeature.Require(SUPPORTS_ITERATOR_REMOVE) + public void testRemoveIf_alwaysFalse() { + assertFalse("removeIf(x -> false) should return false", collection.removeIf(x -> false)); + expectUnchanged(); + } + + @CollectionFeature.Require(SUPPORTS_ITERATOR_REMOVE) + @CollectionSize.Require(absent = ZERO) + public void testRemoveIf_sometimesTrue() { + assertTrue( + "removeIf(isEqual(present)) should return true", + collection.removeIf(Predicate.isEqual(samples.e0()))); + expectMissing(samples.e0()); + } + + @CollectionFeature.Require(SUPPORTS_ITERATOR_REMOVE) + @CollectionSize.Require(absent = ZERO) + public void testRemoveIf_allPresent() { + assertTrue("removeIf(x -> true) should return true", collection.removeIf(x -> true)); + expectContents(); + } + + @CollectionFeature.Require({SUPPORTS_ITERATOR_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) + @CollectionSize.Require(SEVERAL) + public void testRemoveIfSomeMatchesConcurrentWithIteration() { + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = collection.iterator(); + assertTrue(collection.removeIf(Predicate.isEqual(samples.e0()))); + iterator.next(); + }); + } + + @CollectionFeature.Require(absent = SUPPORTS_REMOVE) + @CollectionSize.Require(ZERO) + public void testRemoveIf_unsupportedEmptyCollection() { + try { + assertFalse( + "removeIf(Predicate) should return false or throw UnsupportedOperationException", + collection.removeIf( + x -> { + throw new AssertionError("predicate should never be called"); + })); + } catch (UnsupportedOperationException tolerated) { + } + expectUnchanged(); + } + + @CollectionFeature.Require(absent = SUPPORTS_REMOVE) + @CollectionSize.Require(absent = ZERO) + public void testRemoveIf_alwaysTrueUnsupported() { + assertThrows(UnsupportedOperationException.class, () -> collection.removeIf(x -> true)); + expectUnchanged(); + assertTrue(collection.contains(samples.e0())); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveTester.java index 49568fc28044..74da5dd2f959 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRemoveTester.java @@ -22,6 +22,7 @@ import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractCollectionTester; @@ -38,9 +39,10 @@ * * @author George van den Driessche */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionRemoveTester extends AbstractCollectionTester { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) @@ -57,14 +59,13 @@ public void testRemove_present() { @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION}) @CollectionSize.Require(SEVERAL) public void testRemovePresentConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - assertTrue(collection.remove(e0())); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = collection.iterator(); + assertTrue(collection.remove(e0())); + iterator.next(); + }); } @CollectionFeature.Require(SUPPORTS_REMOVE) @@ -90,11 +91,7 @@ public void testRemove_nullPresent() { @CollectionFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemove_unsupported() { - try { - collection.remove(e0()); - fail("remove(present) should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> collection.remove(e0())); expectUnchanged(); assertTrue("remove(present) should not remove the element", collection.contains(e0())); } @@ -133,11 +130,7 @@ public void testRemove_nullAllowed() { public void testIteratorRemove_unsupported() { Iterator iterator = collection.iterator(); iterator.next(); - try { - iterator.remove(); - fail("iterator.remove() should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, iterator::remove); expectUnchanged(); assertTrue(collection.contains(e0())); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRetainAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRetainAllTester.java index db7aef13929d..dca1c75129b9 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRetainAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionRetainAllTester.java @@ -20,13 +20,14 @@ import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractCollectionTester; import com.google.common.collect.testing.MinimalCollection; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -38,13 +39,14 @@ * * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionRetainAllTester extends AbstractCollectionTester { /** A collection of elements to retain, along with a description for use in failure messages. */ - private class Target { + private final class Target { private final Collection toRetain; private final String description; @@ -79,15 +81,15 @@ public void setUp() throws Exception { * MinimalCollection, which throws NullPointerException on calls to * contains(null). */ - List disjointList = Arrays.asList(e3(), e4()); + List disjointList = asList(e3(), e4()); disjoint = new Target(disjointList, "disjoint"); superset = new Target(MinimalCollection.of(e0(), e1(), e2(), e3(), e4()), "superset"); nonEmptyProperSubset = new Target(MinimalCollection.of(e1()), "subset"); - sameElements = new Target(Arrays.asList(createSamplesArray()), "sameElements"); + sameElements = new Target(asList(createSamplesArray()), "sameElements"); containsDuplicates = new Target(MinimalCollection.of(e0(), e0(), e3(), e3()), "containsDuplicates"); partialOverlap = new Target(MinimalCollection.of(e2(), e3()), "partialOverlap"); - nullSingleton = new Target(Collections.singleton(null), "nullSingleton"); + nullSingleton = new Target(Collections.singleton(null), "nullSingleton"); } // retainAll(empty) @@ -292,11 +294,7 @@ public void testRetainAll_nullCollectionReferenceEmptySubject() { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRetainAll_nullCollectionReferenceNonEmptySubject() { - try { - collection.retainAll(null); - fail("retainAll(null) should throw NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> collection.retainAll(null)); } private void expectReturnsTrue(Target target) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSerializationEqualTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSerializationEqualTester.java index 38c770d2d00f..2220ab779e8b 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSerializationEqualTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSerializationEqualTester.java @@ -31,9 +31,17 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionSerializationEqualTester extends AbstractCollectionTester { @CollectionFeature.Require(SERIALIZABLE) + /* + * As the class docs say, this test only makes sense for collections that define equals(). + * Accordingly, our testing framework adds it only when testing an implementation of List, Set, or + * Multiset. + */ + @SuppressWarnings("UndefinedEquals") public void testReserialize() { assertEquals(SerializableTester.reserialize(actualContents()), actualContents()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSerializationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSerializationTester.java index 18f8c6660f88..e177c6bfb020 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSerializationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSerializationTester.java @@ -16,11 +16,11 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractCollectionTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.testing.SerializableTester; import org.junit.Ignore; @@ -31,12 +31,13 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionSerializationTester extends AbstractCollectionTester { @CollectionFeature.Require(SERIALIZABLE) public void testReserialize() { // For a bare Collection, the most we can guarantee is that the elements are preserved. - Helpers.assertEqualIgnoringOrder( - actualContents(), SerializableTester.reserialize(actualContents())); + assertEqualIgnoringOrder(actualContents(), SerializableTester.reserialize(actualContents())); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSizeTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSizeTester.java index 64d07a0cde8f..53bf99c0b9c9 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSizeTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSizeTester.java @@ -27,7 +27,9 @@ * @author Kevin Bourrillion */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionSizeTester extends AbstractCollectionTester { public void testSize() { assertEquals("size():", getNumElements(), collection.size()); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSpliteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSpliteratorTester.java new file mode 100644 index 000000000000..4ad3d67a4035 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionSpliteratorTester.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; +import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; +import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; +import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.features.CollectionSize.ZERO; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.collect.testing.AbstractCollectionTester; +import com.google.common.collect.testing.Helpers; +import com.google.common.collect.testing.SpliteratorTester; +import com.google.common.collect.testing.features.CollectionFeature; +import com.google.common.collect.testing.features.CollectionSize; +import java.lang.reflect.Method; +import java.util.Spliterator; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@code spliterator} operations on a collection. Can't be invoked + * directly; please see {@link com.google.common.collect.testing.CollectionTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class CollectionSpliteratorTester extends AbstractCollectionTester { + + @CollectionFeature.Require(absent = KNOWN_ORDER) + public void testSpliteratorUnknownOrder() { + SpliteratorTester.of(collection::spliterator).expect(getSampleElements()); + } + + @CollectionFeature.Require(KNOWN_ORDER) + public void testSpliteratorKnownOrder() { + SpliteratorTester.of(collection::spliterator).expect(getOrderedElements()).inOrder(); + } + + @CollectionFeature.Require(ALLOWS_NULL_VALUES) + @CollectionSize.Require(absent = ZERO) + public void testSpliteratorNullable() { + initCollectionWithNullElement(); + assertFalse(collection.spliterator().hasCharacteristics(Spliterator.NONNULL)); + } + + @CollectionFeature.Require(SUPPORTS_ADD) + public void testSpliteratorNotImmutable_collectionAllowsAdd() { + // If add is supported, verify that IMMUTABLE is not reported. + assertFalse(collection.spliterator().hasCharacteristics(Spliterator.IMMUTABLE)); + } + + @CollectionFeature.Require(SUPPORTS_REMOVE) + public void testSpliteratorNotImmutable_collectionAllowsRemove() { + // If remove is supported, verify that IMMUTABLE is not reported. + assertFalse(collection.spliterator().hasCharacteristics(Spliterator.IMMUTABLE)); + } + + @J2ktIncompatible + @GwtIncompatible // reflection + public static Method getSpliteratorNotImmutableCollectionAllowsAddMethod() { + return Helpers.getMethod( + CollectionSpliteratorTester.class, "testSpliteratorNotImmutable_collectionAllowsAdd"); + } + + @J2ktIncompatible + @GwtIncompatible // reflection + public static Method getSpliteratorNotImmutableCollectionAllowsRemoveMethod() { + return Helpers.getMethod( + CollectionSpliteratorTester.class, "testSpliteratorNotImmutable_collectionAllowsRemove"); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionStreamTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionStreamTester.java new file mode 100644 index 000000000000..66b8f4e0a6f5 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionStreamTester.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractCollectionTester; +import com.google.common.collect.testing.Helpers; +import com.google.common.collect.testing.features.CollectionFeature; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@code stream} operations on a collection. Can't be invoked + * directly; please see {@link com.google.common.collect.testing.CollectionTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class CollectionStreamTester extends AbstractCollectionTester { + /* + * We're not really testing the implementation of Stream, only that we're getting a Stream + * that corresponds to the expected elements. + */ + + @CollectionFeature.Require(absent = KNOWN_ORDER) + public void testStreamToArrayUnknownOrder() { + Helpers.assertEqualIgnoringOrder(getSampleElements(), asList(collection.stream().toArray())); + } + + @CollectionFeature.Require(KNOWN_ORDER) + public void testStreamToArrayKnownOrder() { + assertEquals(getOrderedElements(), asList(collection.stream().toArray())); + } + + public void testStreamCount() { + assertEquals(getNumElements(), collection.stream().count()); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionToArrayTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionToArrayTester.java index 83a4ceac869d..8d6c3472142d 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionToArrayTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionToArrayTester.java @@ -16,13 +16,17 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AbstractCollectionTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.WrongType; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -39,8 +43,10 @@ * @author Kevin Bourrillion * @author Chris Povirk */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionToArrayTester extends AbstractCollectionTester { public void testToArray_noArgs() { Object[] array = collection.toArray(); @@ -48,8 +54,8 @@ public void testToArray_noArgs() { } /** - * {@link Collection#toArray(Object[])} says: "Note that toArray(new Object[0]) is - * identical in function to toArray()." + * {@link Collection#toArray(Object[])} says: "Note that {@code toArray(new Object[0])} is + * identical in function to {@code toArray()}." * *

For maximum effect, the collection under test should be created from an element array of a * type other than {@code Object[]}. @@ -130,7 +136,7 @@ public void testToArray_oversizedArray() { assertSame( "toArray(overSizedE[]) should return the given array", array, collection.toArray(array)); - List subArray = Arrays.asList(array).subList(0, getNumElements()); + List subArray = asList(array).subList(0, getNumElements()); E[] expectedSubArray = createSamplesArray(); for (int i = 0; i < getNumElements(); i++) { assertTrue( @@ -163,12 +169,12 @@ public void testToArray_oversizedArray_ordered() { @CollectionSize.Require(absent = ZERO) public void testToArray_emptyArrayOfWrongTypeForNonEmptyCollection() { - try { - WrongType[] array = new WrongType[0]; - collection.toArray(array); - fail("toArray(notAssignableTo[]) should throw"); - } catch (ArrayStoreException expected) { - } + assertThrows( + ArrayStoreException.class, + () -> { + WrongType[] array = new WrongType[0]; + collection.toArray(array); + }); } @CollectionSize.Require(ZERO) @@ -180,22 +186,23 @@ public void testToArray_emptyArrayOfWrongTypeForEmptyCollection() { collection.toArray(array)); } - private void expectArrayContentsAnyOrder(Object[] expected, Object[] actual) { - Helpers.assertEqualIgnoringOrder(Arrays.asList(expected), Arrays.asList(actual)); + private static void expectArrayContentsAnyOrder(Object[] expected, Object[] actual) { + assertEqualIgnoringOrder(asList(expected), asList(actual)); } private void expectArrayContentsInOrder(List expected, Object[] actual) { - assertEquals("toArray() ordered contents: ", expected, Arrays.asList(actual)); + assertEquals("toArray() ordered contents: ", expected, asList(actual)); } /** * Returns the {@link Method} instance for {@link #testToArray_isPlainObjectArray()} so that tests * of {@link Arrays#asList(Object[])} can suppress it with {@code * FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 6260652 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-6260652">JDK-6260652 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getToArrayIsPlainObjectArrayMethod() { - return Helpers.getMethod(CollectionToArrayTester.class, "testToArray_isPlainObjectArray"); + return getMethod(CollectionToArrayTester.class, "testToArray_isPlainObjectArray"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionToStringTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionToStringTester.java index f138ccfa9192..a5c8f463e7bb 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionToStringTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/CollectionToStringTester.java @@ -16,6 +16,7 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionFeature.NON_STANDARD_TOSTRING; @@ -25,7 +26,6 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractCollectionTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import org.junit.Ignore; @@ -37,7 +37,9 @@ * @author Kevin Bourrillion */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class CollectionToStringTester extends AbstractCollectionTester { public void testToString_minimal() { assertNotNull("toString() should not return null", collection.toString()); @@ -61,7 +63,7 @@ public void testToString_size1() { @CollectionSize.Require(SEVERAL) @CollectionFeature.Require(value = KNOWN_ORDER, absent = NON_STANDARD_TOSTRING) public void testToString_sizeSeveral() { - String expected = Helpers.copyToList(getOrderedElements()).toString(); + String expected = copyToList(getOrderedElements()).toString(); assertEquals("collection.toString() incorrect", expected, collection.toString()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapPutIfAbsentTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapPutIfAbsentTester.java index 6cf5be2327ea..78395adbc801 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapPutIfAbsentTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapPutIfAbsentTester.java @@ -20,13 +20,16 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; +import org.jspecify.annotations.NullMarked; import org.junit.Ignore; /** @@ -37,7 +40,10 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked public class ConcurrentMapPutIfAbsentTester extends AbstractMapTester { @Override protected ConcurrentMap getMap() { @@ -62,11 +68,7 @@ public void testPutIfAbsent_supportedPresent() { @MapFeature.Require(absent = SUPPORTS_PUT) public void testPutIfAbsent_unsupportedAbsent() { - try { - putIfAbsent(e3()); - fail("putIfAbsent(notPresent, value) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> putIfAbsent(e3())); expectUnchanged(); expectMissing(e3()); } @@ -96,11 +98,7 @@ public void testPutIfAbsent_unsupportedPresentDifferentValue() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS) public void testPutIfAbsent_nullKeyUnsupported() { - try { - getMap().putIfAbsent(null, v3()); - fail("putIfAbsent(null, value) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getMap().putIfAbsent(null, v3())); expectUnchanged(); expectNullKeyMissingWhenNullKeysUnsupported( "Should not contain null key after unsupported putIfAbsent(null, value)"); @@ -108,11 +106,7 @@ public void testPutIfAbsent_nullKeyUnsupported() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) public void testPutIfAbsent_nullValueUnsupported() { - try { - getMap().putIfAbsent(k3(), null); - fail("putIfAbsent(key, null) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getMap().putIfAbsent(k3(), null)); expectUnchanged(); expectNullValueMissingWhenNullValuesUnsupported( "Should not contain null value after unsupported put(key, null)"); @@ -130,6 +124,7 @@ public void testPutIfAbsent_putWithNullValueUnsupported() { "Should not contain null after unsupported putIfAbsent(present, null)"); } + @CanIgnoreReturnValue private V putIfAbsent(Entry entry) { return getMap().putIfAbsent(entry.getKey(), entry.getValue()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapRemoveTester.java index 87cc319b3b18..2d5b1014e0c8 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapRemoveTester.java @@ -20,12 +20,14 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.concurrent.ConcurrentMap; +import org.jspecify.annotations.NullMarked; import org.junit.Ignore; /** @@ -35,7 +37,10 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked public class ConcurrentMapRemoveTester extends AbstractMapTester { @Override protected ConcurrentMap getMap() { @@ -90,11 +95,7 @@ public void testRemove_nullValueQueriesUnsupported() { @MapFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemove_unsupportedPresent() { - try { - getMap().remove(k0(), v0()); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getMap().remove(k0(), v0())); expectUnchanged(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapReplaceEntryTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapReplaceEntryTester.java index 57f631cd8b24..7cd1ba6693bb 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapReplaceEntryTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapReplaceEntryTester.java @@ -20,12 +20,14 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.concurrent.ConcurrentMap; +import org.jspecify.annotations.NullMarked; import org.junit.Ignore; /** @@ -36,7 +38,10 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked public class ConcurrentMapReplaceEntryTester extends AbstractMapTester { @Override protected ConcurrentMap getMap() { @@ -73,11 +78,7 @@ public void testReplaceEntry_supportedAbsentKey() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) @CollectionSize.Require(absent = ZERO) public void testReplaceEntry_presentNullValueUnsupported() { - try { - getMap().replace(k0(), v0(), null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getMap().replace(k0(), v0(), null)); expectUnchanged(); } @@ -121,11 +122,7 @@ public void testReplaceEntry_expectNullUnsupported() { @MapFeature.Require(absent = SUPPORTS_PUT) @CollectionSize.Require(absent = ZERO) public void testReplaceEntry_unsupportedPresent() { - try { - getMap().replace(k0(), v0(), v3()); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getMap().replace(k0(), v0(), v3())); expectUnchanged(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapReplaceTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapReplaceTester.java index f0bc16447239..f65d5fb326d0 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapReplaceTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ConcurrentMapReplaceTester.java @@ -21,12 +21,14 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.concurrent.ConcurrentMap; +import org.jspecify.annotations.NullMarked; import org.junit.Ignore; /** @@ -37,7 +39,10 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked public class ConcurrentMapReplaceTester extends AbstractMapTester { @Override protected ConcurrentMap getMap() { @@ -67,11 +72,7 @@ public void testReplace_supportedAbsent() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) @CollectionSize.Require(absent = ZERO) public void testReplace_presentNullValueUnsupported() { - try { - getMap().replace(k0(), null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getMap().replace(k0(), null)); expectUnchanged(); } @@ -98,11 +99,7 @@ public void testReplace_absentNullKeyUnsupported() { @MapFeature.Require(absent = SUPPORTS_PUT) @CollectionSize.Require(absent = ZERO) public void testReplace_unsupportedPresent() { - try { - getMap().replace(k0(), v3()); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getMap().replace(k0(), v3())); expectUnchanged(); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/IgnoreJRERequirement.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/IgnoreJRERequirement.java new file mode 100644 index 000000000000..a048c4bb0912 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/IgnoreJRERequirement.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect.testing.testers; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; +import org.jspecify.annotations.NullMarked; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@NullMarked +@interface IgnoreJRERequirement {} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAllAtIndexTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAllAtIndexTester.java index c3e338f1a450..eb2077dbcd54 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAllAtIndexTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAllAtIndexTester.java @@ -20,6 +20,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_ADD_WITH_INDEX; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; @@ -36,9 +37,10 @@ * * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListAddAllAtIndexTester extends AbstractListTester { @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) @CollectionSize.Require(absent = ZERO) @@ -52,11 +54,8 @@ public void testAddAllAtIndex_supportedAllPresent() { @ListFeature.Require(absent = SUPPORTS_ADD_WITH_INDEX) @CollectionSize.Require(absent = ZERO) public void testAddAllAtIndex_unsupportedAllPresent() { - try { - getList().addAll(0, MinimalCollection.of(e0())); - fail("addAll(n, allPresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> getList().addAll(0, MinimalCollection.of(e0()))); expectUnchanged(); } @@ -72,11 +71,9 @@ public void testAddAllAtIndex_supportedSomePresent() { @ListFeature.Require(absent = SUPPORTS_ADD_WITH_INDEX) @CollectionSize.Require(absent = ZERO) public void testAddAllAtIndex_unsupportedSomePresent() { - try { - getList().addAll(0, MinimalCollection.of(e0(), e3())); - fail("addAll(n, allPresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, + () -> getList().addAll(0, MinimalCollection.of(e0(), e3()))); expectUnchanged(); expectMissing(e3()); } @@ -121,11 +118,7 @@ public void testAddAllAtIndex_nullSupported() { @CollectionFeature.Require(absent = ALLOWS_NULL_VALUES) public void testAddAllAtIndex_nullUnsupported() { List containsNull = singletonList(null); - try { - getList().addAll(0, containsNull); - fail("addAll(n, containsNull) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getList().addAll(0, containsNull)); expectUnchanged(); expectNullMissingWhenNullUnsupported( "Should not contain null after unsupported addAll(n, containsNull)"); @@ -151,32 +144,23 @@ public void testAddAllAtIndex_end() { @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) public void testAddAllAtIndex_nullCollectionReference() { - try { - getList().addAll(0, null); - fail("addAll(n, null) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getList().addAll(0, null)); expectUnchanged(); } @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) public void testAddAllAtIndex_negative() { - try { - getList().addAll(-1, MinimalCollection.of(e3())); - fail("addAll(-1, e) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, () -> getList().addAll(-1, MinimalCollection.of(e3()))); expectUnchanged(); expectMissing(e3()); } @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) public void testAddAllAtIndex_tooLarge() { - try { - getList().addAll(getNumElements() + 1, MinimalCollection.of(e3())); - fail("addAll(size + 1, e) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, + () -> getList().addAll(getNumElements() + 1, MinimalCollection.of(e3()))); expectUnchanged(); expectMissing(e3()); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAllTester.java index 1504f1a4b195..9d2cf9fd7b6a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAllTester.java @@ -18,6 +18,7 @@ import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.MinimalCollection; @@ -31,9 +32,10 @@ * * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListAddAllTester extends AbstractListTester { @CollectionFeature.Require(SUPPORTS_ADD) @CollectionSize.Require(absent = ZERO) @@ -46,11 +48,8 @@ public void testAddAll_supportedAllPresent() { @CollectionFeature.Require(absent = SUPPORTS_ADD) @CollectionSize.Require(absent = ZERO) public void testAddAll_unsupportedAllPresent() { - try { - getList().addAll(MinimalCollection.of(e0())); - fail("addAll(allPresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> getList().addAll(MinimalCollection.of(e0()))); expectUnchanged(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAtIndexTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAtIndexTester.java index 32310b8d3815..57b63463511a 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAtIndexTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddAtIndexTester.java @@ -16,15 +16,17 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_ADD_WITH_INDEX; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.ListFeature; @@ -39,9 +41,10 @@ * * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListAddAtIndexTester extends AbstractListTester { @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) @CollectionSize.Require(absent = ZERO) @@ -57,11 +60,7 @@ public void testAddAtIndex_supportedPresent() { * throw regardless, but it keeps the method name accurate. */ public void testAddAtIndex_unsupportedPresent() { - try { - getList().add(0, e0()); - fail("add(n, present) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getList().add(0, e0())); expectUnchanged(); } @@ -74,23 +73,18 @@ public void testAddAtIndex_supportedNotPresent() { @CollectionFeature.Require(FAILS_FAST_ON_CONCURRENT_MODIFICATION) @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) public void testAddAtIndexConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - getList().add(0, e3()); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = collection.iterator(); + getList().add(0, e3()); + iterator.next(); + }); } @ListFeature.Require(absent = SUPPORTS_ADD_WITH_INDEX) public void testAddAtIndex_unsupportedNotPresent() { - try { - getList().add(0, e3()); - fail("add(n, notPresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getList().add(0, e3())); expectUnchanged(); expectMissing(e3()); } @@ -119,33 +113,21 @@ public void testAddAtIndex_nullSupported() { @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) @CollectionFeature.Require(absent = ALLOWS_NULL_VALUES) public void testAddAtIndex_nullUnsupported() { - try { - getList().add(0, null); - fail("add(n, null) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getList().add(0, null)); expectUnchanged(); expectNullMissingWhenNullUnsupported("Should not contain null after unsupported add(n, null)"); } @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) public void testAddAtIndex_negative() { - try { - getList().add(-1, e3()); - fail("add(-1, e) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().add(-1, e3())); expectUnchanged(); expectMissing(e3()); } @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX) public void testAddAtIndex_tooLarge() { - try { - getList().add(getNumElements() + 1, e3()); - fail("add(size + 1, e) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().add(getNumElements() + 1, e3())); expectUnchanged(); expectMissing(e3()); } @@ -154,8 +136,9 @@ public void testAddAtIndex_tooLarge() { * Returns the {@link Method} instance for {@link #testAddAtIndex_nullSupported()} so that tests * can suppress it. See {@link CollectionAddTester#getAddNullSupportedMethod()} for details. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddNullSupportedMethod() { - return Helpers.getMethod(ListAddAtIndexTester.class, "testAddAtIndex_nullSupported"); + return getMethod(ListAddAtIndexTester.class, "testAddAtIndex_nullSupported"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddTester.java index 8559d3464d91..231ea3499427 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListAddTester.java @@ -16,13 +16,16 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; @@ -35,9 +38,10 @@ * * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListAddTester extends AbstractListTester { @CollectionFeature.Require(SUPPORTS_ADD) @CollectionSize.Require(absent = ZERO) @@ -53,11 +57,7 @@ public void testAdd_supportedPresent() { * throw regardless, but it keeps the method name accurate. */ public void testAdd_unsupportedPresent() { - try { - getList().add(e0()); - fail("add(present) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getList().add(e0())); } @CollectionFeature.Require(value = {SUPPORTS_ADD, ALLOWS_NULL_VALUES}) @@ -67,7 +67,7 @@ public void testAdd_supportedNullPresent() { collection = getSubjectGenerator().create(array); assertTrue("add(nullPresent) should return true", getList().add(null)); - List expected = Helpers.copyToList(array); + List expected = copyToList(array); expected.add(null); expectContents(expected); } @@ -76,8 +76,9 @@ public void testAdd_supportedNullPresent() { * Returns the {@link Method} instance for {@link #testAdd_supportedNullPresent()} so that tests * can suppress it. See {@link CollectionAddTester#getAddNullSupportedMethod()} for details. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddSupportedNullPresentMethod() { - return Helpers.getMethod(ListAddTester.class, "testAdd_supportedNullPresent"); + return getMethod(ListAddTester.class, "testAdd_supportedNullPresent"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListCreationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListCreationTester.java index 9d0b77ab2de5..7d2ab03b90fa 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListCreationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListCreationTester.java @@ -33,7 +33,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListCreationTester extends AbstractListTester { @CollectionFeature.Require(absent = REJECTS_DUPLICATES_AT_CREATION) @CollectionSize.Require(absent = {ZERO, ONE}) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListEqualsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListEqualsTester.java index 3a09586f80a2..e692f7643236 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListEqualsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListEqualsTester.java @@ -33,7 +33,9 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListEqualsTester extends AbstractListTester { public void testEquals_otherListWithSameElements() { assertTrue( diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListGetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListGetTester.java index 2ca16c4950a1..8f67f03680e0 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListGetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListGetTester.java @@ -16,6 +16,8 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import org.junit.Ignore; @@ -26,7 +28,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListGetTester extends AbstractListTester { public void testGet_valid() { // This calls get() on each index and checks the result: @@ -34,18 +38,10 @@ public void testGet_valid() { } public void testGet_negative() { - try { - getList().get(-1); - fail("get(-1) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().get(-1)); } public void testGet_tooLarge() { - try { - getList().get(getNumElements()); - fail("get(size) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().get(getNumElements())); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListHashCodeTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListHashCodeTester.java index 93a8526791d8..f31ab39ad2c6 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListHashCodeTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListHashCodeTester.java @@ -16,9 +16,11 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import java.lang.reflect.Method; import org.junit.Ignore; @@ -27,8 +29,10 @@ * * @author George van den Driessche */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListHashCodeTester extends AbstractListTester { public void testHashCode() { int expectedHashCode = 1; @@ -45,8 +49,9 @@ public void testHashCode() { * Returns the {@link Method} instance for {@link #testHashCode()} so that list tests on * unhashable objects can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()}. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getHashCodeMethod() { - return Helpers.getMethod(ListHashCodeTester.class, "testHashCode"); + return getMethod(ListHashCodeTester.class, "testHashCode"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListIndexOfTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListIndexOfTester.java index 7afb8c82812a..c79cb901a316 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListIndexOfTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListIndexOfTester.java @@ -32,7 +32,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListIndexOfTester extends AbstractListIndexOfTester { @Override protected int find(Object o) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListLastIndexOfTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListLastIndexOfTester.java index 19f7f1e123da..4120963469b2 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListLastIndexOfTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListLastIndexOfTester.java @@ -32,7 +32,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListLastIndexOfTester extends AbstractListIndexOfTester { @Override protected int find(Object o) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListListIteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListListIteratorTester.java index 0d4b13e68413..be62791111ac 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListListIteratorTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListListIteratorTester.java @@ -16,17 +16,20 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_ADD_WITH_INDEX; import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_SET; import static com.google.common.collect.testing.testers.Platform.listListIteratorTesterNumIterations; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.IteratorFeature; import com.google.common.collect.testing.ListIteratorTester; import com.google.common.collect.testing.features.CollectionFeature; @@ -36,6 +39,8 @@ import java.util.ListIterator; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -45,9 +50,12 @@ * @author Chris Povirk * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class ListListIteratorTester extends AbstractListTester { +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class ListListIteratorTester extends AbstractListTester { @CollectionFeature.Require(absent = SUPPORTS_REMOVE) @ListFeature.Require(absent = {SUPPORTS_SET, SUPPORTS_ADD_WITH_INDEX}) public void testListIterator_unmodifiable() { @@ -69,7 +77,7 @@ private void runListIteratorTest(Set features) { listListIteratorTesterNumIterations(), singleton(e4()), features, - Helpers.copyToList(getOrderedElements()), + copyToList(getOrderedElements()), 0) { @Override protected ListIterator newTargetIterator() { @@ -85,23 +93,16 @@ protected void verify(List elements) { } public void testListIterator_tooLow() { - try { - getList().listIterator(-1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().listIterator(-1)); } public void testListIterator_tooHigh() { - try { - getList().listIterator(getNumElements() + 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, () -> getList().listIterator(getNumElements() + 1)); } public void testListIterator_atSize() { - getList().listIterator(getNumElements()); + assertNotNull(getList().listIterator(getNumElements())); // TODO: run the iterator through ListIteratorTester } @@ -109,19 +110,21 @@ public void testListIterator_atSize() { * Returns the {@link Method} instance for {@link #testListIterator_fullyModifiable()} so that * tests of {@link CopyOnWriteArraySet} can suppress it with {@code * FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 6570575 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-6570575">JDK-6570575 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getListIteratorFullyModifiableMethod() { - return Helpers.getMethod(ListListIteratorTester.class, "testListIterator_fullyModifiable"); + return getMethod(ListListIteratorTester.class, "testListIterator_fullyModifiable"); } /** * Returns the {@link Method} instance for {@link #testListIterator_unmodifiable()} so that it can * be suppressed in GWT tests. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getListIteratorUnmodifiableMethod() { - return Helpers.getMethod(ListListIteratorTester.class, "testListIterator_unmodifiable"); + return getMethod(ListListIteratorTester.class, "testListIterator_unmodifiable"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveAllTester.java index 513134cd446d..cbaf769b24be 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveAllTester.java @@ -32,9 +32,10 @@ * * @author George van den Driessche */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListRemoveAllTester extends AbstractListTester { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = {ZERO, ONE}) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveAtIndexTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveAtIndexTester.java index 97142515272b..47a817ae0089 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveAtIndexTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveAtIndexTester.java @@ -16,13 +16,14 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_REMOVE_WITH_INDEX; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.ListFeature; @@ -38,36 +39,26 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListRemoveAtIndexTester extends AbstractListTester { @ListFeature.Require(absent = SUPPORTS_REMOVE_WITH_INDEX) @CollectionSize.Require(absent = ZERO) public void testRemoveAtIndex_unsupported() { - try { - getList().remove(0); - fail("remove(i) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getList().remove(0)); expectUnchanged(); } @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX) public void testRemoveAtIndex_negative() { - try { - getList().remove(-1); - fail("remove(-1) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().remove(-1)); expectUnchanged(); } @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX) public void testRemoveAtIndex_tooLarge() { - try { - getList().remove(getNumElements()); - fail("remove(size) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().remove(getNumElements())); expectUnchanged(); } @@ -87,14 +78,13 @@ public void testRemoveAtIndex_middle() { @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX) @CollectionSize.Require(absent = ZERO) public void testRemoveAtIndexConcurrentWithIteration() { - try { - Iterator iterator = collection.iterator(); - getList().remove(getNumElements() / 2); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = collection.iterator(); + getList().remove(getNumElements() / 2); + iterator.next(); + }); } @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX) @@ -108,7 +98,7 @@ private void runRemoveTest(int index) { Platform.format("remove(%d) should return the element at index %d", index, index), getList().get(index), getList().remove(index)); - List expected = Helpers.copyToList(createSamplesArray()); + List expected = copyToList(createSamplesArray()); expected.remove(index); expectContents(expected); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveTester.java index 9c2c688aba4c..383fbb86f25f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRemoveTester.java @@ -32,7 +32,9 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListRemoveTester extends AbstractListTester { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = {ZERO, ONE}) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListReplaceAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListReplaceAllTester.java new file mode 100644 index 000000000000..2158f241ca96 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListReplaceAllTester.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_SET; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.ListFeature; +import java.util.Collections; +import java.util.List; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link List#replaceAll}. Can't be invoked directly; please see + * {@link com.google.common.collect.testing.ListTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class ListReplaceAllTester extends AbstractListTester { + @ListFeature.Require(SUPPORTS_SET) + public void testReplaceAll() { + getList().replaceAll(e -> samples.e3()); + expectContents(Collections.nCopies(getNumElements(), samples.e3())); + } + + @ListFeature.Require(SUPPORTS_SET) + public void testReplaceAll_changesSome() { + getList().replaceAll(e -> e.equals(samples.e0()) ? samples.e3() : e); + E[] expected = createSamplesArray(); + for (int i = 0; i < expected.length; i++) { + if (expected[i].equals(samples.e0())) { + expected[i] = samples.e3(); + } + } + expectContents(expected); + } + + @CollectionSize.Require(absent = ZERO) + @ListFeature.Require(absent = SUPPORTS_SET) + public void testReplaceAll_unsupported() { + assertThrows(UnsupportedOperationException.class, () -> getList().replaceAll(e -> e)); + expectUnchanged(); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRetainAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRetainAllTester.java index 96bd3b40042e..6bc0deeead97 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRetainAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListRetainAllTester.java @@ -21,12 +21,12 @@ import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.MinimalCollection; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Arrays; import org.junit.Ignore; /** @@ -36,7 +36,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListRetainAllTester extends AbstractListTester { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = {ZERO, ONE}) @@ -50,7 +52,6 @@ public void testRetainAll_duplicatesKept() { expectContents(array); } - @SuppressWarnings("unchecked") @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testRetainAll_duplicatesRemoved() { @@ -63,12 +64,11 @@ public void testRetainAll_duplicatesRemoved() { expectContents(e2()); } - @SuppressWarnings("unchecked") @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testRetainAll_countIgnored() { resetContainer(getSubjectGenerator().create(e0(), e2(), e1(), e0())); - assertTrue(getList().retainAll(Arrays.asList(e0(), e1()))); + assertTrue(getList().retainAll(asList(e0(), e1()))); assertContentsInOrder(getList(), e0(), e1(), e0()); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListSetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListSetTester.java index 844f1b4d6052..8eb012ed6731 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListSetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListSetTester.java @@ -16,13 +16,15 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_SET; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.ListFeature; @@ -35,8 +37,10 @@ * * @author George van den Driessche */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListSetTester extends AbstractListTester { @ListFeature.Require(SUPPORTS_SET) @CollectionSize.Require(absent = ZERO) @@ -76,33 +80,21 @@ private void doTestSet(E newValue) { @ListFeature.Require(SUPPORTS_SET) public void testSet_indexTooLow() { - try { - getList().set(-1, e3()); - fail("set(-1) should throw IndexOutOfBoundsException"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().set(-1, e3())); expectUnchanged(); } @ListFeature.Require(SUPPORTS_SET) public void testSet_indexTooHigh() { int index = getNumElements(); - try { - getList().set(index, e3()); - fail("set(size) should throw IndexOutOfBoundsException"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().set(index, e3())); expectUnchanged(); } @CollectionSize.Require(absent = ZERO) @ListFeature.Require(absent = SUPPORTS_SET) public void testSet_unsupported() { - try { - getList().set(aValidIndex(), e3()); - fail("set() should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getList().set(aValidIndex(), e3())); expectUnchanged(); } @@ -112,7 +104,7 @@ public void testSet_unsupportedByEmptyList() { try { getList().set(0, e3()); fail("set() should throw UnsupportedOperationException or IndexOutOfBoundsException"); - } catch (UnsupportedOperationException | IndexOutOfBoundsException tolerated) { + } catch (UnsupportedOperationException | IndexOutOfBoundsException expected) { } expectUnchanged(); } @@ -121,11 +113,7 @@ public void testSet_unsupportedByEmptyList() { @ListFeature.Require(SUPPORTS_SET) @CollectionFeature.Require(absent = ALLOWS_NULL_VALUES) public void testSet_nullUnsupported() { - try { - getList().set(aValidIndex(), null); - fail("set(null) should throw NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getList().set(aValidIndex(), null)); expectUnchanged(); } @@ -137,13 +125,14 @@ private int aValidIndex() { * Returns the {@link java.lang.reflect.Method} instance for {@link #testSet_null()} so that tests * of {@link java.util.Collections#checkedCollection(java.util.Collection, Class)} can suppress it * with {@code FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 6409434 is fixed. - * It's unclear whether nulls were to be permitted or forbidden, but presumably the eventual fix - * will be to permit them, as it seems more likely that code would depend on that behavior than on - * the other. Thus, we say the bug is in set(), which fails to support null. + * href="https://bugs.openjdk.org/browse/JDK-6409434">JDK-6409434 is fixed. It's unclear + * whether nulls were to be permitted or forbidden, but presumably the eventual fix will be to + * permit them, as it seems more likely that code would depend on that behavior than on the other. + * Thus, we say the bug is in set(), which fails to support null. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getSetNullSupportedMethod() { - return Helpers.getMethod(ListSetTester.class, "testSet_null"); + return getMethod(ListSetTester.class, "testSet_null"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListSubListTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListSubListTester.java index 553b693584a4..983130d733c0 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListSubListTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListSubListTester.java @@ -16,6 +16,7 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS; import static com.google.common.collect.testing.features.CollectionSize.ONE; @@ -23,18 +24,19 @@ import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_ADD_WITH_INDEX; import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_REMOVE_WITH_INDEX; import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_SET; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.ListFeature; import com.google.common.testing.SerializableTester; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.junit.Ignore; @@ -45,24 +47,17 @@ * * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListSubListTester extends AbstractListTester { public void testSubList_startNegative() { - try { - getList().subList(-1, 0); - fail("subList(-1, 0) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().subList(-1, 0)); } public void testSubList_endTooLarge() { - try { - getList().subList(0, getNumElements() + 1); - fail("subList(0, size + 1) should throw"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> getList().subList(0, getNumElements() + 1)); } public void testSubList_startGreaterThanEnd() { @@ -75,11 +70,12 @@ public void testSubList_startGreaterThanEnd() { * The subList() docs claim that this should be an * IndexOutOfBoundsException, but many JDK implementations throw * IllegalArgumentException: - * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4506427 + * https://bugs.openjdk.org/browse/JDK-4506427 */ } } + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types public void testSubList_empty() { assertEquals("subList(0, 0) should be empty", emptyList(), getList().subList(0, 0)); } @@ -96,7 +92,7 @@ public void testSubList_entireList() { public void testSubList_subListRemoveAffectsOriginal() { List subList = getList().subList(0, 1); subList.remove(0); - List expected = Arrays.asList(createSamplesArray()).subList(1, getNumElements()); + List expected = asList(createSamplesArray()).subList(1, getNumElements()); expectContents(expected); } @@ -105,7 +101,7 @@ public void testSubList_subListRemoveAffectsOriginal() { public void testSubList_subListClearAffectsOriginal() { List subList = getList().subList(0, 1); subList.clear(); - List expected = Arrays.asList(createSamplesArray()).subList(1, getNumElements()); + List expected = asList(createSamplesArray()).subList(1, getNumElements()); expectContents(expected); } @@ -121,7 +117,7 @@ public void testSubList_subListAddAffectsOriginal() { public void testSubList_subListSetAffectsOriginal() { List subList = getList().subList(0, 1); subList.set(0, e3()); - List expected = Helpers.copyToList(createSamplesArray()); + List expected = copyToList(createSamplesArray()); expected.set(0, e3()); expectContents(expected); } @@ -134,7 +130,7 @@ public void testSubList_originalListSetAffectsSubList() { assertEquals( "A set() call to a list after a sublist has been created " + "should be reflected in the sublist", - Collections.singletonList(e3()), + singletonList(e3()), subList); } @@ -143,7 +139,7 @@ public void testSubList_originalListSetAffectsSubList() { public void testSubList_subListRemoveAffectsOriginalLargeList() { List subList = getList().subList(1, 3); subList.remove(e2()); - List expected = Helpers.copyToList(createSamplesArray()); + List expected = copyToList(createSamplesArray()); expected.remove(2); expectContents(expected); } @@ -161,7 +157,7 @@ public void testSubList_subListAddAtIndexAffectsOriginalLargeList() { public void testSubList_subListSetAffectsOriginalLargeList() { List subList = getList().subList(1, 2); subList.set(0, e3()); - List expected = Helpers.copyToList(createSamplesArray()); + List expected = copyToList(createSamplesArray()); expected.set(1, e3()); expectContents(expected); } @@ -174,10 +170,11 @@ public void testSubList_originalListSetAffectsSubListLargeList() { assertEquals( "A set() call to a list after a sublist has been created " + "should be reflected in the sublist", - Arrays.asList(e3(), e2()), + asList(e3(), e2()), subList); } + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types public void testSubList_ofSubListEmpty() { List subList = getList().subList(0, 0).subList(0, 0); assertEquals("subList(0, 0).subList(0, 0) should be an empty list", emptyList(), subList); @@ -189,7 +186,7 @@ public void testSubList_ofSubListNonEmpty() { assertEquals( "subList(0, 2).subList(1, 2) " + "should be a single-element list of the element at index 1", - Collections.singletonList(getOrderedElements().get(1)), + singletonList(getOrderedElements().get(1)), subList); } @@ -209,7 +206,7 @@ public void testSubList_isEmpty() { List list = getList(); int size = getNumElements(); for (List subList : - Arrays.asList( + asList( list.subList(0, size), list.subList(0, size - 1), list.subList(1, size), @@ -232,13 +229,9 @@ public void testSubList_get() { assertEquals(list.get(size - 1), tail.get(size - 2)); assertEquals(list.get(0), head.get(0)); assertEquals(list.get(size - 2), head.get(size - 2)); - for (List subList : Arrays.asList(copy, head, tail)) { - for (int index : Arrays.asList(-1, subList.size())) { - try { - subList.get(index); - fail("expected IndexOutOfBoundsException"); - } catch (IndexOutOfBoundsException expected) { - } + for (List subList : asList(copy, head, tail)) { + for (int index : asList(-1, subList.size())) { + assertThrows(IndexOutOfBoundsException.class, () -> subList.get(index)); } } } @@ -317,8 +310,9 @@ public void testReserializeSubList() { * Returns the {@link Method} instance for {@link #testSubList_originalListSetAffectsSubList()} so * that tests of {@link CopyOnWriteArrayList} can suppress them with {@code * FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 6570631 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-6570631">JDK-6570631 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getSubListOriginalListSetAffectsSubListMethod() { return getMethod(ListSubListTester.class, "testSubList_originalListSetAffectsSubList"); @@ -326,11 +320,12 @@ public static Method getSubListOriginalListSetAffectsSubListMethod() { /** * Returns the {@link Method} instance for {@link - * #testSubList_originalListSetAffectsSubListLargeList()} ()} so that tests of {@link + * #testSubList_originalListSetAffectsSubListLargeList()} so that tests of {@link * CopyOnWriteArrayList} can suppress them with {@code * FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 6570631 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-6570631">JDK-6570631 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getSubListOriginalListSetAffectsSubListLargeListMethod() { return getMethod(ListSubListTester.class, "testSubList_originalListSetAffectsSubListLargeList"); @@ -341,8 +336,9 @@ public static Method getSubListOriginalListSetAffectsSubListLargeListMethod() { * #testSubList_subListRemoveAffectsOriginalLargeList()} so that tests of {@link * CopyOnWriteArrayList} can suppress it with {@code * FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 6570575 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-6570575">JDK-6570575 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getSubListSubListRemoveAffectsOriginalLargeListMethod() { return getMethod(ListSubListTester.class, "testSubList_subListRemoveAffectsOriginalLargeList"); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListToArrayTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListToArrayTester.java index 4c5eb091fe83..54be14ad313c 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/ListToArrayTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ListToArrayTester.java @@ -17,10 +17,10 @@ package com.google.common.collect.testing.testers; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Arrays; import org.junit.Ignore; /** @@ -30,7 +30,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class ListToArrayTester extends AbstractListTester { // CollectionToArrayTester tests everything except ordering. @@ -51,6 +53,6 @@ public void testToArray_largeEnough() { } private static void assertArrayEquals(String message, Object[] expected, Object[] actual) { - assertEquals(message, Arrays.asList(expected), Arrays.asList(actual)); + assertEquals(message, asList(expected), asList(actual)); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapClearTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapClearTester.java index 64f5127e7f14..ab5c2f35163f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapClearTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapClearTester.java @@ -20,6 +20,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; @@ -38,7 +39,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapClearTester extends AbstractMapTester { @MapFeature.Require(SUPPORTS_REMOVE) public void testClear() { @@ -51,52 +54,43 @@ public void testClear() { @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_REMOVE}) @CollectionSize.Require(SEVERAL) public void testClearConcurrentWithEntrySetIteration() { - try { - Iterator> iterator = getMap().entrySet().iterator(); - getMap().clear(); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator> iterator = getMap().entrySet().iterator(); + getMap().clear(); + iterator.next(); + }); } @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_REMOVE}) @CollectionSize.Require(SEVERAL) public void testClearConcurrentWithKeySetIteration() { - try { - Iterator iterator = getMap().keySet().iterator(); - getMap().clear(); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = getMap().keySet().iterator(); + getMap().clear(); + iterator.next(); + }); } @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_REMOVE}) @CollectionSize.Require(SEVERAL) public void testClearConcurrentWithValuesIteration() { - try { - Iterator iterator = getMap().values().iterator(); - getMap().clear(); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = getMap().values().iterator(); + getMap().clear(); + iterator.next(); + }); } @MapFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testClear_unsupported() { - try { - getMap().clear(); - fail( - "clear() should throw UnsupportedOperation if a map does " - + "not support it and is not empty."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getMap().clear()); expectUnchanged(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeIfAbsentTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeIfAbsentTester.java new file mode 100644 index 000000000000..4ebe32cf4179 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeIfAbsentTester.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import com.google.common.collect.testing.testers.TestExceptions.SomeUncheckedException; +import java.util.Map; +import junit.framework.AssertionFailedError; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#computeIfAbsent}. Can't be invoked directly; please + * see {@link com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapComputeIfAbsentTester extends AbstractMapTester { + + @MapFeature.Require(SUPPORTS_PUT) + public void testComputeIfAbsent_supportedAbsent() { + assertEquals( + "computeIfAbsent(notPresent, function) should return new value", + v3(), + getMap() + .computeIfAbsent( + k3(), + k -> { + assertEquals(k3(), k); + return v3(); + })); + expectAdded(e3()); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfAbsent_supportedPresent() { + assertEquals( + "computeIfAbsent(present, function) should return existing value", + v0(), + getMap() + .computeIfAbsent( + k0(), + k -> { + throw new AssertionFailedError(); + })); + expectUnchanged(); + } + + @MapFeature.Require(SUPPORTS_PUT) + public void testComputeIfAbsent_functionReturnsNullNotInserted() { + assertNull( + "computeIfAbsent(absent, returnsNull) should return null", + getMap() + .computeIfAbsent( + k3(), + k -> { + assertEquals(k3(), k); + return null; + })); + expectUnchanged(); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfAbsent_nullTreatedAsAbsent() { + initMapWithNullValue(); + assertEquals( + "computeIfAbsent(presentAssignedToNull, function) should return newValue", + getValueForNullKey(), + getMap() + .computeIfAbsent( + getKeyForNullValue(), + k -> { + assertEquals(getKeyForNullValue(), k); + return getValueForNullKey(); + })); + expectReplacement(entry(getKeyForNullValue(), getValueForNullKey())); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS}) + public void testComputeIfAbsent_nullKeySupported() { + getMap() + .computeIfAbsent( + null, + k -> { + assertNull(k); + return v3(); + }); + expectAdded(entry(null, v3())); + } + + @MapFeature.Require(SUPPORTS_PUT) + public void testComputeIfAbsent_functionThrows() { + assertThrows( + SomeUncheckedException.class, + () -> + getMap() + .computeIfAbsent( + k3(), + k -> { + assertEquals(k3(), k); + throw new SomeUncheckedException(); + })); + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + public void testComputeIfAbsent_unsupportedAbsent() { + assertThrows( + UnsupportedOperationException.class, + () -> + getMap() + .computeIfAbsent( + k3(), + k -> { + // allowed to be called + assertEquals(k3(), k); + return v3(); + })); + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfAbsent_unsupportedPresentExistingValue() { + try { + assertEquals( + "computeIfAbsent(present, returnsCurrentValue) should return present or throw", + v0(), + getMap() + .computeIfAbsent( + k0(), + k -> { + assertEquals(k0(), k); + return v0(); + })); + } catch (UnsupportedOperationException tolerated) { + } + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfAbsent_unsupportedPresentDifferentValue() { + try { + assertEquals( + "computeIfAbsent(present, returnsDifferentValue) should return present or throw", + v0(), + getMap() + .computeIfAbsent( + k0(), + k -> { + assertEquals(k0(), k); + return v3(); + })); + } catch (UnsupportedOperationException tolerated) { + } + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS) + public void testComputeIfAbsent_nullKeyUnsupported() { + assertThrows( + NullPointerException.class, + () -> + getMap() + .computeIfAbsent( + null, + k -> { + assertNull(k); + return v3(); + })); + expectUnchanged(); + expectNullKeyMissingWhenNullKeysUnsupported( + "Should not contain null key after unsupported computeIfAbsent(null, function)"); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeIfPresentTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeIfPresentTester.java new file mode 100644 index 000000000000..711ac125a1dc --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeIfPresentTester.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import com.google.common.collect.testing.testers.TestExceptions.SomeUncheckedException; +import java.util.Map; +import java.util.Map.Entry; +import junit.framework.AssertionFailedError; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#computeIfPresent}. Can't be invoked directly; please + * see {@link com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapComputeIfPresentTester extends AbstractMapTester { + + @MapFeature.Require(SUPPORTS_PUT) + public void testComputeIfPresent_supportedAbsent() { + assertNull( + "computeIfPresent(notPresent, function) should return null", + getMap() + .computeIfPresent( + k3(), + (k, v) -> { + throw new AssertionFailedError(); + })); + expectUnchanged(); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfPresent_supportedPresent() { + assertEquals( + "computeIfPresent(present, function) should return new value", + v3(), + getMap() + .computeIfPresent( + k0(), + (k, v) -> { + assertEquals(k0(), k); + assertEquals(v0(), v); + return v3(); + })); + expectReplacement(entry(k0(), v3())); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfPresent_functionReturnsNull() { + assertNull( + "computeIfPresent(present, returnsNull) should return null", + getMap() + .computeIfPresent( + k0(), + (k, v) -> { + assertEquals(k0(), k); + assertEquals(v0(), v); + return null; + })); + expectMissing(e0()); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfPresent_nullTreatedAsAbsent() { + initMapWithNullValue(); + assertNull( + "computeIfPresent(presentAssignedToNull, function) should return null", + getMap() + .computeIfPresent( + getKeyForNullValue(), + (k, v) -> { + throw new AssertionFailedError(); + })); + expectReplacement(entry(getKeyForNullValue(), null)); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfPresent_functionThrows() { + assertThrows( + SomeUncheckedException.class, + () -> + getMap() + .computeIfPresent( + k0(), + (k, v) -> { + assertEquals(k0(), k); + assertEquals(v0(), v); + throw new SomeUncheckedException(); + })); + expectUnchanged(); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS}) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfPresent_nullKeySupportedPresent() { + initMapWithNullKey(); + assertEquals( + "computeIfPresent(null, function) should return new value", + v3(), + getMap() + .computeIfPresent( + null, + (k, v) -> { + assertNull(k); + assertEquals(getValueForNullKey(), v); + return v3(); + })); + + Entry[] expected = createArrayWithNullKey(); + expected[getNullLocation()] = entry(null, v3()); + expectContents(expected); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS}) + public void testComputeIfPresent_nullKeySupportedAbsent() { + assertNull( + "computeIfPresent(null, function) should return null", + getMap() + .computeIfPresent( + null, + (k, v) -> { + throw new AssertionFailedError(); + })); + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + public void testComputeIfPresent_unsupportedAbsent() { + try { + getMap() + .computeIfPresent( + k3(), + (k, v) -> { + throw new AssertionFailedError(); + }); + } catch (UnsupportedOperationException tolerated) { + } + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testComputeIfPresent_unsupportedPresent() { + assertThrows( + UnsupportedOperationException.class, () -> getMap().computeIfPresent(k0(), (k, v) -> v3())); + expectUnchanged(); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeTester.java new file mode 100644 index 000000000000..31cecda1247d --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapComputeTester.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import com.google.common.collect.testing.testers.TestExceptions.SomeUncheckedException; +import java.util.Map; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#compute}. Can't be invoked directly; please see + * {@link com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapComputeTester extends AbstractMapTester { + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) + public void testCompute_absentToPresent() { + assertEquals( + "Map.compute(absent, functionReturningValue) should return value", + v3(), + getMap() + .compute( + k3(), + (k, v) -> { + assertEquals(k3(), k); + assertNull(v); + return v3(); + })); + expectAdded(e3()); + assertEquals(getNumElements() + 1, getMap().size()); + } + + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) + public void testCompute_absentToAbsent() { + assertNull( + "Map.compute(absent, functionReturningNull) should return null", + getMap() + .compute( + k3(), + (k, v) -> { + assertEquals(k3(), k); + assertNull(v); + return null; + })); + expectUnchanged(); + assertEquals(getNumElements(), getMap().size()); + } + + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) + @CollectionSize.Require(absent = ZERO) + public void testCompute_presentToPresent() { + assertEquals( + "Map.compute(present, functionReturningValue) should return new value", + v3(), + getMap() + .compute( + k0(), + (k, v) -> { + assertEquals(k0(), k); + assertEquals(v0(), v); + return v3(); + })); + expectReplacement(entry(k0(), v3())); + assertEquals(getNumElements(), getMap().size()); + } + + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) + @CollectionSize.Require(absent = ZERO) + public void testCompute_presentToAbsent() { + assertNull( + "Map.compute(present, functionReturningNull) should return null", + getMap() + .compute( + k0(), + (k, v) -> { + assertEquals(k0(), k); + assertEquals(v0(), v); + return null; + })); + expectMissing(e0()); + expectMissingKeys(k0()); + assertEquals(getNumElements() - 1, getMap().size()); + } + + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE, ALLOWS_NULL_VALUES}) + @CollectionSize.Require(absent = ZERO) + public void testCompute_presentNullToPresentNonnull() { + initMapWithNullValue(); + V value = getValueForNullKey(); + assertEquals( + "Map.compute(presentMappedToNull, functionReturningValue) should return new value", + value, + getMap() + .compute( + getKeyForNullValue(), + (k, v) -> { + assertEquals(getKeyForNullValue(), k); + assertNull(v); + return value; + })); + expectReplacement(entry(getKeyForNullValue(), value)); + assertEquals(getNumElements(), getMap().size()); + } + + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE, ALLOWS_NULL_VALUES}) + @CollectionSize.Require(absent = ZERO) + public void testCompute_presentNullToNull() { + // The spec is somewhat ambiguous about this case, but the actual default implementation + // in Map will remove a present null. + initMapWithNullValue(); + assertNull( + "Map.compute(presentMappedToNull, functionReturningNull) should return null", + getMap() + .compute( + getKeyForNullValue(), + (k, v) -> { + assertEquals(getKeyForNullValue(), k); + assertNull(v); + return null; + })); + expectMissingKeys(getKeyForNullValue()); + assertEquals(getNumElements() - 1, getMap().size()); + } + + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE, ALLOWS_NULL_KEYS}) + @CollectionSize.Require(absent = ZERO) + public void testCompute_nullKeyPresentToPresent() { + initMapWithNullKey(); + assertEquals( + "Map.compute(present, functionReturningValue) should return new value", + v3(), + getMap() + .compute( + null, + (k, v) -> { + assertNull(k); + assertEquals(getValueForNullKey(), v); + return v3(); + })); + assertEquals(getNumElements(), getMap().size()); + } + + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) + @CollectionSize.Require(absent = ZERO) + public void testCompute_presentFunctionThrows() { + assertThrows( + SomeUncheckedException.class, + () -> + getMap() + .compute( + k0(), + (k, v) -> { + assertEquals(k0(), k); + assertEquals(v0(), v); + throw new SomeUncheckedException(); + })); + expectUnchanged(); + } + + @MapFeature.Require({SUPPORTS_PUT, SUPPORTS_REMOVE}) + public void testCompute_absentFunctionThrows() { + assertThrows( + SomeUncheckedException.class, + () -> + getMap() + .compute( + k3(), + (k, v) -> { + assertEquals(k3(), k); + assertNull(v); + throw new SomeUncheckedException(); + })); + expectUnchanged(); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapContainsKeyTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapContainsKeyTester.java index 3721db1174ed..f62badcd4515 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapContainsKeyTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapContainsKeyTester.java @@ -19,6 +19,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; @@ -34,7 +35,9 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapContainsKeyTester extends AbstractMapTester { @CollectionSize.Require(absent = ZERO) public void testContains_yes() { @@ -69,6 +72,15 @@ public void testContains_nullContained() { assertTrue("containsKey(null) should return true", getMap().containsKey(null)); } + @MapFeature.Require(ALLOWS_NULL_VALUES) + @CollectionSize.Require(absent = ZERO) + public void testContains_keyWithNullValueContained() { + initMapWithNullValue(); + assertTrue( + "containsKey(keyForNullValue) should return true", + getMap().containsKey(getKeyForNullValue())); + } + public void testContains_wrongType() { try { // noinspection SuspiciousMethodCalls diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapContainsValueTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapContainsValueTester.java index 044562ab6e94..701d1d2ef5b2 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapContainsValueTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapContainsValueTester.java @@ -35,7 +35,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapContainsValueTester extends AbstractMapTester { @CollectionSize.Require(absent = ZERO) public void testContains_yes() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapCreationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapCreationTester.java index 0810dea28a54..f1e2e106d631 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapCreationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapCreationTester.java @@ -16,20 +16,22 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.REJECTS_DUPLICATES_AT_CREATION; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AbstractMapTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.List; import java.util.Map.Entry; import org.junit.Ignore; @@ -42,8 +44,10 @@ * @author Chris Povirk * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapCreationTester extends AbstractMapTester { @MapFeature.Require(ALLOWS_NULL_KEYS) @CollectionSize.Require(absent = ZERO) @@ -55,11 +59,7 @@ public void testCreateWithNullKeySupported() { @MapFeature.Require(absent = ALLOWS_NULL_KEYS) @CollectionSize.Require(absent = ZERO) public void testCreateWithNullKeyUnsupported() { - try { - initMapWithNullKey(); - fail("Creating a map containing a null key should fail"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> initMapWithNullKey()); } @MapFeature.Require(ALLOWS_NULL_VALUES) @@ -72,11 +72,7 @@ public void testCreateWithNullValueSupported() { @MapFeature.Require(absent = ALLOWS_NULL_VALUES) @CollectionSize.Require(absent = ZERO) public void testCreateWithNullValueUnsupported() { - try { - initMapWithNullValue(); - fail("Creating a map containing a null value should fail"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> initMapWithNullValue()); } @MapFeature.Require({ALLOWS_NULL_KEYS, ALLOWS_NULL_VALUES}) @@ -104,22 +100,14 @@ public void testCreateWithDuplicates_nonNullDuplicatesNotRejected() { @CollectionSize.Require(absent = {ZERO, ONE}) public void testCreateWithDuplicates_nullDuplicatesRejected() { Entry[] entries = getEntriesMultipleNullKeys(); - try { - resetMap(entries); - fail("Should reject duplicate null elements at creation"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> resetMap(entries)); } @MapFeature.Require(REJECTS_DUPLICATES_AT_CREATION) @CollectionSize.Require(absent = {ZERO, ONE}) public void testCreateWithDuplicates_nonNullDuplicatesRejected() { Entry[] entries = getEntriesMultipleNonNullKeys(); - try { - resetMap(entries); - fail("Should reject duplicate non-null elements at creation"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> resetMap(entries)); } private Entry[] getEntriesMultipleNullKeys() { @@ -137,18 +125,18 @@ private Entry[] getEntriesMultipleNonNullKeys() { private void expectFirstRemoved(Entry[] entries) { resetMap(entries); - List> expectedWithDuplicateRemoved = - Arrays.asList(entries).subList(1, getNumElements()); + List> expectedWithDuplicateRemoved = asList(entries).subList(1, getNumElements()); expectContents(expectedWithDuplicateRemoved); } /** * Returns the {@link Method} instance for {@link #testCreateWithNullKeyUnsupported()} so that * tests can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 5045147 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getCreateWithNullKeyUnsupportedMethod() { - return Helpers.getMethod(MapCreationTester.class, "testCreateWithNullKeyUnsupported"); + return getMethod(MapCreationTester.class, "testCreateWithNullKeyUnsupported"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapEntrySetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapEntrySetTester.java index 537f091b4a06..4c9c8bcd6cd2 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapEntrySetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapEntrySetTester.java @@ -16,6 +16,8 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ITERATOR_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; @@ -24,11 +26,12 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AbstractMapTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -46,7 +49,9 @@ * @param The value type of the map implementation under test. */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapEntrySetTester extends AbstractMapTester { private enum IncomparableType { INSTANCE; @@ -65,7 +70,7 @@ public void testEntrySetIteratorRemove() { public void testContainsEntryWithIncomparableKey() { try { - assertFalse(getMap().entrySet().contains(Helpers.mapEntry(IncomparableType.INSTANCE, v0()))); + assertFalse(getMap().entrySet().contains(mapEntry(IncomparableType.INSTANCE, v0()))); } catch (ClassCastException acceptable) { // allowed by the spec } @@ -73,7 +78,7 @@ public void testContainsEntryWithIncomparableKey() { public void testContainsEntryWithIncomparableValue() { try { - assertFalse(getMap().entrySet().contains(Helpers.mapEntry(k0(), IncomparableType.INSTANCE))); + assertFalse(getMap().entrySet().contains(mapEntry(k0(), IncomparableType.INSTANCE))); } catch (ClassCastException acceptable) { // allowed by the spec } @@ -81,26 +86,26 @@ public void testContainsEntryWithIncomparableValue() { @MapFeature.Require(ALLOWS_NULL_KEY_QUERIES) public void testContainsEntryWithNullKeyAbsent() { - assertFalse(getMap().entrySet().contains(Helpers.mapEntry(null, v0()))); + assertFalse(getMap().entrySet().contains(mapEntry(null, v0()))); } @CollectionSize.Require(absent = ZERO) @MapFeature.Require(ALLOWS_NULL_KEYS) public void testContainsEntryWithNullKeyPresent() { initMapWithNullKey(); - assertTrue(getMap().entrySet().contains(Helpers.mapEntry(null, getValueForNullKey()))); + assertTrue(getMap().entrySet().contains(mapEntry(null, getValueForNullKey()))); } @MapFeature.Require(ALLOWS_NULL_VALUE_QUERIES) public void testContainsEntryWithNullValueAbsent() { - assertFalse(getMap().entrySet().contains(Helpers.mapEntry(k0(), null))); + assertFalse(getMap().entrySet().contains(mapEntry(k0(), null))); } @CollectionSize.Require(absent = ZERO) @MapFeature.Require(ALLOWS_NULL_VALUES) public void testContainsEntryWithNullValuePresent() { initMapWithNullValue(); - assertTrue(getMap().entrySet().contains(Helpers.mapEntry(getKeyForNullValue(), null))); + assertTrue(getMap().entrySet().contains(mapEntry(getKeyForNullValue(), null))); } @MapFeature.Require(SUPPORTS_PUT) @@ -131,38 +136,39 @@ public void testSetValueWithNullValuesPresent() { @CollectionSize.Require(absent = ZERO) public void testSetValueWithNullValuesAbsent() { for (Entry entry : getMap().entrySet()) { - try { - entry.setValue(null); - fail("Expected NullPointerException"); - } catch (NullPointerException exception) { - break; - } + assertThrows(NullPointerException.class, () -> entry.setValue(null)); + break; } expectUnchanged(); } + @J2ktIncompatible @GwtIncompatible // reflection public static Method getContainsEntryWithIncomparableKeyMethod() { - return Helpers.getMethod(MapEntrySetTester.class, "testContainsEntryWithIncomparableKey"); + return getMethod(MapEntrySetTester.class, "testContainsEntryWithIncomparableKey"); } + @J2ktIncompatible @GwtIncompatible // reflection public static Method getContainsEntryWithIncomparableValueMethod() { - return Helpers.getMethod(MapEntrySetTester.class, "testContainsEntryWithIncomparableValue"); + return getMethod(MapEntrySetTester.class, "testContainsEntryWithIncomparableValue"); } + @J2ktIncompatible @GwtIncompatible // reflection public static Method getSetValueMethod() { - return Helpers.getMethod(MapEntrySetTester.class, "testSetValue"); + return getMethod(MapEntrySetTester.class, "testSetValue"); } + @J2ktIncompatible @GwtIncompatible // reflection public static Method getSetValueWithNullValuesPresentMethod() { - return Helpers.getMethod(MapEntrySetTester.class, "testSetValueWithNullValuesPresent"); + return getMethod(MapEntrySetTester.class, "testSetValueWithNullValuesPresent"); } + @J2ktIncompatible @GwtIncompatible // reflection public static Method getSetValueWithNullValuesAbsentMethod() { - return Helpers.getMethod(MapEntrySetTester.class, "testSetValueWithNullValuesAbsent"); + return getMethod(MapEntrySetTester.class, "testSetValueWithNullValuesAbsent"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapEqualsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapEqualsTester.java index 2408ad41e2af..1c19de02cd2b 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapEqualsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapEqualsTester.java @@ -16,12 +16,12 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.util.Collection; @@ -37,7 +37,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapEqualsTester extends AbstractMapTester { public void testEquals_otherMapWithSameEntries() { assertTrue( @@ -116,11 +118,10 @@ public void testEquals_largerMap() { public void testEquals_list() { assertFalse( - "A List should never equal a Map.", - getMap().equals(Helpers.copyToList(getMap().entrySet()))); + "A List should never equal a Map.", getMap().equals(copyToList(getMap().entrySet()))); } - private static HashMap newHashMap( + private static Map newHashMap( Collection> entries) { HashMap map = new HashMap<>(); for (Entry entry : entries) { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapForEachTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapForEachTester.java new file mode 100644 index 000000000000..d8b12093a0fa --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapForEachTester.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.Helpers; +import com.google.common.collect.testing.features.CollectionFeature; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#forEach}. Can't be invoked directly; please see + * {@link com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapForEachTester extends AbstractMapTester { + @CollectionFeature.Require(KNOWN_ORDER) + public void testForEachKnownOrder() { + List> entries = new ArrayList<>(); + getMap().forEach((k, v) -> entries.add(entry(k, v))); + assertEquals(getOrderedElements(), entries); + } + + @CollectionFeature.Require(absent = KNOWN_ORDER) + public void testForEachUnknownOrder() { + List> entries = new ArrayList<>(); + getMap().forEach((k, v) -> entries.add(entry(k, v))); + Helpers.assertEqualIgnoringOrder(getSampleEntries(), entries); + } + + @MapFeature.Require(ALLOWS_NULL_KEYS) + @CollectionSize.Require(absent = ZERO) + public void testForEach_nullKeys() { + initMapWithNullKey(); + List> expectedEntries = asList(createArrayWithNullKey()); + List> entries = new ArrayList<>(); + getMap().forEach((k, v) -> entries.add(entry(k, v))); + Helpers.assertEqualIgnoringOrder(expectedEntries, entries); + } + + @MapFeature.Require(ALLOWS_NULL_VALUES) + @CollectionSize.Require(absent = ZERO) + public void testForEach_nullValues() { + initMapWithNullValue(); + List> expectedEntries = asList(createArrayWithNullValue()); + List> entries = new ArrayList<>(); + getMap().forEach((k, v) -> entries.add(entry(k, v))); + Helpers.assertEqualIgnoringOrder(expectedEntries, entries); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapGetOrDefaultTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapGetOrDefaultTester.java new file mode 100644 index 000000000000..2170566c8e98 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapGetOrDefaultTester.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.WrongType; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import java.util.Map; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#getOrDefault}. Can't be invoked directly; please see + * {@link com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapGetOrDefaultTester extends AbstractMapTester { + @CollectionSize.Require(absent = ZERO) + public void testGetOrDefault_present() { + assertEquals( + "getOrDefault(present, def) should return the associated value", + v0(), + getMap().getOrDefault(k0(), v3())); + } + + @CollectionSize.Require(absent = ZERO) + public void testGetOrDefault_presentNullDefault() { + assertEquals( + "getOrDefault(present, null) should return the associated value", + v0(), + getMap().getOrDefault(k0(), null)); + } + + public void testGetOrDefault_absent() { + assertEquals( + "getOrDefault(absent, def) should return the default value", + v3(), + getMap().getOrDefault(k3(), v3())); + } + + public void testGetOrDefault_absentNullDefault() { + assertNull("getOrDefault(absent, null) should return null", getMap().getOrDefault(k3(), null)); + } + + @MapFeature.Require(ALLOWS_NULL_KEY_QUERIES) + public void testGetOrDefault_absentNull() { + assertEquals( + "getOrDefault(null, def) should return the default value", + v3(), + getMap().getOrDefault(null, v3())); + } + + @MapFeature.Require(absent = ALLOWS_NULL_KEY_QUERIES) + public void testGetOrDefault_nullAbsentAndUnsupported() { + try { + assertEquals( + "getOrDefault(null, def) should return default or throw", + v3(), + getMap().getOrDefault(null, v3())); + } catch (NullPointerException tolerated) { + } + } + + @MapFeature.Require(ALLOWS_NULL_KEYS) + @CollectionSize.Require(absent = ZERO) + public void testGetOrDefault_nonNullWhenNullContained() { + initMapWithNullKey(); + assertEquals( + "getOrDefault(absent, default) should return default", + v3(), + getMap().getOrDefault(k3(), v3())); + } + + @MapFeature.Require(ALLOWS_NULL_KEYS) + @CollectionSize.Require(absent = ZERO) + public void testGetOrDefault_presentNull() { + initMapWithNullKey(); + assertEquals( + "getOrDefault(null, default) should return the associated value", + getValueForNullKey(), + getMap().getOrDefault(null, v3())); + } + + @MapFeature.Require(ALLOWS_NULL_VALUES) + @CollectionSize.Require(absent = ZERO) + public void testGetOrDefault_presentMappedToNull() { + initMapWithNullValue(); + assertNull( + "getOrDefault(mappedToNull, default) should return null", + getMap().getOrDefault(getKeyForNullValue(), v3())); + } + + public void testGet_wrongType() { + try { + assertEquals( + "getOrDefault(wrongType, default) should return default or throw", + v3(), + getMap().getOrDefault(WrongType.VALUE, v3())); + } catch (ClassCastException tolerated) { + } + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapGetTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapGetTester.java index 89610f26c768..31eba25a921f 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapGetTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapGetTester.java @@ -35,7 +35,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapGetTester extends AbstractMapTester { @CollectionSize.Require(absent = ZERO) public void testGet_yes() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapHashCodeTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapHashCodeTester.java index 97ad5c4d5c77..cf82d186f453 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapHashCodeTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapHashCodeTester.java @@ -34,7 +34,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapHashCodeTester extends AbstractMapTester { public void testHashCode() { int expectedHashCode = 0; diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapIsEmptyTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapIsEmptyTester.java index 548ebe5fa5a8..46722d72d935 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapIsEmptyTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapIsEmptyTester.java @@ -30,7 +30,9 @@ * @author Kevin Bourrillion */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapIsEmptyTester extends AbstractMapTester { @CollectionSize.Require(ZERO) public void testIsEmpty_yes() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapMergeTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapMergeTester.java new file mode 100644 index 000000000000..10c591e204d0 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapMergeTester.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.Helpers; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import com.google.common.collect.testing.testers.TestExceptions.SomeUncheckedException; +import java.lang.reflect.Method; +import java.util.Hashtable; +import java.util.Map; +import junit.framework.AssertionFailedError; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#merge}. Can't be invoked directly; please see {@link + * com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapMergeTester extends AbstractMapTester { + @MapFeature.Require(SUPPORTS_PUT) + public void testAbsent() { + assertEquals( + "Map.merge(absent, value, function) should return value", + v3(), + getMap() + .merge( + k3(), + v3(), + (oldV, newV) -> { + throw new AssertionFailedError( + "Should not call merge function if key was absent"); + })); + expectAdded(e3()); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) + @CollectionSize.Require(absent = ZERO) + public void testMappedToNull() { + initMapWithNullValue(); + assertEquals( + "Map.merge(keyMappedToNull, value, function) should return value", + v3(), + getMap() + .merge( + getKeyForNullValue(), + v3(), + (oldV, newV) -> { + throw new AssertionFailedError( + "Should not call merge function if key was mapped to null"); + })); + expectReplacement(entry(getKeyForNullValue(), v3())); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS}) + public void testMergeAbsentNullKey() { + assertEquals( + "Map.merge(null, value, function) should return value", + v3(), + getMap() + .merge( + null, + v3(), + (oldV, newV) -> { + throw new AssertionFailedError( + "Should not call merge function if key was absent"); + })); + expectAdded(entry(null, v3())); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testMergePresent() { + assertEquals( + "Map.merge(present, value, function) should return function result", + v4(), + getMap() + .merge( + k0(), + v3(), + (oldV, newV) -> { + assertEquals(v0(), oldV); + assertEquals(v3(), newV); + return v4(); + })); + expectReplacement(entry(k0(), v4())); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testMergeFunctionThrows() { + assertThrows( + SomeUncheckedException.class, + () -> + getMap() + .merge( + k0(), + v3(), + (oldV, newV) -> { + assertEquals(v0(), oldV); + assertEquals(v3(), newV); + throw new SomeUncheckedException(); + })); + expectUnchanged(); + } + + @MapFeature.Require(SUPPORTS_REMOVE) + @CollectionSize.Require(absent = ZERO) + public void testMergePresentToNull() { + assertNull( + "Map.merge(present, value, functionReturningNull) should return null", + getMap() + .merge( + k0(), + v3(), + (oldV, newV) -> { + assertEquals(v0(), oldV); + assertEquals(v3(), newV); + return null; + })); + expectMissing(e0()); + } + + public void testMergeNullValue() { + try { + getMap() + .merge( + k0(), + null, + (oldV, newV) -> { + throw new AssertionFailedError("Should not call merge function if value was null"); + }); + fail("Expected NullPointerException or UnsupportedOperationException"); + } catch (NullPointerException | UnsupportedOperationException expected) { + } + } + + public void testMergeNullFunction() { + try { + getMap().merge(k0(), v3(), null); + fail("Expected NullPointerException or UnsupportedOperationException"); + } catch (NullPointerException | UnsupportedOperationException expected) { + } + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + public void testMergeUnsupported() { + assertThrows( + UnsupportedOperationException.class, + () -> + getMap() + .merge( + k3(), + v3(), + (oldV, newV) -> { + throw new AssertionFailedError(); + })); + } + + /** + * Returns the {@link Method} instance for {@link #testMergeNullValue()} so that tests of {@link + * Hashtable} can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()}. + */ + @J2ktIncompatible + @GwtIncompatible // reflection + public static Method getMergeNullValueMethod() { + return Helpers.getMethod(MapMergeTester.class, "testMergeNullValue"); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutAllTester.java index 21d89d8d68d7..5bee3eaca492 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutAllTester.java @@ -16,28 +16,32 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AbstractMapTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.MinimalCollection; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import java.lang.reflect.Method; -import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -47,10 +51,13 @@ * @author Chris Povirk * @author Kevin Bourrillion */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class MapPutAllTester extends AbstractMapTester { +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class MapPutAllTester + extends AbstractMapTester { private List> containsNullKey; private List> containsNullValue; @@ -84,11 +91,7 @@ public void testPutAll_supportedNonePresent() { @MapFeature.Require(absent = SUPPORTS_PUT) public void testPutAll_unsupportedNonePresent() { - try { - putAll(createDisjointCollection()); - fail("putAll(nonePresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> putAll(createDisjointCollection())); expectUnchanged(); expectMissing(e3(), e4()); } @@ -103,24 +106,20 @@ public void testPutAll_supportedSomePresent() { @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT}) @CollectionSize.Require(absent = ZERO) public void testPutAllSomePresentConcurrentWithEntrySetIteration() { - try { - Iterator> iterator = getMap().entrySet().iterator(); - putAll(MinimalCollection.of(e3(), e0())); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator> iterator = getMap().entrySet().iterator(); + putAll(MinimalCollection.of(e3(), e0())); + iterator.next(); + }); } @MapFeature.Require(absent = SUPPORTS_PUT) @CollectionSize.Require(absent = ZERO) public void testPutAll_unsupportedSomePresent() { - try { - putAll(MinimalCollection.of(e3(), e0())); - fail("putAll(somePresent) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> putAll(MinimalCollection.of(e3(), e0()))); expectUnchanged(); } @@ -142,11 +141,7 @@ public void testPutAll_nullKeySupported() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS) public void testPutAll_nullKeyUnsupported() { - try { - putAll(containsNullKey); - fail("putAll(containsNullKey) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> putAll(containsNullKey)); expectUnchanged(); expectNullKeyMissingWhenNullKeysUnsupported( "Should not contain null key after unsupported putAll(containsNullKey)"); @@ -160,11 +155,7 @@ public void testPutAll_nullValueSupported() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) public void testPutAll_nullValueUnsupported() { - try { - putAll(containsNullValue); - fail("putAll(containsNullValue) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> putAll(containsNullValue)); expectUnchanged(); expectNullValueMissingWhenNullValuesUnsupported( "Should not contain null value after unsupported putAll(containsNullValue)"); @@ -172,15 +163,7 @@ public void testPutAll_nullValueUnsupported() { @MapFeature.Require(SUPPORTS_PUT) public void testPutAll_nullCollectionReference() { - try { - getMap().putAll(null); - fail("putAll(null) should throw NullPointerException"); - } catch (NullPointerException expected) { - } - } - - private Map emptyMap() { - return Collections.emptyMap(); + assertThrows(NullPointerException.class, () -> getMap().putAll(null)); } private void putAll(Iterable> entries) { @@ -194,10 +177,11 @@ private void putAll(Iterable> entries) { /** * Returns the {@link Method} instance for {@link #testPutAll_nullKeyUnsupported()} so that tests * can suppress it with {@code FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 5045147 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getPutAllNullKeyUnsupportedMethod() { - return Helpers.getMethod(MapPutAllTester.class, "testPutAll_nullKeyUnsupported"); + return getMethod(MapPutAllTester.class, "testPutAll_nullKeyUnsupported"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutIfAbsentTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutIfAbsentTester.java new file mode 100644 index 000000000000..6d05f72bb39b --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutIfAbsentTester.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import java.util.Map; +import java.util.Map.Entry; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#putIfAbsent}. Can't be invoked directly; please see + * {@link com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapPutIfAbsentTester extends AbstractMapTester { + + @MapFeature.Require(SUPPORTS_PUT) + public void testPutIfAbsent_supportedAbsent() { + assertNull( + "putIfAbsent(notPresent, value) should return null", getMap().putIfAbsent(k3(), v3())); + expectAdded(e3()); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testPutIfAbsent_supportedPresent() { + assertEquals( + "putIfAbsent(present, value) should return existing value", + v0(), + getMap().putIfAbsent(k0(), v3())); + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + public void testPutIfAbsent_unsupportedAbsent() { + assertThrows(UnsupportedOperationException.class, () -> getMap().putIfAbsent(k3(), v3())); + expectUnchanged(); + expectMissing(e3()); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testPutIfAbsent_unsupportedPresentExistingValue() { + try { + assertEquals( + "putIfAbsent(present, existingValue) should return present or throw", + v0(), + getMap().putIfAbsent(k0(), v0())); + } catch (UnsupportedOperationException tolerated) { + } + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testPutIfAbsent_unsupportedPresentDifferentValue() { + try { + getMap().putIfAbsent(k0(), v3()); + } catch (UnsupportedOperationException tolerated) { + } + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS) + public void testPutIfAbsent_nullKeyUnsupported() { + assertThrows(NullPointerException.class, () -> getMap().putIfAbsent(null, v3())); + expectUnchanged(); + expectNullKeyMissingWhenNullKeysUnsupported( + "Should not contain null key after unsupported putIfAbsent(null, value)"); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) + public void testPutIfAbsent_nullValueUnsupportedAndKeyAbsent() { + assertThrows(NullPointerException.class, () -> getMap().putIfAbsent(k3(), null)); + expectUnchanged(); + expectNullValueMissingWhenNullValuesUnsupported( + "Should not contain null value after unsupported putIfAbsent(key, null)"); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) + @CollectionSize.Require(absent = ZERO) + public void testPutIfAbsent_nullValueUnsupportedAndKeyPresent() { + try { + getMap().putIfAbsent(k0(), null); + } catch (NullPointerException tolerated) { + } + expectUnchanged(); + expectNullValueMissingWhenNullValuesUnsupported( + "Should not contain null after unsupported putIfAbsent(present, null)"); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) + public void testPut_nullValueSupported() { + Entry nullValueEntry = entry(k3(), null); + assertNull( + "putIfAbsent(key, null) should return null", + getMap().putIfAbsent(nullValueEntry.getKey(), nullValueEntry.getValue())); + expectAdded(nullValueEntry); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES}) + @CollectionSize.Require(absent = ZERO) + public void testPutIfAbsent_replacesNullValue() { + initMapWithNullValue(); + + // putIfAbsent should replace the null value with the new value + assertNull( + "putIfAbsent(existingKeyWithNullValue, value) should return null", + getMap().putIfAbsent(getKeyForNullValue(), v3())); + assertEquals("Map should now contain the new value", v3(), getMap().get(getKeyForNullValue())); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutTester.java index c9a745d4addf..834823e34950 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapPutTester.java @@ -16,18 +16,21 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AbstractMapTester; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.Method; import java.util.ConcurrentModificationException; import java.util.Iterator; @@ -41,9 +44,10 @@ * @author Chris Povirk * @author Kevin Bourrillion */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapPutTester extends AbstractMapTester { private Entry nullKeyEntry; private Entry nullValueEntry; @@ -75,49 +79,42 @@ public void testPut_supportedNotPresent() { @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT}) @CollectionSize.Require(absent = ZERO) public void testPutAbsentConcurrentWithEntrySetIteration() { - try { - Iterator> iterator = getMap().entrySet().iterator(); - put(e3()); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator> iterator = getMap().entrySet().iterator(); + put(e3()); + iterator.next(); + }); } @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT}) @CollectionSize.Require(absent = ZERO) public void testPutAbsentConcurrentWithKeySetIteration() { - try { - Iterator iterator = getMap().keySet().iterator(); - put(e3()); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = getMap().keySet().iterator(); + put(e3()); + iterator.next(); + }); } @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT}) @CollectionSize.Require(absent = ZERO) public void testPutAbsentConcurrentWithValueIteration() { - try { - Iterator iterator = getMap().values().iterator(); - put(e3()); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = getMap().values().iterator(); + put(e3()); + iterator.next(); + }); } @MapFeature.Require(absent = SUPPORTS_PUT) public void testPut_unsupportedNotPresent() { - try { - put(e3()); - fail("put(notPresent, value) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> put(e3())); expectUnchanged(); expectMissing(e3()); } @@ -135,11 +132,7 @@ public void testPut_unsupportedPresentExistingValue() { @MapFeature.Require(absent = SUPPORTS_PUT) @CollectionSize.Require(absent = ZERO) public void testPut_unsupportedPresentDifferentValue() { - try { - getMap().put(k0(), v3()); - fail("put(present, differentValue) should throw"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getMap().put(k0(), v3())); expectUnchanged(); } @@ -166,11 +159,7 @@ public void testPut_nullKeySupportedPresent() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS) public void testPut_nullKeyUnsupported() { - try { - put(nullKeyEntry); - fail("put(null, value) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> put(nullKeyEntry)); expectUnchanged(); expectNullKeyMissingWhenNullKeysUnsupported( "Should not contain null key after unsupported put(null, value)"); @@ -184,11 +173,7 @@ public void testPut_nullValueSupported() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) public void testPut_nullValueUnsupported() { - try { - put(nullValueEntry); - fail("put(key, null) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> put(nullValueEntry)); expectUnchanged(); expectNullValueMissingWhenNullValuesUnsupported( "Should not contain null value after unsupported put(key, null)"); @@ -207,11 +192,7 @@ public void testPut_replaceWithNullValueSupported() { @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) @CollectionSize.Require(absent = ZERO) public void testPut_replaceWithNullValueUnsupported() { - try { - put(presentKeyNullValueEntry); - fail("put(present, null) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> put(presentKeyNullValueEntry)); expectUnchanged(); expectNullValueMissingWhenNullValuesUnsupported( "Should not contain null after unsupported put(present, null)"); @@ -245,6 +226,7 @@ public void testPut_nullKeyAndValueSupported() { expectAdded(nullKeyValueEntry); } + @CanIgnoreReturnValue private V put(Entry entry) { return getMap().put(entry.getKey(), entry.getValue()); } @@ -253,10 +235,11 @@ private V put(Entry entry) { * Returns the {@link Method} instance for {@link #testPut_nullKeyUnsupported()} so that tests of * {@link java.util.TreeMap} can suppress it with {@code * FeatureSpecificTestSuiteBuilder.suppressing()} until Sun bug 5045147 is fixed. + * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147 is fixed. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getPutNullKeyUnsupportedMethod() { - return Helpers.getMethod(MapPutTester.class, "testPut_nullKeyUnsupported"); + return getMethod(MapPutTester.class, "testPut_nullKeyUnsupported"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapRemoveEntryTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapRemoveEntryTester.java new file mode 100644 index 000000000000..584f46cc1518 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapRemoveEntryTester.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import java.util.Map; +import org.junit.Ignore; + +/** + * Tester for {@link Map#remove(Object, Object)}. Can't be invoked directly; please see {@link + * com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapRemoveEntryTester extends AbstractMapTester { + @MapFeature.Require(SUPPORTS_REMOVE) + @CollectionSize.Require(absent = ZERO) + public void testRemove_supportedPresent() { + assertTrue(getMap().remove(k0(), v0())); + expectMissing(e0()); + } + + @MapFeature.Require(SUPPORTS_REMOVE) + public void testRemove_supportedPresentKeyWrongValue() { + assertFalse(getMap().remove(k0(), v3())); + expectUnchanged(); + } + + @MapFeature.Require(SUPPORTS_REMOVE) + public void testRemove_supportedWrongKeyPresentValue() { + assertFalse(getMap().remove(k3(), v0())); + expectUnchanged(); + } + + @MapFeature.Require(SUPPORTS_REMOVE) + public void testRemove_supportedAbsentKeyAbsentValue() { + assertFalse(getMap().remove(k3(), v3())); + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_REMOVE, absent = ALLOWS_NULL_KEY_QUERIES) + public void testRemove_nullKeyQueriesUnsupported() { + try { + assertFalse(getMap().remove(null, v3())); + } catch (NullPointerException tolerated) { + // since the operation would be a no-op, the exception is not required + } + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_REMOVE, absent = ALLOWS_NULL_VALUE_QUERIES) + public void testRemove_nullValueQueriesUnsupported() { + try { + assertFalse(getMap().remove(k3(), null)); + } catch (NullPointerException tolerated) { + // since the operation would be a no-op, the exception is not required + } + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_REMOVE) + @CollectionSize.Require(absent = ZERO) + public void testRemove_unsupportedPresent() { + assertThrows(UnsupportedOperationException.class, () -> getMap().remove(k0(), v0())); + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_REMOVE) + public void testRemove_unsupportedAbsent() { + try { + assertFalse(getMap().remove(k0(), v3())); + } catch (UnsupportedOperationException tolerated) { + // since the operation would be a no-op, the exception is not required + } + expectUnchanged(); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapRemoveTester.java index 00c074e5e8fc..36623a655fb4 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapRemoveTester.java @@ -22,6 +22,7 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; import static com.google.common.collect.testing.features.MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; @@ -40,9 +41,10 @@ * @author George van den Driessche * @author Chris Povirk */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapRemoveTester extends AbstractMapTester { @MapFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) @@ -57,40 +59,37 @@ public void testRemove_present() { @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_REMOVE}) @CollectionSize.Require(SEVERAL) public void testRemovePresentConcurrentWithEntrySetIteration() { - try { - Iterator> iterator = getMap().entrySet().iterator(); - getMap().remove(k0()); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator> iterator = getMap().entrySet().iterator(); + getMap().remove(k0()); + iterator.next(); + }); } @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_REMOVE}) @CollectionSize.Require(SEVERAL) public void testRemovePresentConcurrentWithKeySetIteration() { - try { - Iterator iterator = getMap().keySet().iterator(); - getMap().remove(k0()); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = getMap().keySet().iterator(); + getMap().remove(k0()); + iterator.next(); + }); } @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_REMOVE}) @CollectionSize.Require(SEVERAL) public void testRemovePresentConcurrentWithValuesIteration() { - try { - Iterator iterator = getMap().values().iterator(); - getMap().remove(k0()); - iterator.next(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - // success - } + assertThrows( + ConcurrentModificationException.class, + () -> { + Iterator iterator = getMap().values().iterator(); + getMap().remove(k0()); + iterator.next(); + }); } @MapFeature.Require(SUPPORTS_REMOVE) @@ -117,11 +116,7 @@ public void testRemove_nullPresent() { @MapFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) public void testRemove_unsupported() { - try { - getMap().remove(k0()); - fail("remove(present) should throw UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> getMap().remove(k0())); expectUnchanged(); assertEquals("remove(present) should not remove the element", v0(), get(k0())); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceAllTester.java new file mode 100644 index 000000000000..c503addd1ab7 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceAllTester.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.Helpers; +import com.google.common.collect.testing.SampleElements; +import com.google.common.collect.testing.features.CollectionFeature; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@code replaceAll()} operations on a map. Can't be invoked + * directly; please see {@link com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapReplaceAllTester extends AbstractMapTester { + private SampleElements keys() { + return new SampleElements<>(k0(), k1(), k2(), k3(), k4()); + } + + private SampleElements values() { + return new SampleElements<>(v0(), v1(), v2(), v3(), v4()); + } + + @MapFeature.Require(SUPPORTS_PUT) + public void testReplaceAllRotate() { + getMap() + .replaceAll( + (K k, V v) -> { + int index = keys().asList().indexOf(k); + return values().asList().get(index + 1); + }); + List> expectedEntries = new ArrayList<>(); + for (Entry entry : getSampleEntries()) { + int index = keys().asList().indexOf(entry.getKey()); + expectedEntries.add(Helpers.mapEntry(entry.getKey(), values().asList().get(index + 1))); + } + expectContents(expectedEntries); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionFeature.Require(KNOWN_ORDER) + public void testReplaceAllPreservesOrder() { + getMap() + .replaceAll( + (K k, V v) -> { + int index = keys().asList().indexOf(k); + return values().asList().get(index + 1); + }); + List> orderedEntries = getOrderedElements(); + int index = 0; + for (K key : getMap().keySet()) { + assertEquals(orderedEntries.get(index).getKey(), key); + index++; + } + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplaceAll_unsupported() { + assertThrows( + UnsupportedOperationException.class, + () -> + getMap() + .replaceAll( + (K k, V v) -> { + int index = keys().asList().indexOf(k); + return values().asList().get(index + 1); + })); + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(ZERO) + public void testReplaceAll_unsupportedByEmptyCollection() { + try { + getMap() + .replaceAll( + (K k, V v) -> { + int index = keys().asList().indexOf(k); + return values().asList().get(index + 1); + }); + } catch (UnsupportedOperationException tolerated) { + } + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + public void testReplaceAll_unsupportedNoOpFunction() { + try { + getMap().replaceAll((K k, V v) -> v); + } catch (UnsupportedOperationException tolerated) { + } + expectUnchanged(); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceEntryTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceEntryTester.java new file mode 100644 index 000000000000..124a81bebdf3 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceEntryTester.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import java.util.Map; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#replace(Object, Object, Object)}. Can't be invoked + * directly; please see {@link com.google.common.collect.testing.MapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapReplaceEntryTester extends AbstractMapTester { + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplaceEntry_supportedPresent() { + try { + assertTrue(getMap().replace(k0(), v0(), v3())); + expectReplacement(entry(k0(), v3())); + } catch (ClassCastException tolerated) { // for ClassToInstanceMap + expectUnchanged(); + } + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplaceEntry_supportedPresentUnchanged() { + assertTrue(getMap().replace(k0(), v0(), v0())); + expectUnchanged(); + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplaceEntry_supportedWrongValue() { + assertFalse(getMap().replace(k0(), v3(), v4())); + expectUnchanged(); + } + + @MapFeature.Require(SUPPORTS_PUT) + public void testReplaceEntry_supportedAbsentKey() { + assertFalse(getMap().replace(k3(), v3(), v4())); + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) + @CollectionSize.Require(absent = ZERO) + public void testReplaceEntry_presentNullValueUnsupported() { + assertThrows(NullPointerException.class, () -> getMap().replace(k0(), v0(), null)); + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUE_QUERIES) + @CollectionSize.Require(absent = ZERO) + public void testReplaceEntry_wrongValueNullValueUnsupported() { + try { + assertFalse(getMap().replace(k0(), v3(), null)); + } catch (NullPointerException tolerated) { + // the operation would be a no-op, so exceptions are allowed but not required + } + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUE_QUERIES) + public void testReplaceEntry_absentKeyNullValueUnsupported() { + try { + assertFalse(getMap().replace(k3(), v3(), null)); + } catch (NullPointerException tolerated) { + // the operation would be a no-op, so exceptions are allowed but not required + } + expectUnchanged(); + } + + @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUE_QUERIES}) + public void testReplaceEntry_nullDifferentFromAbsent() { + assertFalse(getMap().replace(k3(), null, v3())); + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUE_QUERIES) + public void testReplaceEntry_expectNullUnsupported() { + try { + assertFalse(getMap().replace(k3(), null, v3())); + } catch (NullPointerException tolerated) { + // the operation would be a no-op, so exceptions are allowed but not required + } + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplaceEntry_unsupportedPresent() { + assertThrows(UnsupportedOperationException.class, () -> getMap().replace(k0(), v0(), v3())); + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplaceEntry_unsupportedWrongValue() { + try { + getMap().replace(k0(), v3(), v4()); + } catch (UnsupportedOperationException tolerated) { + // the operation would be a no-op, so exceptions are allowed but not required + } + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + public void testReplaceEntry_unsupportedAbsentKey() { + try { + getMap().replace(k3(), v3(), v4()); + } catch (UnsupportedOperationException tolerated) { + // the operation would be a no-op, so exceptions are allowed but not required + } + expectUnchanged(); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceTester.java new file mode 100644 index 000000000000..e231b0f33f04 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapReplaceTester.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEY_QUERIES; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; +import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUE_QUERIES; +import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.AbstractMapTester; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.collect.testing.features.MapFeature; +import java.util.Map; +import org.junit.Ignore; + +/** + * A generic JUnit test which tests {@link Map#replace(Object, Object)}. Can't be invoked directly; + * please see {@link com.google.common.collect.testing.ConcurrentMapTestSuiteBuilder}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@IgnoreJRERequirement // We opt into library desugaring for our tests. +public class MapReplaceTester extends AbstractMapTester { + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplace_supportedPresent() { + try { + assertEquals(v0(), getMap().replace(k0(), v3())); + expectReplacement(entry(k0(), v3())); + } catch (ClassCastException tolerated) { // for ClassToInstanceMap + expectUnchanged(); + } + } + + @MapFeature.Require(SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplace_supportedPresentNoChange() { + assertEquals(v0(), getMap().replace(k0(), v0())); + expectUnchanged(); + } + + @MapFeature.Require(SUPPORTS_PUT) + public void testReplace_supportedAbsent() { + assertNull(getMap().replace(k3(), v3())); + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES) + @CollectionSize.Require(absent = ZERO) + public void testReplace_presentNullValueUnsupported() { + assertThrows(NullPointerException.class, () -> getMap().replace(k0(), null)); + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUE_QUERIES) + public void testReplace_absentNullValueUnsupported() { + try { + getMap().replace(k3(), null); + } catch (NullPointerException tolerated) { + // permitted not to throw because it would be a no-op + } + expectUnchanged(); + } + + @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEY_QUERIES) + public void testReplace_absentNullKeyUnsupported() { + try { + getMap().replace(null, v3()); + } catch (NullPointerException tolerated) { + // permitted not to throw because it would be a no-op + } + expectUnchanged(); + } + + @MapFeature.Require(absent = SUPPORTS_PUT) + @CollectionSize.Require(absent = ZERO) + public void testReplace_unsupportedPresent() { + try { + getMap().replace(k0(), v3()); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + } catch (ClassCastException tolerated) { + // for ClassToInstanceMap + } + + expectUnchanged(); + } +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapSerializationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapSerializationTester.java index dfa0a1ee0c3b..54463605c482 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapSerializationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapSerializationTester.java @@ -32,7 +32,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapSerializationTester extends AbstractMapTester { @CollectionFeature.Require(SERIALIZABLE) public void testReserializeMap() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapSizeTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapSizeTester.java index b35d64aca786..83b21c3a7489 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapSizeTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapSizeTester.java @@ -27,7 +27,9 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapSizeTester extends AbstractMapTester { public void testSize() { assertEquals("size():", getNumElements(), getMap().size()); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapToStringTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapToStringTester.java index 429f8f495749..32fab911c1b9 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/MapToStringTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/MapToStringTester.java @@ -39,7 +39,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class MapToStringTester extends AbstractMapTester { public void testToString_minimal() { assertNotNull("toString() should not return null", getMap().toString()); diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/NavigableMapNavigationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/NavigableMapNavigationTester.java index ebb86b6156f8..8d79dce3b0be 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/NavigableMapNavigationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/NavigableMapNavigationTester.java @@ -16,10 +16,13 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_REMOVE; +import static java.util.Collections.sort; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.AbstractMapTester; @@ -41,7 +44,9 @@ * @author Louis Wasserman */ @GwtIncompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class NavigableMapNavigationTester extends AbstractMapTester { private NavigableMap navigableMap; @@ -55,10 +60,10 @@ public void setUp() throws Exception { super.setUp(); navigableMap = (NavigableMap) getMap(); entries = - Helpers.copyToList( + copyToList( getSubjectGenerator() .getSampleElements(getSubjectGenerator().getCollectionSize().getNumElements())); - Collections.sort(entries, Helpers.entryComparator(navigableMap.comparator())); + sort(entries, Helpers.entryComparator(navigableMap.comparator())); // some tests assume SEVERAL == 3 if (entries.size() >= 1) { @@ -73,7 +78,7 @@ public void setUp() throws Exception { /** Resets the contents of navigableMap to have entries a, c, for the navigation tests. */ @SuppressWarnings("unchecked") // Needed to stop Eclipse whining private void resetWithHole() { - Entry[] entries = new Entry[] {a, c}; + Entry[] entries = (Entry[]) new Entry[] {a, c}; super.resetMap(entries); navigableMap = (NavigableMap) getMap(); } @@ -157,16 +162,12 @@ public void testFirst() { @CollectionSize.Require(SEVERAL) public void testPollFirst() { assertEquals(a, navigableMap.pollFirstEntry()); - assertEquals(entries.subList(1, entries.size()), Helpers.copyToList(navigableMap.entrySet())); + assertEquals(entries.subList(1, entries.size()), copyToList(navigableMap.entrySet())); } @MapFeature.Require(absent = SUPPORTS_REMOVE) public void testPollFirstUnsupported() { - try { - navigableMap.pollFirstEntry(); - fail(); - } catch (UnsupportedOperationException e) { - } + assertThrows(UnsupportedOperationException.class, () -> navigableMap.pollFirstEntry()); } @CollectionSize.Require(SEVERAL) @@ -222,18 +223,13 @@ public void testLast() { @CollectionSize.Require(SEVERAL) public void testPollLast() { assertEquals(c, navigableMap.pollLastEntry()); - assertEquals( - entries.subList(0, entries.size() - 1), Helpers.copyToList(navigableMap.entrySet())); + assertEquals(entries.subList(0, entries.size() - 1), copyToList(navigableMap.entrySet())); } @MapFeature.Require(absent = SUPPORTS_REMOVE) @CollectionSize.Require(SEVERAL) public void testPollLastUnsupported() { - try { - navigableMap.pollLastEntry(); - fail(); - } catch (UnsupportedOperationException e) { - } + assertThrows(UnsupportedOperationException.class, () -> navigableMap.pollLastEntry()); } @CollectionSize.Require(SEVERAL) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/NavigableSetNavigationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/NavigableSetNavigationTester.java index 39016169d986..428610b2f1cc 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/NavigableSetNavigationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/NavigableSetNavigationTester.java @@ -16,13 +16,16 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static java.util.Collections.sort; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; @@ -42,7 +45,9 @@ * @author Louis Wasserman */ @GwtIncompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class NavigableSetNavigationTester extends AbstractSetTester { private NavigableSet navigableSet; @@ -56,10 +61,10 @@ public void setUp() throws Exception { super.setUp(); navigableSet = (NavigableSet) getSet(); values = - Helpers.copyToList( + copyToList( getSubjectGenerator() .getSampleElements(getSubjectGenerator().getCollectionSize().getNumElements())); - Collections.sort(values, navigableSet.comparator()); + sort(values, navigableSet.comparator()); // some tests assume SEVERAL == 3 if (values.size() >= 1) { @@ -123,16 +128,12 @@ public void testSingletonSetPollLast() { @CollectionSize.Require(SEVERAL) public void testPollFirst() { assertEquals(a, navigableSet.pollFirst()); - assertEquals(values.subList(1, values.size()), Helpers.copyToList(navigableSet)); + assertEquals(values.subList(1, values.size()), copyToList(navigableSet)); } @CollectionFeature.Require(absent = SUPPORTS_REMOVE) public void testPollFirstUnsupported() { - try { - navigableSet.pollFirst(); - fail(); - } catch (UnsupportedOperationException e) { - } + assertThrows(UnsupportedOperationException.class, () -> navigableSet.pollFirst()); } @CollectionSize.Require(SEVERAL) @@ -204,21 +205,17 @@ public void testHigher() { @CollectionSize.Require(SEVERAL) public void testPollLast() { assertEquals(c, navigableSet.pollLast()); - assertEquals(values.subList(0, values.size() - 1), Helpers.copyToList(navigableSet)); + assertEquals(values.subList(0, values.size() - 1), copyToList(navigableSet)); } @CollectionFeature.Require(absent = SUPPORTS_REMOVE) public void testPollLastUnsupported() { - try { - navigableSet.pollLast(); - fail(); - } catch (UnsupportedOperationException e) { - } + assertThrows(UnsupportedOperationException.class, () -> navigableSet.pollLast()); } @CollectionSize.Require(SEVERAL) public void testDescendingNavigation() { - List descending = new ArrayList(); + List descending = new ArrayList<>(); for (Iterator i = navigableSet.descendingIterator(); i.hasNext(); ) { descending.add(i.next()); } @@ -249,10 +246,10 @@ public void testEmptySubSet() { */ public static Method[] getHoleMethods() { return new Method[] { - Helpers.getMethod(NavigableSetNavigationTester.class, "testLowerHole"), - Helpers.getMethod(NavigableSetNavigationTester.class, "testFloorHole"), - Helpers.getMethod(NavigableSetNavigationTester.class, "testCeilingHole"), - Helpers.getMethod(NavigableSetNavigationTester.class, "testHigherHole"), + getMethod(NavigableSetNavigationTester.class, "testLowerHole"), + getMethod(NavigableSetNavigationTester.class, "testFloorHole"), + getMethod(NavigableSetNavigationTester.class, "testCeilingHole"), + getMethod(NavigableSetNavigationTester.class, "testHigherHole"), }; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueElementTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueElementTester.java index 3275e43c6930..e60dfc80f143 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueElementTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueElementTester.java @@ -20,6 +20,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionFeature; @@ -34,15 +35,13 @@ * @author Jared Levy */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class QueueElementTester extends AbstractQueueTester { @CollectionSize.Require(ZERO) public void testElement_empty() { - try { - getQueue().element(); - fail("emptyQueue.element() should throw"); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> getQueue().element()); expectUnchanged(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueOfferTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueOfferTester.java index 3b17289538c5..4c766c22c958 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueOfferTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueOfferTester.java @@ -18,6 +18,7 @@ import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionFeature; @@ -29,9 +30,10 @@ * * @author Jared Levy */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class QueueOfferTester extends AbstractQueueTester { @CollectionFeature.Require(SUPPORTS_ADD) public void testOffer_supportedNotPresent() { @@ -47,11 +49,7 @@ public void testOffer_nullSupported() { @CollectionFeature.Require(value = SUPPORTS_ADD, absent = ALLOWS_NULL_VALUES) public void testOffer_nullUnsupported() { - try { - getQueue().offer(null); - fail("offer(null) should throw"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getQueue().offer(null)); expectUnchanged(); expectNullMissingWhenNullUnsupported("Should not contain null after unsupported offer(null)"); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueuePeekTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueuePeekTester.java index 641f171cafd2..8fc5c3361972 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueuePeekTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueuePeekTester.java @@ -33,7 +33,9 @@ * @author Jared Levy */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class QueuePeekTester extends AbstractQueueTester { @CollectionSize.Require(ZERO) public void testPeek_empty() { diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueuePollTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueuePollTester.java index 544b14ab627b..a8003a823865 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueuePollTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueuePollTester.java @@ -33,9 +33,10 @@ * * @author Jared Levy */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class QueuePollTester extends AbstractQueueTester { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(ZERO) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueRemoveTester.java index 2b02cea6e7e5..da794ae35cb7 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/QueueRemoveTester.java @@ -21,6 +21,7 @@ import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionFeature; @@ -34,18 +35,15 @@ * * @author Jared Levy */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class QueueRemoveTester extends AbstractQueueTester { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(ZERO) public void testRemove_empty() { - try { - getQueue().remove(); - fail("emptyQueue.remove() should throw"); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> getQueue().remove()); expectUnchanged(); } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/ReflectionFreeAssertThrows.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..69e24cdf1f0e --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/ReflectionFreeAssertThrows.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect.testing.testers; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.testing.testers.TestExceptions.SomeCheckedException; +import com.google.common.collect.testing.testers.TestExceptions.SomeError; +import com.google.common.collect.testing.testers.TestExceptions.SomeOtherCheckedException; +import com.google.common.collect.testing.testers.TestExceptions.SomeUncheckedException; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(SomeCheckedException.class, e -> e instanceof SomeCheckedException) + .put(SomeError.class, e -> e instanceof SomeError) + .put(SomeOtherCheckedException.class, e -> e instanceof SomeOtherCheckedException) + .put(SomeUncheckedException.class, e -> e instanceof SomeUncheckedException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetAddAllTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetAddAllTester.java index 8e917cde761a..757638ab1c16 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetAddAllTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetAddAllTester.java @@ -31,9 +31,10 @@ * * @author Kevin Bourrillion */ -@SuppressWarnings("unchecked") // too many "unchecked generic array creations" @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetAddAllTester extends AbstractSetTester { @CollectionFeature.Require(SUPPORTS_ADD) @CollectionSize.Require(absent = ZERO) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetAddTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetAddTester.java index 197496827e01..b49f371a3ec5 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetAddTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetAddTester.java @@ -16,13 +16,14 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD; import static com.google.common.collect.testing.features.CollectionSize.ZERO; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; @@ -34,8 +35,10 @@ * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetAddTester extends AbstractSetTester { @CollectionFeature.Require(SUPPORTS_ADD) @CollectionSize.Require(absent = ZERO) @@ -57,8 +60,9 @@ public void testAdd_supportedNullPresent() { * Returns the {@link Method} instance for {@link #testAdd_supportedNullPresent()} so that tests * can suppress it. See {@link CollectionAddTester#getAddNullSupportedMethod()} for details. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method getAddSupportedNullPresentMethod() { - return Helpers.getMethod(SetAddTester.class, "testAdd_supportedNullPresent"); + return getMethod(SetAddTester.class, "testAdd_supportedNullPresent"); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetCreationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetCreationTester.java index 0c1be6b7abae..43f29a292ede 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetCreationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetCreationTester.java @@ -20,11 +20,12 @@ import static com.google.common.collect.testing.features.CollectionFeature.REJECTS_DUPLICATES_AT_CREATION; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Arrays; import java.util.List; import org.junit.Ignore; @@ -36,7 +37,9 @@ * @author Chris Povirk */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetCreationTester extends AbstractSetTester { @CollectionFeature.Require(value = ALLOWS_NULL_VALUES, absent = REJECTS_DUPLICATES_AT_CREATION) @CollectionSize.Require(absent = {ZERO, ONE}) @@ -45,7 +48,7 @@ public void testCreateWithDuplicates_nullDuplicatesNotRejected() { array[0] = null; collection = getSubjectGenerator().create(array); - List expectedWithDuplicateRemoved = Arrays.asList(array).subList(1, getNumElements()); + List expectedWithDuplicateRemoved = asList(array).subList(1, getNumElements()); expectContents(expectedWithDuplicateRemoved); } @@ -56,7 +59,7 @@ public void testCreateWithDuplicates_nonNullDuplicatesNotRejected() { array[1] = e0(); collection = getSubjectGenerator().create(array); - List expectedWithDuplicateRemoved = Arrays.asList(array).subList(1, getNumElements()); + List expectedWithDuplicateRemoved = asList(array).subList(1, getNumElements()); expectContents(expectedWithDuplicateRemoved); } @@ -65,11 +68,8 @@ public void testCreateWithDuplicates_nonNullDuplicatesNotRejected() { public void testCreateWithDuplicates_nullDuplicatesRejected() { E[] array = createArrayWithNullElement(); array[0] = null; - try { - collection = getSubjectGenerator().create(array); - fail("Should reject duplicate null elements at creation"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> collection = getSubjectGenerator().create(array)); } @CollectionFeature.Require(REJECTS_DUPLICATES_AT_CREATION) @@ -77,10 +77,7 @@ public void testCreateWithDuplicates_nullDuplicatesRejected() { public void testCreateWithDuplicates_nonNullDuplicatesRejected() { E[] array = createSamplesArray(); array[1] = e0(); - try { - collection = getSubjectGenerator().create(array); - fail("Should reject duplicate non-null elements at creation"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> collection = getSubjectGenerator().create(array)); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetEqualsTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetEqualsTester.java index 839e1737c787..a63bcbceb557 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetEqualsTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetEqualsTester.java @@ -16,10 +16,10 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.MinimalSet; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -33,7 +33,9 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetEqualsTester extends AbstractSetTester { public void testEquals_otherSetWithSameElements() { assertTrue( @@ -91,6 +93,6 @@ public void testEquals_largerSet() { } public void testEquals_list() { - assertFalse("A List should never equal a Set.", getSet().equals(Helpers.copyToList(getSet()))); + assertFalse("A List should never equal a Set.", getSet().equals(copyToList(getSet()))); } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetHashCodeTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetHashCodeTester.java index 5f60327d47af..c462a2c41021 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetHashCodeTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetHashCodeTester.java @@ -16,11 +16,12 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.getMethod; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import java.lang.reflect.Method; @@ -32,13 +33,15 @@ * * @author George van den Driessche */ -@GwtCompatible(emulated = true) -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@GwtCompatible +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetHashCodeTester extends AbstractSetTester { public void testHashCode() { int expectedHashCode = 0; for (E element : getSampleElements()) { - expectedHashCode += ((element == null) ? 0 : element.hashCode()); + expectedHashCode += (element == null) ? 0 : element.hashCode(); } assertEquals( "A Set's hashCode() should be the sum of those of its elements.", @@ -52,7 +55,7 @@ public void testHashCode_containingNull() { Collection elements = getSampleElements(getNumElements() - 1); int expectedHashCode = 0; for (E element : elements) { - expectedHashCode += ((element == null) ? 0 : element.hashCode()); + expectedHashCode += (element == null) ? 0 : element.hashCode(); } elements.add(null); @@ -69,11 +72,12 @@ public void testHashCode_containingNull() { * hashCode()} on the set values so that set tests on unhashable objects can suppress it with * {@code FeatureSpecificTestSuiteBuilder.suppressing()}. */ + @J2ktIncompatible @GwtIncompatible // reflection public static Method[] getHashCodeMethods() { return new Method[] { - Helpers.getMethod(SetHashCodeTester.class, "testHashCode"), - Helpers.getMethod(SetHashCodeTester.class, "testHashCode_containingNull") + getMethod(SetHashCodeTester.class, "testHashCode"), + getMethod(SetHashCodeTester.class, "testHashCode_containingNull") }; } } diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetRemoveTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetRemoveTester.java index 7f1f88dead3f..5e547ef49f45 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/SetRemoveTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/SetRemoveTester.java @@ -31,7 +31,9 @@ * @author George van den Driessche */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SetRemoveTester extends AbstractSetTester { @CollectionFeature.Require(SUPPORTS_REMOVE) @CollectionSize.Require(absent = ZERO) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/SortedMapNavigationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/SortedMapNavigationTester.java index 691fee139bcf..2de44f32cbc1 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/SortedMapNavigationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/SortedMapNavigationTester.java @@ -17,15 +17,17 @@ package com.google.common.collect.testing.testers; import static com.google.common.collect.testing.Helpers.assertEqualInOrder; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.sort; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.AbstractMapTester; import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -42,7 +44,9 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") public class SortedMapNavigationTester extends AbstractMapTester { private SortedMap navigableMap; @@ -54,10 +58,10 @@ public void setUp() throws Exception { super.setUp(); navigableMap = (SortedMap) getMap(); List> entries = - Helpers.copyToList( + copyToList( getSubjectGenerator() .getSampleElements(getSubjectGenerator().getCollectionSize().getNumElements())); - Collections.sort(entries, Helpers.entryComparator(navigableMap.comparator())); + sort(entries, Helpers.entryComparator(navigableMap.comparator())); // some tests assume SEVERAL == 3 if (entries.size() >= 1) { @@ -70,20 +74,12 @@ public void setUp() throws Exception { @CollectionSize.Require(ZERO) public void testEmptyMapFirst() { - try { - navigableMap.firstKey(); - fail(); - } catch (NoSuchElementException e) { - } + assertThrows(NoSuchElementException.class, () -> navigableMap.firstKey()); } @CollectionSize.Require(ZERO) public void testEmptyMapLast() { - try { - assertNull(navigableMap.lastKey()); - fail(); - } catch (NoSuchElementException e) { - } + assertThrows(NoSuchElementException.class, () -> assertNull(navigableMap.lastKey())); } @CollectionSize.Require(ONE) @@ -118,10 +114,10 @@ public void testTailMapInclusive() { public void testHeadMap() { List> entries = - Helpers.copyToList( + copyToList( getSubjectGenerator() .getSampleElements(getSubjectGenerator().getCollectionSize().getNumElements())); - Collections.sort(entries, Helpers.entryComparator(navigableMap.comparator())); + sort(entries, Helpers.entryComparator(navigableMap.comparator())); for (int i = 0; i < entries.size(); i++) { assertEqualInOrder( entries.subList(0, i), navigableMap.headMap(entries.get(i).getKey()).entrySet()); @@ -130,10 +126,10 @@ public void testHeadMap() { public void testTailMap() { List> entries = - Helpers.copyToList( + copyToList( getSubjectGenerator() .getSampleElements(getSubjectGenerator().getCollectionSize().getNumElements())); - Collections.sort(entries, Helpers.entryComparator(navigableMap.comparator())); + sort(entries, Helpers.entryComparator(navigableMap.comparator())); for (int i = 0; i < entries.size(); i++) { assertEqualInOrder( entries.subList(i, entries.size()), @@ -143,10 +139,10 @@ public void testTailMap() { public void testSubMap() { List> entries = - Helpers.copyToList( + copyToList( getSubjectGenerator() .getSampleElements(getSubjectGenerator().getCollectionSize().getNumElements())); - Collections.sort(entries, Helpers.entryComparator(navigableMap.comparator())); + sort(entries, Helpers.entryComparator(navigableMap.comparator())); for (int i = 0; i < entries.size(); i++) { for (int j = i + 1; j < entries.size(); j++) { assertEqualInOrder( @@ -158,16 +154,11 @@ public void testSubMap() { @CollectionSize.Require(SEVERAL) public void testSubMapIllegal() { - try { - navigableMap.subMap(c.getKey(), a.getKey()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> navigableMap.subMap(c.getKey(), a.getKey())); } @CollectionSize.Require(absent = ZERO) public void testOrderedByComparator() { - @SuppressWarnings("unchecked") Comparator comparator = navigableMap.comparator(); if (comparator == null) { comparator = diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/SortedSetNavigationTester.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/SortedSetNavigationTester.java index bf5ac223a9c5..7023cf47b980 100644 --- a/android/guava-testlib/src/com/google/common/collect/testing/testers/SortedSetNavigationTester.java +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/SortedSetNavigationTester.java @@ -16,17 +16,20 @@ package com.google.common.collect.testing.testers; +import static com.google.common.collect.testing.Helpers.copyToList; import static com.google.common.collect.testing.features.CollectionSize.ONE; import static com.google.common.collect.testing.features.CollectionSize.SEVERAL; import static com.google.common.collect.testing.features.CollectionSize.ZERO; +import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.sort; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.features.CollectionSize; -import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.SortedSet; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; /** @@ -37,24 +40,27 @@ * @author Louis Wasserman */ @GwtCompatible -@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. -public class SortedSetNavigationTester extends AbstractSetTester { +@Ignore("test runners must not instantiate and run this directly, only via suites we build") +// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@NullMarked +public class SortedSetNavigationTester extends AbstractSetTester { private SortedSet sortedSet; private List values; - private E a; - private E b; - private E c; + private @Nullable E a; + private @Nullable E b; + private @Nullable E c; @Override public void setUp() throws Exception { super.setUp(); sortedSet = (SortedSet) getSet(); values = - Helpers.copyToList( + copyToList( getSubjectGenerator() .getSampleElements(getSubjectGenerator().getCollectionSize().getNumElements())); - Collections.sort(values, sortedSet.comparator()); + sort(values, sortedSet.comparator()); // some tests assume SEVERAL == 3 if (values.size() >= 1) { @@ -68,20 +74,12 @@ public void setUp() throws Exception { @CollectionSize.Require(ZERO) public void testEmptySetFirst() { - try { - sortedSet.first(); - fail(); - } catch (NoSuchElementException e) { - } + assertThrows(NoSuchElementException.class, () -> sortedSet.first()); } @CollectionSize.Require(ZERO) public void testEmptySetLast() { - try { - sortedSet.last(); - fail(); - } catch (NoSuchElementException e) { - } + assertThrows(NoSuchElementException.class, () -> sortedSet.last()); } @CollectionSize.Require(ONE) diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/TestExceptions.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/TestExceptions.java new file mode 100644 index 000000000000..86b8b5dab76e --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/TestExceptions.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing.testers; + +import com.google.common.annotations.GwtCompatible; + +/** Exception classes for use in tests. */ +@GwtCompatible +final class TestExceptions { + static final class SomeError extends Error {} + + static final class SomeCheckedException extends Exception {} + + static final class SomeOtherCheckedException extends Exception {} + + static final class YetAnotherCheckedException extends Exception {} + + static final class SomeUncheckedException extends RuntimeException {} + + static final class SomeChainingException extends RuntimeException { + public SomeChainingException(Throwable cause) { + super(cause); + } + } + + private TestExceptions() {} +} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/testers/package-info.java b/android/guava-testlib/src/com/google/common/collect/testing/testers/package-info.java new file mode 100644 index 000000000000..fa9439065965 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/testers/package-info.java @@ -0,0 +1,2 @@ +@com.google.errorprone.annotations.CheckReturnValue +package com.google.common.collect.testing.testers; diff --git a/android/guava-testlib/src/com/google/common/escape/testing/EscaperAsserts.java b/android/guava-testlib/src/com/google/common/escape/testing/EscaperAsserts.java index 3920afe4c137..31ac2015dd04 100644 --- a/android/guava-testlib/src/com/google/common/escape/testing/EscaperAsserts.java +++ b/android/guava-testlib/src/com/google/common/escape/testing/EscaperAsserts.java @@ -18,7 +18,6 @@ import static com.google.common.escape.Escapers.computeReplacement; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.escape.CharEscaper; import com.google.common.escape.Escaper; @@ -32,7 +31,6 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible public final class EscaperAsserts { private EscaperAsserts() {} diff --git a/android/guava-testlib/src/com/google/common/escape/testing/package-info.java b/android/guava-testlib/src/com/google/common/escape/testing/package-info.java index 869884734e45..6b50614695c1 100644 --- a/android/guava-testlib/src/com/google/common/escape/testing/package-info.java +++ b/android/guava-testlib/src/com/google/common/escape/testing/package-info.java @@ -17,7 +17,7 @@ /** * Testing utilities for use in tests of {@code com.google.common.escape}. * - *

This package is a part of the open-source Guava + *

This package is a part of the open-source Guava * library. */ @CheckReturnValue diff --git a/android/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java b/android/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java index 962b15f6d677..ee900f8b45e0 100644 --- a/android/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java +++ b/android/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java @@ -20,30 +20,28 @@ import static com.google.common.base.Predicates.not; import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; import com.google.common.reflect.ClassPath; import com.google.common.testing.NullPointerTester.Visibility; import com.google.j2objc.annotations.J2ObjCIncompatible; import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; -import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.junit.Test; @@ -101,10 +99,10 @@ * @author Ben Yu * @since 14.0 */ -@Beta // TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass // Note: @Test annotations are deliberate, as some subclasses specify @RunWith(JUnit4). @GwtIncompatible +@J2ktIncompatible @J2ObjCIncompatible // com.google.common.reflect.ClassPath public abstract class AbstractPackageSanityTests extends TestCase { @@ -116,12 +114,7 @@ public abstract class AbstractPackageSanityTests extends TestCase { * @since 19.0 */ public static final Predicate> UNDERSCORE_IN_NAME = - new Predicate>() { - @Override - public boolean apply(Class c) { - return c.getSimpleName().contains("_"); - } - }; + (Class c) -> c.getSimpleName().contains("_"); /* The names of the expected method that tests null checks. */ private static final ImmutableList NULL_TEST_METHOD_NAMES = @@ -152,12 +145,7 @@ public boolean apply(Class c) { private final ClassSanityTester tester = new ClassSanityTester(); private Visibility visibility = Visibility.PACKAGE; private Predicate> classFilter = - new Predicate>() { - @Override - public boolean apply(Class cls) { - return visibility.isVisible(cls.getModifiers()); - } - }; + (Class cls) -> visibility.isVisible(cls.getModifiers()); /** * Restricts the sanity tests for public API only. By default, package-private API are also @@ -243,6 +231,15 @@ public void testSerializable() throws Exception { @Test public void testNulls() throws Exception { for (Class classToTest : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) { + if (classToTest.getSimpleName().equals("ReflectionFreeAssertThrows")) { + /* + * These classes handle null properly but throw IllegalArgumentException for the default + * Class argument that this test uses. Normally we'd fix that by declaring a + * ReflectionFreeAssertThrowsTest with a testNulls method, but that's annoying to have to do + * for a package-private utility class. So we skip the class entirely instead. + */ + continue; + } try { tester.doTestNulls(classToTest, visibility); } catch (Throwable e) { @@ -317,7 +314,7 @@ protected final void ignoreClasses(Predicate> condition) { this.classFilter = and(this.classFilter, not(condition)); } - private static AssertionFailedError sanityError( + private static AssertionError sanityError( Class cls, List explicitTestNames, String description, Throwable e) { String message = String.format( @@ -328,14 +325,12 @@ private static AssertionFailedError sanityError( cls, explicitTestNames.get(0), cls.getName()); - AssertionFailedError error = new AssertionFailedError(message); - error.initCause(e); - return error; + return new AssertionError(message, e); } /** * Finds the classes not ending with a test suffix and not covered by an explicit test whose name - * is {@code explicitTestName}. + * is {@code explicitTestNames}. */ @VisibleForTesting List> findClassesToTest( @@ -347,7 +342,7 @@ List> findClassesToTest( } // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...] Multimap, Class> testClasses = HashMultimap.create(); - LinkedHashSet> candidateClasses = Sets.newLinkedHashSet(); + LinkedHashSet> candidateClasses = new LinkedHashSet<>(); for (Class cls : classes) { Optional testedClassName = TEST_SUFFIX.chop(cls.getName()); if (testedClassName.isPresent()) { @@ -359,7 +354,7 @@ List> findClassesToTest( candidateClasses.add(cls); } } - List> result = Lists.newArrayList(); + List> result = new ArrayList<>(); NEXT_CANDIDATE: for (Class candidate : Iterables.filter(candidateClasses, classFilter)) { for (Class testClass : testClasses.get(candidate)) { @@ -374,7 +369,7 @@ List> findClassesToTest( } private List> loadClassesInPackage() throws IOException { - List> classes = Lists.newArrayList(); + List> classes = new ArrayList<>(); String packageName = getClass().getPackage().getName(); for (ClassPath.ClassInfo classInfo : ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) { @@ -415,8 +410,8 @@ private static boolean isEqualsDefined(Class cls) { abstract static class Chopper { - final Chopper or(final Chopper you) { - final Chopper i = this; + final Chopper or(Chopper you) { + Chopper i = this; return new Chopper() { @Override Optional chop(String str) { @@ -427,7 +422,7 @@ Optional chop(String str) { abstract Optional chop(String str); - static Chopper suffix(final String suffix) { + static Chopper suffix(String suffix) { return new Chopper() { @Override Optional chop(String str) { diff --git a/android/guava-testlib/src/com/google/common/testing/ArbitraryInstances.java b/android/guava-testlib/src/com/google/common/testing/ArbitraryInstances.java index 01904d6c220c..0ecc76ae861a 100644 --- a/android/guava-testlib/src/com/google/common/testing/ArbitraryInstances.java +++ b/android/guava-testlib/src/com/google/common/testing/ArbitraryInstances.java @@ -17,11 +17,14 @@ package com.google.common.testing; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterators.peekingIterator; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.SECONDS; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.CharMatcher; -import com.google.common.base.Charsets; import com.google.common.base.Defaults; import com.google.common.base.Equivalence; import com.google.common.base.Joiner; @@ -46,7 +49,6 @@ import com.google.common.collect.ImmutableSortedMultiset; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Iterators; import com.google.common.collect.ListMultimap; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; @@ -74,6 +76,7 @@ import com.google.common.primitives.Primitives; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; +import com.google.errorprone.annotations.Keep; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -142,7 +145,8 @@ import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Supplies an arbitrary "default" instance for a wide range of types, often useful in testing @@ -166,8 +170,9 @@ * @author Ben Yu * @since 12.0 */ -@Beta @GwtIncompatible +@J2ktIncompatible +@NullMarked public final class ArbitraryInstances { private static final Ordering BY_FIELD_NAME = @@ -181,9 +186,9 @@ public int compare(Field left, Field right) { /** * Returns a new {@code MatchResult} that corresponds to a successful match. Apache Harmony (used * in Android) requires a successful match in order to generate a {@code MatchResult}: - * http://goo.gl/5VQFmC + * https://cs.android.com/android/platform/superproject/+/android-2.3.7_r1:libcore/luni/src/main/java/java/util/regex/Matcher.java;l=550;drc=5850271b4ab93ebc27c1d49169a348c6be3c7f04 */ - private static MatchResult newMatchResult() { + private static MatchResult createMatchResult() { Matcher matcher = Pattern.compile(".").matcher("X"); matcher.find(); return matcher.toMatchResult(); @@ -201,9 +206,9 @@ private static MatchResult newMatchResult() { .put(CharSequence.class, "") .put(String.class, "") .put(Pattern.class, Pattern.compile("")) - .put(MatchResult.class, newMatchResult()) - .put(TimeUnit.class, TimeUnit.SECONDS) - .put(Charset.class, Charsets.UTF_8) + .put(MatchResult.class, createMatchResult()) + .put(TimeUnit.class, SECONDS) + .put(Charset.class, UTF_8) .put(Currency.class, Currency.getInstance(Locale.US)) .put(Locale.class, Locale.US) .put(UUID.class, UUID.randomUUID()) @@ -234,10 +239,10 @@ private static MatchResult newMatchResult() { .put(ByteSource.class, ByteSource.empty()) .put(CharSource.class, CharSource.empty()) .put(ByteSink.class, NullByteSink.INSTANCE) - .put(CharSink.class, NullByteSink.INSTANCE.asCharSink(Charsets.UTF_8)) + .put(CharSink.class, NullByteSink.INSTANCE.asCharSink(UTF_8)) // All collections are immutable empty. So safe for any type parameter. .put(Iterator.class, ImmutableSet.of().iterator()) - .put(PeekingIterator.class, Iterators.peekingIterator(ImmutableSet.of().iterator())) + .put(PeekingIterator.class, peekingIterator(ImmutableSet.of().iterator())) .put(ListIterator.class, ImmutableList.of().listIterator()) .put(Iterable.class, ImmutableSet.of()) .put(Collection.class, ImmutableList.of()) @@ -327,8 +332,7 @@ private static void setImplementation(Class type, Class impl } @SuppressWarnings("unchecked") // it's a subtype map - @NullableDecl - private static Class getImplementation(Class type) { + private static @Nullable Class getImplementation(Class type) { return (Class) implementations.get(type); } @@ -338,8 +342,7 @@ private static Class getImplementation(Class type) { * Returns an arbitrary instance for {@code type}, or {@code null} if no arbitrary instance can be * determined. */ - @NullableDecl - public static T get(Class type) { + public static @Nullable T get(Class type) { T defaultValue = DEFAULTS.getInstance(type); if (defaultValue != null) { return defaultValue; @@ -350,7 +353,7 @@ public static T get(Class type) { } if (type.isEnum()) { T[] enumConstants = type.getEnumConstants(); - return (enumConstants.length == 0) ? null : enumConstants[0]; + return (enumConstants == null || enumConstants.length == 0) ? null : enumConstants[0]; } if (type.isArray()) { return createEmptyArray(type); @@ -362,7 +365,7 @@ public static T get(Class type) { if (Modifier.isAbstract(type.getModifiers()) || !Modifier.isPublic(type.getModifiers())) { return arbitraryConstantInstanceOrNull(type); } - final Constructor constructor; + Constructor constructor; try { constructor = type.getConstructor(); } catch (NoSuchMethodException e) { @@ -371,14 +374,7 @@ public static T get(Class type) { constructor.setAccessible(true); // accessibility check is too slow try { return constructor.newInstance(); - /* - * Do not merge the 2 catch blocks below. javac would infer a type of - * ReflectiveOperationException, which Animal Sniffer would reject. (Old versions of - * Android don't *seem* to mind, but there might be edge cases of which we're unaware.) - */ - } catch (InstantiationException impossible) { - throw new AssertionError(impossible); - } catch (IllegalAccessException impossible) { + } catch (InstantiationException | IllegalAccessException impossible) { throw new AssertionError(impossible); } catch (InvocationTargetException e) { logger.log(Level.WARNING, "Exception while invoking default constructor.", e.getCause()); @@ -386,8 +382,7 @@ public static T get(Class type) { } } - @NullableDecl - private static T arbitraryConstantInstanceOrNull(Class type) { + private static @Nullable T arbitraryConstantInstanceOrNull(Class type) { Field[] fields = type.getDeclaredFields(); Arrays.sort(fields, BY_FIELD_NAME); for (Field field : fields) { @@ -411,47 +406,60 @@ private static T arbitraryConstantInstanceOrNull(Class type) { } private static T createEmptyArray(Class arrayType) { - return arrayType.cast(Array.newInstance(arrayType.getComponentType(), 0)); + // getComponentType() is non-null because we call createEmptyArray only with an array type. + return arrayType.cast(Array.newInstance(requireNonNull(arrayType.getComponentType()), 0)); } // Internal implementations of some classes, with public default constructor that get() needs. private static final class Dummies { + @Keep public static final class InMemoryPrintStream extends PrintStream { + @Keep public InMemoryPrintStream() { super(new ByteArrayOutputStream()); } } + @Keep public static final class InMemoryPrintWriter extends PrintWriter { + @Keep public InMemoryPrintWriter() { super(new StringWriter()); } } + @Keep public static final class DeterministicRandom extends Random { + @Keep public DeterministicRandom() { super(0); } } + @Keep public static final class DummyScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor { + @Keep public DummyScheduledThreadPoolExecutor() { super(1); } } + @Keep public static final class DummyCountDownLatch extends CountDownLatch { + @Keep public DummyCountDownLatch() { super(0); } } + @Keep public static final class DummyRunnable implements Runnable, Serializable { @Override public void run() {} } + @Keep public static final class DummyThreadFactory implements ThreadFactory, Serializable { @Override public Thread newThread(Runnable r) { @@ -459,6 +467,7 @@ public Thread newThread(Runnable r) { } } + @Keep public static final class DummyExecutor implements Executor, Serializable { @Override public void execute(Runnable command) {} @@ -497,11 +506,13 @@ private Object readResolve() { } // Always equal is a valid total ordering. And it works for any Object. - private static final class AlwaysEqual extends Ordering implements Serializable { + private static final class AlwaysEqual extends Ordering<@Nullable Object> + implements Serializable { private static final AlwaysEqual INSTANCE = new AlwaysEqual(); @Override - public int compare(Object o1, Object o2) { + @SuppressWarnings("UnusedVariable") // intentionally weird Comparator + public int compare(@Nullable Object o1, @Nullable Object o2) { return 0; } diff --git a/android/guava-testlib/src/com/google/common/testing/ClassSanityTester.java b/android/guava-testlib/src/com/google/common/testing/ClassSanityTester.java index 55f167eddd86..d96ea43a563c 100644 --- a/android/guava-testlib/src/com/google/common/testing/ClassSanityTester.java +++ b/android/guava-testlib/src/com/google/common/testing/ClassSanityTester.java @@ -21,38 +21,38 @@ import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.testing.NullPointerTester.isNullable; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; -import com.google.common.base.Objects; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.MutableClassToInstanceMap; import com.google.common.collect.Ordering; -import com.google.common.collect.Sets; -import com.google.common.primitives.Ints; import com.google.common.reflect.Invokable; import com.google.common.reflect.Parameter; import com.google.common.reflect.Reflection; import com.google.common.reflect.TypeToken; import com.google.common.testing.NullPointerTester.Visibility; -import com.google.common.testing.RelationshipTester.Item; -import com.google.common.testing.RelationshipTester.ItemReporter; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import junit.framework.Assert; import junit.framework.AssertionFailedError; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tester that runs automated sanity tests for any given class. A typical use case is to test static @@ -78,8 +78,10 @@ * @author Ben Yu * @since 14.0 */ -@Beta @GwtIncompatible +@J2ktIncompatible +@NullUnmarked +@SuppressWarnings("nullness") public final class ClassSanityTester { private static final Ordering> BY_METHOD_NAME = @@ -102,7 +104,7 @@ public int compare(Invokable left, Invokable right) { new Ordering>() { @Override public int compare(Invokable left, Invokable right) { - return Ints.compare(left.getParameters().size(), right.getParameters().size()); + return Integer.compare(left.getParameters().size(), right.getParameters().size()); } }; @@ -133,6 +135,7 @@ public ClassSanityTester() { * Object#equals} because more than one sample instances are needed for testing inequality. To set * distinct values for equality testing, use {@link #setDistinctValues} instead. */ + @CanIgnoreReturnValue public ClassSanityTester setDefault(Class type, T value) { nullPointerTester.setDefault(type, value); defaultValues.putInstance(type, value); @@ -154,11 +157,12 @@ public ClassSanityTester setDefault(Class type, T value) { * @return this tester instance * @since 17.0 */ + @CanIgnoreReturnValue public ClassSanityTester setDistinctValues(Class type, T value1, T value2) { checkNotNull(type); checkNotNull(value1); checkNotNull(value2); - checkArgument(!Objects.equal(value1, value2), "Duplicate value provided."); + checkArgument(!Objects.equals(value1, value2), "Duplicate value provided."); distinctValues.replaceValues(type, ImmutableList.of(value1, value2)); setDefault(type, value1); return this; @@ -198,7 +202,9 @@ public void testNulls(Class cls) { } void doTestNulls(Class cls, Visibility visibility) - throws ParameterNotInstantiableException, IllegalAccessException, InvocationTargetException, + throws ParameterNotInstantiableException, + IllegalAccessException, + InvocationTargetException, FactoryMethodReturnsNullException { if (!Modifier.isAbstract(cls.getModifiers())) { nullPointerTester.testConstructors(cls, visibility); @@ -257,7 +263,7 @@ private boolean hasInstanceMethodToTestNulls(Class c, Visibility visibility) *
    * public class FooTest {
    *
-   *   private static class FooFactoryForTest {
+   *   private static final class FooFactoryForTest {
    *     public static Foo create(String a, String b, int c, boolean d) {
    *       return Foo.builder()
    *           .setA(a)
@@ -290,8 +296,11 @@ public void testEquals(Class cls) {
   }
 
   void doTestEquals(Class cls)
-      throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
-          IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
+      throws ParameterNotInstantiableException,
+          ParameterHasNoDistinctValueException,
+          IllegalAccessException,
+          InvocationTargetException,
+          FactoryMethodReturnsNullException {
     if (cls.isEnum()) {
       return;
     }
@@ -300,10 +309,10 @@ void doTestEquals(Class cls)
       return;
     }
     int numberOfParameters = factories.get(0).getParameters().size();
-    List paramErrors = Lists.newArrayList();
-    List distinctValueErrors = Lists.newArrayList();
-    List instantiationExceptions = Lists.newArrayList();
-    List nullErrors = Lists.newArrayList();
+    List paramErrors = new ArrayList<>();
+    List distinctValueErrors = new ArrayList<>();
+    List instantiationExceptions = new ArrayList<>();
+    List nullErrors = new ArrayList<>();
     // Try factories with the greatest number of parameters.
     for (Invokable factory : factories) {
       if (factory.getParameters().size() == numberOfParameters) {
@@ -334,22 +343,23 @@ void doTestEquals(Class cls)
    * @return The instantiated instance, or {@code null} if the class has no non-private constructor
    *     or factory method to be constructed.
    */
-  @NullableDecl
-   T instantiate(Class cls)
-      throws ParameterNotInstantiableException, IllegalAccessException, InvocationTargetException,
+   @Nullable T instantiate(Class cls)
+      throws ParameterNotInstantiableException,
+          IllegalAccessException,
+          InvocationTargetException,
           FactoryMethodReturnsNullException {
     if (cls.isEnum()) {
       T[] constants = cls.getEnumConstants();
-      if (constants.length > 0) {
+      if (constants != null && constants.length > 0) {
         return constants[0];
       } else {
         return null;
       }
     }
     TypeToken type = TypeToken.of(cls);
-    List paramErrors = Lists.newArrayList();
-    List instantiationExceptions = Lists.newArrayList();
-    List nullErrors = Lists.newArrayList();
+    List paramErrors = new ArrayList<>();
+    List instantiationExceptions = new ArrayList<>();
+    List nullErrors = new ArrayList<>();
     for (Invokable factory : getFactories(type)) {
       T instance;
       try {
@@ -383,8 +393,7 @@  T instantiate(Class cls)
    *     class, preventing its methods from being accessible.
    * @throws InvocationTargetException if a static method threw exception.
    */
-  @NullableDecl
-  private  T instantiate(Invokable factory)
+  private  @Nullable T instantiate(Invokable factory)
       throws ParameterNotInstantiableException, InvocationTargetException, IllegalAccessException {
     return invoke(factory, getDummyArguments(factory));
   }
@@ -407,7 +416,7 @@ public FactoryMethodReturnValueTester forAllPublicStaticMethods(Class cls) {
 
   /** Runs sanity tests against return values of static factory methods declared by a class. */
   public final class FactoryMethodReturnValueTester {
-    private final Set packagesToTest = Sets.newHashSet();
+    private final Set packagesToTest = new HashSet<>();
     private final Class declaringClass;
     private final ImmutableList> factories;
     private final String factoryMethodsDescription;
@@ -429,6 +438,7 @@ private FactoryMethodReturnValueTester(
      *
      * @return this tester object
      */
+    @CanIgnoreReturnValue
     public FactoryMethodReturnValueTester thatReturn(Class returnType) {
       this.returnTypeToTest = returnType;
       return this;
@@ -442,6 +452,7 @@ public FactoryMethodReturnValueTester thatReturn(Class returnType) {
      *
      * @return this tester
      */
+    @CanIgnoreReturnValue
     public FactoryMethodReturnValueTester testNulls() throws Exception {
       for (Invokable factory : getFactoriesToTest()) {
         Object instance = instantiate(factory);
@@ -450,10 +461,7 @@ public FactoryMethodReturnValueTester testNulls() throws Exception {
           try {
             nullPointerTester.testAllPublicInstanceMethods(instance);
           } catch (AssertionError e) {
-            AssertionError error =
-                new AssertionFailedError("Null check failed on return value of " + factory);
-            error.initCause(e);
-            throw error;
+            throw new AssertionError("Null check failed on return value of " + factory, e);
           }
         }
       }
@@ -470,6 +478,7 @@ public FactoryMethodReturnValueTester testNulls() throws Exception {
      *
      * @return this tester
      */
+    @CanIgnoreReturnValue
     public FactoryMethodReturnValueTester testEquals() throws Exception {
       for (Invokable factory : getFactoriesToTest()) {
         try {
@@ -489,17 +498,17 @@ public FactoryMethodReturnValueTester testEquals() throws Exception {
      *
      * @return this tester
      */
+    @CanIgnoreReturnValue
+    @SuppressWarnings("CatchingUnchecked") // sneaky checked exception
     public FactoryMethodReturnValueTester testSerializable() throws Exception {
       for (Invokable factory : getFactoriesToTest()) {
         Object instance = instantiate(factory);
         if (instance != null) {
           try {
             SerializableTester.reserialize(instance);
-          } catch (RuntimeException e) {
-            AssertionError error =
-                new AssertionFailedError("Serialization failed on return value of " + factory);
-            error.initCause(e.getCause());
-            throw error;
+          } catch (Exception e) { // sneaky checked exception
+            throw new AssertionError(
+                "Serialization failed on return value of " + factory, e.getCause());
           }
         }
       }
@@ -514,6 +523,8 @@ public FactoryMethodReturnValueTester testSerializable() throws Exception {
      *
      * @return this tester
      */
+    @CanIgnoreReturnValue
+    @SuppressWarnings("CatchingUnchecked") // sneaky checked exception
     public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Exception {
       for (Invokable factory : getFactoriesToTest()) {
         try {
@@ -525,17 +536,12 @@ public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Excepti
         if (instance != null) {
           try {
             SerializableTester.reserializeAndAssert(instance);
-          } catch (RuntimeException e) {
-            AssertionError error =
-                new AssertionFailedError("Serialization failed on return value of " + factory);
-            error.initCause(e.getCause());
-            throw error;
+          } catch (Exception e) { // sneaky checked exception
+            throw new AssertionError(
+                "Serialization failed on return value of " + factory, e.getCause());
           } catch (AssertionFailedError e) {
-            AssertionError error =
-                new AssertionFailedError(
-                    "Return value of " + factory + " reserialized to an unequal value");
-            error.initCause(e);
-            throw error;
+            throw new AssertionError(
+                "Return value of " + factory + " reserialized to an unequal value", e);
           }
         }
       }
@@ -563,12 +569,15 @@ public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Excepti
     }
   }
 
-  private void testEqualsUsing(final Invokable factory)
-      throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
-          IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
+  private void testEqualsUsing(Invokable factory)
+      throws ParameterNotInstantiableException,
+          ParameterHasNoDistinctValueException,
+          IllegalAccessException,
+          InvocationTargetException,
+          FactoryMethodReturnsNullException {
     List params = factory.getParameters();
     List argGenerators = Lists.newArrayListWithCapacity(params.size());
-    List args = Lists.newArrayListWithCapacity(params.size());
+    List<@Nullable Object> args = Lists.newArrayListWithCapacity(params.size());
     for (Parameter param : params) {
       FreshValueGenerator generator = newFreshValueGenerator();
       argGenerators.add(generator);
@@ -577,26 +586,23 @@ private void testEqualsUsing(final Invokable factory)
     Object instance = createInstance(factory, args);
     List equalArgs = generateEqualFactoryArguments(factory, params, args);
     // Each group is a List of items, each item has a list of factory args.
-    final List>> argGroups = Lists.newArrayList();
+    List>> argGroups = new ArrayList<>();
     argGroups.add(ImmutableList.of(args, equalArgs));
     EqualsTester tester =
         new EqualsTester(
-            new ItemReporter() {
-              @Override
-              String reportItem(Item item) {
-                List factoryArgs = argGroups.get(item.groupNumber).get(item.itemNumber);
-                return factory.getName()
-                    + "("
-                    + Joiner.on(", ").useForNull("null").join(factoryArgs)
-                    + ")";
-              }
+            /* itemReporter= */ item -> {
+              List factoryArgs = argGroups.get(item.groupNumber).get(item.itemNumber);
+              return factory.getName()
+                  + "("
+                  + Joiner.on(", ").useForNull("null").join(factoryArgs)
+                  + ")";
             });
     tester.addEqualityGroup(instance, createInstance(factory, equalArgs));
     for (int i = 0; i < params.size(); i++) {
-      List newArgs = Lists.newArrayList(args);
+      List newArgs = new ArrayList<>(args);
       Object newArg = argGenerators.get(i).generateFresh(params.get(i).getType());
 
-      if (newArg == null || Objects.equal(args.get(i), newArg)) {
+      if (newArg == null || Objects.equals(args.get(i), newArg)) {
         if (params.get(i).getType().getRawType().isEnum()) {
           continue; // Nothing better we can do if it's single-value enum
         }
@@ -615,9 +621,11 @@ String reportItem(Item item) {
    */
   private List generateEqualFactoryArguments(
       Invokable factory, List params, List args)
-      throws ParameterNotInstantiableException, FactoryMethodReturnsNullException,
-          InvocationTargetException, IllegalAccessException {
-    List equalArgs = Lists.newArrayList(args);
+      throws ParameterNotInstantiableException,
+          FactoryMethodReturnsNullException,
+          InvocationTargetException,
+          IllegalAccessException {
+    List equalArgs = new ArrayList<>(args);
     for (int i = 0; i < args.size(); i++) {
       Parameter param = params.get(i);
       Object arg = args.get(i);
@@ -625,8 +633,8 @@ private List generateEqualFactoryArguments(
       // Two newFreshValueGenerator() instances should normally generate equal value sequence.
       Object shouldBeEqualArg = generateDummyArg(param, newFreshValueGenerator());
       if (arg != shouldBeEqualArg
-          && Objects.equal(arg, shouldBeEqualArg)
-          && hashCodeInsensitiveToArgReference(factory, args, i, shouldBeEqualArg)
+          && Objects.equals(arg, shouldBeEqualArg)
+          && hashCodeInsensitiveToArgReference(factory, args, i, checkNotNull(shouldBeEqualArg))
           && hashCodeInsensitiveToArgReference(
               factory, args, i, generateDummyArg(param, newFreshValueGenerator()))) {
         // If the implementation uses identityHashCode(), referential equality is
@@ -641,7 +649,7 @@ factory, args, i, generateDummyArg(param, newFreshValueGenerator()))) {
   private static boolean hashCodeInsensitiveToArgReference(
       Invokable factory, List args, int i, Object alternateArg)
       throws FactoryMethodReturnsNullException, InvocationTargetException, IllegalAccessException {
-    List tentativeArgs = Lists.newArrayList(args);
+    List tentativeArgs = new ArrayList<>(args);
     tentativeArgs.set(i, alternateArg);
     return createInstance(factory, tentativeArgs).hashCode()
         == createInstance(factory, args).hashCode();
@@ -654,7 +662,7 @@ private FreshValueGenerator newFreshValueGenerator() {
     FreshValueGenerator generator =
         new FreshValueGenerator() {
           @Override
-          Object interfaceMethodCalled(Class interfaceType, Method method) {
+          @Nullable Object interfaceMethodCalled(Class interfaceType, Method method) {
             return getDummyValue(TypeToken.of(interfaceType).method(method).getReturnType());
           }
         };
@@ -664,8 +672,7 @@ Object interfaceMethodCalled(Class interfaceType, Method method) {
     return generator;
   }
 
-  @NullableDecl
-  private static Object generateDummyArg(Parameter param, FreshValueGenerator generator)
+  private static @Nullable Object generateDummyArg(Parameter param, FreshValueGenerator generator)
       throws ParameterNotInstantiableException {
     if (isNullable(param)) {
       return null;
@@ -685,7 +692,7 @@ private static  void throwFirst(List exceptions) throws
 
   /** Factories with the least number of parameters are listed first. */
   private static  ImmutableList> getFactories(TypeToken type) {
-    List> factories = Lists.newArrayList();
+    List> factories = new ArrayList<>();
     for (Method method : type.getRawType().getDeclaredMethods()) {
       Invokable invokable = type.method(method);
       if (!invokable.isPrivate()
@@ -708,9 +715,9 @@ private static  void throwFirst(List exceptions) throws
     for (Invokable factory : factories) {
       factory.setAccessible(true);
     }
-    // Sorts methods/constructors with least number of parameters first since it's likely easier to
-    // fill dummy parameter values for them. Ties are broken by name then by the string form of the
-    // parameter list.
+    // Sorts methods/constructors with the least number of parameters first since it's likely easier
+    // to fill dummy parameter values for them. Ties are broken by name then by the string form of
+    // the parameter list.
     return BY_NUMBER_OF_PARAMETERS
         .compound(BY_METHOD_NAME)
         .compound(BY_PARAMETERS)
@@ -719,7 +726,7 @@ private static  void throwFirst(List exceptions) throws
 
   private List getDummyArguments(Invokable invokable)
       throws ParameterNotInstantiableException {
-    List args = Lists.newArrayList();
+    List args = new ArrayList<>();
     for (Parameter param : invokable.getParameters()) {
       if (isNullable(param)) {
         args.add(null);
@@ -734,7 +741,7 @@ private List getDummyArguments(Invokable invokable)
     return args;
   }
 
-  private  T getDummyValue(TypeToken type) {
+  private  @Nullable T getDummyValue(TypeToken type) {
     Class rawType = type.getRawType();
     @SuppressWarnings("unchecked") // Assume all default values are generics safe.
     T defaultValue = (T) defaultValues.getInstance(rawType);
@@ -761,8 +768,7 @@ private static  T createInstance(Invokable factory, List a
     return instance;
   }
 
-  @NullableDecl
-  private static  T invoke(Invokable factory, List args)
+  private static  @Nullable T invoke(Invokable factory, List args)
       throws InvocationTargetException, IllegalAccessException {
     T returnValue = factory.invoke(null, args.toArray());
     if (returnValue == null) {
@@ -777,7 +783,7 @@ private static  T invoke(Invokable factory, List args)
    * the dummy value of a constructor or method parameter is unknown.
    */
   @VisibleForTesting
-  static class ParameterNotInstantiableException extends Exception {
+  static final class ParameterNotInstantiableException extends Exception {
     public ParameterNotInstantiableException(Parameter parameter) {
       super(
           "Cannot determine value for parameter "
@@ -793,7 +799,7 @@ public ParameterNotInstantiableException(Parameter parameter) {
    * class.
    */
   @VisibleForTesting
-  static class ParameterHasNoDistinctValueException extends Exception {
+  static final class ParameterHasNoDistinctValueException extends Exception {
     ParameterHasNoDistinctValueException(Parameter parameter) {
       super(
           "Cannot generate distinct value for parameter "
@@ -808,7 +814,7 @@ static class ParameterHasNoDistinctValueException extends Exception {
    * factory returned null.
    */
   @VisibleForTesting
-  static class FactoryMethodReturnsNullException extends Exception {
+  static final class FactoryMethodReturnsNullException extends Exception {
     public FactoryMethodReturnsNullException(Invokable factory) {
       super(factory + " returns null and cannot be used to test instance methods.");
     }
@@ -828,7 +834,7 @@  R dummyReturnValue(TypeToken returnType) {
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
       return obj instanceof SerializableDummyProxy;
     }
 
diff --git a/android/guava-testlib/src/com/google/common/testing/ClusterException.java b/android/guava-testlib/src/com/google/common/testing/ClusterException.java
deleted file mode 100644
index 7665ab1d435b..000000000000
--- a/android/guava-testlib/src/com/google/common/testing/ClusterException.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2009 The Guava Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.common.testing;
-
-import com.google.common.annotations.GwtCompatible;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * An {@link ClusterException} is a data structure that allows for some code to "throw multiple
- * exceptions", or something close to it. The prototypical code that calls for this class is
- * presented below:
- *
- * 
- * void runManyThings({@literal List} thingsToRun) {
- *   for (ThingToRun thingToRun : thingsToRun) {
- *     thingToRun.run(); // say this may throw an exception, but you want to
- *                       // always run all thingsToRun
- *   }
- * }
- * 
- * - *

This is what the code would become: - * - *

- * void runManyThings({@literal List} thingsToRun) {
- *   {@literal List} exceptions = Lists.newArrayList();
- *   for (ThingToRun thingToRun : thingsToRun) {
- *     try {
- *       thingToRun.run();
- *     } catch (Exception e) {
- *       exceptions.add(e);
- *     }
- *   }
- *   if (exceptions.size() > 0) {
- *     throw ClusterException.create(exceptions);
- *   }
- * }
- * 
- * - *

See semantic details at {@link #create(Collection)}. - * - * @author Luiz-Otavio Zorzella - */ -@GwtCompatible -final class ClusterException extends RuntimeException { - - public final Collection exceptions; - - private ClusterException(Collection exceptions) { - super( - exceptions.size() + " exceptions were thrown. The first exception is listed as a cause.", - exceptions.iterator().next()); - ArrayList temp = new ArrayList<>(exceptions); - this.exceptions = Collections.unmodifiableCollection(temp); - } - - /** @see #create(Collection) */ - public static RuntimeException create(Throwable... exceptions) { - ArrayList temp = new ArrayList<>(Arrays.asList(exceptions)); - return create(temp); - } - - /** - * Given a collection of exceptions, returns a {@link RuntimeException}, with the following rules: - * - *

    - *
  • If {@code exceptions} has a single exception and that exception is a {@link - * RuntimeException}, return it - *
  • If {@code exceptions} has a single exceptions and that exceptions is not a - * {@link RuntimeException}, return a simple {@code RuntimeException} that wraps it - *
  • Otherwise, return an instance of {@link ClusterException} that wraps the first exception - * in the {@code exceptions} collection. - *
- * - *

Though this method takes any {@link Collection}, it often makes most sense to pass a {@link - * java.util.List} or some other collection that preserves the order in which the exceptions got - * added. - * - * @throws NullPointerException if {@code exceptions} is null - * @throws IllegalArgumentException if {@code exceptions} is empty - */ - public static RuntimeException create(Collection exceptions) { - if (exceptions.size() == 0) { - throw new IllegalArgumentException("Can't create an ExceptionCollection with no exceptions"); - } - if (exceptions.size() == 1) { - Throwable temp = exceptions.iterator().next(); - if (temp instanceof RuntimeException) { - return (RuntimeException) temp; - } else { - return new RuntimeException(temp); - } - } - return new ClusterException(exceptions); - } -} diff --git a/android/guava-testlib/src/com/google/common/testing/CollectorTester.java b/android/guava-testlib/src/com/google/common/testing/CollectorTester.java new file mode 100644 index 000000000000..263d5b661ef6 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/testing/CollectorTester.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.testing; + +import static com.google.common.base.Preconditions.checkNotNull; +import static junit.framework.Assert.assertTrue; + +import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.BiPredicate; +import java.util.stream.Collector; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Tester for {@code Collector} implementations. + * + *

Example usage: + * + *

+ * CollectorTester.of(Collectors.summingInt(Integer::parseInt))
+ *     .expectCollects(3, "1", "2")
+ *     .expectCollects(10, "1", "4", "3", "2")
+ *     .expectCollects(5, "-3", "0", "8");
+ * 
+ * + * @author Louis Wasserman + * @since 33.5.0 (but since 21.0 in the JRE flavor) + */ +@GwtCompatible +@NullMarked +@IgnoreJRERequirement // Users will use this only if they're already using Collector. +public final class CollectorTester< + T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object> { + /** + * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code + * Collector} will be compared to the expected value using {@link Object#equals}. + */ + public static + CollectorTester of(Collector collector) { + return of(collector, Objects::equals); + } + + /** + * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code + * Collector} will be compared to the expected value using the specified {@code equivalence}. + */ + public static + CollectorTester of( + Collector collector, BiPredicate equivalence) { + return new CollectorTester<>(collector, equivalence); + } + + private final Collector collector; + private final BiPredicate equivalence; + + private CollectorTester( + Collector collector, BiPredicate equivalence) { + this.collector = checkNotNull(collector); + this.equivalence = checkNotNull(equivalence); + } + + /** + * Different orderings for combining the elements of an input array, which must all produce the + * same result. + */ + @IgnoreJRERequirement // *should* be redundant with the one on CollectorTester + enum CollectStrategy { + /** Get one accumulator and accumulate the elements into it sequentially. */ + SEQUENTIAL { + @Override + final + A result(Collector collector, Iterable inputs) { + A accum = collector.supplier().get(); + for (T input : inputs) { + collector.accumulator().accept(accum, input); + } + return accum; + } + }, + /** Get one accumulator for each element and merge the accumulators left-to-right. */ + MERGE_LEFT_ASSOCIATIVE { + @Override + final + A result(Collector collector, Iterable inputs) { + A accum = collector.supplier().get(); + for (T input : inputs) { + A newAccum = collector.supplier().get(); + collector.accumulator().accept(newAccum, input); + accum = collector.combiner().apply(accum, newAccum); + } + return accum; + } + }, + /** Get one accumulator for each element and merge the accumulators right-to-left. */ + MERGE_RIGHT_ASSOCIATIVE { + @Override + final + A result(Collector collector, Iterable inputs) { + List stack = new ArrayList<>(); + for (T input : inputs) { + A newAccum = collector.supplier().get(); + collector.accumulator().accept(newAccum, input); + push(stack, newAccum); + } + push(stack, collector.supplier().get()); + while (stack.size() > 1) { + A right = pop(stack); + A left = pop(stack); + push(stack, collector.combiner().apply(left, right)); + } + return pop(stack); + } + + void push(List stack, E value) { + stack.add(value); + } + + E pop(List stack) { + return stack.remove(stack.size() - 1); + } + }; + + abstract + A result(Collector collector, Iterable inputs); + } + + /** + * Verifies that the specified expected result is always produced by collecting the specified + * inputs, regardless of how the elements are divided. + */ + @SafeVarargs + @CanIgnoreReturnValue + @SuppressWarnings("nullness") // TODO(cpovirk): Remove after we fix whatever the bug is. + public final CollectorTester expectCollects(R expectedResult, T... inputs) { + List list = Arrays.asList(inputs); + doExpectCollects(expectedResult, list); + if (collector.characteristics().contains(Collector.Characteristics.UNORDERED)) { + Collections.reverse(list); + doExpectCollects(expectedResult, list); + } + return this; + } + + private void doExpectCollects(R expectedResult, List inputs) { + for (CollectStrategy scheme : CollectStrategy.values()) { + A finalAccum = scheme.result(collector, inputs); + if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { + @SuppressWarnings("unchecked") // `R` and `A` match for an `IDENTITY_FINISH` + R result = (R) finalAccum; + assertEquivalent(expectedResult, result); + } + assertEquivalent(expectedResult, collector.finisher().apply(finalAccum)); + } + } + + private void assertEquivalent(R expected, R actual) { + assertTrue( + "Expected " + expected + " got " + actual + " modulo equivalence " + equivalence, + equivalence.test(expected, actual)); + } +} diff --git a/android/guava-testlib/src/com/google/common/testing/DummyProxy.java b/android/guava-testlib/src/com/google/common/testing/DummyProxy.java index 85e229d51831..e34e0f2040ba 100644 --- a/android/guava-testlib/src/com/google/common/testing/DummyProxy.java +++ b/android/guava-testlib/src/com/google/common/testing/DummyProxy.java @@ -20,8 +20,8 @@ import static com.google.common.testing.NullPointerTester.isNullable; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Sets; import com.google.common.reflect.AbstractInvocationHandler; import com.google.common.reflect.Invokable; import com.google.common.reflect.Parameter; @@ -29,7 +29,11 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Generates a dummy interface proxy that simply returns a dummy value for each method. @@ -37,6 +41,8 @@ * @author Ben Yu */ @GwtIncompatible +@J2ktIncompatible +@NullMarked abstract class DummyProxy { /** @@ -44,8 +50,23 @@ abstract class DummyProxy { * other if the {@link DummyProxy} instance that created the proxies are equal. */ final T newProxy(TypeToken interfaceType) { - Set> interfaceClasses = Sets.newLinkedHashSet(); - interfaceClasses.addAll(interfaceType.getTypes().interfaces().rawTypes()); + Set> interfaceClasses = new LinkedHashSet<>(); + Set> allInterfaceClasses = interfaceType.getTypes().interfaces().rawTypes(); + for (Class itf : allInterfaceClasses) { + Iterator> iterator = interfaceClasses.iterator(); + boolean addToSet = true; + while (iterator.hasNext()) { + Class current = iterator.next(); + if (current == itf || itf.isAssignableFrom(current)) { + // Skip any super interface of the ones that are already included. + addToSet = false; + break; + } + } + if (addToSet) { + interfaceClasses.add(itf); + } + } // Make the proxy serializable to work with SerializableTester interfaceClasses.add(Serializable.class); Object dummy = @@ -59,9 +80,9 @@ final T newProxy(TypeToken interfaceType) { } /** Returns the dummy return value for {@code returnType}. */ - abstract R dummyReturnValue(TypeToken returnType); + abstract @Nullable R dummyReturnValue(TypeToken returnType); - private class DummyHandler extends AbstractInvocationHandler implements Serializable { + private final class DummyHandler extends AbstractInvocationHandler implements Serializable { private final TypeToken interfaceType; DummyHandler(TypeToken interfaceType) { @@ -69,7 +90,8 @@ private class DummyHandler extends AbstractInvocationHandler implements Serializ } @Override - protected Object handleInvocation(Object proxy, Method method, Object[] args) { + protected @Nullable Object handleInvocation( + Object proxy, Method method, @Nullable Object[] args) { Invokable invokable = interfaceType.method(method); ImmutableList params = invokable.getParameters(); for (int i = 0; i < args.length; i++) { @@ -87,7 +109,7 @@ public int hashCode() { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof DummyHandler) { DummyHandler that = (DummyHandler) obj; return identity().equals(that.identity()); @@ -107,7 +129,7 @@ public String toString() { // Since type variables aren't serializable, reduce the type down to raw type before // serialization. - private Object writeReplace() { + private Object writeReplace() { return new DummyHandler(TypeToken.of(interfaceType.getRawType())); } } diff --git a/android/guava-testlib/src/com/google/common/testing/EqualsTester.java b/android/guava-testlib/src/com/google/common/testing/EqualsTester.java index 2c8e08b87c57..531d7453f095 100644 --- a/android/guava-testlib/src/com/google/common/testing/EqualsTester.java +++ b/android/guava-testlib/src/com/google/common/testing/EqualsTester.java @@ -20,13 +20,15 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Equivalence; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; +import com.google.common.testing.RelationshipTester.Item; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tester for equals() and hashCode() methods of a class. @@ -74,17 +76,17 @@ * @author Jige Yu * @since 10.0 */ -@Beta @GwtCompatible +@NullMarked public final class EqualsTester { private static final int REPETITIONS = 3; - private final List> equalityGroups = Lists.newArrayList(); + private final List> equalityGroups = new ArrayList<>(); private final RelationshipTester.ItemReporter itemReporter; /** Constructs an empty EqualsTester instance */ public EqualsTester() { - this(new RelationshipTester.ItemReporter()); + this(/* itemReporter= */ Item::toString); } EqualsTester(RelationshipTester.ItemReporter itemReporter) { @@ -94,14 +96,34 @@ public EqualsTester() { /** * Adds {@code equalityGroup} with objects that are supposed to be equal to each other and not * equal to any other equality groups added to this tester. + * + *

The {@code @Nullable} annotations on the {@code equalityGroup} parameter imply that the + * objects, and the array itself, can be null. That is for programmer convenience, when the + * objects come from factory methods that are themselves {@code @Nullable}. In reality neither the + * array nor its contents can be null, but it is not useful to force the use of {@code + * requireNonNull} or the like just to assert that. + * + *

{@code EqualsTester} will always check that every object it is given returns false from + * {@code equals(null)}, so it is neither useful nor allowed to include a null value in any + * equality group. */ - public EqualsTester addEqualityGroup(Object... equalityGroup) { + @CanIgnoreReturnValue + public EqualsTester addEqualityGroup(@Nullable Object @Nullable ... equalityGroup) { checkNotNull(equalityGroup); - equalityGroups.add(ImmutableList.copyOf(equalityGroup)); + List list = new ArrayList<>(equalityGroup.length); + for (int i = 0; i < equalityGroup.length; i++) { + Object element = equalityGroup[i]; + if (element == null) { + throw new NullPointerException("at index " + i); + } + list.add(element); + } + equalityGroups.add(list); return this; } /** Run tests on equals method, throwing a failure on an invalid test */ + @CanIgnoreReturnValue public EqualsTester testEquals() { RelationshipTester delegate = new RelationshipTester<>( @@ -122,7 +144,7 @@ private void testItems() { assertTrue( item + " must not be Object#equals to an arbitrary object of another class", !item.equals(NotAnInstance.EQUAL_TO_NOTHING)); - assertEquals(item + " must be Object#equals to itself", item, item); + assertTrue(item + " must be Object#equals to itself", item.equals(item)); assertEquals( "the Object#hashCode of " + item + " must be consistent", item.hashCode(), diff --git a/android/guava-testlib/src/com/google/common/testing/EquivalenceTester.java b/android/guava-testlib/src/com/google/common/testing/EquivalenceTester.java index ce1dc98c440e..ae0961cef4ed 100644 --- a/android/guava-testlib/src/com/google/common/testing/EquivalenceTester.java +++ b/android/guava-testlib/src/com/google/common/testing/EquivalenceTester.java @@ -20,13 +20,15 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.common.testing.RelationshipTester.ItemReporter; +import com.google.common.testing.RelationshipTester.Item; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.NullMarked; /** * Tester for {@link Equivalence} relationships between groups of objects. @@ -35,12 +37,12 @@ * contains objects that are supposed to be equal to each other. Objects of different groups are * expected to be unequal. For example: * - *
{@code
+ * {@snippet :
  * EquivalenceTester.of(someStringEquivalence)
  *     .addEquivalenceGroup("hello", "h" + "ello")
  *     .addEquivalenceGroup("world", "wor" + "ld")
  *     .test();
- * }
+ * } * *

Note that testing {@link Object#equals(Object)} is more simply done using the {@link * EqualsTester}. It includes an extra test against an instance of an arbitrary class without having @@ -49,34 +51,37 @@ * @author Gregory Kick * @since 10.0 */ -@Beta @GwtCompatible +@NullMarked public final class EquivalenceTester { private static final int REPETITIONS = 3; private final Equivalence equivalence; private final RelationshipTester delegate; - private final List items = Lists.newArrayList(); + private final List items = new ArrayList<>(); private EquivalenceTester(Equivalence equivalence) { this.equivalence = checkNotNull(equivalence); this.delegate = - new RelationshipTester(equivalence, "equivalent", "hash", new ItemReporter()); + new RelationshipTester<>( + equivalence, "equivalent", "hash", /* itemReporter= */ Item::toString); } public static EquivalenceTester of(Equivalence equivalence) { - return new EquivalenceTester(equivalence); + return new EquivalenceTester<>(equivalence); } /** * Adds a group of objects that are supposed to be equivalent to each other and not equivalent to * objects in any other equivalence group added to this tester. */ + @CanIgnoreReturnValue public EquivalenceTester addEquivalenceGroup(T first, T... rest) { addEquivalenceGroup(Lists.asList(first, rest)); return this; } + @CanIgnoreReturnValue public EquivalenceTester addEquivalenceGroup(Iterable group) { delegate.addRelatedGroup(group); items.addAll(ImmutableList.copyOf(group)); @@ -84,6 +89,7 @@ public EquivalenceTester addEquivalenceGroup(Iterable group) { } /** Run tests on equivalence methods, throwing a failure on an invalid test */ + @CanIgnoreReturnValue public EquivalenceTester test() { for (int run = 0; run < REPETITIONS; run++) { testItems(); diff --git a/android/guava-testlib/src/com/google/common/testing/FakeTicker.java b/android/guava-testlib/src/com/google/common/testing/FakeTicker.java index 698db6a002d5..888cfbe23e77 100644 --- a/android/guava-testlib/src/com/google/common/testing/FakeTicker.java +++ b/android/guava-testlib/src/com/google/common/testing/FakeTicker.java @@ -17,12 +17,18 @@ package com.google.common.testing; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Ticker; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import org.jspecify.annotations.NullMarked; /** * A Ticker whose value can be advanced programmatically in test. @@ -35,7 +41,7 @@ * @author Jige Yu * @since 10.0 */ -@Beta +@NullMarked @GwtCompatible public class FakeTicker extends Ticker { @@ -44,17 +50,34 @@ public class FakeTicker extends Ticker { /** Advances the ticker value by {@code time} in {@code timeUnit}. */ @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @CanIgnoreReturnValue public FakeTicker advance(long time, TimeUnit timeUnit) { return advance(timeUnit.toNanos(time)); } /** Advances the ticker value by {@code nanoseconds}. */ @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @CanIgnoreReturnValue public FakeTicker advance(long nanoseconds) { nanos.addAndGet(nanoseconds); return this; } + /** + * Advances the ticker value by {@code duration}. + * + * @since 33.1.0 (but since 28.0 in the JRE flavor) + */ + @GwtIncompatible + @J2ktIncompatible + @CanIgnoreReturnValue + @IgnoreJRERequirement // TODO: b/288085449 - Remove this once we use library-desugaring scents. + @Beta // TODO: b/288085449 - Remove @Beta after we're sure that Java 8 APIs are safe for Android + public FakeTicker advance(Duration duration) { + return advance(duration.toNanos()); + } + /** * Sets the increment applied to the ticker whenever it is queried. * @@ -62,12 +85,31 @@ public FakeTicker advance(long nanoseconds) { * queried. */ @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @CanIgnoreReturnValue public FakeTicker setAutoIncrementStep(long autoIncrementStep, TimeUnit timeUnit) { checkArgument(autoIncrementStep >= 0, "May not auto-increment by a negative amount"); this.autoIncrementStepNanos = timeUnit.toNanos(autoIncrementStep); return this; } + /** + * Sets the increment applied to the ticker whenever it is queried. + * + *

The default behavior is to auto increment by zero. i.e: The ticker is left unchanged when + * queried. + * + * @since 33.1.0 (but since 28.0 in the JRE flavor) + */ + @GwtIncompatible + @J2ktIncompatible + @CanIgnoreReturnValue + @IgnoreJRERequirement // TODO: b/288085449 - Remove this once we use library-desugaring scents. + @Beta // TODO: b/288085449 - Remove @Beta after we're sure that Java 8 APIs are safe for Android + public FakeTicker setAutoIncrementStep(Duration autoIncrementStep) { + return setAutoIncrementStep(autoIncrementStep.toNanos(), NANOSECONDS); + } + @Override public long read() { return nanos.getAndAdd(autoIncrementStepNanos); diff --git a/android/guava-testlib/src/com/google/common/testing/ForwardingWrapperTester.java b/android/guava-testlib/src/com/google/common/testing/ForwardingWrapperTester.java index 4ee461eb245f..c0b390ecc468 100644 --- a/android/guava-testlib/src/com/google/common/testing/ForwardingWrapperTester.java +++ b/android/guava-testlib/src/com/google/common/testing/ForwardingWrapperTester.java @@ -22,19 +22,22 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Throwables; -import com.google.common.collect.Lists; import com.google.common.reflect.AbstractInvocationHandler; import com.google.common.reflect.Reflection; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.AccessibleObject; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tester to ensure forwarding wrapper works by delegating calls to the corresponding method with @@ -42,19 +45,20 @@ * *

For example: * - *

{@code
+ * {@snippet :
  * new ForwardingWrapperTester().testForwarding(Foo.class, new Function() {
  *   public Foo apply(Foo foo) {
  *     return new ForwardingFoo(foo);
  *   }
  * });
- * }
+ * } * * @author Ben Yu * @since 14.0 */ -@Beta @GwtIncompatible +@J2ktIncompatible +@NullMarked public final class ForwardingWrapperTester { private boolean testsEquals = false; @@ -63,6 +67,7 @@ public final class ForwardingWrapperTester { * Asks for {@link Object#equals} and {@link Object#hashCode} to be tested. That is, forwarding * wrappers of equal instances should be equal. */ + @CanIgnoreReturnValue public ForwardingWrapperTester includingEquals() { this.testsEquals = true; return this; @@ -80,9 +85,9 @@ public void testForwarding( Method[] methods = getMostConcreteMethods(interfaceType); AccessibleObject.setAccessible(methods, true); for (Method method : methods) { - // Under java 8, interfaces can have default methods that aren't abstract. + // Interfaces can have default methods that aren't abstract. // No need to verify them. - // Can't check isDefault() for JDK 7 compatibility. + // Can't check isDefault() for Android compatibility. if (!Modifier.isAbstract(method.getModifiers())) { continue; } @@ -129,13 +134,13 @@ private static void testSuccessfulForwarding( private static void testExceptionPropagation( Class interfaceType, Method method, Function wrapperFunction) { - final RuntimeException exception = new RuntimeException(); + RuntimeException exception = new RuntimeException(); T proxy = Reflection.newProxy( interfaceType, new AbstractInvocationHandler() { @Override - protected Object handleInvocation(Object p, Method m, Object[] args) + protected Object handleInvocation(Object p, Method m, @Nullable Object[] args) throws Throwable { throw exception; } @@ -173,9 +178,9 @@ private static void testToString( wrapperFunction.apply(proxy).toString()); } - private static Object[] getParameterValues(Method method) { + private static @Nullable Object[] getParameterValues(Method method) { FreshValueGenerator paramValues = new FreshValueGenerator(); - final List passedArgs = Lists.newArrayList(); + List<@Nullable Object> passedArgs = new ArrayList<>(); for (Class paramType : method.getParameterTypes()) { passedArgs.add(paramValues.generateFresh(paramType)); } @@ -187,8 +192,8 @@ private static final class InteractionTester extends AbstractInvocationHandle private final Class interfaceType; private final Method method; - private final Object[] passedArgs; - private final Object returnValue; + private final @Nullable Object[] passedArgs; + private final @Nullable Object returnValue; private final AtomicInteger called = new AtomicInteger(); InteractionTester(Class interfaceType, Method method) { @@ -199,8 +204,8 @@ private static final class InteractionTester extends AbstractInvocationHandle } @Override - protected Object handleInvocation(Object p, Method calledMethod, Object[] args) - throws Throwable { + protected @Nullable Object handleInvocation( + Object p, Method calledMethod, @Nullable Object[] args) throws Throwable { assertEquals(method, calledMethod); assertEquals(method + " invoked more than once.", 0, called.get()); for (int i = 0; i < passedArgs.length; i++) { diff --git a/android/guava-testlib/src/com/google/common/testing/FreshValueGenerator.java b/android/guava-testlib/src/com/google/common/testing/FreshValueGenerator.java index 21e25cb96c93..63d5e4a492d4 100644 --- a/android/guava-testlib/src/com/google/common/testing/FreshValueGenerator.java +++ b/android/guava-testlib/src/com/google/common/testing/FreshValueGenerator.java @@ -18,10 +18,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Throwables.throwIfUnchecked; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.CharMatcher; -import com.google.common.base.Charsets; import com.google.common.base.Equivalence; import com.google.common.base.Joiner; import com.google.common.base.Splitter; @@ -118,7 +120,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Generates fresh instances of types that are different from each other (if possible). @@ -126,6 +129,9 @@ * @author Ben Yu */ @GwtIncompatible +@J2ktIncompatible +@NullUnmarked +@SuppressWarnings("nullness") class FreshValueGenerator { private static final ImmutableMap, Method> GENERATORS; @@ -137,7 +143,7 @@ class FreshValueGenerator { builder.put(method.getReturnType(), method); } } - GENERATORS = builder.build(); + GENERATORS = builder.buildOrThrow(); } private static final ImmutableMap, Method> EMPTY_GENERATORS; @@ -149,7 +155,7 @@ class FreshValueGenerator { builder.put(method.getReturnType(), method); } } - EMPTY_GENERATORS = builder.build(); + EMPTY_GENERATORS = builder.buildOrThrow(); } private final AtomicInteger freshness = new AtomicInteger(1); @@ -175,8 +181,7 @@ final void addSampleInstances(Class type, Iterable instances *
  • null if no value can be generated. * */ - @NullableDecl - final Object generateFresh(TypeToken type) { + final @Nullable Object generateFresh(TypeToken type) { Object generated = generate(type); if (generated != null) { freshness.incrementAndGet(); @@ -184,12 +189,11 @@ final Object generateFresh(TypeToken type) { return generated; } - @NullableDecl - final T generateFresh(Class type) { + final @Nullable T generateFresh(Class type) { return Primitives.wrap(type).cast(generateFresh(TypeToken.of(type))); } - final T newFreshProxy(final Class interfaceType) { + final T newFreshProxy(Class interfaceType) { T proxy = newProxy(interfaceType); freshness.incrementAndGet(); return proxy; @@ -199,7 +203,7 @@ final T newFreshProxy(final Class interfaceType) { * Generates an instance for {@code type} using the current {@link #freshness}. The generated * instance may or may not be unique across different calls. */ - private Object generate(TypeToken type) { + private @Nullable Object generate(TypeToken type) { Class rawType = type.getRawType(); List samples = sampleInstances.get(rawType); Object sample = pickInstance(samples, null); @@ -210,7 +214,7 @@ private Object generate(TypeToken type) { return pickInstance(rawType.getEnumConstants(), null); } if (type.isArray()) { - TypeToken componentType = type.getComponentType(); + TypeToken componentType = requireNonNull(type.getComponentType()); Object array = Array.newInstance(componentType.getRawType(), 1); Array.set(array, 0, generate(componentType)); return array; @@ -256,7 +260,7 @@ private Object generate(TypeToken type) { return defaultGenerate(rawType); } - private T defaultGenerate(Class rawType) { + private @Nullable T defaultGenerate(Class rawType) { if (rawType.isInterface()) { // always create a new proxy return newProxy(rawType); @@ -264,7 +268,7 @@ private T defaultGenerate(Class rawType) { return ArbitraryInstances.get(rawType); } - private T newProxy(final Class interfaceType) { + private T newProxy(Class interfaceType) { return Reflection.newProxy(interfaceType, new FreshInvocationHandler(interfaceType)); } @@ -289,7 +293,8 @@ private final class FreshInvocationHandler extends AbstractInvocationHandler { } @Override - protected Object handleInvocation(Object proxy, Method method, Object[] args) { + protected @Nullable Object handleInvocation( + Object proxy, Method method, @Nullable Object[] args) { return interfaceMethodCalled(interfaceType, method); } @@ -299,7 +304,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof FreshInvocationHandler) { FreshInvocationHandler that = (FreshInvocationHandler) obj; return identity == that.identity; @@ -314,7 +319,7 @@ public String toString() { } /** Subclasses can override to provide different return value for proxied interface methods. */ - Object interfaceMethodCalled(Class interfaceType, Method method) { + @Nullable Object interfaceMethodCalled(Class interfaceType, Method method) { throw new UnsupportedOperationException(); } @@ -354,7 +359,7 @@ private static String paramString(Class type, int i) { private @interface Empty {} @Generates - private Class generateClass() { + Class generateClass() { return pickInstance( ImmutableList.of( int.class, long.class, void.class, Object.class, Object[].class, Iterable.class), @@ -362,203 +367,181 @@ private Class generateClass() { } @Generates - private Object generateObject() { + Object generateObject() { return generateString(); } @Generates - private Number generateNumber() { + Number generateNumber() { return generateInt(); } @Generates - private int generateInt() { + int generateInt() { return freshness.get(); } + @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates - private Integer generateInteger() { + Integer generateInteger() { return new Integer(generateInt()); } @Generates - private long generateLong() { + long generateLong() { return generateInt(); } + @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates - private Long generateLongObject() { + Long generateLongObject() { return new Long(generateLong()); } @Generates - private float generateFloat() { + float generateFloat() { return generateInt(); } + @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates - private Float generateFloatObject() { + Float generateFloatObject() { return new Float(generateFloat()); } @Generates - private double generateDouble() { + double generateDouble() { return generateInt(); } + @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates - private Double generateDoubleObject() { + Double generateDoubleObject() { return new Double(generateDouble()); } @Generates - private short generateShort() { + short generateShort() { return (short) generateInt(); } + @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates - private Short generateShortObject() { + Short generateShortObject() { return new Short(generateShort()); } @Generates - private byte generateByte() { + byte generateByte() { return (byte) generateInt(); } + @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates - private Byte generateByteObject() { + Byte generateByteObject() { return new Byte(generateByte()); } @Generates - private char generateChar() { + char generateChar() { return generateString().charAt(0); } + @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates - private Character generateCharacter() { + Character generateCharacter() { return new Character(generateChar()); } @Generates - private boolean generateBoolean() { + boolean generateBoolean() { return generateInt() % 2 == 0; } + @SuppressWarnings("removal") // b/321209431 -- maybe just use valueOf here? @Generates - private Boolean generateBooleanObject() { + Boolean generateBooleanObject() { return new Boolean(generateBoolean()); } @Generates - private UnsignedInteger generateUnsignedInteger() { + UnsignedInteger generateUnsignedInteger() { return UnsignedInteger.fromIntBits(generateInt()); } @Generates - private UnsignedLong generateUnsignedLong() { + UnsignedLong generateUnsignedLong() { return UnsignedLong.fromLongBits(generateLong()); } @Generates - private BigInteger generateBigInteger() { + BigInteger generateBigInteger() { return BigInteger.valueOf(generateInt()); } @Generates - private BigDecimal generateBigDecimal() { + BigDecimal generateBigDecimal() { return BigDecimal.valueOf(generateInt()); } @Generates - private CharSequence generateCharSequence() { + CharSequence generateCharSequence() { return generateString(); } @Generates - private String generateString() { + String generateString() { return Integer.toString(generateInt()); } @Generates - private Comparable generateComparable() { + Comparable generateComparable() { return generateString(); } @Generates - private Pattern generatePattern() { + Pattern generatePattern() { return Pattern.compile(generateString()); } @Generates - private Charset generateCharset() { - return pickInstance(Charset.availableCharsets().values(), Charsets.UTF_8); + Charset generateCharset() { + return pickInstance(Charset.availableCharsets().values(), UTF_8); } @Generates - private Locale generateLocale() { + Locale generateLocale() { return pickInstance(Locale.getAvailableLocales(), Locale.US); } @Generates - private Currency generateCurrency() { - try { - Method method = Currency.class.getMethod("getAvailableCurrencies"); - @SuppressWarnings("unchecked") // getAvailableCurrencies() returns Set. - Set currencies = (Set) method.invoke(null); - return pickInstance(currencies, Currency.getInstance(Locale.US)); - /* - * Do not merge the 2 catch blocks below. javac would infer a type of - * ReflectiveOperationException, which Animal Sniffer would reject. (Old versions of - * Android don't *seem* to mind, but there might be edge cases of which we're unaware.) - */ - } catch (NoSuchMethodException notJava7) { - return preJava7FreshCurrency(); - } catch (InvocationTargetException notJava7) { - return preJava7FreshCurrency(); - } catch (IllegalAccessException impossible) { - throw new AssertionError(impossible); - } - } - - private Currency preJava7FreshCurrency() { - for (Set uselessLocales = Sets.newHashSet(); ; ) { - Locale locale = generateLocale(); - if (uselessLocales.contains(locale)) { // exhausted all locales - return Currency.getInstance(Locale.US); - } - try { - return Currency.getInstance(locale); - } catch (IllegalArgumentException e) { - uselessLocales.add(locale); - } - } + Currency generateCurrency() { + return pickInstance(Currency.getAvailableCurrencies(), Currency.getInstance(Locale.US)); } // common.base @Empty - private com.google.common.base.Optional generateGoogleOptional() { + com.google.common.base.Optional generateGoogleOptional() { return com.google.common.base.Optional.absent(); } @Generates - private com.google.common.base.Optional generateGoogleOptional(T value) { + com.google.common.base.Optional generateGoogleOptional(T value) { return com.google.common.base.Optional.of(value); } @Generates - private Joiner generateJoiner() { + Joiner generateJoiner() { return Joiner.on(generateString()); } @Generates - private Splitter generateSplitter() { + Splitter generateSplitter() { return Splitter.on(generateString()); } @Generates - private Equivalence generateEquivalence() { + Equivalence generateEquivalence() { return new Equivalence() { @Override protected boolean doEquivalent(T a, T b) { @@ -580,7 +563,7 @@ public String toString() { } @Generates - private CharMatcher generateCharMatcher() { + CharMatcher generateCharMatcher() { return new CharMatcher() { @Override public boolean matches(char c) { @@ -597,7 +580,7 @@ public String toString() { } @Generates - private Ticker generateTicker() { + Ticker generateTicker() { return new Ticker() { @Override public long read() { @@ -615,14 +598,15 @@ public String toString() { // collect @Generates - private Comparator generateComparator() { + Comparator generateComparator() { return generateOrdering(); } @Generates - private Ordering generateOrdering() { + Ordering generateOrdering() { return new Ordering() { @Override + @SuppressWarnings("UnusedVariable") // intentionally weird Comparator public int compare(T left, T right) { return 0; } @@ -637,279 +621,278 @@ public String toString() { } @Empty - private static > Range generateRange() { + static > Range generateRange() { return Range.all(); } @Generates - private static > Range generateRange(C freshElement) { + static > Range generateRange(C freshElement) { return Range.singleton(freshElement); } @Generates - private static Iterable generateIterable(E freshElement) { + static Iterable generateIterable(@Nullable E freshElement) { return generateList(freshElement); } @Generates - private static Collection generateCollection(E freshElement) { + static Collection generateCollection(@Nullable E freshElement) { return generateList(freshElement); } @Generates - private static List generateList(E freshElement) { + static List generateList(@Nullable E freshElement) { return generateArrayList(freshElement); } @Generates - private static ArrayList generateArrayList(E freshElement) { - ArrayList list = Lists.newArrayList(); + static ArrayList generateArrayList(@Nullable E freshElement) { + ArrayList list = new ArrayList<>(); list.add(freshElement); return list; } @Generates - private static LinkedList generateLinkedList(E freshElement) { - LinkedList list = Lists.newLinkedList(); + static LinkedList generateLinkedList(@Nullable E freshElement) { + LinkedList list = new LinkedList<>(); list.add(freshElement); return list; } @Generates - private static ImmutableList generateImmutableList(E freshElement) { + static ImmutableList generateImmutableList(E freshElement) { return ImmutableList.of(freshElement); } @Generates - private static ImmutableCollection generateImmutableCollection(E freshElement) { + static ImmutableCollection generateImmutableCollection(E freshElement) { return generateImmutableList(freshElement); } @Generates - private static Set generateSet(E freshElement) { + static Set generateSet(@Nullable E freshElement) { return generateHashSet(freshElement); } @Generates - private static HashSet generateHashSet(E freshElement) { + static HashSet generateHashSet(@Nullable E freshElement) { return generateLinkedHashSet(freshElement); } @Generates - private static LinkedHashSet generateLinkedHashSet(E freshElement) { - LinkedHashSet set = Sets.newLinkedHashSet(); + static LinkedHashSet generateLinkedHashSet(@Nullable E freshElement) { + LinkedHashSet set = new LinkedHashSet<>(); set.add(freshElement); return set; } @Generates - private static ImmutableSet generateImmutableSet(E freshElement) { + static ImmutableSet generateImmutableSet(E freshElement) { return ImmutableSet.of(freshElement); } @Generates - private static > SortedSet generateSortedSet(E freshElement) { + static > SortedSet generateSortedSet(E freshElement) { return generateNavigableSet(freshElement); } @Generates - private static > NavigableSet generateNavigableSet( - E freshElement) { + static > NavigableSet generateNavigableSet(E freshElement) { return generateTreeSet(freshElement); } @Generates - private static > TreeSet generateTreeSet(E freshElement) { + static > TreeSet generateTreeSet(E freshElement) { TreeSet set = Sets.newTreeSet(); set.add(freshElement); return set; } @Generates - private static > ImmutableSortedSet generateImmutableSortedSet( + static > ImmutableSortedSet generateImmutableSortedSet( E freshElement) { return ImmutableSortedSet.of(freshElement); } @Generates - private static Multiset generateMultiset(E freshElement) { + static Multiset generateMultiset(@Nullable E freshElement) { return generateHashMultiset(freshElement); } @Generates - private static HashMultiset generateHashMultiset(E freshElement) { + static HashMultiset generateHashMultiset(@Nullable E freshElement) { HashMultiset multiset = HashMultiset.create(); multiset.add(freshElement); return multiset; } @Generates - private static LinkedHashMultiset generateLinkedHashMultiset(E freshElement) { + static LinkedHashMultiset generateLinkedHashMultiset(@Nullable E freshElement) { LinkedHashMultiset multiset = LinkedHashMultiset.create(); multiset.add(freshElement); return multiset; } @Generates - private static ImmutableMultiset generateImmutableMultiset(E freshElement) { + static ImmutableMultiset generateImmutableMultiset(E freshElement) { return ImmutableMultiset.of(freshElement); } @Generates - private static > SortedMultiset generateSortedMultiset( - E freshElement) { + static > SortedMultiset generateSortedMultiset(E freshElement) { return generateTreeMultiset(freshElement); } @Generates - private static > TreeMultiset generateTreeMultiset(E freshElement) { + static > TreeMultiset generateTreeMultiset(E freshElement) { TreeMultiset multiset = TreeMultiset.create(); multiset.add(freshElement); return multiset; } @Generates - private static > - ImmutableSortedMultiset generateImmutableSortedMultiset(E freshElement) { + static > ImmutableSortedMultiset generateImmutableSortedMultiset( + E freshElement) { return ImmutableSortedMultiset.of(freshElement); } @Generates - private static Map generateMap(K key, V value) { + static Map generateMap(@Nullable K key, @Nullable V value) { return generateHashdMap(key, value); } @Generates - private static HashMap generateHashdMap(K key, V value) { + static HashMap generateHashdMap(@Nullable K key, @Nullable V value) { return generateLinkedHashMap(key, value); } @Generates - private static LinkedHashMap generateLinkedHashMap(K key, V value) { - LinkedHashMap map = Maps.newLinkedHashMap(); + static LinkedHashMap generateLinkedHashMap(@Nullable K key, @Nullable V value) { + LinkedHashMap map = new LinkedHashMap<>(); map.put(key, value); return map; } @Generates - private static ImmutableMap generateImmutableMap(K key, V value) { + static ImmutableMap generateImmutableMap(K key, V value) { return ImmutableMap.of(key, value); } @Empty - private static ConcurrentMap generateConcurrentMap() { + static ConcurrentMap generateConcurrentMap() { return Maps.newConcurrentMap(); } @Generates - private static ConcurrentMap generateConcurrentMap(K key, V value) { + static ConcurrentMap generateConcurrentMap(K key, V value) { ConcurrentMap map = Maps.newConcurrentMap(); map.put(key, value); return map; } @Generates - private static , V> SortedMap generateSortedMap( - K key, V value) { + static , V> SortedMap generateSortedMap( + K key, @Nullable V value) { return generateNavigableMap(key, value); } @Generates - private static , V> NavigableMap generateNavigableMap( - K key, V value) { + static , V> NavigableMap generateNavigableMap( + K key, @Nullable V value) { return generateTreeMap(key, value); } @Generates - private static , V> TreeMap generateTreeMap( - K key, V value) { + static , V> TreeMap generateTreeMap( + K key, @Nullable V value) { TreeMap map = Maps.newTreeMap(); map.put(key, value); return map; } @Generates - private static , V> - ImmutableSortedMap generateImmutableSortedMap(K key, V value) { + static , V> ImmutableSortedMap generateImmutableSortedMap( + K key, V value) { return ImmutableSortedMap.of(key, value); } @Generates - private static Multimap generateMultimap(K key, V value) { + static Multimap generateMultimap(@Nullable K key, @Nullable V value) { return generateListMultimap(key, value); } @Generates - private static ImmutableMultimap generateImmutableMultimap(K key, V value) { + static ImmutableMultimap generateImmutableMultimap(K key, V value) { return ImmutableMultimap.of(key, value); } @Generates - private static ListMultimap generateListMultimap(K key, V value) { + static ListMultimap generateListMultimap(@Nullable K key, @Nullable V value) { return generateArrayListMultimap(key, value); } @Generates - private static ArrayListMultimap generateArrayListMultimap(K key, V value) { + static ArrayListMultimap generateArrayListMultimap( + @Nullable K key, @Nullable V value) { ArrayListMultimap multimap = ArrayListMultimap.create(); multimap.put(key, value); return multimap; } @Generates - private static ImmutableListMultimap generateImmutableListMultimap(K key, V value) { + static ImmutableListMultimap generateImmutableListMultimap(K key, V value) { return ImmutableListMultimap.of(key, value); } @Generates - private static SetMultimap generateSetMultimap(K key, V value) { + static SetMultimap generateSetMultimap(@Nullable K key, @Nullable V value) { return generateLinkedHashMultimap(key, value); } @Generates - private static HashMultimap generateHashMultimap(K key, V value) { + static HashMultimap generateHashMultimap(@Nullable K key, @Nullable V value) { HashMultimap multimap = HashMultimap.create(); multimap.put(key, value); return multimap; } @Generates - private static LinkedHashMultimap generateLinkedHashMultimap(K key, V value) { + static LinkedHashMultimap generateLinkedHashMultimap( + @Nullable K key, @Nullable V value) { LinkedHashMultimap multimap = LinkedHashMultimap.create(); multimap.put(key, value); return multimap; } @Generates - private static ImmutableSetMultimap generateImmutableSetMultimap(K key, V value) { + static ImmutableSetMultimap generateImmutableSetMultimap(K key, V value) { return ImmutableSetMultimap.of(key, value); } @Generates - private static BiMap generateBimap(K key, V value) { + static BiMap generateBimap(@Nullable K key, @Nullable V value) { return generateHashBiMap(key, value); } @Generates - private static HashBiMap generateHashBiMap(K key, V value) { + static HashBiMap generateHashBiMap(@Nullable K key, @Nullable V value) { HashBiMap bimap = HashBiMap.create(); bimap.put(key, value); return bimap; } @Generates - private static ImmutableBiMap generateImmutableBimap(K key, V value) { + static ImmutableBiMap generateImmutableBimap(K key, V value) { return ImmutableBiMap.of(key, value); } @Generates - private static Table generateTable(R row, C column, V value) { + static Table generateTable(R row, C column, V value) { return generateHashBasedTable(row, column, value); } @Generates - private static HashBasedTable generateHashBasedTable( - R row, C column, V value) { + static HashBasedTable generateHashBasedTable(R row, C column, V value) { HashBasedTable table = HashBasedTable.create(); table.put(row, column, value); return table; @@ -917,14 +900,14 @@ private static HashBasedTable generateHashBasedTable( @SuppressWarnings("rawtypes") // TreeBasedTable.create() is defined as such @Generates - private static + static RowSortedTable generateRowSortedTable(R row, C column, V value) { return generateTreeBasedTable(row, column, value); } @SuppressWarnings("rawtypes") // TreeBasedTable.create() is defined as such @Generates - private static + static TreeBasedTable generateTreeBasedTable(R row, C column, V value) { TreeBasedTable table = TreeBasedTable.create(); table.put(row, column, value); @@ -932,85 +915,84 @@ TreeBasedTable generateTreeBasedTable(R row, C column, V value) { } @Generates - private static ImmutableTable generateImmutableTable( - R row, C column, V value) { + static ImmutableTable generateImmutableTable(R row, C column, V value) { return ImmutableTable.of(row, column, value); } // common.reflect @Generates - private TypeToken generateTypeToken() { + TypeToken generateTypeToken() { return TypeToken.of(generateClass()); } // io types @Generates - private File generateFile() { + File generateFile() { return new File(generateString()); } @Generates - private static ByteArrayInputStream generateByteArrayInputStream() { + static ByteArrayInputStream generateByteArrayInputStream() { return new ByteArrayInputStream(new byte[0]); } @Generates - private static InputStream generateInputStream() { + static InputStream generateInputStream() { return generateByteArrayInputStream(); } @Generates - private StringReader generateStringReader() { + StringReader generateStringReader() { return new StringReader(generateString()); } @Generates - private Reader generateReader() { + Reader generateReader() { return generateStringReader(); } @Generates - private Readable generateReadable() { + Readable generateReadable() { return generateReader(); } @Generates - private Buffer generateBuffer() { + Buffer generateBuffer() { return generateCharBuffer(); } @Generates - private CharBuffer generateCharBuffer() { + CharBuffer generateCharBuffer() { return CharBuffer.allocate(generateInt()); } @Generates - private ByteBuffer generateByteBuffer() { + ByteBuffer generateByteBuffer() { return ByteBuffer.allocate(generateInt()); } @Generates - private ShortBuffer generateShortBuffer() { + ShortBuffer generateShortBuffer() { return ShortBuffer.allocate(generateInt()); } @Generates - private IntBuffer generateIntBuffer() { + IntBuffer generateIntBuffer() { return IntBuffer.allocate(generateInt()); } @Generates - private LongBuffer generateLongBuffer() { + LongBuffer generateLongBuffer() { return LongBuffer.allocate(generateInt()); } @Generates - private FloatBuffer generateFloatBuffer() { + FloatBuffer generateFloatBuffer() { return FloatBuffer.allocate(generateInt()); } @Generates - private DoubleBuffer generateDoubleBuffer() { + DoubleBuffer generateDoubleBuffer() { return DoubleBuffer.allocate(generateInt()); } } diff --git a/android/guava-testlib/src/com/google/common/testing/GcFinalization.java b/android/guava-testlib/src/com/google/common/testing/GcFinalization.java index 015afea54809..201e69d94cbe 100644 --- a/android/guava-testlib/src/com/google/common/testing/GcFinalization.java +++ b/android/guava-testlib/src/com/google/common/testing/GcFinalization.java @@ -16,10 +16,11 @@ package com.google.common.testing; +import static java.lang.Math.max; import static java.util.concurrent.TimeUnit.SECONDS; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.DoNotMock; import com.google.j2objc.annotations.J2ObjCIncompatible; import java.lang.ref.WeakReference; @@ -29,6 +30,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.NullMarked; /** * Testing utilities relating to garbage collection finalization. @@ -54,7 +56,7 @@ * *

    Here's an example that tests a {@code finalize} method: * - *

    {@code
    + * {@snippet :
      * final CountDownLatch latch = new CountDownLatch(1);
      * Object x = new MyClass() {
      *   ...
    @@ -62,11 +64,11 @@
      * };
      * x = null;  // Hint to the JIT that x is stack-unreachable
      * GcFinalization.await(latch);
    - * }
    + * } * *

    Here's an example that uses a user-defined finalization predicate: * - *

    {@code
    + * {@snippet :
      * final WeakHashMap map = new WeakHashMap<>();
      * map.put(new Object(), Boolean.TRUE);
      * GcFinalization.awaitDone(new FinalizationPredicate() {
    @@ -74,12 +76,12 @@
      *     return map.isEmpty();
      *   }
      * });
    - * }
    + * } * *

    Even if your non-test code does not use finalization, you can use this class to test for * leaks, by ensuring that objects are no longer strongly referenced: * - *

    {@code
    + * {@snippet :
      * // Helper function keeps victim stack-unreachable.
      * private WeakReference fooWeakRef() {
      *   Foo x = ....;
    @@ -91,7 +93,7 @@
      * public void testFooLeak() {
      *   GcFinalization.awaitClear(fooWeakRef());
      * }
    - * }
    + * } * *

    This class cannot currently be used to test soft references, since this class does not try to * create the memory pressure required to cause soft references to be cleared. @@ -103,9 +105,10 @@ * @author Martin Buchholz * @since 11.0 */ -@Beta @GwtIncompatible +@J2ktIncompatible @J2ObjCIncompatible // gc +@NullMarked public final class GcFinalization { private GcFinalization() {} @@ -125,7 +128,7 @@ private static long timeoutSeconds() { // // TODO(user): Consider scaling by number of mutator threads, // e.g. using Thread#activeCount() - return Math.max(10L, Runtime.getRuntime().totalMemory() / (32L * 1024L * 1024L)); + return max(10L, Runtime.getRuntime().totalMemory() / (32L * 1024L * 1024L)); } /** @@ -134,12 +137,13 @@ private static long timeoutSeconds() { * * @throws RuntimeException if timed out or interrupted while waiting */ + @SuppressWarnings("removal") // b/260137033 public static void awaitDone(Future future) { if (future.isDone()) { return; } - final long timeoutSeconds = timeoutSeconds(); - final long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); + long timeoutSeconds = timeoutSeconds(); + long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); do { System.runFinalization(); if (future.isDone()) { @@ -166,12 +170,13 @@ public static void awaitDone(Future future) { * * @throws RuntimeException if timed out or interrupted while waiting */ + @SuppressWarnings("removal") // b/260137033 public static void awaitDone(FinalizationPredicate predicate) { if (predicate.isDone()) { return; } - final long timeoutSeconds = timeoutSeconds(); - final long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); + long timeoutSeconds = timeoutSeconds(); + long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); do { System.runFinalization(); if (predicate.isDone()) { @@ -194,12 +199,13 @@ public static void awaitDone(FinalizationPredicate predicate) { * * @throws RuntimeException if timed out or interrupted while waiting */ + @SuppressWarnings("removal") // b/260137033 public static void await(CountDownLatch latch) { if (latch.getCount() == 0) { return; } - final long timeoutSeconds = timeoutSeconds(); - final long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); + long timeoutSeconds = timeoutSeconds(); + long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds); do { System.runFinalization(); if (latch.getCount() == 0) { @@ -222,13 +228,15 @@ public static void await(CountDownLatch latch) { * Creates a garbage object that counts down the latch in its finalizer. Sequestered into a * separate method to make it somewhat more likely to be unreachable. */ - private static void createUnreachableLatchFinalizer(final CountDownLatch latch) { - new Object() { - @Override - protected void finalize() { - latch.countDown(); - } - }; + private static void createUnreachableLatchFinalizer(CountDownLatch latch) { + Object unused = + new Object() { + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 + @Override + protected void finalize() { + latch.countDown(); + } + }; } /** @@ -253,24 +261,18 @@ public interface FinalizationPredicate { * *

    This is a convenience method, equivalent to: * - *

    {@code
    +   * {@snippet :
        * awaitDone(new FinalizationPredicate() {
        *   public boolean isDone() {
        *     return ref.get() == null;
        *   }
        * });
    -   * }
    + * } * * @throws RuntimeException if timed out or interrupted while waiting */ - public static void awaitClear(final WeakReference ref) { - awaitDone( - new FinalizationPredicate() { - @Override - public boolean isDone() { - return ref.get() == null; - } - }); + public static void awaitClear(WeakReference ref) { + awaitDone(() -> ref.get() == null); } /** @@ -295,10 +297,11 @@ public boolean isDone() { * @throws RuntimeException if timed out or interrupted while waiting * @since 12.0 */ + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 public static void awaitFullGc() { - final CountDownLatch finalizerRan = new CountDownLatch(1); + CountDownLatch finalizerRan = new CountDownLatch(1); WeakReference ref = - new WeakReference( + new WeakReference<>( new Object() { @Override protected void finalize() { diff --git a/android/guava-testlib/src/com/google/common/testing/IgnoreJRERequirement.java b/android/guava-testlib/src/com/google/common/testing/IgnoreJRERequirement.java new file mode 100644 index 000000000000..cd9cb9c61c2e --- /dev/null +++ b/android/guava-testlib/src/com/google/common/testing/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.testing; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava-testlib/src/com/google/common/testing/NullPointerTester.java b/android/guava-testlib/src/com/google/common/testing/NullPointerTester.java index e32a950ac6f2..ce26d7648727 100644 --- a/android/guava-testlib/src/com/google/common/testing/NullPointerTester.java +++ b/android/guava-testlib/src/com/google/common/testing/NullPointerTester.java @@ -18,23 +18,25 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Arrays.stream; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Stream.concat; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; -import com.google.common.base.Objects; import com.google.common.collect.ClassToInstanceMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.MutableClassToInstanceMap; import com.google.common.reflect.Invokable; import com.google.common.reflect.Parameter; import com.google.common.reflect.Reflection; import com.google.common.reflect.TypeToken; +import com.google.common.util.concurrent.AbstractFuture; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; @@ -42,18 +44,20 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.concurrent.ConcurrentMap; import junit.framework.Assert; -import junit.framework.AssertionFailedError; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A test utility that verifies that your methods and constructors throw {@link * NullPointerException} or {@link UnsupportedOperationException} whenever null is passed to a - * parameter that isn't annotated with an annotation with the simple name {@code Nullable}, {@code - * CheckForNull}, {@link NullableType}, or {@link NullableDecl}. + * parameter whose declaration or type isn't annotated with an annotation with the simple name + * {@code Nullable}, {@code CheckForNull}, {@code NullableType}, or {@code NullableDecl}. * *

    The tested methods and constructors are invoked -- each time with one parameter being null and * the rest not null -- and the test fails if no expected exception is thrown. {@code @@ -66,19 +70,63 @@ * @author Kevin Bourrillion * @since 10.0 */ -@Beta @GwtIncompatible +@J2ktIncompatible +@NullMarked public final class NullPointerTester { private final ClassToInstanceMap defaults = MutableClassToInstanceMap.create(); - private final List ignoredMembers = Lists.newArrayList(); + private final List ignoredMembers = new ArrayList<>(); private ExceptionTypePolicy policy = ExceptionTypePolicy.NPE_OR_UOE; + /* + * Requiring desugaring for guava-*testlib* is likely safe, at least for the reflection-based + * NullPointerTester. But if you are a user who is reading this because this change caused you + * trouble, please let us know: https://github.com/google/guava/issues/new + */ + @IgnoreJRERequirement + public NullPointerTester() { + try { + /* + * Converter.apply has a non-nullable parameter type but doesn't throw for null arguments. For + * more information, see the comments in that class. + * + * We already know that that's how it behaves, and subclasses of Converter can't change that + * behavior. So there's no sense in making all subclass authors exclude the method from any + * NullPointerTester tests that they have. + */ + ignoredMembers.add(Converter.class.getMethod("apply", Object.class)); + } catch (NoSuchMethodException shouldBeImpossible) { + // Fine: If it doesn't exist, then there's no chance that we're going to be asked to test it. + } + + /* + * These methods "should" call checkNotNull. However, I'm wary of accidentally introducing + * anything that might slow down execution on such a hot path. Given that the methods are only + * package-private, I feel OK with just not testing them for NPE. + * + * Note that testing casValue is particularly dangerous because it uses Unsafe under some + * versions of Java, and apparently Unsafe can cause SIGSEGV instead of NPE—almost as if it's + * not safe. + */ + concat( + stream(AbstractFuture.class.getDeclaredMethods()), + stream(requireNonNull(AbstractFuture.class.getSuperclass()).getDeclaredMethods())) + .filter( + m -> + m.getName().equals("getDoneValue") + || m.getName().equals("casValue") + || m.getName().equals("casListeners") + || m.getName().equals("gasListeners")) + .forEach(ignoredMembers::add); + } + /** * Sets a default value that can be used for any parameter of type {@code type}. Returns this * object. */ + @CanIgnoreReturnValue public NullPointerTester setDefault(Class type, T value) { defaults.putInstance(type, checkNotNull(value)); return this; @@ -89,6 +137,7 @@ public NullPointerTester setDefault(Class type, T value) { * * @since 13.0 */ + @CanIgnoreReturnValue public NullPointerTester ignore(Method method) { ignoredMembers.add(checkNotNull(method)); return this; @@ -99,6 +148,7 @@ public NullPointerTester ignore(Method method) { * * @since 22.0 */ + @CanIgnoreReturnValue public NullPointerTester ignore(Constructor constructor) { ignoredMembers.add(checkNotNull(constructor)); return this; @@ -176,7 +226,7 @@ public void testAllPublicInstanceMethods(Object instance) { * * @param instance the instance to invoke {@code method} on, or null if {@code method} is static */ - public void testMethod(@NullableDecl Object instance, Method method) { + public void testMethod(@Nullable Object instance, Method method) { Class[] types = method.getParameterTypes(); for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { testMethodParameter(instance, method, nullIndex); @@ -207,8 +257,7 @@ public void testConstructor(Constructor ctor) { * * @param instance the instance to invoke {@code method} on, or null if {@code method} is static */ - public void testMethodParameter( - @NullableDecl final Object instance, final Method method, int paramIndex) { + public void testMethodParameter(@Nullable Object instance, Method method, int paramIndex) { method.setAccessible(true); testParameter(instance, invokable(instance, method), paramIndex, method.getDeclaringClass()); } @@ -306,7 +355,7 @@ private static final class Signature { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof Signature) { Signature that = (Signature) obj; return name.equals(that.name) && parameterTypes.equals(that.parameterTypes); @@ -316,7 +365,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hashCode(name, parameterTypes); + return Objects.hash(name, parameterTypes); } } @@ -329,11 +378,18 @@ public int hashCode() { * static */ private void testParameter( - Object instance, Invokable invokable, int paramIndex, Class testedClass) { + @Nullable Object instance, Invokable invokable, int paramIndex, Class testedClass) { + /* + * com.google.common is starting to rely on type-use annotations, which aren't visible under + * Android VMs and in open-source guava-android. So we skip testing there. + */ + if (Reflection.getPackageName(testedClass).startsWith("com.google.common")) { + return; + } if (isPrimitiveOrNullable(invokable.getParameters().get(paramIndex))) { return; // there's nothing to test } - Object[] params = buildParamList(invokable, paramIndex); + @Nullable Object[] params = buildParamList(invokable, paramIndex); try { @SuppressWarnings("unchecked") // We'll get a runtime exception if the type is wrong. Invokable unsafe = (Invokable) invokable; @@ -351,27 +407,26 @@ private void testParameter( if (policy.isExpectedType(cause)) { return; } - AssertionFailedError error = - new AssertionFailedError( - String.format( - "wrong exception thrown from %s when passing null to %s parameter at index %s.%n" - + "Full parameters: %s%n" - + "Actual exception message: %s", - invokable, - invokable.getParameters().get(paramIndex).getType(), - paramIndex, - Arrays.toString(params), - cause)); - error.initCause(cause); - throw error; + throw new AssertionError( + String.format( + "wrong exception thrown from %s when passing null to %s parameter at index %s.%n" + + "Full parameters: %s%n" + + "Actual exception message: %s", + invokable, + invokable.getParameters().get(paramIndex).getType(), + paramIndex, + Arrays.toString(params), + cause), + cause); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } - private Object[] buildParamList(Invokable invokable, int indexOfParamToSetToNull) { + private @Nullable Object[] buildParamList( + Invokable invokable, int indexOfParamToSetToNull) { ImmutableList params = invokable.getParameters(); - Object[] args = new Object[params.size()]; + @Nullable Object[] args = new Object[params.size()]; for (int i = 0; i < args.length; i++) { Parameter param = params.get(i); @@ -387,7 +442,7 @@ private Object[] buildParamList(Invokable invokable, int indexOfParamToSet return args; } - private T getDefaultValue(TypeToken type) { + private @Nullable T getDefaultValue(TypeToken type) { // We assume that all defaults are generics-safe, even if they aren't, // we take the risk. @SuppressWarnings("unchecked") @@ -426,7 +481,7 @@ private T getDefaultValue(TypeToken type) { } private Converter defaultConverter( - final TypeToken convertFromType, final TypeToken convertToType) { + TypeToken convertFromType, TypeToken convertToType) { return new Converter() { @Override protected T doForward(F a) { @@ -452,16 +507,16 @@ private static TypeToken getFirstTypeParameter(Type type) { } } - private T newDefaultReturningProxy(final TypeToken type) { + private T newDefaultReturningProxy(TypeToken type) { return new DummyProxy() { @Override - R dummyReturnValue(TypeToken returnType) { + @Nullable R dummyReturnValue(TypeToken returnType) { return getDefaultValue(returnType); } }.newProxy(type); } - private static Invokable invokable(@NullableDecl Object instance, Method method) { + private static Invokable invokable(@Nullable Object instance, Method method) { if (instance == null) { return Invokable.from(method); } else { @@ -474,11 +529,18 @@ static boolean isPrimitiveOrNullable(Parameter param) { } private static final ImmutableSet NULLABLE_ANNOTATION_SIMPLE_NAMES = - ImmutableSet.of( - "CheckForNull", "Nullable", "NullableDecl", "NullableType", "ParametricNullness"); + ImmutableSet.of("CheckForNull", "Nullable", "NullableDecl", "NullableType"); - static boolean isNullable(AnnotatedElement e) { - for (Annotation annotation : e.getAnnotations()) { + static boolean isNullable(Invokable invokable) { + return NULLNESS_ANNOTATION_READER.isNullable(invokable); + } + + static boolean isNullable(Parameter param) { + return NULLNESS_ANNOTATION_READER.isNullable(param); + } + + private static boolean containsNullable(Annotation[] annotations) { + for (Annotation annotation : annotations) { if (NULLABLE_ANNOTATION_SIMPLE_NAMES.contains(annotation.annotationType().getSimpleName())) { return true; } @@ -487,7 +549,29 @@ static boolean isNullable(AnnotatedElement e) { } private boolean isIgnored(Member member) { - return member.isSynthetic() || ignoredMembers.contains(member) || isEquals(member); + return member.isSynthetic() + || ignoredMembers.contains(member) + || isEquals(member) + /* + * We can assume that Kotlin code is performing the null checks that we want, since kotlinc + * inserts checks automatically for non-private functions (which are the only kind that we + * check). + * + * We *would* just check such functions redundantly, but kotlinc emits its nullness + * annotations in the form of JetBrains annotations, which have only class retention and + * thus are invisible at runtime. Thus, we conclude that the parameter types are + * *non*-nullable, even when they are declared as `Foo?`. + */ + || hasAutomaticNullChecksFromKotlin(member); + } + + private static boolean hasAutomaticNullChecksFromKotlin(Member member) { + for (Annotation annotation : member.getDeclaringClass().getAnnotations()) { + if (annotation.annotationType().getName().equals("kotlin.Metadata")) { + return true; + } + } + return false; } /** @@ -547,6 +631,63 @@ public boolean isExpectedType(Throwable cause) { } }; - public abstract boolean isExpectedType(Throwable cause); + abstract boolean isExpectedType(Throwable cause); + } + + private static boolean annotatedTypeExists() { + try { + Class.forName("java.lang.reflect.AnnotatedType"); + } catch (ClassNotFoundException e) { + return false; + } + return true; + } + + private static final NullnessAnnotationReader NULLNESS_ANNOTATION_READER = + annotatedTypeExists() + ? NullnessAnnotationReader.FROM_DECLARATION_AND_TYPE_USE_ANNOTATIONS + : NullnessAnnotationReader.FROM_DECLARATION_ANNOTATIONS_ONLY; + + /** + * Looks for declaration nullness annotations and, if supported, type-use nullness annotations. + * + *

    Under Android VMs, the methods for retrieving type-use annotations don't exist. This means + * that {@link NullPointerTester} may misbehave under Android when used on classes that rely on + * type-use annotations. + * + *

    Under j2objc, the necessary APIs exist, but some (perhaps all) return stub values, like + * empty arrays. Presumably {@link NullPointerTester} could likewise misbehave under j2objc, but I + * don't know that anyone uses it there, anyway. + */ + private enum NullnessAnnotationReader { + FROM_DECLARATION_AND_TYPE_USE_ANNOTATIONS { + @Override + boolean isNullable(Invokable invokable) { + return FROM_DECLARATION_ANNOTATIONS_ONLY.isNullable(invokable) + ; + // TODO(cpovirk): Should we also check isNullableTypeVariable? + } + + @Override + boolean isNullable(Parameter param) { + return FROM_DECLARATION_ANNOTATIONS_ONLY.isNullable(param) + ; + } + }, + FROM_DECLARATION_ANNOTATIONS_ONLY { + @Override + boolean isNullable(Invokable invokable) { + return containsNullable(invokable.getAnnotations()); + } + + @Override + boolean isNullable(Parameter param) { + return containsNullable(param.getAnnotations()); + } + }; + + abstract boolean isNullable(Invokable invokable); + + abstract boolean isNullable(Parameter param); } } diff --git a/android/guava-testlib/src/com/google/common/testing/Platform.java b/android/guava-testlib/src/com/google/common/testing/Platform.java index b107966ec97a..78881245cf55 100644 --- a/android/guava-testlib/src/com/google/common/testing/Platform.java +++ b/android/guava-testlib/src/com/google/common/testing/Platform.java @@ -17,6 +17,7 @@ package com.google.common.testing; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import java.io.ByteArrayInputStream; @@ -24,13 +25,15 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import org.jspecify.annotations.NullMarked; /** * Methods factored out so that they can be emulated differently in GWT. * * @author Chris Povirk */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked final class Platform { /** Serializes and deserializes the specified object. */ @SuppressWarnings("unchecked") @@ -41,7 +44,7 @@ static T reserialize(T object) { ObjectOutputStream out = new ObjectOutputStream(bytes); out.writeObject(object); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())); - return (T) in.readObject(); + return (T) requireNonNull(in.readObject()); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } diff --git a/android/guava-testlib/src/com/google/common/testing/RelationshipTester.java b/android/guava-testlib/src/com/google/common/testing/RelationshipTester.java index 5adf01091a85..ebf0d2677d14 100644 --- a/android/guava-testlib/src/com/google/common/testing/RelationshipTester.java +++ b/android/guava-testlib/src/com/google/common/testing/RelationshipTester.java @@ -21,9 +21,11 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; import java.util.List; import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; /** * Implementation helper for {@link EqualsTester} and {@link EquivalenceTester} that tests for @@ -32,12 +34,10 @@ * @author Gregory Kick */ @GwtCompatible +@NullMarked final class RelationshipTester { - - static class ItemReporter { - String reportItem(Item item) { - return item.toString(); - } + interface ItemReporter { + String reportItem(Item item); } /** @@ -52,7 +52,7 @@ String reportItem(Item item) { private final String relationshipName; private final String hashName; private final ItemReporter itemReporter; - private final List> groups = Lists.newArrayList(); + private final List> groups = new ArrayList<>(); RelationshipTester( Equivalence equivalence, @@ -66,6 +66,7 @@ String reportItem(Item item) { } // TODO(cpovirk): should we reject null items, since the tests already check null automatically? + @CanIgnoreReturnValue public RelationshipTester addRelatedGroup(Iterable group) { groups.add(ImmutableList.copyOf(group)); return this; @@ -147,7 +148,7 @@ private void assertWithTemplate(String template, Item item, Item other, bo } private Item getItem(int groupNumber, int itemNumber) { - return new Item(groups.get(groupNumber).get(itemNumber), groupNumber, itemNumber); + return new Item<>(groups.get(groupNumber).get(itemNumber), groupNumber, itemNumber); } static final class Item { diff --git a/android/guava-testlib/src/com/google/common/testing/SerializableTester.java b/android/guava-testlib/src/com/google/common/testing/SerializableTester.java index 62980764d65f..7be2f47586e3 100644 --- a/android/guava-testlib/src/com/google/common/testing/SerializableTester.java +++ b/android/guava-testlib/src/com/google/common/testing/SerializableTester.java @@ -16,10 +16,11 @@ package com.google.common.testing; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import junit.framework.Assert; import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; /** * Tests serialization and deserialization of an object, optionally asserting that the resulting @@ -32,8 +33,8 @@ * @author Mike Bostock * @since 10.0 */ -@Beta @GwtCompatible // but no-op! +@NullMarked public final class SerializableTester { private SerializableTester() {} @@ -52,7 +53,7 @@ private SerializableTester() {} * @throws RuntimeException if the specified object was not successfully serialized or * deserialized */ - @SuppressWarnings("unchecked") + @CanIgnoreReturnValue public static T reserialize(T object) { return Platform.reserialize(object); } @@ -84,6 +85,7 @@ public static T reserialize(T object) { * @throws AssertionFailedError if the re-serialized object is not equal to the original object, * or if the hashcodes are different. */ + @CanIgnoreReturnValue public static T reserializeAndAssert(T object) { T copy = reserialize(object); new EqualsTester().addEqualityGroup(object, copy).testEquals(); diff --git a/android/guava-testlib/src/com/google/common/testing/SloppyTearDown.java b/android/guava-testlib/src/com/google/common/testing/SloppyTearDown.java index 95ff34e33dda..1790499ca801 100644 --- a/android/guava-testlib/src/com/google/common/testing/SloppyTearDown.java +++ b/android/guava-testlib/src/com/google/common/testing/SloppyTearDown.java @@ -16,10 +16,10 @@ package com.google.common.testing; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import java.util.logging.Level; import java.util.logging.Logger; +import org.jspecify.annotations.NullMarked; /** * Simple utility for when you want to create a {@link TearDown} that may throw an exception but @@ -30,8 +30,8 @@ * @author Luiz-Otavio Zorzella * @since 10.0 */ -@Beta @GwtCompatible +@NullMarked public abstract class SloppyTearDown implements TearDown { private static final Logger logger = Logger.getLogger(SloppyTearDown.class.getName()); diff --git a/android/guava-testlib/src/com/google/common/testing/TearDown.java b/android/guava-testlib/src/com/google/common/testing/TearDown.java index 50485348f99f..dbd04e2090ca 100644 --- a/android/guava-testlib/src/com/google/common/testing/TearDown.java +++ b/android/guava-testlib/src/com/google/common/testing/TearDown.java @@ -16,8 +16,8 @@ package com.google.common.testing; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullMarked; /** * An object that can perform a {@link #tearDown} operation. @@ -25,8 +25,8 @@ * @author Kevin Bourrillion * @since 10.0 */ -@Beta @GwtCompatible +@NullMarked public interface TearDown { /** * Performs a single tear-down operation. See test-libraries-for-java's {@code diff --git a/android/guava-testlib/src/com/google/common/testing/TearDownAccepter.java b/android/guava-testlib/src/com/google/common/testing/TearDownAccepter.java index bad1f1997867..ed514d2d4b1b 100644 --- a/android/guava-testlib/src/com/google/common/testing/TearDownAccepter.java +++ b/android/guava-testlib/src/com/google/common/testing/TearDownAccepter.java @@ -16,9 +16,9 @@ package com.google.common.testing; -import com.google.common.annotations.Beta; -import com.google.errorprone.annotations.DoNotMock; import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.DoNotMock; +import org.jspecify.annotations.NullMarked; /** * Any object which can accept registrations of {@link TearDown} instances. @@ -26,9 +26,9 @@ * @author Kevin Bourrillion * @since 10.0 */ -@Beta @DoNotMock("Implement with a lambda") @GwtCompatible +@NullMarked public interface TearDownAccepter { /** * Registers a TearDown implementor which will be run after the test proper. diff --git a/android/guava-testlib/src/com/google/common/testing/TearDownStack.java b/android/guava-testlib/src/com/google/common/testing/TearDownStack.java index bab025a61fc2..5dff3b86df38 100644 --- a/android/guava-testlib/src/com/google/common/testing/TearDownStack.java +++ b/android/guava-testlib/src/com/google/common/testing/TearDownStack.java @@ -17,16 +17,18 @@ package com.google.common.testing; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Throwables.throwIfUnchecked; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.Lists; +import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.concurrent.GuardedBy; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.LinkedList; +import java.util.Deque; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import org.jspecify.annotations.NullMarked; /** * A {@code TearDownStack} contains a stack of {@link TearDown} instances. @@ -36,13 +38,15 @@ * @author Kevin Bourrillion * @since 10.0 */ -@Beta @GwtCompatible +@NullMarked public class TearDownStack implements TearDownAccepter { private static final Logger logger = Logger.getLogger(TearDownStack.class.getName()); - @GuardedBy("stack") - final LinkedList stack = new LinkedList<>(); + @VisibleForTesting final Object lock = new Object(); + + @GuardedBy("lock") + final Deque stack = new ArrayDeque<>(); private final boolean suppressThrows; @@ -56,17 +60,17 @@ public TearDownStack(boolean suppressThrows) { @Override public final void addTearDown(TearDown tearDown) { - synchronized (stack) { + synchronized (lock) { stack.addFirst(checkNotNull(tearDown)); } } /** Causes teardown to execute. */ public final void runTearDown() { - List exceptions = new ArrayList<>(); + Throwable exception = null; List stackCopy; - synchronized (stack) { - stackCopy = Lists.newArrayList(stack); + synchronized (lock) { + stackCopy = new ArrayList<>(stack); stack.clear(); } for (TearDown tearDown : stackCopy) { @@ -76,12 +80,17 @@ public final void runTearDown() { if (suppressThrows) { logger.log(Level.INFO, "exception thrown during tearDown", t); } else { - exceptions.add(t); + if (exception == null) { + exception = t; + } else { + exception.addSuppressed(t); + } } } } - if (!suppressThrows && (exceptions.size() > 0)) { - throw ClusterException.create(exceptions); + if (exception != null) { + throwIfUnchecked(exception); + throw new RuntimeException("failure during tearDown", exception); } } } diff --git a/android/guava-testlib/src/com/google/common/testing/TestLogHandler.java b/android/guava-testlib/src/com/google/common/testing/TestLogHandler.java index c03093be52f3..f21a49d77695 100644 --- a/android/guava-testlib/src/com/google/common/testing/TestLogHandler.java +++ b/android/guava-testlib/src/com/google/common/testing/TestLogHandler.java @@ -16,14 +16,15 @@ package com.google.common.testing; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.concurrent.GuardedBy; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Handler; import java.util.logging.LogRecord; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests may use this to intercept messages that are logged by the code under test. Example: @@ -52,16 +53,23 @@ * @author Kevin Bourrillion * @since 10.0 */ -@Beta @GwtCompatible +@NullMarked public class TestLogHandler extends Handler { + private final Object lock = new Object(); + /** We will keep a private list of all logged records */ + @GuardedBy("lock") private final List list = new ArrayList<>(); /** Adds the most recently logged record to our list. */ @Override - public synchronized void publish(@NullableDecl LogRecord record) { - list.add(record); + public void publish(@Nullable LogRecord record) { + synchronized (lock) { + if (record != null) { + list.add(record); + } + } } @Override @@ -70,8 +78,10 @@ public void flush() {} @Override public void close() {} - public synchronized void clear() { - list.clear(); + public void clear() { + synchronized (lock) { + list.clear(); + } } /** Returns a snapshot of the logged records. */ @@ -82,8 +92,10 @@ public synchronized void clear() { * TODO(cpovirk): consider renaming this method to reflect that it takes a snapshot (and/or return * an ImmutableList) */ - public synchronized List getStoredLogRecords() { - List result = new ArrayList<>(list); - return Collections.unmodifiableList(result); + public List getStoredLogRecords() { + synchronized (lock) { + List result = new ArrayList<>(list); + return Collections.unmodifiableList(result); + } } } diff --git a/android/guava-testlib/src/com/google/common/testing/package-info.java b/android/guava-testlib/src/com/google/common/testing/package-info.java index e6762f98c693..138d074788a6 100644 --- a/android/guava-testlib/src/com/google/common/testing/package-info.java +++ b/android/guava-testlib/src/com/google/common/testing/package-info.java @@ -15,8 +15,12 @@ */ /** - * This package contains testing utilities. It is a part of the open-source Guava library. + * Testing utilities. This package is a part of the open-source Guava library. */ -@javax.annotation.ParametersAreNonnullByDefault +@CheckReturnValue +@NullMarked package com.google.common.testing; + +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava-testlib/src/com/google/common/util/concurrent/testing/AbstractListenableFutureTest.java b/android/guava-testlib/src/com/google/common/util/concurrent/testing/AbstractListenableFutureTest.java index 2ba751537396..b26c1c8264c2 100644 --- a/android/guava-testlib/src/com/google/common/util/concurrent/testing/AbstractListenableFutureTest.java +++ b/android/guava-testlib/src/com/google/common/util/concurrent/testing/AbstractListenableFutureTest.java @@ -16,18 +16,23 @@ package com.google.common.util.concurrent.testing; -import com.google.common.annotations.Beta; +import static java.util.concurrent.Executors.newCachedThreadPool; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; + import com.google.common.annotations.GwtIncompatible; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.Nullable; /** * Abstract test case parent for anything implementing {@link ListenableFuture}. Tests the two get @@ -36,7 +41,6 @@ * @author Sven Mawson * @since 10.0 */ -@Beta @GwtIncompatible public abstract class AbstractListenableFutureTest extends TestCase { @@ -60,7 +64,7 @@ protected void tearDown() throws Exception { /** Constructs a listenable future with a value available after the latch has counted down. */ protected abstract ListenableFuture createListenableFuture( - V value, Exception except, CountDownLatch waitOn); + V value, @Nullable Exception except, CountDownLatch waitOn); /** Tests that the {@link Future#get()} method blocks until a value is available. */ public void testGetBlocksUntilValueAvailable() throws Throwable { @@ -68,32 +72,17 @@ public void testGetBlocksUntilValueAvailable() throws Throwable { assertFalse(future.isDone()); assertFalse(future.isCancelled()); - final CountDownLatch successLatch = new CountDownLatch(1); - final Throwable[] badness = new Throwable[1]; - - // Wait on the future in a separate thread. - new Thread( - new Runnable() { - @Override - public void run() { - try { - assertSame(Boolean.TRUE, future.get()); - successLatch.countDown(); - } catch (Throwable t) { - t.printStackTrace(); - badness[0] = t; - } - } - }) - .start(); + ExecutorService executor = newSingleThreadExecutor(); - // Release the future value. - latch.countDown(); + try { + Future getResult = executor.submit(() -> future.get()); - assertTrue(successLatch.await(10, TimeUnit.SECONDS)); + // Release the future value. + latch.countDown(); - if (badness[0] != null) { - throw badness[0]; + assertTrue(getResult.get(10, SECONDS)); + } finally { + executor.shutdownNow(); } assertTrue(future.isDone()); @@ -105,7 +94,7 @@ public void testTimeoutOnGetWorksCorrectly() throws InterruptedException, Execut // The task thread waits for the latch, so we expect a timeout here. try { - future.get(20, TimeUnit.MILLISECONDS); + future.get(20, MILLISECONDS); fail("Should have timed out trying to get the value."); } catch (TimeoutException expected) { } finally { @@ -123,21 +112,13 @@ public void testCanceledFutureThrowsCancellation() throws Exception { assertFalse(future.isDone()); assertFalse(future.isCancelled()); - final CountDownLatch successLatch = new CountDownLatch(1); + CountDownLatch successLatch = new CountDownLatch(1); // Run cancellation in a separate thread as an extra thread-safety test. new Thread( - new Runnable() { - @Override - public void run() { - try { - future.get(); - } catch (CancellationException expected) { - successLatch.countDown(); - } catch (Exception ignored) { - // All other errors are ignored, we expect a cancellation. - } - } + () -> { + assertThrows(CancellationException.class, future::get); + successLatch.countDown(); }) .start(); @@ -149,38 +130,23 @@ public void run() { assertTrue(future.isDone()); assertTrue(future.isCancelled()); - assertTrue(successLatch.await(200, TimeUnit.MILLISECONDS)); + assertTrue(successLatch.await(200, MILLISECONDS)); latch.countDown(); } public void testListenersNotifiedOnError() throws Exception { - final CountDownLatch successLatch = new CountDownLatch(1); - final CountDownLatch listenerLatch = new CountDownLatch(1); + CountDownLatch successLatch = new CountDownLatch(1); + CountDownLatch listenerLatch = new CountDownLatch(1); - ExecutorService exec = Executors.newCachedThreadPool(); + ExecutorService exec = newCachedThreadPool(); - future.addListener( - new Runnable() { - @Override - public void run() { - listenerLatch.countDown(); - } - }, - exec); + future.addListener(listenerLatch::countDown, exec); new Thread( - new Runnable() { - @Override - public void run() { - try { - future.get(); - } catch (CancellationException expected) { - successLatch.countDown(); - } catch (Exception ignored) { - // No success latch count down. - } - } + () -> { + assertThrows(CancellationException.class, future::get); + successLatch.countDown(); }) .start(); @@ -189,13 +155,13 @@ public void run() { assertTrue(future.isCancelled()); assertTrue(future.isDone()); - assertTrue(successLatch.await(200, TimeUnit.MILLISECONDS)); - assertTrue(listenerLatch.await(200, TimeUnit.MILLISECONDS)); + assertTrue(successLatch.await(200, MILLISECONDS)); + assertTrue(listenerLatch.await(200, MILLISECONDS)); latch.countDown(); exec.shutdown(); - exec.awaitTermination(100, TimeUnit.MILLISECONDS); + exec.awaitTermination(100, MILLISECONDS); } /** @@ -206,10 +172,10 @@ public void run() { public void testAllListenersCompleteSuccessfully() throws InterruptedException, ExecutionException { - ExecutorService exec = Executors.newCachedThreadPool(); + ExecutorService exec = newCachedThreadPool(); int listenerCount = 20; - final CountDownLatch listenerLatch = new CountDownLatch(listenerCount); + CountDownLatch listenerLatch = new CountDownLatch(listenerCount); // Test that listeners added both before and after the value is available // get called correctly. @@ -217,31 +183,17 @@ public void testAllListenersCompleteSuccessfully() // Right in the middle start up a thread to close the latch. if (i == 10) { - new Thread( - new Runnable() { - @Override - public void run() { - latch.countDown(); - } - }) - .start(); + new Thread(() -> latch.countDown()).start(); } - future.addListener( - new Runnable() { - @Override - public void run() { - listenerLatch.countDown(); - } - }, - exec); + future.addListener(listenerLatch::countDown, exec); } assertSame(Boolean.TRUE, future.get()); // Wait for the listener latch to complete. - listenerLatch.await(500, TimeUnit.MILLISECONDS); + listenerLatch.await(500, MILLISECONDS); exec.shutdown(); - exec.awaitTermination(500, TimeUnit.MILLISECONDS); + exec.awaitTermination(500, MILLISECONDS); } } diff --git a/android/guava-testlib/src/com/google/common/util/concurrent/testing/MockFutureListener.java b/android/guava-testlib/src/com/google/common/util/concurrent/testing/MockFutureListener.java index fc3ed21f1ba9..87eb73aa9163 100644 --- a/android/guava-testlib/src/com/google/common/util/concurrent/testing/MockFutureListener.java +++ b/android/guava-testlib/src/com/google/common/util/concurrent/testing/MockFutureListener.java @@ -17,13 +17,12 @@ package com.google.common.util.concurrent.testing; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.concurrent.TimeUnit.SECONDS; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import junit.framework.Assert; /** @@ -32,7 +31,6 @@ * @author Nishant Thakkar * @since 10.0 */ -@Beta @GwtIncompatible public class MockFutureListener implements Runnable { private final CountDownLatch countDownLatch; @@ -59,7 +57,7 @@ public void run() { */ public void assertSuccess(Object expectedData) throws Throwable { // Verify that the listener executed in a reasonable amount of time. - Assert.assertTrue(countDownLatch.await(1L, TimeUnit.SECONDS)); + Assert.assertTrue(countDownLatch.await(1L, SECONDS)); try { Assert.assertEquals(expectedData, future.get()); @@ -75,7 +73,7 @@ public void assertSuccess(Object expectedData) throws Throwable { */ public void assertException(Throwable expectedCause) throws Exception { // Verify that the listener executed in a reasonable amount of time. - Assert.assertTrue(countDownLatch.await(1L, TimeUnit.SECONDS)); + Assert.assertTrue(countDownLatch.await(1L, SECONDS)); try { future.get(); @@ -88,6 +86,6 @@ public void assertException(Throwable expectedCause) throws Exception { public void assertTimeout() throws Exception { // Verify that the listener does not get called in a reasonable amount of // time. - Assert.assertFalse(countDownLatch.await(1L, TimeUnit.SECONDS)); + Assert.assertFalse(countDownLatch.await(1L, SECONDS)); } } diff --git a/android/guava-testlib/src/com/google/common/util/concurrent/testing/SameThreadScheduledExecutorService.java b/android/guava-testlib/src/com/google/common/util/concurrent/testing/SameThreadScheduledExecutorService.java index c232218fb4bf..53150beff496 100644 --- a/android/guava-testlib/src/com/google/common/util/concurrent/testing/SameThreadScheduledExecutorService.java +++ b/android/guava-testlib/src/com/google/common/util/concurrent/testing/SameThreadScheduledExecutorService.java @@ -17,6 +17,7 @@ package com.google.common.util.concurrent.testing; import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; +import static java.util.concurrent.Executors.callable; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Preconditions; @@ -44,6 +45,7 @@ * @author Zach van Schouwen */ @GwtIncompatible +// TODO(cpovirk): Make this final (but that may break Mockito spy calls). class SameThreadScheduledExecutorService extends AbstractExecutorService implements ListeningScheduledExecutorService { @@ -135,23 +137,21 @@ public void execute(Runnable command) { public ListenableScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { Preconditions.checkNotNull(command, "command must not be null"); Preconditions.checkNotNull(unit, "unit must not be null!"); - return schedule(java.util.concurrent.Executors.callable(command), delay, unit); + return schedule(callable(command), delay, unit); } @Override public ListenableScheduledFuture schedule( - final Callable callable, long delay, TimeUnit unit) { + Callable callable, long delay, TimeUnit unit) { Preconditions.checkNotNull(callable, "callable must not be null!"); Preconditions.checkNotNull(unit, "unit must not be null!"); ListenableFuture delegateFuture = submit(callable); - return new ImmediateScheduledFuture(delegateFuture); + return new ImmediateScheduledFuture<>(delegateFuture); } - private static class ImmediateScheduledFuture extends SimpleForwardingListenableFuture + private static final class ImmediateScheduledFuture extends SimpleForwardingListenableFuture implements ListenableScheduledFuture { - private ExecutionException exception; - - protected ImmediateScheduledFuture(ListenableFuture future) { + ImmediateScheduledFuture(ListenableFuture future) { super(future); } diff --git a/android/guava-testlib/src/com/google/common/util/concurrent/testing/TestingExecutors.java b/android/guava-testlib/src/com/google/common/util/concurrent/testing/TestingExecutors.java index 421243043245..39d1beeb2b82 100644 --- a/android/guava-testlib/src/com/google/common/util/concurrent/testing/TestingExecutors.java +++ b/android/guava-testlib/src/com/google/common/util/concurrent/testing/TestingExecutors.java @@ -16,10 +16,10 @@ package com.google.common.util.concurrent.testing; -import com.google.common.annotations.Beta; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Longs; import com.google.common.util.concurrent.AbstractFuture; import com.google.common.util.concurrent.AbstractListeningExecutorService; import com.google.common.util.concurrent.ListenableScheduledFuture; @@ -28,7 +28,10 @@ import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; import java.util.concurrent.TimeUnit; /** @@ -37,7 +40,6 @@ * @author Chris Nokleberg * @since 14.0 */ -@Beta @GwtIncompatible public final class TestingExecutors { private TestingExecutors() {} @@ -86,9 +88,9 @@ public static ListeningScheduledExecutorService noOpScheduledExecutor() { * invokeAll/invokeAny} throwing RejectedExecutionException, although a subset of the tasks may * already have been executed. * - * @since 15.0 + * @since 32.0.0 (taking the place of a method with a different return type from 15.0) */ - public static SameThreadScheduledExecutorService sameThreadScheduledExecutor() { + public static ListeningScheduledExecutorService sameThreadScheduledExecutor() { return new SameThreadScheduledExecutorService(); } @@ -149,11 +151,11 @@ public ListenableScheduledFuture scheduleWithFixedDelay( return NeverScheduledFuture.create(); } - private static class NeverScheduledFuture extends AbstractFuture + private static final class NeverScheduledFuture extends AbstractFuture implements ListenableScheduledFuture { static NeverScheduledFuture create() { - return new NeverScheduledFuture(); + return new NeverScheduledFuture<>(); } @Override @@ -163,7 +165,7 @@ public long getDelay(TimeUnit unit) { @Override public int compareTo(Delayed other) { - return Longs.compare(getDelay(TimeUnit.NANOSECONDS), other.getDelay(TimeUnit.NANOSECONDS)); + return Long.compare(getDelay(NANOSECONDS), other.getDelay(NANOSECONDS)); } } } diff --git a/android/guava/src/com/google/common/hash/ElementTypesAreNonnullByDefault.java b/android/guava-testlib/test/com/google/common/collect/testing/AndroidIncompatible.java old mode 100755 new mode 100644 similarity index 53% rename from android/guava/src/com/google/common/hash/ElementTypesAreNonnullByDefault.java rename to android/guava-testlib/test/com/google/common/collect/testing/AndroidIncompatible.java index a2382b3514cc..bbd60d7a2206 --- a/android/guava/src/com/google/common/hash/ElementTypesAreNonnullByDefault.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/AndroidIncompatible.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Guava Authors + * Copyright (C) 2015 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,28 +14,28 @@ * limitations under the License. */ -package com.google.common.hash; +package com.google.common.collect.testing; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.lang.annotation.RetentionPolicy.CLASS; import com.google.common.annotations.GwtCompatible; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; /** - * Marks all "top-level" types as non-null in a way that is recognized by Kotlin. Note that this - * unfortunately includes type-variable usages, so we also provide {@link ParametricNullness} to - * "undo" it as best we can. + * Signifies that a test should not be run under Android. This annotation is respected only by our + * Google-internal Android suite generators. Note that those generators also suppress any test + * annotated with MediumTest or LargeTest. + * + *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the + * documentation on another copy of this annotation}. */ +@Retention(CLASS) +@Target({ANNOTATION_TYPE, CONSTRUCTOR, FIELD, METHOD, TYPE}) @GwtCompatible -@Retention(RUNTIME) -@Target(TYPE) -@TypeQualifierDefault({FIELD, METHOD, PARAMETER}) -@Nonnull -@interface ElementTypesAreNonnullByDefault {} +@interface AndroidIncompatible {} diff --git a/android/guava-testlib/test/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilderTest.java b/android/guava-testlib/test/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilderTest.java index dc0fe37b4471..682fd909d4ad 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilderTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/FeatureSpecificTestSuiteBuilderTest.java @@ -22,37 +22,23 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestResult; -import org.junit.Ignore; -/** @author Max Ross */ +/** + * @author Max Ross + */ +@AndroidIncompatible // test-suite builders public class FeatureSpecificTestSuiteBuilderTest extends TestCase { - - static boolean testWasRun; - - @Override - protected void setUp() throws Exception { - super.setUp(); - testWasRun = false; - } - - @Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests. - public static final class MyAbstractTester extends AbstractTester { - public void testNothing() { - testWasRun = true; - } - } - private static final class MyTestSuiteBuilder extends FeatureSpecificTestSuiteBuilder { - + @SuppressWarnings("rawtypes") // class literals @Override protected List> getTesters() { - return Collections.>singletonList(MyAbstractTester.class); + return Collections.>singletonList(MyTester.class); } } public void testLifecycle() { - final boolean setUp[] = {false}; + boolean[] setUp = {false}; Runnable setUpRunnable = new Runnable() { @Override @@ -61,7 +47,7 @@ public void run() { } }; - final boolean tearDown[] = {false}; + boolean[] tearDown = {false}; Runnable tearDownRunnable = new Runnable() { @Override @@ -80,8 +66,9 @@ public void run() { .withTearDown(tearDownRunnable) .createTestSuite(); TestResult result = new TestResult(); + int timesMyTesterWasRunBeforeSuite = MyTester.timesTestClassWasRun; test.run(result); - assertTrue(testWasRun); + assertEquals(timesMyTesterWasRunBeforeSuite + 1, MyTester.timesTestClassWasRun); assertTrue(setUp[0]); assertTrue(tearDown[0]); } diff --git a/android/guava-testlib/test/com/google/common/collect/testing/HelpersTest.java b/android/guava-testlib/test/com/google/common/collect/testing/HelpersTest.java index 41f4bfb27501..e7491dd03254 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/HelpersTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/HelpersTest.java @@ -17,12 +17,18 @@ package com.google.common.collect.testing; import static com.google.common.collect.testing.Helpers.NullsBeforeB; +import static com.google.common.collect.testing.Helpers.assertContains; +import static com.google.common.collect.testing.Helpers.assertContainsAllOf; +import static com.google.common.collect.testing.Helpers.assertContentsInOrder; +import static com.google.common.collect.testing.Helpers.assertEmpty; +import static com.google.common.collect.testing.Helpers.assertEqualInOrder; import static com.google.common.collect.testing.Helpers.testComparator; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyIterator; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -43,27 +49,21 @@ public void testNullsBeforeB() { public void testIsEmpty_iterable() { List list = new ArrayList<>(); - Helpers.assertEmpty(list); - Helpers.assertEmpty( - new Iterable() { - @Override - public Iterator iterator() { - return Collections.emptyList().iterator(); - } - }); + assertEmpty(list); + assertEmpty(() -> emptyIterator()); list.add("a"); try { - Helpers.assertEmpty(list); + assertEmpty(list); throw new Error(); } catch (AssertionFailedError expected) { } try { - Helpers.assertEmpty( + assertEmpty( new Iterable() { @Override public Iterator iterator() { - return Collections.singleton("a").iterator(); + return singleton("a").iterator(); } }); throw new Error(); @@ -73,110 +73,110 @@ public Iterator iterator() { public void testIsEmpty_map() { Map map = new HashMap<>(); - Helpers.assertEmpty(map); + assertEmpty(map); map.put("a", "b"); try { - Helpers.assertEmpty(map); + assertEmpty(map); throw new Error(); } catch (AssertionFailedError expected) { } } public void testAssertEqualInOrder() { - List list = Arrays.asList("a", "b", "c"); - Helpers.assertEqualInOrder(list, list); + List list = asList("a", "b", "c"); + assertEqualInOrder(list, list); - List fewer = Arrays.asList("a", "b"); + List fewer = asList("a", "b"); try { - Helpers.assertEqualInOrder(list, fewer); + assertEqualInOrder(list, fewer); throw new Error(); } catch (AssertionFailedError expected) { } try { - Helpers.assertEqualInOrder(fewer, list); + assertEqualInOrder(fewer, list); throw new Error(); } catch (AssertionFailedError expected) { } - List differentOrder = Arrays.asList("a", "c", "b"); + List differentOrder = asList("a", "c", "b"); try { - Helpers.assertEqualInOrder(list, differentOrder); + assertEqualInOrder(list, differentOrder); throw new Error(); } catch (AssertionFailedError expected) { } - List differentContents = Arrays.asList("a", "b", "C"); + List differentContents = asList("a", "b", "C"); try { - Helpers.assertEqualInOrder(list, differentContents); + assertEqualInOrder(list, differentContents); throw new Error(); } catch (AssertionFailedError expected) { } } public void testAssertContentsInOrder() { - List list = Arrays.asList("a", "b", "c"); - Helpers.assertContentsInOrder(list, "a", "b", "c"); + List list = asList("a", "b", "c"); + assertContentsInOrder(list, "a", "b", "c"); try { - Helpers.assertContentsInOrder(list, "a", "b"); + assertContentsInOrder(list, "a", "b"); throw new Error(); } catch (AssertionFailedError expected) { } try { - Helpers.assertContentsInOrder(list, "a", "b", "c", "d"); + assertContentsInOrder(list, "a", "b", "c", "d"); throw new Error(); } catch (AssertionFailedError expected) { } try { - Helpers.assertContentsInOrder(list, "a", "c", "b"); + assertContentsInOrder(list, "a", "c", "b"); throw new Error(); } catch (AssertionFailedError expected) { } try { - Helpers.assertContentsInOrder(list, "a", "B", "c"); + assertContentsInOrder(list, "a", "B", "c"); throw new Error(); } catch (AssertionFailedError expected) { } } public void testAssertContains() { - List list = Arrays.asList("a", "b"); - Helpers.assertContains(list, "a"); - Helpers.assertContains(list, "b"); + List list = asList("a", "b"); + assertContains(list, "a"); + assertContains(list, "b"); try { - Helpers.assertContains(list, "c"); + assertContains(list, "c"); throw new Error(); } catch (AssertionFailedError expected) { } } public void testAssertContainsAllOf() { - List list = Arrays.asList("a", "a", "b", "c"); - Helpers.assertContainsAllOf(list, "a"); - Helpers.assertContainsAllOf(list, "a", "a"); - Helpers.assertContainsAllOf(list, "a", "b", "c"); - Helpers.assertContainsAllOf(list, "a", "b", "c", "a"); + List list = asList("a", "a", "b", "c"); + assertContainsAllOf(list, "a"); + assertContainsAllOf(list, "a", "a"); + assertContainsAllOf(list, "a", "b", "c"); + assertContainsAllOf(list, "a", "b", "c", "a"); try { - Helpers.assertContainsAllOf(list, "d"); + assertContainsAllOf(list, "d"); throw new Error(); } catch (AssertionFailedError expected) { } try { - Helpers.assertContainsAllOf(list, "a", "b", "c", "d"); + assertContainsAllOf(list, "a", "b", "c", "d"); throw new Error(); } catch (AssertionFailedError expected) { } try { - Helpers.assertContainsAllOf(list, "a", "a", "a"); + assertContainsAllOf(list, "a", "a", "a"); throw new Error(); } catch (AssertionFailedError expected) { } diff --git a/android/guava-testlib/test/com/google/common/collect/testing/IteratorTesterTest.java b/android/guava-testlib/test/com/google/common/collect/testing/IteratorTesterTest.java index 283f51efe66a..dc9b1d3d8bbb 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/IteratorTesterTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/IteratorTesterTest.java @@ -18,15 +18,14 @@ import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; -import static java.util.Collections.emptyList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; -import junit.framework.AssertionFailedError; import junit.framework.TestCase; /** @@ -104,13 +103,13 @@ protected Iterator newTargetIterator() { * to remove() will incorrectly throw an IllegalStateException, instead of removing the last * element returned. * - *

    See Sun bug 6529795 + *

    See JDK-6529795 */ - static class IteratorWithSunJavaBug6529795 implements Iterator { + static class IteratorWithJdkBug6529795 implements Iterator { Iterator iterator; boolean nextThrewException; - IteratorWithSunJavaBug6529795(Iterator iterator) { + IteratorWithJdkBug6529795(Iterator iterator) { this.iterator = iterator; } @@ -138,7 +137,7 @@ public void remove() { } } - public void testCanCatchSunJavaBug6529795InTargetIterator() { + public void testCanCatchJdkBug6529795InTargetIterator() { try { /* Choose 4 steps to get sequence [next, next, next, remove] */ new IteratorTester( @@ -146,10 +145,10 @@ public void testCanCatchSunJavaBug6529795InTargetIterator() { @Override protected Iterator newTargetIterator() { Iterator iterator = Lists.newArrayList(1, 2).iterator(); - return new IteratorWithSunJavaBug6529795<>(iterator); + return new IteratorWithJdkBug6529795<>(iterator); } }.test(); - } catch (AssertionFailedError e) { + } catch (AssertionError e) { return; } fail("Should have caught jdk6 bug in target iterator"); @@ -190,7 +189,7 @@ public void testVerifyGetsCalled() { } public void testVerifyCanThrowAssertionThatFailsTest() { - final String message = "Important info about why verify failed"; + String message = "Important info about why verify failed"; IteratorTester tester = new IteratorTester( 1, MODIFIABLE, newArrayList(1, 2, 3), IteratorTester.KnownOrder.KNOWN_ORDER) { @@ -201,23 +200,23 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - throw new AssertionFailedError(message); + throw new AssertionError(message); } }; - AssertionFailedError actual = null; + AssertionError actual = null; try { tester.test(); - } catch (AssertionFailedError e) { + } catch (AssertionError e) { actual = e; } assertNotNull("verify() should be able to cause test failure", actual); assertTrue( - "AssertionFailedError should have info about why test failed", + "AssertionError should have info about why test failed", actual.getCause().getMessage().contains(message)); } public void testMissingException() { - List emptyList = newArrayList(); + List emptyList = new ArrayList<>(); IteratorTester tester = new IteratorTester( @@ -233,7 +232,7 @@ public void remove() { @Override public Integer next() { // We should throw here, but we won't! - return null; + return 0; } @Override @@ -259,10 +258,10 @@ protected Iterator newTargetIterator() { } public void testSimilarException() { - List emptyList = emptyList(); + List expectedElements = ImmutableList.of(); IteratorTester tester = new IteratorTester( - 1, MODIFIABLE, emptyList, IteratorTester.KnownOrder.KNOWN_ORDER) { + 1, MODIFIABLE, expectedElements, IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { return new Iterator() { @@ -291,10 +290,10 @@ public boolean hasNext() { } public void testMismatchedException() { - List emptyList = emptyList(); + List expectedElements = ImmutableList.of(); IteratorTester tester = new IteratorTester( - 1, MODIFIABLE, emptyList, IteratorTester.KnownOrder.KNOWN_ORDER) { + 1, MODIFIABLE, expectedElements, IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { return new Iterator() { @@ -323,7 +322,7 @@ public boolean hasNext() { private static void assertFailure(IteratorTester tester) { try { tester.test(); - } catch (AssertionFailedError expected) { + } catch (AssertionError expected) { return; } fail(); diff --git a/android/guava-testlib/test/com/google/common/collect/testing/MapTestSuiteBuilderTests.java b/android/guava-testlib/test/com/google/common/collect/testing/MapTestSuiteBuilderTests.java index 1a54f6862ded..6ef3030b85ed 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/MapTestSuiteBuilderTests.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/MapTestSuiteBuilderTests.java @@ -21,11 +21,15 @@ import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.Feature; import com.google.common.collect.testing.features.MapFeature; +import com.google.common.reflect.Reflection; +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Collection; @@ -36,15 +40,18 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.Nullable; /** * Tests {@link MapTestSuiteBuilder} by using it against maps that have various negative behaviors. * * @author George van den Driessche */ +@AndroidIncompatible // test-suite builders public final class MapTestSuiteBuilderTests extends TestCase { private MapTestSuiteBuilderTests() {} @@ -52,13 +59,14 @@ public static Test suite() { TestSuite suite = new TestSuite(MapTestSuiteBuilderTests.class.getSimpleName()); suite.addTest(testsForHashMapNullKeysForbidden()); suite.addTest(testsForHashMapNullValuesForbidden()); + suite.addTest(testsForSetUpTearDown()); return suite; } private abstract static class WrappedHashMapGenerator extends TestStringMapGenerator { @Override protected final Map create(Entry[] entries) { - HashMap map = Maps.newHashMap(); + HashMap map = new HashMap<>(); for (Entry entry : entries) { map.put(entry.getKey(), entry.getValue()); } @@ -88,7 +96,7 @@ private static Test testsForHashMapNullKeysForbidden() { return wrappedHashMapTests( new WrappedHashMapGenerator() { @Override - Map wrap(final HashMap map) { + Map wrap(HashMap map) { if (map.containsKey(null)) { throw new NullPointerException(); } @@ -99,7 +107,7 @@ public Set> entrySet() { } @Override - public String put(String key, String value) { + public @Nullable String put(String key, String value) { checkNotNull(key); return map.put(key, value); } @@ -114,7 +122,7 @@ private static Test testsForHashMapNullValuesForbidden() { return wrappedHashMapTests( new WrappedHashMapGenerator() { @Override - Map wrap(final HashMap map) { + Map wrap(HashMap map) { if (map.containsValue(null)) { throw new NullPointerException(); } @@ -131,7 +139,7 @@ public int hashCode() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return map.equals(o); } @@ -141,7 +149,7 @@ public String toString() { } @Override - public String remove(Object key) { + public @Nullable String remove(Object key) { return map.remove(key); } @@ -167,7 +175,7 @@ public Entry next() { return transform(iterator.next()); } - private Entry transform(final Entry next) { + private Entry transform(Entry next) { return new Entry() { @Override @@ -187,7 +195,7 @@ public String getKey() { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return next.equals(obj); } @@ -231,7 +239,7 @@ public int hashCode() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return map.entrySet().equals(o); } @@ -242,7 +250,7 @@ public String toString() { } @Override - public String put(String key, String value) { + public @Nullable String put(String key, String value) { checkNotNull(value); return map.put(key, value); } @@ -252,4 +260,87 @@ public String put(String key, String value) { "HashMap w/out null values", ALLOWS_NULL_KEYS); } + + /** + * Map generator that verifies that {@code setUp()} methods are called in all the test cases. The + * {@code setUpRan} parameter is set true by the {@code setUp} that every test case is supposed to + * have registered, and set false by the {@code tearDown}. We use a dynamic proxy to intercept all + * of the {@code Map} method calls and check that {@code setUpRan} is true. + */ + private static class CheckSetUpHashMapGenerator extends WrappedHashMapGenerator { + private final AtomicBoolean setUpRan; + + CheckSetUpHashMapGenerator(AtomicBoolean setUpRan) { + this.setUpRan = setUpRan; + } + + @Override + Map wrap(HashMap map) { + @SuppressWarnings("unchecked") + Map proxy = + Reflection.newProxy(Map.class, new CheckSetUpInvocationHandler(map, setUpRan)); + return proxy; + } + } + + /** + * Intercepts calls to a {@code Map} to check that {@code setUpRan} is true when they happen. Then + * forwards the calls to the underlying {@code Map}. + */ + private static class CheckSetUpInvocationHandler implements InvocationHandler, Serializable { + private final Map map; + private final AtomicBoolean setUpRan; + + CheckSetUpInvocationHandler(Map map, AtomicBoolean setUpRan) { + this.map = map; + this.setUpRan = setUpRan; + } + + @Override + public Object invoke(Object target, Method method, Object[] args) throws Throwable { + assertTrue("setUp should have run", setUpRan.get()); + try { + return method.invoke(map, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } catch (IllegalAccessException e) { + throw newLinkageError(e); + } + } + } + + /** Verifies that {@code setUp} and {@code tearDown} are called in all map test cases. */ + private static Test testsForSetUpTearDown() { + AtomicBoolean setUpRan = new AtomicBoolean(); + Runnable setUp = + new Runnable() { + @Override + public void run() { + assertFalse("previous tearDown should have run before setUp", setUpRan.getAndSet(true)); + } + }; + Runnable tearDown = + new Runnable() { + @Override + public void run() { + assertTrue("setUp should have run", setUpRan.getAndSet(false)); + } + }; + return MapTestSuiteBuilder.using(new CheckSetUpHashMapGenerator(setUpRan)) + .named("setUpTearDown") + .withFeatures( + MapFeature.GENERAL_PURPOSE, + MapFeature.ALLOWS_NULL_KEYS, + MapFeature.ALLOWS_NULL_VALUES, + CollectionFeature.SERIALIZABLE, + CollectionFeature.SUPPORTS_ITERATOR_REMOVE, + CollectionSize.ANY) + .withSetUp(setUp) + .withTearDown(tearDown) + .createTestSuite(); + } + + private static LinkageError newLinkageError(Throwable cause) { + return new LinkageError(cause.toString(), cause); + } } diff --git a/android/guava-testlib/test/com/google/common/collect/testing/MinimalCollectionTest.java b/android/guava-testlib/test/com/google/common/collect/testing/MinimalCollectionTest.java index 38cadf3bff76..83489295a478 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/MinimalCollectionTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/MinimalCollectionTest.java @@ -27,6 +27,7 @@ * * @author Kevin Bourrillion */ +@AndroidIncompatible // test-suite builders public class MinimalCollectionTest extends TestCase { public static Test suite() { return CollectionTestSuiteBuilder.using( diff --git a/android/guava-testlib/test/com/google/common/collect/testing/MinimalIterableTest.java b/android/guava-testlib/test/com/google/common/collect/testing/MinimalIterableTest.java index f6fae3105646..e9a9e0a4f4e4 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/MinimalIterableTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/MinimalIterableTest.java @@ -16,6 +16,9 @@ package com.google.common.collect.testing; +import static com.google.common.collect.testing.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.singleton; + import com.google.common.annotations.GwtCompatible; import java.util.Collections; import java.util.Iterator; @@ -34,16 +37,8 @@ public void testOf_empty() { Iterable iterable = MinimalIterable.of(); Iterator iterator = iterable.iterator(); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } - try { - iterable.iterator(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); + assertThrows(IllegalStateException.class, () -> iterable.iterator()); } public void testOf_one() { @@ -52,54 +47,26 @@ public void testOf_one() { assertTrue(iterator.hasNext()); assertEquals("a", iterator.next()); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } - try { - iterable.iterator(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); + assertThrows(IllegalStateException.class, () -> iterable.iterator()); } public void testFrom_empty() { Iterable iterable = MinimalIterable.from(Collections.emptySet()); Iterator iterator = iterable.iterator(); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } - try { - iterable.iterator(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); + assertThrows(IllegalStateException.class, () -> iterable.iterator()); } public void testFrom_one() { - Iterable iterable = MinimalIterable.from(Collections.singleton("a")); + Iterable iterable = MinimalIterable.from(singleton("a")); Iterator iterator = iterable.iterator(); assertTrue(iterator.hasNext()); assertEquals("a", iterator.next()); - try { - iterator.remove(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } - try { - iterable.iterator(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); + assertThrows(IllegalStateException.class, () -> iterable.iterator()); } } diff --git a/android/guava-testlib/test/com/google/common/collect/testing/MinimalSetTest.java b/android/guava-testlib/test/com/google/common/collect/testing/MinimalSetTest.java index 51cc4c9561a9..037473508d83 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/MinimalSetTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/MinimalSetTest.java @@ -27,6 +27,7 @@ * * @author Regina O'Dell */ +@AndroidIncompatible // test-suite builders public class MinimalSetTest extends TestCase { public static Test suite() { return SetTestSuiteBuilder.using( diff --git a/android/guava-testlib/test/com/google/common/collect/testing/MyTester.java b/android/guava-testlib/test/com/google/common/collect/testing/MyTester.java new file mode 100644 index 000000000000..b82dc8b29a6e --- /dev/null +++ b/android/guava-testlib/test/com/google/common/collect/testing/MyTester.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing; + +import org.jspecify.annotations.Nullable; +import org.junit.Ignore; + +/** Support class added to a suite as part of {@link FeatureSpecificTestSuiteBuilderTest}. */ +/* + * @Ignore affects the Android test runner (and only the Android test runner): It respects JUnit 4 + * annotations even on JUnit 3 tests. + * + * TODO(b/225350400): Remove @Ignore, which doesn't seem like it should be necessary and probably + * soon won't be. + */ +@SuppressWarnings("JUnit4ClassUsedInJUnit3") +@Ignore +public final class MyTester extends AbstractTester<@Nullable Void> { + static int timesTestClassWasRun = 0; + + public void testNothing() { + timesTestClassWasRun++; + } +} diff --git a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6ListTests.java b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6ListTests.java index 7c5118e0b233..3ba5482c4ea3 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6ListTests.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6ListTests.java @@ -19,11 +19,11 @@ import static com.google.common.collect.testing.testers.CollectionToArrayTester.getToArrayIsPlainObjectArrayMethod; import static com.google.common.collect.testing.testers.ListAddTester.getAddSupportedNullPresentMethod; import static com.google.common.collect.testing.testers.ListSetTester.getSetNullSupportedMethod; +import static java.util.Arrays.asList; import com.google.common.collect.testing.testers.CollectionAddTester; import com.google.common.collect.testing.testers.ListAddAtIndexTester; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Collection; import java.util.List; import junit.framework.Test; @@ -34,6 +34,7 @@ * * @author Kevin Bourrillion */ +@AndroidIncompatible // test-suite builders public class OpenJdk6ListTests extends TestsForListsInJavaUtil { public static Test suite() { return new OpenJdk6ListTests().allTests(); @@ -41,12 +42,12 @@ public static Test suite() { @Override protected Collection suppressForArraysAsList() { - return Arrays.asList(getToArrayIsPlainObjectArrayMethod()); + return asList(getToArrayIsPlainObjectArrayMethod()); } @Override protected Collection suppressForCheckedList() { - return Arrays.asList( + return asList( CollectionAddTester.getAddNullSupportedMethod(), getAddSupportedNullPresentMethod(), ListAddAtIndexTester.getAddNullSupportedMethod(), diff --git a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6MapTests.java b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6MapTests.java index 8e18d654b5aa..31cdfa4c043d 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6MapTests.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6MapTests.java @@ -16,7 +16,6 @@ package com.google.common.collect.testing; -import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.testing.testers.CollectionAddAllTester.getAddAllUnsupportedNonePresentMethod; import static com.google.common.collect.testing.testers.CollectionAddAllTester.getAddAllUnsupportedSomePresentMethod; import static com.google.common.collect.testing.testers.CollectionAddTester.getAddUnsupportedNotPresentMethod; @@ -26,9 +25,10 @@ import static com.google.common.collect.testing.testers.MapEntrySetTester.getContainsEntryWithIncomparableValueMethod; import static com.google.common.collect.testing.testers.MapPutAllTester.getPutAllNullKeyUnsupportedMethod; import static com.google.common.collect.testing.testers.MapPutTester.getPutNullKeyUnsupportedMethod; +import static java.util.Arrays.asList; import java.lang.reflect.Method; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -40,10 +40,8 @@ * * @author Kevin Bourrillion */ -/* - * TODO(cpovirk): consider renaming this class in light of our now running it - * under JDK7 - */ +// TODO(cpovirk): consider renaming this class in light of our now running it under newer JDKs. +@AndroidIncompatible // test-suite builders public class OpenJdk6MapTests extends TestsForMapsInJavaUtil { public static Test suite() { return new OpenJdk6MapTests().allTests(); @@ -51,7 +49,7 @@ public static Test suite() { @Override protected Collection suppressForTreeMapNatural() { - return Arrays.asList( + return asList( getPutNullKeyUnsupportedMethod(), getPutAllNullKeyUnsupportedMethod(), getCreateWithNullKeyUnsupportedMethod(), @@ -66,14 +64,14 @@ protected Collection suppressForConcurrentHashMap() { * The entrySet() of ConcurrentHashMap, unlike that of other Map * implementations, supports add() under JDK8. This seems problematic, but I * didn't see that discussed in the review, which included many other - * changes: http://goo.gl/okTTdr + * changes: https://mail.openjdk.org/pipermail/core-libs-dev/2013-May/thread.html#17367 * * TODO(cpovirk): decide what the best long-term action here is: force users * to suppress (as we do now), stop testing entrySet().add() at all, make * entrySet().add() tests tolerant of either behavior, introduce a map * feature for entrySet() that supports add(), or something else */ - return Arrays.asList( + return asList( getAddUnsupportedNotPresentMethod(), getAddAllUnsupportedNonePresentMethod(), getAddAllUnsupportedSomePresentMethod()); @@ -81,7 +79,7 @@ protected Collection suppressForConcurrentHashMap() { @Override protected Collection suppressForConcurrentSkipListMap() { - List methods = newArrayList(); + List methods = new ArrayList<>(); methods.addAll(super.suppressForConcurrentSkipListMap()); methods.add(getContainsEntryWithIncomparableKeyMethod()); methods.add(getContainsEntryWithIncomparableValueMethod()); diff --git a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6QueueTests.java b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6QueueTests.java index f56fee2923ce..002b519f338a 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6QueueTests.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6QueueTests.java @@ -17,9 +17,9 @@ package com.google.common.collect.testing; import static com.google.common.collect.testing.testers.CollectionCreationTester.getCreateWithNullUnsupportedMethod; +import static java.util.Arrays.asList; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Queue; @@ -31,13 +31,13 @@ * * @author Kevin Bourrillion */ +@AndroidIncompatible // test-suite builders public class OpenJdk6QueueTests extends TestsForQueuesInJavaUtil { public static Test suite() { return new OpenJdk6QueueTests().allTests(); } - private static final List PQ_SUPPRESS = - Arrays.asList(getCreateWithNullUnsupportedMethod()); + private static final List PQ_SUPPRESS = asList(getCreateWithNullUnsupportedMethod()); @Override protected Collection suppressForPriorityBlockingQueue() { diff --git a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6SetTests.java b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6SetTests.java index 67ef67bf0552..d5ccce9146bd 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6SetTests.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6SetTests.java @@ -21,9 +21,9 @@ import static com.google.common.collect.testing.testers.CollectionAddTester.getAddNullUnsupportedMethod; import static com.google.common.collect.testing.testers.CollectionCreationTester.getCreateWithNullUnsupportedMethod; import static com.google.common.collect.testing.testers.SetAddTester.getAddSupportedNullPresentMethod; +import static java.util.Arrays.asList; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Collection; import java.util.Set; import junit.framework.Test; @@ -34,6 +34,7 @@ * * @author Kevin Bourrillion */ +@AndroidIncompatible // test-suite builders public class OpenJdk6SetTests extends TestsForSetsInJavaUtil { public static Test suite() { return new OpenJdk6SetTests().allTests(); @@ -41,7 +42,7 @@ public static Test suite() { @Override protected Collection suppressForTreeSetNatural() { - return Arrays.asList( + return asList( getAddNullUnsupportedMethod(), getAddAllNullUnsupportedMethod(), getCreateWithNullUnsupportedMethod()); @@ -49,6 +50,6 @@ protected Collection suppressForTreeSetNatural() { @Override protected Collection suppressForCheckedSet() { - return Arrays.asList(getAddNullSupportedMethod(), getAddSupportedNullPresentMethod()); + return asList(getAddNullSupportedMethod(), getAddSupportedNullPresentMethod()); } } diff --git a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6Tests.java b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6Tests.java index db60982ca779..c97d2bf92f92 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6Tests.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/OpenJdk6Tests.java @@ -27,6 +27,7 @@ * * @author Kevin Bourrillion */ +@AndroidIncompatible // test-suite builders public class OpenJdk6Tests extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); diff --git a/android/guava-testlib/test/com/google/common/collect/testing/ReserializedSafeTreeMapMapInterfaceTest.java b/android/guava-testlib/test/com/google/common/collect/testing/ReserializedSafeTreeMapMapInterfaceTest.java new file mode 100644 index 000000000000..46d76a3532b3 --- /dev/null +++ b/android/guava-testlib/test/com/google/common/collect/testing/ReserializedSafeTreeMapMapInterfaceTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect.testing; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.testing.SerializableTester; +import java.util.NavigableMap; +import java.util.SortedMap; + +@GwtIncompatible // SerializableTester +public class ReserializedSafeTreeMapMapInterfaceTest + extends SortedMapInterfaceTest { + public ReserializedSafeTreeMapMapInterfaceTest() { + super(false, true, true, true, true); + } + + @Override + protected SortedMap makePopulatedMap() { + NavigableMap map = new SafeTreeMap<>(); + map.put("one", 1); + map.put("two", 2); + map.put("three", 3); + return SerializableTester.reserialize(map); + } + + @Override + protected SortedMap makeEmptyMap() throws UnsupportedOperationException { + NavigableMap map = new SafeTreeMap<>(); + return SerializableTester.reserialize(map); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} diff --git a/android/guava-testlib/test/com/google/common/collect/testing/SafeTreeMapTest.java b/android/guava-testlib/test/com/google/common/collect/testing/SafeTreeMapTest.java index f03ff0a09fe1..ecc6b354f651 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/SafeTreeMapTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/SafeTreeMapTest.java @@ -20,13 +20,13 @@ import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.ImmutableSortedMap; -import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.common.collect.testing.Helpers.NullsBeforeTwo; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; import com.google.common.testing.SerializableTester; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -42,6 +42,7 @@ * @author Louis Wasserman */ public class SafeTreeMapTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(SafeTreeMapTest.class); @@ -107,39 +108,7 @@ public void testViewSerialization() { SerializableTester.reserializeAndAssert(map.entrySet()); SerializableTester.reserializeAndAssert(map.keySet()); assertEquals( - Lists.newArrayList(map.values()), - Lists.newArrayList(SerializableTester.reserialize(map.values()))); - } - - @GwtIncompatible // SerializableTester - public static class ReserializedMapTests extends SortedMapInterfaceTest { - public ReserializedMapTests() { - super(false, true, true, true, true); - } - - @Override - protected SortedMap makePopulatedMap() { - NavigableMap map = new SafeTreeMap<>(); - map.put("one", 1); - map.put("two", 2); - map.put("three", 3); - return SerializableTester.reserialize(map); - } - - @Override - protected SortedMap makeEmptyMap() throws UnsupportedOperationException { - NavigableMap map = new SafeTreeMap<>(); - return SerializableTester.reserialize(map); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; - } + new ArrayList<>(map.values()), + new ArrayList<>(SerializableTester.reserialize(map.values()))); } } diff --git a/android/guava-testlib/test/com/google/common/collect/testing/SafeTreeSetTest.java b/android/guava-testlib/test/com/google/common/collect/testing/SafeTreeSetTest.java index ea5a7840adb1..1ff8c41afd23 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/SafeTreeSetTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/SafeTreeSetTest.java @@ -16,15 +16,16 @@ package com.google.common.collect.testing; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.ImmutableSortedMap; -import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.testing.SerializableTester; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -36,6 +37,7 @@ import junit.framework.TestSuite; public class SafeTreeSetTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(SafeTreeSetTest.class); @@ -44,12 +46,12 @@ public static Test suite() { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return new SafeTreeSet<>(Arrays.asList(elements)); + return new SafeTreeSet<>(asList(elements)); } @Override public List order(List insertionOrder) { - return Lists.newArrayList(Sets.newTreeSet(insertionOrder)); + return new ArrayList<>(Sets.newTreeSet(insertionOrder)); } }) .withFeatures( @@ -70,7 +72,7 @@ protected Set create(String[] elements) { @Override public List order(List insertionOrder) { - return Lists.newArrayList(Sets.newTreeSet(insertionOrder)); + return new ArrayList<>(Sets.newTreeSet(insertionOrder)); } }) .withFeatures( @@ -89,8 +91,8 @@ public void testViewSerialization() { SerializableTester.reserializeAndAssert(map.entrySet()); SerializableTester.reserializeAndAssert(map.keySet()); assertEquals( - Lists.newArrayList(map.values()), - Lists.newArrayList(SerializableTester.reserialize(map.values()))); + new ArrayList<>(map.values()), + new ArrayList<>(SerializableTester.reserialize(map.values()))); } @GwtIncompatible // SerializableTester diff --git a/android/guava-testlib/test/com/google/common/collect/testing/features/AndroidIncompatible.java b/android/guava-testlib/test/com/google/common/collect/testing/features/AndroidIncompatible.java index 46c212307716..efcaa6727eee 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/features/AndroidIncompatible.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/features/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-testlib/test/com/google/common/collect/testing/features/FeatureEnumTest.java b/android/guava-testlib/test/com/google/common/collect/testing/features/FeatureEnumTest.java index 983a4954f125..ff5785436894 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/features/FeatureEnumTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/features/FeatureEnumTest.java @@ -16,6 +16,8 @@ package com.google.common.collect.testing.features; +import static com.google.common.truth.Truth.assertWithMessage; + import java.lang.annotation.Annotation; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; @@ -32,30 +34,30 @@ */ public class FeatureEnumTest extends TestCase { private static void assertGoodTesterAnnotation(Class annotationClass) { - assertNotNull( - rootLocaleFormat("%s must be annotated with @TesterAnnotation.", annotationClass), - annotationClass.getAnnotation(TesterAnnotation.class)); - final Retention retentionPolicy = annotationClass.getAnnotation(Retention.class); - assertNotNull( - rootLocaleFormat("%s must have a @Retention annotation.", annotationClass), - retentionPolicy); + assertWithMessage( + rootLocaleFormat("%s must be annotated with @TesterAnnotation.", annotationClass)) + .that(annotationClass.getAnnotation(TesterAnnotation.class)) + .isNotNull(); + Retention retentionPolicy = annotationClass.getAnnotation(Retention.class); + assertWithMessage(rootLocaleFormat("%s must have a @Retention annotation.", annotationClass)) + .that(retentionPolicy) + .isNotNull(); assertEquals( rootLocaleFormat("%s must have RUNTIME RetentionPolicy.", annotationClass), RetentionPolicy.RUNTIME, retentionPolicy.value()); - assertNotNull( - rootLocaleFormat("%s must be inherited.", annotationClass), - annotationClass.getAnnotation(Inherited.class)); + assertWithMessage(rootLocaleFormat("%s must be inherited.", annotationClass)) + .that(annotationClass.getAnnotation(Inherited.class)) + .isNotNull(); for (String propertyName : new String[] {"value", "absent"}) { Method method = null; try { method = annotationClass.getMethod(propertyName); } catch (NoSuchMethodException e) { - fail( - rootLocaleFormat("%s must have a property named '%s'.", annotationClass, propertyName)); + throw new AssertionError("Annotation is missing required method", e); } - final Class returnType = method.getReturnType(); + Class returnType = method.getReturnType(); assertTrue( rootLocaleFormat("%s.%s() must return an array.", annotationClass, propertyName), returnType.isArray()); @@ -72,7 +74,7 @@ private static void assertGoodTesterAnnotation(Class annot // can reuse it. public static & Feature> void assertGoodFeatureEnum( Class featureEnumClass) { - final Class[] classes = featureEnumClass.getDeclaredClasses(); + Class[] classes = featureEnumClass.getDeclaredClasses(); for (Class containedClass : classes) { if (containedClass.getSimpleName().equals("Require")) { if (containedClass.isAnnotation()) { @@ -89,8 +91,7 @@ public static & Feature> void assertGoodFeatureEnum( } fail( rootLocaleFormat( - "Feature enum %s should contain an " + "annotation named 'Require'.", - featureEnumClass)); + "Feature enum %s should contain an annotation named 'Require'.", featureEnumClass)); } @SuppressWarnings("unchecked") diff --git a/android/guava-testlib/test/com/google/common/collect/testing/features/FeatureUtilTest.java b/android/guava-testlib/test/com/google/common/collect/testing/features/FeatureUtilTest.java index 37af90bdab89..6339bd2eae0b 100644 --- a/android/guava-testlib/test/com/google/common/collect/testing/features/FeatureUtilTest.java +++ b/android/guava-testlib/test/com/google/common/collect/testing/features/FeatureUtilTest.java @@ -16,256 +16,304 @@ package com.google.common.collect.testing.features; +import static com.google.common.collect.Sets.newHashSet; +import static com.google.common.collect.testing.features.FeatureEnumTest.assertGoodFeatureEnum; +import static com.google.common.collect.testing.features.FeatureUtil.addImpliedFeatures; +import static com.google.common.collect.testing.features.FeatureUtil.buildDeclaredTesterRequirements; +import static com.google.common.collect.testing.features.FeatureUtil.buildTesterRequirements; +import static com.google.common.collect.testing.features.FeatureUtil.getTesterAnnotations; +import static com.google.common.collect.testing.features.FeatureUtil.impliedFeatures; +import static com.google.common.collect.testing.features.FeatureUtilTest.ExampleFeature.BAR; +import static com.google.common.collect.testing.features.FeatureUtilTest.ExampleFeature.FOO; +import static com.google.common.collect.testing.features.FeatureUtilTest.ExampleFeature.IMPLIES_BAR; +import static com.google.common.collect.testing.features.FeatureUtilTest.ExampleFeature.IMPLIES_FOO; +import static com.google.common.collect.testing.features.FeatureUtilTest.ExampleFeature.IMPLIES_IMPLIES_FOO; +import static com.google.common.collect.testing.features.FeatureUtilTest.ExampleFeature.IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR; import static com.google.common.truth.Truth.assertThat; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; +import com.google.common.collect.testing.features.FeatureUtilTest.ExampleFeature.NotTesterAnnotation; +import com.google.common.collect.testing.features.FeatureUtilTest.ExampleFeature.Require; +import com.google.errorprone.annotations.Keep; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; -import java.util.Collections; import java.util.Set; import junit.framework.TestCase; -/** @author George van den Driessche */ -// Enum values use constructors with generic varargs. -@SuppressWarnings("unchecked") +/** + * @author George van den Driessche + */ public class FeatureUtilTest extends TestCase { - interface ExampleBaseInterface { - void behave(); - } - - interface ExampleDerivedInterface extends ExampleBaseInterface { - void misbehave(); - } - - enum ExampleBaseFeature implements Feature { - BASE_FEATURE_1, - BASE_FEATURE_2; + enum ExampleFeature implements Feature { + FOO, + IMPLIES_FOO, + IMPLIES_IMPLIES_FOO, + BAR, + IMPLIES_BAR, + IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR; @Override - public Set> getImpliedFeatures() { - return Collections.emptySet(); + public ImmutableSet> getImpliedFeatures() { + switch (this) { + case IMPLIES_FOO: + return ImmutableSet.of(FOO); + case IMPLIES_IMPLIES_FOO: + return ImmutableSet.of(IMPLIES_FOO); + case IMPLIES_BAR: + return ImmutableSet.of(BAR); + case IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR: + return ImmutableSet.of(IMPLIES_FOO, IMPLIES_BAR); + default: + return ImmutableSet.of(); + } } - @Retention(RetentionPolicy.RUNTIME) + @Retention(RUNTIME) @Inherited @TesterAnnotation @interface Require { - ExampleBaseFeature[] value() default {}; + ExampleFeature[] value() default {}; - ExampleBaseFeature[] absent() default {}; + ExampleFeature[] absent() default {}; + } + + @Retention(RUNTIME) + @Inherited + @interface NotTesterAnnotation { + ExampleFeature[] value() default {}; + + ExampleFeature[] absent() default {}; } } - enum ExampleDerivedFeature implements Feature { - DERIVED_FEATURE_1, - DERIVED_FEATURE_2(ExampleBaseFeature.BASE_FEATURE_1), - DERIVED_FEATURE_3, + public void testTestFeatureEnums() { + // Haha! Let's test our own test rig! + assertGoodFeatureEnum(ExampleFeature.class); + } - COMPOUND_DERIVED_FEATURE( - DERIVED_FEATURE_1, DERIVED_FEATURE_2, ExampleBaseFeature.BASE_FEATURE_2); + public void testAddImpliedFeatures_returnsSameSetInstance() { + Set> features = newHashSet(FOO); + assertThat(addImpliedFeatures(features)).isSameInstanceAs(features); + } - private Set> implied; + public void testAddImpliedFeatures_addsImpliedFeatures() { + assertThat(addImpliedFeatures(newHashSet(FOO))).containsExactly(FOO); - ExampleDerivedFeature(Feature... implied) { - this.implied = ImmutableSet.copyOf(implied); - } + assertThat(addImpliedFeatures(newHashSet(IMPLIES_IMPLIES_FOO))) + .containsExactly(IMPLIES_IMPLIES_FOO, IMPLIES_FOO, FOO); - @Override - public Set> getImpliedFeatures() { - return implied; - } + assertThat(addImpliedFeatures(newHashSet(IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR))) + .containsExactly(IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR, IMPLIES_FOO, FOO, IMPLIES_BAR, BAR); + } - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @TesterAnnotation - @interface Require { - ExampleDerivedFeature[] value() default {}; + public void testImpliedFeatures_returnsNewSetInstance() { + Set> features = newHashSet(IMPLIES_FOO); + assertThat(impliedFeatures(features)).isNotSameInstanceAs(features); + } - ExampleDerivedFeature[] absent() default {}; - } + public void testImpliedFeatures_returnsImpliedFeatures() { + assertThat(impliedFeatures(newHashSet(FOO))).isEmpty(); + + assertThat(impliedFeatures(newHashSet(IMPLIES_IMPLIES_FOO))).containsExactly(IMPLIES_FOO, FOO); + + assertThat(impliedFeatures(newHashSet(IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR))) + .containsExactly(IMPLIES_FOO, FOO, IMPLIES_BAR, BAR); } - @Retention(RetentionPolicy.RUNTIME) - @interface NonTesterAnnotation {} + public void testBuildTesterRequirements_class_notAnnotated() throws Exception { + class Tester {} - @ExampleBaseFeature.Require({ExampleBaseFeature.BASE_FEATURE_1}) - private abstract static class ExampleBaseInterfaceTester extends TestCase { - protected final void doNotActuallyRunThis() { - fail("Nobody's meant to actually run this!"); - } + TesterRequirements requirements = buildTesterRequirements(Tester.class); + + assertThat(requirements.getPresentFeatures()).isEmpty(); + assertThat(requirements.getAbsentFeatures()).isEmpty(); } - @AndroidIncompatible // Android attempts to run directly - @NonTesterAnnotation - @ExampleDerivedFeature.Require({ExampleDerivedFeature.DERIVED_FEATURE_2}) - private static class ExampleDerivedInterfaceTester extends ExampleBaseInterfaceTester { - // Exists to test that our framework doesn't run it: - @SuppressWarnings("unused") - @ExampleDerivedFeature.Require({ - ExampleDerivedFeature.DERIVED_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_2 - }) - public void testRequiringTwoExplicitDerivedFeatures() throws Exception { - doNotActuallyRunThis(); - } + public void testBuildTesterRequirements_class_empty() throws Exception { + @Require + class Tester {} - // Exists to test that our framework doesn't run it: - @SuppressWarnings("unused") - @ExampleDerivedFeature.Require({ - ExampleDerivedFeature.DERIVED_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_3 - }) - public void testRequiringAllThreeDerivedFeatures() { - doNotActuallyRunThis(); - } + TesterRequirements requirements = buildTesterRequirements(Tester.class); - // Exists to test that our framework doesn't run it: - @SuppressWarnings("unused") - @ExampleBaseFeature.Require(absent = {ExampleBaseFeature.BASE_FEATURE_1}) - public void testRequiringConflictingFeatures() throws Exception { - doNotActuallyRunThis(); - } + assertThat(requirements.getPresentFeatures()).isEmpty(); + assertThat(requirements.getAbsentFeatures()).isEmpty(); } - @ExampleDerivedFeature.Require(absent = {ExampleDerivedFeature.DERIVED_FEATURE_2}) - private static class ConflictingRequirementsExampleDerivedInterfaceTester - extends ExampleBaseInterfaceTester {} + public void testBuildTesterRequirements_class_present() throws Exception { + @Require({IMPLIES_IMPLIES_FOO, IMPLIES_BAR}) + class Tester {} - public void testTestFeatureEnums() throws Exception { - // Haha! Let's test our own test rig! - FeatureEnumTest.assertGoodFeatureEnum(FeatureUtilTest.ExampleBaseFeature.class); - FeatureEnumTest.assertGoodFeatureEnum(FeatureUtilTest.ExampleDerivedFeature.class); + TesterRequirements requirements = buildTesterRequirements(Tester.class); + + assertThat(requirements.getPresentFeatures()) + .containsExactly(IMPLIES_IMPLIES_FOO, IMPLIES_FOO, FOO, IMPLIES_BAR, BAR); + assertThat(requirements.getAbsentFeatures()).isEmpty(); } - public void testAddImpliedFeatures_returnsSameSetInstance() throws Exception { - Set> features = Sets.>newHashSet(ExampleBaseFeature.BASE_FEATURE_1); - assertSame(features, FeatureUtil.addImpliedFeatures(features)); + public void testBuildTesterRequirements_class_absent() throws Exception { + @Require(absent = {IMPLIES_IMPLIES_FOO, IMPLIES_BAR}) + class Tester {} + + TesterRequirements requirements = buildTesterRequirements(Tester.class); + + assertThat(requirements.getPresentFeatures()).isEmpty(); + assertThat(requirements.getAbsentFeatures()).containsExactly(IMPLIES_IMPLIES_FOO, IMPLIES_BAR); } - public void testAddImpliedFeatures_addsImpliedFeatures() throws Exception { - Set> features; - - features = Sets.>newHashSet(ExampleDerivedFeature.DERIVED_FEATURE_1); - assertThat(FeatureUtil.addImpliedFeatures(features)) - .contains(ExampleDerivedFeature.DERIVED_FEATURE_1); - - features = Sets.>newHashSet(ExampleDerivedFeature.DERIVED_FEATURE_2); - assertThat(FeatureUtil.addImpliedFeatures(features)) - .containsExactly( - ExampleDerivedFeature.DERIVED_FEATURE_2, ExampleBaseFeature.BASE_FEATURE_1); - - features = Sets.>newHashSet(ExampleDerivedFeature.COMPOUND_DERIVED_FEATURE); - assertThat(FeatureUtil.addImpliedFeatures(features)) - .containsExactly( - ExampleDerivedFeature.COMPOUND_DERIVED_FEATURE, - ExampleDerivedFeature.DERIVED_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_2, - ExampleBaseFeature.BASE_FEATURE_1, - ExampleBaseFeature.BASE_FEATURE_2); + public void testBuildTesterRequirements_class_present_and_absent() throws Exception { + @Require(value = IMPLIES_FOO, absent = IMPLIES_IMPLIES_FOO) + class Tester {} + + TesterRequirements requirements = buildTesterRequirements(Tester.class); + + assertThat(requirements.getPresentFeatures()).containsExactly(IMPLIES_FOO, FOO); + assertThat(requirements.getAbsentFeatures()).containsExactly(IMPLIES_IMPLIES_FOO); } - public void testImpliedFeatures_returnsNewSetInstance() throws Exception { - Set> features = Sets.>newHashSet(ExampleBaseFeature.BASE_FEATURE_1); - assertNotSame(features, FeatureUtil.impliedFeatures(features)); + public void testBuildTesterRequirements_class_present_method_present() throws Exception { + @Require(IMPLIES_BAR) + class Tester { + @Keep + @Require(IMPLIES_IMPLIES_FOO) + public void test() {} + } + + TesterRequirements requirements = buildTesterRequirements(Tester.class.getMethod("test")); + + assertThat(requirements.getPresentFeatures()) + .containsExactly(IMPLIES_IMPLIES_FOO, IMPLIES_FOO, FOO, IMPLIES_BAR, BAR); + assertThat(requirements.getAbsentFeatures()).isEmpty(); } - public void testImpliedFeatures_returnsImpliedFeatures() throws Exception { - Set> features; + public void testBuildTesterRequirements_class_absent_method_absent() throws Exception { + @Require(absent = IMPLIES_BAR) + class Tester { + @Keep + @Require(absent = IMPLIES_IMPLIES_FOO) + public void test() {} + } + + TesterRequirements requirements = buildTesterRequirements(Tester.class.getMethod("test")); + + assertThat(requirements.getPresentFeatures()).isEmpty(); + assertThat(requirements.getAbsentFeatures()).containsExactly(IMPLIES_IMPLIES_FOO, IMPLIES_BAR); + } + + public void testBuildTesterRequirements_class_present_method_absent() throws Exception { + @Require(IMPLIES_IMPLIES_FOO) + class Tester { + @Keep + @Require(absent = IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR) + public void test() {} + } + + TesterRequirements requirements = buildTesterRequirements(Tester.class.getMethod("test")); + + assertThat(requirements.getPresentFeatures()) + .containsExactly(IMPLIES_IMPLIES_FOO, IMPLIES_FOO, FOO); + assertThat(requirements.getAbsentFeatures()) + .containsExactly(IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR); + } - features = Sets.>newHashSet(ExampleDerivedFeature.DERIVED_FEATURE_1); - assertTrue(FeatureUtil.impliedFeatures(features).isEmpty()); + public void testBuildTesterRequirements_class_absent_method_present() throws Exception { + @Require(absent = IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR) + class Tester { + @Keep + @Require(IMPLIES_IMPLIES_FOO) + public void test() {} + } - features = Sets.>newHashSet(ExampleDerivedFeature.DERIVED_FEATURE_2); - assertThat(FeatureUtil.impliedFeatures(features)).contains(ExampleBaseFeature.BASE_FEATURE_1); + TesterRequirements requirements = buildTesterRequirements(Tester.class.getMethod("test")); - features = Sets.>newHashSet(ExampleDerivedFeature.COMPOUND_DERIVED_FEATURE); - assertThat(FeatureUtil.impliedFeatures(features)) - .containsExactly( - ExampleDerivedFeature.DERIVED_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_2, - ExampleBaseFeature.BASE_FEATURE_1, - ExampleBaseFeature.BASE_FEATURE_2); + assertThat(requirements.getPresentFeatures()) + .containsExactly(IMPLIES_IMPLIES_FOO, IMPLIES_FOO, FOO); + assertThat(requirements.getAbsentFeatures()) + .containsExactly(IMPLIES_IMPLIES_FOO_AND_IMPLIES_BAR); } - @AndroidIncompatible // Android runs ExampleDerivedInterfaceTester directly if it exists - public void testBuildTesterRequirements_class() throws Exception { - assertEquals( - FeatureUtil.buildTesterRequirements(ExampleBaseInterfaceTester.class), - new TesterRequirements( - Sets.>newHashSet(ExampleBaseFeature.BASE_FEATURE_1), - Collections.>emptySet())); - - assertEquals( - FeatureUtil.buildTesterRequirements(ExampleDerivedInterfaceTester.class), - new TesterRequirements( - Sets.>newHashSet( - ExampleBaseFeature.BASE_FEATURE_1, ExampleDerivedFeature.DERIVED_FEATURE_2), - Collections.>emptySet())); + public void testBuildTesterRequirements_classClassConflict() { + @Require(value = FOO, absent = FOO) + class Tester {} + + ConflictingRequirementsException e = + assertThrows( + ConflictingRequirementsException.class, () -> buildTesterRequirements(Tester.class)); + assertThat(e.getConflicts()).containsExactly(FOO); + assertThat(e.getSource()).isEqualTo(Tester.class.getAnnotation(Require.class)); } - @AndroidIncompatible // Android runs ExampleDerivedInterfaceTester directly if it exists - public void testBuildTesterRequirements_method() throws Exception { - assertEquals( - FeatureUtil.buildTesterRequirements( - ExampleDerivedInterfaceTester.class.getMethod( - "testRequiringTwoExplicitDerivedFeatures")), - new TesterRequirements( - Sets.>newHashSet( - ExampleBaseFeature.BASE_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_2), - Collections.>emptySet())); - assertEquals( - FeatureUtil.buildTesterRequirements( - ExampleDerivedInterfaceTester.class.getMethod("testRequiringAllThreeDerivedFeatures")), - new TesterRequirements( - Sets.>newHashSet( - ExampleBaseFeature.BASE_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_2, - ExampleDerivedFeature.DERIVED_FEATURE_3), - Collections.>emptySet())); + public void testBuildTesterRequirements_classClassConflict_inherited() { + @Require(FOO) + abstract class BaseTester {} + @Require(absent = FOO) + class Tester extends BaseTester {} + + ConflictingRequirementsException e = + assertThrows( + ConflictingRequirementsException.class, () -> buildTesterRequirements(Tester.class)); + assertThat(e.getConflicts()).containsExactly(FOO); + assertThat(e.getSource()).isEqualTo(Tester.class); } - @AndroidIncompatible // Android runs ExampleDerivedInterfaceTester directly if it exists - public void testBuildTesterRequirements_classClassConflict() throws Exception { - try { - FeatureUtil.buildTesterRequirements( - ConflictingRequirementsExampleDerivedInterfaceTester.class); - fail("Expected ConflictingRequirementsException"); - } catch (ConflictingRequirementsException e) { - assertThat(e.getConflicts()).contains(ExampleBaseFeature.BASE_FEATURE_1); - assertEquals(ConflictingRequirementsExampleDerivedInterfaceTester.class, e.getSource()); - } + public void testBuildTesterRequirements_classClassConflict_implied() { + @Require(value = IMPLIES_FOO, absent = FOO) + class Tester {} + + ConflictingRequirementsException e = + assertThrows( + ConflictingRequirementsException.class, () -> buildTesterRequirements(Tester.class)); + assertThat(e.getConflicts()).containsExactly(FOO); + assertThat(e.getSource()).isEqualTo(Tester.class.getAnnotation(Require.class)); } - @AndroidIncompatible // Android runs ExampleDerivedInterfaceTester directly if it exists public void testBuildTesterRequirements_methodClassConflict() throws Exception { - final Method method = - ExampleDerivedInterfaceTester.class.getMethod("testRequiringConflictingFeatures"); - try { - FeatureUtil.buildTesterRequirements(method); - fail("Expected ConflictingRequirementsException"); - } catch (ConflictingRequirementsException e) { - assertThat(e.getConflicts()).contains(ExampleBaseFeature.BASE_FEATURE_1); - assertEquals(method, e.getSource()); + @Require(IMPLIES_FOO) + class Tester { + @Keep + @Require(absent = FOO) + public void test() {} } + + Method method = Tester.class.getMethod("test"); + ConflictingRequirementsException e = + assertThrows(ConflictingRequirementsException.class, () -> buildTesterRequirements(method)); + assertThat(e.getConflicts()).containsExactly(FOO); + assertThat(e.getSource()).isEqualTo(method); } - @AndroidIncompatible // Android runs ExampleDerivedInterfaceTester directly if it exists public void testBuildDeclaredTesterRequirements() throws Exception { - assertEquals( - FeatureUtil.buildDeclaredTesterRequirements( - ExampleDerivedInterfaceTester.class.getMethod( - "testRequiringTwoExplicitDerivedFeatures")), - new TesterRequirements( - FeatureUtil.addImpliedFeatures( - Sets.>newHashSet( - ExampleDerivedFeature.DERIVED_FEATURE_1, - ExampleDerivedFeature.DERIVED_FEATURE_2)), - Collections.>emptySet())); + @Require(IMPLIES_FOO) + abstract class BaseTester {} + @Require(IMPLIES_BAR) + class Tester extends BaseTester {} + + TesterRequirements requirements = buildDeclaredTesterRequirements(Tester.class); + + assertThat(requirements.getPresentFeatures()).containsExactly(IMPLIES_BAR, BAR); + assertThat(requirements.getAbsentFeatures()).isEmpty(); + } + + public void testGetTesterAnnotations_class() { + @Require + @NotTesterAnnotation + class Tester {} + + assertThat(getTesterAnnotations(Tester.class)) + .containsExactly(Tester.class.getAnnotation(Require.class)); + } + + public void testGetTesterAnnotations_method() throws Exception { + class Tester { + @Keep + @Require + @NotTesterAnnotation + public void test() {} + } + Method method = Tester.class.getMethod("test"); + + assertThat(getTesterAnnotations(method)).containsExactly(method.getAnnotation(Require.class)); } } diff --git a/android/guava-testlib/test/com/google/common/testing/AbstractPackageSanityTestsTest.java b/android/guava-testlib/test/com/google/common/testing/AbstractPackageSanityTestsTest.java index cb6ba6abf457..9712d8aa0112 100644 --- a/android/guava-testlib/test/com/google/common/testing/AbstractPackageSanityTestsTest.java +++ b/android/guava-testlib/test/com/google/common/testing/AbstractPackageSanityTestsTest.java @@ -23,12 +23,14 @@ import java.util.Arrays; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link AbstractPackageSanityTests}. * * @author Ben Yu */ +@NullUnmarked public class AbstractPackageSanityTestsTest extends TestCase { /* * This is a public type so that the Android test runner can create an instance directly as it @@ -108,6 +110,7 @@ static class EmptyTestSuite {} static class Foo {} + @SuppressWarnings("IdentifierName") // We're testing that we ignore classes with underscores. static class Foo_Bar {} public static class PublicFoo {} diff --git a/android/guava-testlib/test/com/google/common/testing/AndroidIncompatible.java b/android/guava-testlib/test/com/google/common/testing/AndroidIncompatible.java index 326d7b86e2e2..d1a3721e50d6 100644 --- a/android/guava-testlib/test/com/google/common/testing/AndroidIncompatible.java +++ b/android/guava-testlib/test/com/google/common/testing/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-testlib/test/com/google/common/testing/ArbitraryInstancesTest.java b/android/guava-testlib/test/com/google/common/testing/ArbitraryInstancesTest.java index b47672f83dc6..3de4b548be03 100644 --- a/android/guava-testlib/test/com/google/common/testing/ArbitraryInstancesTest.java +++ b/android/guava-testlib/test/com/google/common/testing/ArbitraryInstancesTest.java @@ -17,9 +17,12 @@ package com.google.common.testing; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.base.CharMatcher; -import com.google.common.base.Charsets; import com.google.common.base.Equivalence; import com.google.common.base.Joiner; import com.google.common.base.Predicate; @@ -60,6 +63,7 @@ import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; import com.google.common.util.concurrent.AtomicDouble; +import com.google.errorprone.annotations.Keep; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -127,17 +131,20 @@ import java.util.regex.MatchResult; import java.util.regex.Pattern; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link ArbitraryInstances}. * * @author Ben Yu */ +@NullUnmarked public class ArbitraryInstancesTest extends TestCase { public void testGet_primitives() { - assertNull(ArbitraryInstances.get(void.class)); - assertNull(ArbitraryInstances.get(Void.class)); + assertThat(ArbitraryInstances.get(void.class)).isNull(); + assertThat(ArbitraryInstances.get(Void.class)).isNull(); assertEquals(Boolean.FALSE, ArbitraryInstances.get(boolean.class)); assertEquals(Boolean.FALSE, ArbitraryInstances.get(Boolean.class)); assertEquals(Character.valueOf('\0'), ArbitraryInstances.get(char.class)); @@ -152,19 +159,19 @@ public void testGet_primitives() { assertEquals(Long.valueOf(0), ArbitraryInstances.get(Long.class)); assertEquals(Float.valueOf(0), ArbitraryInstances.get(float.class)); assertEquals(Float.valueOf(0), ArbitraryInstances.get(Float.class)); - assertEquals(Double.valueOf(0), ArbitraryInstances.get(double.class)); - assertEquals(Double.valueOf(0), ArbitraryInstances.get(Double.class)); + assertThat(ArbitraryInstances.get(double.class)).isEqualTo(Double.valueOf(0)); + assertThat(ArbitraryInstances.get(Double.class)).isEqualTo(Double.valueOf(0)); assertEquals(UnsignedInteger.ZERO, ArbitraryInstances.get(UnsignedInteger.class)); assertEquals(UnsignedLong.ZERO, ArbitraryInstances.get(UnsignedLong.class)); assertEquals(0, ArbitraryInstances.get(BigDecimal.class).intValue()); assertEquals(0, ArbitraryInstances.get(BigInteger.class).intValue()); assertEquals("", ArbitraryInstances.get(String.class)); assertEquals("", ArbitraryInstances.get(CharSequence.class)); - assertEquals(TimeUnit.SECONDS, ArbitraryInstances.get(TimeUnit.class)); - assertNotNull(ArbitraryInstances.get(Object.class)); + assertEquals(SECONDS, ArbitraryInstances.get(TimeUnit.class)); + assertThat(ArbitraryInstances.get(Object.class)).isNotNull(); assertEquals(0, ArbitraryInstances.get(Number.class)); - assertEquals(Charsets.UTF_8, ArbitraryInstances.get(Charset.class)); - assertNotNull(ArbitraryInstances.get(UUID.class)); + assertEquals(UTF_8, ArbitraryInstances.get(Charset.class)); + assertThat(ArbitraryInstances.get(UUID.class)).isNotNull(); } public void testGet_collections() { @@ -224,14 +231,14 @@ public void testGet_collections() { } public void testGet_misc() { - assertNotNull(ArbitraryInstances.get(CharMatcher.class)); - assertNotNull(ArbitraryInstances.get(Currency.class).getCurrencyCode()); - assertNotNull(ArbitraryInstances.get(Locale.class)); - assertNotNull(ArbitraryInstances.get(Joiner.class).join(ImmutableList.of("a"))); - assertNotNull(ArbitraryInstances.get(Splitter.class).split("a,b")); + assertThat(ArbitraryInstances.get(CharMatcher.class)).isNotNull(); + assertThat(ArbitraryInstances.get(Currency.class).getCurrencyCode()).isNotNull(); + assertThat(ArbitraryInstances.get(Locale.class)).isNotNull(); + assertThat(ArbitraryInstances.get(Joiner.class).join(ImmutableList.of("a"))).isNotNull(); + assertThat(ArbitraryInstances.get(Splitter.class).split("a,b")).isNotNull(); assertThat(ArbitraryInstances.get(com.google.common.base.Optional.class)).isAbsent(); ArbitraryInstances.get(Stopwatch.class).start(); - assertNotNull(ArbitraryInstances.get(Ticker.class)); + assertThat(ArbitraryInstances.get(Ticker.class)).isNotNull(); assertFreshInstanceReturned(Random.class); assertEquals( ArbitraryInstances.get(Random.class).nextInt(), @@ -247,7 +254,7 @@ public void testGet_concurrent() { assertTrue(ArbitraryInstances.get(ConcurrentMap.class).isEmpty()); assertTrue(ArbitraryInstances.get(ConcurrentNavigableMap.class).isEmpty()); ArbitraryInstances.get(Executor.class).execute(ArbitraryInstances.get(Runnable.class)); - assertNotNull(ArbitraryInstances.get(ThreadFactory.class)); + assertThat(ArbitraryInstances.get(ThreadFactory.class)).isNotNull(); assertFreshInstanceReturned( BlockingQueue.class, BlockingDeque.class, @@ -276,12 +283,8 @@ public void testGet_comparable() { @SuppressWarnings("unchecked") // The null value can compare with any Object Comparable comparable = ArbitraryInstances.get(Comparable.class); assertEquals(0, comparable.compareTo(comparable)); - assertTrue(comparable.compareTo("") > 0); - try { - comparable.compareTo(null); - fail(); - } catch (NullPointerException expected) { - } + assertThat(comparable.compareTo("")).isGreaterThan(0); + assertThrows(NullPointerException.class, () -> comparable.compareTo(null)); } public void testGet_array() { @@ -291,12 +294,12 @@ public void testGet_array() { } public void testGet_enum() { - assertNull(ArbitraryInstances.get(EmptyEnum.class)); + assertThat(ArbitraryInstances.get(EmptyEnum.class)).isNull(); assertEquals(Direction.UP, ArbitraryInstances.get(Direction.class)); } public void testGet_interface() { - assertNull(ArbitraryInstances.get(SomeInterface.class)); + assertThat(ArbitraryInstances.get(SomeInterface.class)).isNull(); } public void testGet_runnable() { @@ -307,11 +310,11 @@ public void testGet_class() { assertSame(SomeAbstractClass.INSTANCE, ArbitraryInstances.get(SomeAbstractClass.class)); assertSame( WithPrivateConstructor.INSTANCE, ArbitraryInstances.get(WithPrivateConstructor.class)); - assertNull(ArbitraryInstances.get(NoDefaultConstructor.class)); + assertThat(ArbitraryInstances.get(NoDefaultConstructor.class)).isNull(); assertSame( WithExceptionalConstructor.INSTANCE, ArbitraryInstances.get(WithExceptionalConstructor.class)); - assertNull(ArbitraryInstances.get(NonPublicClass.class)); + assertThat(ArbitraryInstances.get(NonPublicClass.class)).isNull(); } public void testGet_mutable() { @@ -346,21 +349,21 @@ public void testGet_io() throws IOException { assertEquals(0, ArbitraryInstances.get(DoubleBuffer.class).capacity()); ArbitraryInstances.get(PrintStream.class).println("test"); ArbitraryInstances.get(PrintWriter.class).println("test"); - assertNotNull(ArbitraryInstances.get(File.class)); + assertThat(ArbitraryInstances.get(File.class)).isNotNull(); assertFreshInstanceReturned( ByteArrayOutputStream.class, OutputStream.class, Writer.class, StringWriter.class, PrintStream.class, PrintWriter.class); assertEquals(ByteSource.empty(), ArbitraryInstances.get(ByteSource.class)); assertEquals(CharSource.empty(), ArbitraryInstances.get(CharSource.class)); - assertNotNull(ArbitraryInstances.get(ByteSink.class)); - assertNotNull(ArbitraryInstances.get(CharSink.class)); + assertThat(ArbitraryInstances.get(ByteSink.class)).isNotNull(); + assertThat(ArbitraryInstances.get(CharSink.class)).isNotNull(); } public void testGet_reflect() { - assertNotNull(ArbitraryInstances.get(Type.class)); - assertNotNull(ArbitraryInstances.get(AnnotatedElement.class)); - assertNotNull(ArbitraryInstances.get(GenericDeclaration.class)); + assertThat(ArbitraryInstances.get(Type.class)).isNotNull(); + assertThat(ArbitraryInstances.get(AnnotatedElement.class)).isNotNull(); + assertThat(ArbitraryInstances.get(GenericDeclaration.class)).isNotNull(); } public void testGet_regex() { @@ -381,40 +384,42 @@ public void testGet_nullConstantIgnored() { } public void testGet_constantWithGenericsNotUsed() { - assertNull(ArbitraryInstances.get(WithGenericConstant.class)); + assertThat(ArbitraryInstances.get(WithGenericConstant.class)).isNull(); } public void testGet_nullConstant() { - assertNull(ArbitraryInstances.get(WithNullConstant.class)); + assertThat(ArbitraryInstances.get(WithNullConstant.class)).isNull(); } public void testGet_constantTypeDoesNotMatch() { - assertNull(ArbitraryInstances.get(ParentClassHasConstant.class)); + assertThat(ArbitraryInstances.get(ParentClassHasConstant.class)).isNull(); } public void testGet_nonPublicConstantNotUsed() { - assertNull(ArbitraryInstances.get(NonPublicConstantIgnored.class)); + assertThat(ArbitraryInstances.get(NonPublicConstantIgnored.class)).isNull(); } public void testGet_nonStaticFieldNotUsed() { - assertNull(ArbitraryInstances.get(NonStaticFieldIgnored.class)); + assertThat(ArbitraryInstances.get(NonStaticFieldIgnored.class)).isNull(); } public void testGet_constructorPreferredOverConstants() { - assertNotNull(ArbitraryInstances.get(WithPublicConstructorAndConstant.class)); + assertThat(ArbitraryInstances.get(WithPublicConstructorAndConstant.class)).isNotNull(); assertTrue( ArbitraryInstances.get(WithPublicConstructorAndConstant.class) != ArbitraryInstances.get(WithPublicConstructorAndConstant.class)); } public void testGet_nonFinalFieldNotUsed() { - assertNull(ArbitraryInstances.get(NonFinalFieldIgnored.class)); + assertThat(ArbitraryInstances.get(NonFinalFieldIgnored.class)).isNull(); } private static void assertFreshInstanceReturned(Class... mutableClasses) { for (Class mutableClass : mutableClasses) { Object instance = ArbitraryInstances.get(mutableClass); - assertNotNull("Expected to return non-null for: " + mutableClass, instance); + assertWithMessage("Expected to return non-null for: " + mutableClass) + .that(instance) + .isNotNull(); assertNotSame( "Expected to return fresh instance for: " + mutableClass, instance, @@ -442,7 +447,7 @@ public NonPublicClass() {} } private static class WithPrivateConstructor { - public static final WithPrivateConstructor INSTANCE = new WithPrivateConstructor(); + @Keep public static final WithPrivateConstructor INSTANCE = new WithPrivateConstructor(); } public static class NoDefaultConstructor { @@ -461,7 +466,7 @@ private WithExceptionalConstructor(String unused) {} } private static class WithPublicConstant { - public static final WithPublicConstant INSTANCE = new WithPublicConstant(); + @Keep public static final WithPublicConstant INSTANCE = new WithPublicConstant(); } private static class ParentClassHasConstant extends WithPublicConstant {} @@ -473,7 +478,7 @@ private WithGenericConstant() {} } public static class WithNullConstant { - public static final WithNullConstant NULL = null; + public static final @Nullable WithNullConstant NULL = null; private WithNullConstant() {} } @@ -486,19 +491,17 @@ public WithPublicConstructorAndConstant() {} } private static class WithPublicConstants { - public static final WithPublicConstants FIRST = new WithPublicConstants(); + @Keep public static final WithPublicConstants FIRST = new WithPublicConstants(); // To test that we pick the first constant alphabetically - @SuppressWarnings("unused") - public static final WithPublicConstants SECOND = new WithPublicConstants(); + @Keep public static final WithPublicConstants SECOND = new WithPublicConstants(); } private static class FirstConstantIsNull { // To test that null constant is ignored - @SuppressWarnings("unused") - public static final FirstConstantIsNull FIRST = null; + @Keep public static final @Nullable FirstConstantIsNull FIRST = null; - public static final FirstConstantIsNull SECOND = new FirstConstantIsNull(); + @Keep public static final FirstConstantIsNull SECOND = new FirstConstantIsNull(); } public static class NonFinalFieldIgnored { diff --git a/android/guava-testlib/test/com/google/common/testing/ClassSanityTesterTest.java b/android/guava-testlib/test/com/google/common/testing/ClassSanityTesterTest.java deleted file mode 100644 index 586eb284eb91..000000000000 --- a/android/guava-testlib/test/com/google/common/testing/ClassSanityTesterTest.java +++ /dev/null @@ -1,1350 +0,0 @@ -/* - * Copyright (C) 2012 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.testing; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.base.Functions; -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableList; -import com.google.common.testing.ClassSanityTester.FactoryMethodReturnsNullException; -import com.google.common.testing.ClassSanityTester.ParameterHasNoDistinctValueException; -import com.google.common.testing.ClassSanityTester.ParameterNotInstantiableException; -import com.google.common.testing.NullPointerTester.Visibility; -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import junit.framework.AssertionFailedError; -import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; - -/** - * Unit tests for {@link ClassSanityTester}. - * - * @author Ben Yu - */ -public class ClassSanityTesterTest extends TestCase { - - private final ClassSanityTester tester = new ClassSanityTester(); - - public void testEqualsOnReturnValues_good() throws Exception { - tester.forAllPublicStaticMethods(GoodEqualsFactory.class).testEquals(); - } - - public static class GoodEqualsFactory { - public static Object good( - String a, - int b, - // oneConstantOnly doesn't matter since it's not nullable and can be only 1 value. - @SuppressWarnings("unused") OneConstantEnum oneConstantOnly, - // noConstant doesn't matter since it can only be null - @SuppressWarnings("unused") @NullableDecl NoConstantEnum noConstant) { - return new GoodEquals(a, b); - } - // instance method ignored - public Object badIgnored() { - return new BadEquals(); - } - // primitive ignored - public int returnsInt() { - throw new UnsupportedOperationException(); - } - // void ignored - public void voidMethod() { - throw new UnsupportedOperationException(); - } - // non-public method ignored - static Object badButNotPublic() { - return new BadEquals(); - } - } - - public void testForAllPublicStaticMethods_noPublicStaticMethods() throws Exception { - try { - tester.forAllPublicStaticMethods(NoPublicStaticMethods.class).testEquals(); - } catch (AssertionFailedError expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo( - "No public static methods that return java.lang.Object or subtype are found in " - + NoPublicStaticMethods.class - + "."); - return; - } - fail(); - } - - public void testEqualsOnReturnValues_bad() throws Exception { - try { - tester.forAllPublicStaticMethods(BadEqualsFactory.class).testEquals(); - } catch (AssertionFailedError expected) { - return; - } - fail(); - } - - private static class BadEqualsFactory { - /** oneConstantOnly matters now since it can be either null or the constant. */ - @SuppressWarnings("unused") // Called by reflection - public static Object bad(String a, int b, @NullableDecl OneConstantEnum oneConstantOnly) { - return new GoodEquals(a, b); - } - } - - public void testNullsOnReturnValues_good() throws Exception { - tester.forAllPublicStaticMethods(GoodNullsFactory.class).testNulls(); - } - - private static class GoodNullsFactory { - @SuppressWarnings("unused") // Called by reflection - public static Object good(String s) { - return new GoodNulls(s); - } - } - - public void testNullsOnReturnValues_bad() throws Exception { - try { - tester.forAllPublicStaticMethods(BadNullsFactory.class).thatReturn(Object.class).testNulls(); - } catch (AssertionFailedError expected) { - return; - } - fail(); - } - - public void testNullsOnReturnValues_returnTypeFiltered() throws Exception { - try { - tester - .forAllPublicStaticMethods(BadNullsFactory.class) - .thatReturn(Iterable.class) - .testNulls(); - } catch (AssertionFailedError expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo( - "No public static methods that return java.lang.Iterable or subtype are found in " - + BadNullsFactory.class - + "."); - return; - } - fail(); - } - - public static class BadNullsFactory { - public static Object bad(@SuppressWarnings("unused") String a) { - return new BadNulls(); - } - } - - @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException... ClassSanityTesterTest$AnInterface - public void testSerializableOnReturnValues_good() throws Exception { - tester.forAllPublicStaticMethods(GoodSerializableFactory.class).testSerializable(); - } - - public static class GoodSerializableFactory { - public static Object good(Runnable r) { - return r; - } - - public static Object good(AnInterface i) { - return i; - } - } - - public void testSerializableOnReturnValues_bad() throws Exception { - try { - tester.forAllPublicStaticMethods(BadSerializableFactory.class).testSerializable(); - } catch (AssertionFailedError expected) { - return; - } - fail(); - } - - public static class BadSerializableFactory { - public static Object bad() { - return new Serializable() { - @SuppressWarnings("unused") - private final Object notSerializable = new Object(); - }; - } - } - - public void testEqualsAndSerializableOnReturnValues_equalsIsGoodButNotSerializable() - throws Exception { - try { - tester.forAllPublicStaticMethods(GoodEqualsFactory.class).testEqualsAndSerializable(); - } catch (AssertionFailedError expected) { - return; - } - fail("should have failed"); - } - - public void testEqualsAndSerializableOnReturnValues_serializableButNotEquals() throws Exception { - try { - tester.forAllPublicStaticMethods(GoodSerializableFactory.class).testEqualsAndSerializable(); - } catch (AssertionFailedError expected) { - return; - } - fail("should have failed"); - } - - @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException... ClassSanityTesterTest$AnInterface - public void testEqualsAndSerializableOnReturnValues_good() throws Exception { - tester - .forAllPublicStaticMethods(GoodEqualsAndSerialiableFactory.class) - .testEqualsAndSerializable(); - } - - public static class GoodEqualsAndSerialiableFactory { - public static Object good(AnInterface s) { - return Functions.constant(s); - } - } - - public void testEqualsForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception { - try { - tester.forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class).testEquals(); - } catch (AssertionFailedError expected) { - return; - } - fail(); - } - - public void testNullsForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception { - try { - tester.forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class).testNulls(); - } catch (AssertionFailedError expected) { - return; - } - fail(); - } - - public void testSerializableForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception { - try { - tester - .forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class) - .testSerializable(); - } catch (AssertionFailedError expected) { - return; - } - fail(); - } - - public void testEqualsAndSerializableForReturnValues_factoryReturnsNullButNotAnnotated() - throws Exception { - try { - tester - .forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class) - .testEqualsAndSerializable(); - } catch (AssertionFailedError expected) { - return; - } - fail(); - } - - public static class FactoryThatReturnsNullButNotAnnotated { - public static Object bad() { - return null; - } - } - - public void testEqualsForReturnValues_factoryReturnsNullAndAnnotated() throws Exception { - tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class).testEquals(); - } - - public void testNullsForReturnValues_factoryReturnsNullAndAnnotated() throws Exception { - tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class).testNulls(); - } - - public void testSerializableForReturnValues_factoryReturnsNullAndAnnotated() throws Exception { - tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class).testSerializable(); - } - - public void testEqualsAndSerializableForReturnValues_factoryReturnsNullAndAnnotated() - throws Exception { - tester - .forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class) - .testEqualsAndSerializable(); - } - - public static class FactoryThatReturnsNullAndAnnotated { - @NullableDecl - public static Object bad() { - return null; - } - } - - public void testGoodEquals() throws Exception { - tester.testEquals(GoodEquals.class); - } - - public void testEquals_interface() { - tester.testEquals(AnInterface.class); - } - - public void testEquals_abstractClass() { - tester.testEquals(AnAbstractClass.class); - } - - public void testEquals_enum() { - tester.testEquals(OneConstantEnum.class); - } - - public void testBadEquals() throws Exception { - try { - tester.testEquals(BadEquals.class); - } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()).contains("create(null)"); - return; - } - fail("should have failed"); - } - - public void testBadEquals_withParameterizedType() throws Exception { - try { - tester.testEquals(BadEqualsWithParameterizedType.class); - } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()).contains("create([[1]])"); - return; - } - fail("should have failed"); - } - - public void testBadEquals_withSingleParameterValue() throws Exception { - try { - tester.doTestEquals(ConstructorParameterWithOptionalNotInstantiable.class); - fail(); - } catch (ParameterHasNoDistinctValueException expected) { - } - } - - public void testGoodReferentialEqualityComparison() throws Exception { - tester.testEquals(UsesEnum.class); - tester.testEquals(UsesReferentialEquality.class); - tester.testEquals(SameListInstance.class); - } - - @AndroidIncompatible // problem with equality of Type objects? - public void testEqualsUsingReferentialEquality() throws Exception { - assertBadUseOfReferentialEquality(SameIntegerInstance.class); - assertBadUseOfReferentialEquality(SameLongInstance.class); - assertBadUseOfReferentialEquality(SameFloatInstance.class); - assertBadUseOfReferentialEquality(SameDoubleInstance.class); - assertBadUseOfReferentialEquality(SameShortInstance.class); - assertBadUseOfReferentialEquality(SameByteInstance.class); - assertBadUseOfReferentialEquality(SameCharacterInstance.class); - assertBadUseOfReferentialEquality(SameBooleanInstance.class); - assertBadUseOfReferentialEquality(SameObjectInstance.class); - assertBadUseOfReferentialEquality(SameStringInstance.class); - assertBadUseOfReferentialEquality(SameInterfaceInstance.class); - } - - private void assertBadUseOfReferentialEquality(Class cls) throws Exception { - try { - tester.testEquals(cls); - } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()).contains(cls.getSimpleName() + "("); - return; - } - fail("should have failed for " + cls); - } - - public void testParameterNotInstantiableForEqualsTest() throws Exception { - try { - tester.doTestEquals(ConstructorParameterNotInstantiable.class); - fail("should have failed"); - } catch (ParameterNotInstantiableException expected) { - } - } - - public void testNoDistinctValueForEqualsTest() throws Exception { - try { - tester.doTestEquals(ConstructorParameterSingleValue.class); - fail("should have failed"); - } catch (ParameterHasNoDistinctValueException expected) { - } - } - - public void testConstructorThrowsForEqualsTest() throws Exception { - try { - tester.doTestEquals(ConstructorThrows.class); - fail("should have failed"); - } catch (InvocationTargetException expected) { - } - } - - public void testFactoryMethodReturnsNullForEqualsTest() throws Exception { - try { - tester.doTestEquals(FactoryMethodReturnsNullAndAnnotated.class); - fail("should have failed"); - } catch (FactoryMethodReturnsNullException expected) { - } - } - - public void testFactoryMethodReturnsNullButNotAnnotatedInEqualsTest() throws Exception { - try { - tester.testEquals(FactoryMethodReturnsNullButNotAnnotated.class); - } catch (AssertionFailedError expected) { - return; - } - fail("should have failed"); - } - - public void testNoEqualsChecksOnEnum() throws Exception { - tester.testEquals(OneConstantEnum.class); - tester.testEquals(NoConstantEnum.class); - tester.testEquals(TimeUnit.class); - } - - public void testNoEqualsChecksOnInterface() throws Exception { - tester.testEquals(Runnable.class); - } - - public void testNoEqualsChecksOnAnnotation() throws Exception { - tester.testEquals(MyAnnotation.class); - } - - public void testGoodNulls() throws Exception { - tester.testNulls(GoodNulls.class); - } - - public void testNoNullCheckNeededDespitNotInstantiable() throws Exception { - tester.doTestNulls(NoNullCheckNeededDespitNotInstantiable.class, Visibility.PACKAGE); - } - - public void testNulls_interface() { - tester.testNulls(AnInterface.class); - } - - public void testNulls_abstractClass() { - tester.testNulls(AnAbstractClass.class); - } - - public void testNulls_enum() throws Exception { - tester.testNulls(OneConstantEnum.class); - tester.testNulls(NoConstantEnum.class); - tester.testNulls(TimeUnit.class); - } - - public void testNulls_parameterOptionalNotInstantiable() throws Exception { - tester.testNulls(ConstructorParameterWithOptionalNotInstantiable.class); - } - - public void testEnumFailsToCheckNull() throws Exception { - try { - tester.testNulls(EnumFailsToCheckNull.class); - } catch (AssertionFailedError expected) { - return; - } - fail("should have failed"); - } - - public void testNoNullChecksOnInterface() throws Exception { - tester.testNulls(Runnable.class); - } - - public void testNoNullChecksOnAnnotation() throws Exception { - tester.testNulls(MyAnnotation.class); - } - - public void testBadNulls() throws Exception { - try { - tester.testNulls(BadNulls.class); - } catch (AssertionFailedError expected) { - return; - } - fail("should have failed"); - } - - public void testInstantiate_factoryMethodReturnsNullButNotAnnotated() throws Exception { - try { - tester.instantiate(FactoryMethodReturnsNullButNotAnnotated.class); - } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()).contains("@Nullable"); - return; - } - fail("should have failed"); - } - - public void testInstantiate_factoryMethodReturnsNullAndAnnotated() throws Exception { - try { - tester.instantiate(FactoryMethodReturnsNullAndAnnotated.class); - fail("should have failed"); - } catch (FactoryMethodReturnsNullException expected) { - } - } - - public void testInstantiate_factoryMethodAcceptsNull() throws Exception { - assertNull(tester.instantiate(FactoryMethodAcceptsNull.class).name); - } - - public void testInstantiate_factoryMethodDoesNotAcceptNull() throws Exception { - assertNotNull(tester.instantiate(FactoryMethodDoesNotAcceptNull.class).name); - } - - public void testInstantiate_constructorAcceptsNull() throws Exception { - assertNull(tester.instantiate(ConstructorAcceptsNull.class).name); - } - - public void testInstantiate_constructorDoesNotAcceptNull() throws Exception { - assertNotNull(tester.instantiate(ConstructorDoesNotAcceptNull.class).name); - } - - public void testInstantiate_notInstantiable() throws Exception { - assertNull(tester.instantiate(NotInstantiable.class)); - } - - public void testInstantiate_noConstantEnum() throws Exception { - assertNull(tester.instantiate(NoConstantEnum.class)); - } - - public void testInstantiate_oneConstantEnum() throws Exception { - assertEquals(OneConstantEnum.A, tester.instantiate(OneConstantEnum.class)); - } - - public void testInstantiate_interface() throws Exception { - assertNull(tester.instantiate(Runnable.class)); - } - - public void testInstantiate_abstractClass() throws Exception { - assertNull(tester.instantiate(AbstractList.class)); - } - - public void testInstantiate_annotation() throws Exception { - assertNull(tester.instantiate(MyAnnotation.class)); - } - - public void testInstantiate_setDefault() throws Exception { - NotInstantiable x = new NotInstantiable(); - tester.setDefault(NotInstantiable.class, x); - assertNotNull(tester.instantiate(ConstructorParameterNotInstantiable.class)); - } - - public void testSetDistinctValues_equalInstances() { - try { - tester.setDistinctValues(String.class, "", ""); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - public void testInstantiate_setDistinctValues() throws Exception { - NotInstantiable x = new NotInstantiable(); - NotInstantiable y = new NotInstantiable(); - tester.setDistinctValues(NotInstantiable.class, x, y); - assertNotNull(tester.instantiate(ConstructorParameterNotInstantiable.class)); - tester.testEquals(ConstructorParameterMapOfNotInstantiable.class); - } - - public void testInstantiate_constructorThrows() throws Exception { - try { - tester.instantiate(ConstructorThrows.class); - fail(); - } catch (InvocationTargetException expected) { - } - } - - public void testInstantiate_factoryMethodThrows() throws Exception { - try { - tester.instantiate(FactoryMethodThrows.class); - fail(); - } catch (InvocationTargetException expected) { - } - } - - public void testInstantiate_constructorParameterNotInstantiable() throws Exception { - try { - tester.instantiate(ConstructorParameterNotInstantiable.class); - fail(); - } catch (ParameterNotInstantiableException expected) { - } - } - - public void testInstantiate_factoryMethodParameterNotInstantiable() throws Exception { - try { - tester.instantiate(FactoryMethodParameterNotInstantiable.class); - fail(); - } catch (ParameterNotInstantiableException expected) { - } - } - - public void testInstantiate_instantiableFactoryMethodChosen() throws Exception { - assertEquals("good", tester.instantiate(InstantiableFactoryMethodChosen.class).name); - } - - @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException... ClassSanityTesterTest$AnInterface - public void testInterfaceProxySerializable() throws Exception { - SerializableTester.reserializeAndAssert(tester.instantiate(HasAnInterface.class)); - } - - public void testReturnValuesFromAnotherPackageIgnoredForNullTests() throws Exception { - new ClassSanityTester().forAllPublicStaticMethods(JdkObjectFactory.class).testNulls(); - } - - /** String doesn't check nulls as we expect. But the framework should ignore. */ - private static class JdkObjectFactory { - @SuppressWarnings("unused") // Called by reflection - public static Object create() { - return new ArrayList<>(); - } - } - - static class HasAnInterface implements Serializable { - private final AnInterface i; - - public HasAnInterface(AnInterface i) { - this.i = i; - } - - @Override - public boolean equals(@NullableDecl Object obj) { - if (obj instanceof HasAnInterface) { - HasAnInterface that = (HasAnInterface) obj; - return i.equals(that.i); - } else { - return false; - } - } - - @Override - public int hashCode() { - return i.hashCode(); - } - } - - static class InstantiableFactoryMethodChosen { - final String name; - - private InstantiableFactoryMethodChosen(String name) { - this.name = name; - } - - public InstantiableFactoryMethodChosen(NotInstantiable x) { - checkNotNull(x); - this.name = "x1"; - } - - public static InstantiableFactoryMethodChosen create(NotInstantiable x) { - return new InstantiableFactoryMethodChosen(x); - } - - public static InstantiableFactoryMethodChosen create(String s) { - checkNotNull(s); - return new InstantiableFactoryMethodChosen("good"); - } - } - - public void testInstantiate_instantiableConstructorChosen() throws Exception { - assertEquals("good", tester.instantiate(InstantiableConstructorChosen.class).name); - } - - public void testEquals_setOfNonInstantiable() throws Exception { - try { - new ClassSanityTester().doTestEquals(SetWrapper.class); - fail(); - } catch (ParameterNotInstantiableException expected) { - } - } - - private abstract static class Wrapper { - private final Object wrapped; - - Wrapper(Object wrapped) { - this.wrapped = checkNotNull(wrapped); - } - - @Override - public boolean equals(@NullableDecl Object obj) { - // In general getClass().isInstance() is bad for equals. - // But here we fully control the subclasses to ensure symmetry. - if (getClass().isInstance(obj)) { - Wrapper that = (Wrapper) obj; - return wrapped.equals(that.wrapped); - } - return false; - } - - @Override - public int hashCode() { - return wrapped.hashCode(); - } - - @Override - public String toString() { - return wrapped.toString(); - } - } - - private static class SetWrapper extends Wrapper { - public SetWrapper(Set wrapped) { - super(wrapped); - } - } - - static class InstantiableConstructorChosen { - final String name; - - public InstantiableConstructorChosen(String name) { - checkNotNull(name); - this.name = "good"; - } - - public InstantiableConstructorChosen(NotInstantiable x) { - checkNotNull(x); - this.name = "x1"; - } - - public static InstantiableFactoryMethodChosen create(NotInstantiable x) { - return new InstantiableFactoryMethodChosen(x); - } - } - - static class GoodEquals { - - private final String a; - private final int b; - - private GoodEquals(String a, int b) { - this.a = checkNotNull(a); - this.b = b; - } - - // ignored by testEquals() - GoodEquals(@SuppressWarnings("unused") NotInstantiable x) { - this.a = "x"; - this.b = -1; - } - - // will keep trying - public GoodEquals(@SuppressWarnings("unused") NotInstantiable x, int b) { - this.a = "x"; - this.b = b; - } - - // keep trying - @SuppressWarnings("unused") - static GoodEquals create(int a, int b) { - throw new RuntimeException(); - } - - // Good! - static GoodEquals create(String a, int b) { - return new GoodEquals(a, b); - } - - // keep trying - @SuppressWarnings("unused") - @NullableDecl - public static GoodEquals createMayReturnNull(int a, int b) { - return null; - } - - @Override - public boolean equals(@NullableDecl Object obj) { - if (obj instanceof GoodEquals) { - GoodEquals that = (GoodEquals) obj; - return a.equals(that.a) && b == that.b; - } else { - return false; - } - } - - @Override - public int hashCode() { - return 0; - } - } - - static class BadEquals { - - public BadEquals() {} // ignored by testEquals() since it has less parameters. - - public static BadEquals create(@SuppressWarnings("unused") @NullableDecl String s) { - return new BadEquals(); - } - - @Override - public boolean equals(@NullableDecl Object obj) { - return obj instanceof BadEquals; - } - - @Override - public int hashCode() { - return 0; - } - } - - static class SameIntegerInstance { - private final Integer i; - - public SameIntegerInstance(Integer i) { - this.i = checkNotNull(i); - } - - @Override - public int hashCode() { - return i.hashCode(); - } - - @Override - @SuppressWarnings("NumericEquality") - public boolean equals(Object obj) { - if (obj instanceof SameIntegerInstance) { - SameIntegerInstance that = (SameIntegerInstance) obj; - return i == that.i; - } - return false; - } - } - - static class SameLongInstance { - private final Long i; - - public SameLongInstance(Long i) { - this.i = checkNotNull(i); - } - - @Override - public int hashCode() { - return i.hashCode(); - } - - @Override - @SuppressWarnings("NumericEquality") - public boolean equals(Object obj) { - if (obj instanceof SameLongInstance) { - SameLongInstance that = (SameLongInstance) obj; - return i == that.i; - } - return false; - } - } - - static class SameFloatInstance { - private final Float i; - - public SameFloatInstance(Float i) { - this.i = checkNotNull(i); - } - - @Override - public int hashCode() { - return i.hashCode(); - } - - @Override - @SuppressWarnings("NumericEquality") - public boolean equals(Object obj) { - if (obj instanceof SameFloatInstance) { - SameFloatInstance that = (SameFloatInstance) obj; - return i == that.i; - } - return false; - } - } - - static class SameDoubleInstance { - private final Double i; - - public SameDoubleInstance(Double i) { - this.i = checkNotNull(i); - } - - @Override - public int hashCode() { - return i.hashCode(); - } - - @Override - @SuppressWarnings("NumericEquality") - public boolean equals(Object obj) { - if (obj instanceof SameDoubleInstance) { - SameDoubleInstance that = (SameDoubleInstance) obj; - return i == that.i; - } - return false; - } - } - - static class SameShortInstance { - private final Short i; - - public SameShortInstance(Short i) { - this.i = checkNotNull(i); - } - - @Override - public int hashCode() { - return i.hashCode(); - } - - @Override - @SuppressWarnings("NumericEquality") - public boolean equals(Object obj) { - if (obj instanceof SameShortInstance) { - SameShortInstance that = (SameShortInstance) obj; - return i == that.i; - } - return false; - } - } - - static class SameByteInstance { - private final Byte i; - - public SameByteInstance(Byte i) { - this.i = checkNotNull(i); - } - - @Override - public int hashCode() { - return i.hashCode(); - } - - @Override - @SuppressWarnings("NumericEquality") - public boolean equals(Object obj) { - if (obj instanceof SameByteInstance) { - SameByteInstance that = (SameByteInstance) obj; - return i == that.i; - } - return false; - } - } - - static class SameCharacterInstance { - private final Character i; - - public SameCharacterInstance(Character i) { - this.i = checkNotNull(i); - } - - @Override - public int hashCode() { - return i.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SameCharacterInstance) { - SameCharacterInstance that = (SameCharacterInstance) obj; - return i == that.i; - } - return false; - } - } - - static class SameBooleanInstance { - private final Boolean i; - - public SameBooleanInstance(Boolean i) { - this.i = checkNotNull(i); - } - - @Override - public int hashCode() { - return i.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SameBooleanInstance) { - SameBooleanInstance that = (SameBooleanInstance) obj; - return i == that.i; - } - return false; - } - } - - static class SameStringInstance { - private final String s; - - public SameStringInstance(String s) { - this.s = checkNotNull(s); - } - - @Override - public int hashCode() { - return s.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SameStringInstance) { - SameStringInstance that = (SameStringInstance) obj; - return s == that.s; - } - return false; - } - } - - static class SameObjectInstance { - private final Object s; - - public SameObjectInstance(Object s) { - this.s = checkNotNull(s); - } - - @Override - public int hashCode() { - return s.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SameObjectInstance) { - SameObjectInstance that = (SameObjectInstance) obj; - return s == that.s; - } - return false; - } - } - - static class SameInterfaceInstance { - private final Runnable s; - - public SameInterfaceInstance(Runnable s) { - this.s = checkNotNull(s); - } - - @Override - public int hashCode() { - return s.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SameInterfaceInstance) { - SameInterfaceInstance that = (SameInterfaceInstance) obj; - return s == that.s; - } - return false; - } - } - - static class SameListInstance { - private final List s; - - public SameListInstance(List s) { - this.s = checkNotNull(s); - } - - @Override - public int hashCode() { - return System.identityHashCode(s); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SameListInstance) { - SameListInstance that = (SameListInstance) obj; - return s == that.s; - } - return false; - } - } - - static class UsesReferentialEquality { - private final ReferentialEquality s; - - public UsesReferentialEquality(ReferentialEquality s) { - this.s = checkNotNull(s); - } - - @Override - public int hashCode() { - return s.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof UsesReferentialEquality) { - UsesReferentialEquality that = (UsesReferentialEquality) obj; - return s == that.s; - } - return false; - } - } - - static class UsesEnum { - private final TimeUnit s; - - public UsesEnum(TimeUnit s) { - this.s = checkNotNull(s); - } - - @Override - public int hashCode() { - return s.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof UsesEnum) { - UsesEnum that = (UsesEnum) obj; - return s == that.s; - } - return false; - } - } - - public static class ReferentialEquality { - public ReferentialEquality() {} - } - - static class BadEqualsWithParameterizedType { - - // ignored by testEquals() since it has less parameters. - public BadEqualsWithParameterizedType() {} - - public static BadEqualsWithParameterizedType create( - @SuppressWarnings("unused") ImmutableList> s) { - return new BadEqualsWithParameterizedType(); - } - - @Override - public boolean equals(@NullableDecl Object obj) { - return obj instanceof BadEqualsWithParameterizedType; - } - - @Override - public int hashCode() { - return 0; - } - } - - static class GoodNulls { - public GoodNulls(String s) { - checkNotNull(s); - } - - public void rejectNull(String s) { - checkNotNull(s); - } - } - - public static class BadNulls { - public void failsToRejectNull(@SuppressWarnings("unused") String s) {} - } - - public static class NoNullCheckNeededDespitNotInstantiable { - - public NoNullCheckNeededDespitNotInstantiable(NotInstantiable x) { - checkNotNull(x); - } - - @SuppressWarnings("unused") // reflected - void primitiveOnly(int i) {} - - @SuppressWarnings("unused") // reflected - void nullableOnly(@NullableDecl String s) {} - - public void noParameter() {} - - @SuppressWarnings("unused") // reflected - void primitiveAndNullable(@NullableDecl String s, int i) {} - } - - static class FactoryMethodReturnsNullButNotAnnotated { - private FactoryMethodReturnsNullButNotAnnotated() {} - - static FactoryMethodReturnsNullButNotAnnotated returnsNull() { - return null; - } - } - - static class FactoryMethodReturnsNullAndAnnotated { - private FactoryMethodReturnsNullAndAnnotated() {} - - @NullableDecl - public static FactoryMethodReturnsNullAndAnnotated returnsNull() { - return null; - } - } - - static class FactoryMethodAcceptsNull { - - final String name; - - private FactoryMethodAcceptsNull(String name) { - this.name = name; - } - - static FactoryMethodAcceptsNull create(@NullableDecl String name) { - return new FactoryMethodAcceptsNull(name); - } - } - - static class FactoryMethodDoesNotAcceptNull { - - final String name; - - private FactoryMethodDoesNotAcceptNull(String name) { - this.name = checkNotNull(name); - } - - public static FactoryMethodDoesNotAcceptNull create(String name) { - return new FactoryMethodDoesNotAcceptNull(name); - } - } - - static class ConstructorAcceptsNull { - - final String name; - - public ConstructorAcceptsNull(@NullableDecl String name) { - this.name = name; - } - } - - static class ConstructorDoesNotAcceptNull { - - final String name; - - ConstructorDoesNotAcceptNull(String name) { - this.name = checkNotNull(name); - } - } - - static class ConstructorParameterNotInstantiable { - public ConstructorParameterNotInstantiable(@SuppressWarnings("unused") NotInstantiable x) {} - } - - static class ConstructorParameterMapOfNotInstantiable { - private final Map m; - - public ConstructorParameterMapOfNotInstantiable(Map m) { - this.m = checkNotNull(m); - } - - @Override - public boolean equals(@NullableDecl Object obj) { - if (obj instanceof ConstructorParameterMapOfNotInstantiable) { - return m.equals(((ConstructorParameterMapOfNotInstantiable) obj).m); - } else { - return false; - } - } - - @Override - public int hashCode() { - return m.hashCode(); - } - } - - // Test that we should get a distinct parameter error when doing equals test. - static class ConstructorParameterWithOptionalNotInstantiable { - public ConstructorParameterWithOptionalNotInstantiable(Optional x) { - checkNotNull(x); - } - - @Override - public boolean equals(@NullableDecl Object obj) { - throw new UnsupportedOperationException(); - } - - @Override - public int hashCode() { - throw new UnsupportedOperationException(); - } - } - - static class ConstructorParameterSingleValue { - public ConstructorParameterSingleValue(@SuppressWarnings("unused") Singleton s) {} - - @Override - public boolean equals(Object obj) { - return obj instanceof ConstructorParameterSingleValue; - } - - @Override - public int hashCode() { - return 1; - } - - public static class Singleton { - public static final Singleton INSTANCE = new Singleton(); - - private Singleton() {} - } - } - - static class FactoryMethodParameterNotInstantiable { - - private FactoryMethodParameterNotInstantiable() {} - - static FactoryMethodParameterNotInstantiable create( - @SuppressWarnings("unused") NotInstantiable x) { - return new FactoryMethodParameterNotInstantiable(); - } - } - - static class ConstructorThrows { - public ConstructorThrows() { - throw new RuntimeException(); - } - } - - static class FactoryMethodThrows { - private FactoryMethodThrows() {} - - public static FactoryMethodThrows create() { - throw new RuntimeException(); - } - } - - static class NotInstantiable { - private NotInstantiable() {} - } - - private enum NoConstantEnum {} - - private enum OneConstantEnum { - A - } - - private enum EnumFailsToCheckNull { - A; - - @SuppressWarnings("unused") - public void failToCheckNull(String s) {} - } - - private interface AnInterface {} - - private abstract static class AnAbstractClass { - @SuppressWarnings("unused") - public AnAbstractClass(String s) {} - - @SuppressWarnings("unused") - public void failsToCheckNull(String s) {} - } - - private static class NoPublicStaticMethods { - @SuppressWarnings("unused") // To test non-public factory isn't used. - static String notPublic() { - return ""; - } - } - - @interface MyAnnotation {} -} diff --git a/android/guava-testlib/test/com/google/common/testing/EqualsTesterTest.java b/android/guava-testlib/test/com/google/common/testing/EqualsTesterTest.java index d615af6633a2..cd448c460d1b 100644 --- a/android/guava-testlib/test/com/google/common/testing/EqualsTesterTest.java +++ b/android/guava-testlib/test/com/google/common/testing/EqualsTesterTest.java @@ -16,13 +16,19 @@ package com.google.common.testing; +import static com.google.common.testing.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Sets; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.HashSet; import java.util.Set; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit tests for {@link EqualsTester}. @@ -31,6 +37,7 @@ */ @GwtCompatible @SuppressWarnings("MissingTestCall") +@NullUnmarked public class EqualsTesterTest extends TestCase { private ValidTestObject reference; private EqualsTester equalsTester; @@ -50,29 +57,21 @@ public void setUp() throws Exception { /** Test null reference yields error */ public void testAddNullReference() { - try { - equalsTester.addEqualityGroup((Object) null); - fail("Should fail on null reference"); - } catch (NullPointerException e) { - } + assertThrows(NullPointerException.class, () -> equalsTester.addEqualityGroup((Object) null)); } /** Test equalObjects after adding multiple instances at once with a null */ public void testAddTwoEqualObjectsAtOnceWithNull() { - try { - equalsTester.addEqualityGroup(reference, equalObject1, null); - fail("Should fail on null equal object"); - } catch (NullPointerException e) { - } + assertThrows( + NullPointerException.class, + () -> equalsTester.addEqualityGroup(reference, equalObject1, null)); } /** Test adding null equal object yields error */ public void testAddNullEqualObject() { - try { - equalsTester.addEqualityGroup(reference, (Object[]) null); - fail("Should fail on null equal object"); - } catch (NullPointerException e) { - } + assertThrows( + NullPointerException.class, + () -> equalsTester.addEqualityGroup(reference, (Object[]) null)); } /** @@ -114,7 +113,7 @@ public void testTestEqualsEqualsObjects() { } /** Test proper handling of case where an object is not equal to itself */ - public void testNonreflexiveEquals() { + public void testNonReflexiveEquals() { Object obj = new NonReflexiveObject(); equalsTester.addEqualityGroup(obj); try { @@ -194,21 +193,14 @@ public void testInvalidHashCode() { public void testNullEqualityGroup() { EqualsTester tester = new EqualsTester(); - try { - tester.addEqualityGroup((Object[]) null); - fail(); - } catch (NullPointerException e) { - } + assertThrows(NullPointerException.class, () -> tester.addEqualityGroup((Object[]) null)); } public void testNullObjectInEqualityGroup() { EqualsTester tester = new EqualsTester(); - try { - tester.addEqualityGroup(1, null, 3); - fail(); - } catch (NullPointerException e) { - assertErrorMessage(e, "at index 1"); - } + NullPointerException e = + assertThrows(NullPointerException.class, () -> tester.addEqualityGroup(1, null, 3)); + assertErrorMessage(e, "at index 1"); } public void testSymmetryBroken() { @@ -273,12 +265,12 @@ public void testEqualityGroups() { } public void testEqualityBasedOnToString() { - try { - new EqualsTester().addEqualityGroup(new EqualsBasedOnToString("foo")).testEquals(); - fail(); - } catch (AssertionFailedError e) { - assertTrue(e.getMessage().contains("toString representation")); - } + AssertionFailedError e = + assertThrows( + AssertionFailedError.class, + () -> + new EqualsTester().addEqualityGroup(new EqualsBasedOnToString("foo")).testEquals()); + assertThat(e).hasMessageThat().contains("toString representation"); } private static void assertErrorMessage(Throwable e, String message) { @@ -293,8 +285,8 @@ private static void assertErrorMessage(Throwable e, String message) { * should always pass. */ private static class ValidTestObject { - private int aspect1; - private int aspect2; + private final int aspect1; + private final int aspect2; ValidTestObject(int aspect1, int aspect2) { this.aspect1 = aspect1; @@ -302,7 +294,7 @@ private static class ValidTestObject { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (!(o instanceof ValidTestObject)) { return false; } @@ -327,8 +319,8 @@ public int hashCode() { /** Test class with invalid hashCode method. */ private static class InvalidHashCodeObject { - private int aspect1; - private int aspect2; + private final int aspect1; + private final int aspect2; InvalidHashCodeObject(int aspect1, int aspect2) { this.aspect1 = aspect1; @@ -337,7 +329,7 @@ private static class InvalidHashCodeObject { @SuppressWarnings("EqualsHashCode") @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (!(o instanceof InvalidHashCodeObject)) { return false; } @@ -356,7 +348,7 @@ public boolean equals(Object o) { private static class NonReflexiveObject { @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return false; } @@ -370,7 +362,7 @@ public int hashCode() { private static class InvalidEqualsNullObject { @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return o == this || o == null; } @@ -384,7 +376,7 @@ public int hashCode() { private static class InvalidEqualsIncompatibleClassObject { @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return o != null; } @@ -399,7 +391,7 @@ private static NamedObject named(String name) { } private static class NamedObject { - private final Set peerNames = Sets.newHashSet(); + private final Set peerNames = new HashSet<>(); private final String name; @@ -407,13 +399,14 @@ private static class NamedObject { this.name = Preconditions.checkNotNull(name); } + @CanIgnoreReturnValue NamedObject addPeers(String... names) { peerNames.addAll(ImmutableList.copyOf(names)); return this; } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof NamedObject) { NamedObject that = (NamedObject) obj; return name.equals(that.name) || peerNames.contains(that.name); @@ -440,7 +433,7 @@ private EqualsBasedOnToString(String s) { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return obj != null && obj.toString().equals(toString()); } diff --git a/android/guava-testlib/test/com/google/common/testing/EquivalenceTesterTest.java b/android/guava-testlib/test/com/google/common/testing/EquivalenceTesterTest.java index d612b2c2c876..77a35d04ece2 100644 --- a/android/guava-testlib/test/com/google/common/testing/EquivalenceTesterTest.java +++ b/android/guava-testlib/test/com/google/common/testing/EquivalenceTesterTest.java @@ -17,6 +17,7 @@ package com.google.common.testing; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.testing.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; @@ -26,6 +27,7 @@ import com.google.common.collect.ImmutableTable; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link EquivalenceTester}. @@ -33,6 +35,7 @@ * @author Gregory Kick */ @GwtCompatible +@NullUnmarked public class EquivalenceTesterTest extends TestCase { private EquivalenceTester tester; private MockEquivalence equivalenceMock; @@ -45,15 +48,11 @@ public void setUp() throws Exception { } /** Test null reference yields error */ - public void testOf_NullPointerException() { - try { - EquivalenceTester.of(null); - fail("Should fail on null reference"); - } catch (NullPointerException expected) { - } + public void testOf_nullPointerException() { + assertThrows(NullPointerException.class, () -> EquivalenceTester.of(null)); } - public void testTest_NoData() { + public void testTest_noData() { tester.test(); } @@ -104,7 +103,8 @@ public void testTest_symmetric() { try { tester.addEquivalenceGroup(group1Item1, group1Item2).test(); } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()) + assertThat(expected) + .hasMessageThat() .contains( "TestObject{group=1, item=2} [group 1, item 2] must be equivalent to " + "TestObject{group=1, item=1} [group 1, item 1]"); @@ -113,7 +113,7 @@ public void testTest_symmetric() { fail(); } - public void testTest_trasitive() { + public void testTest_transitive() { Object group1Item1 = new TestObject(1, 1); Object group1Item2 = new TestObject(1, 2); Object group1Item3 = new TestObject(1, 3); @@ -134,7 +134,8 @@ public void testTest_trasitive() { try { tester.addEquivalenceGroup(group1Item1, group1Item2, group1Item3).test(); } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()) + assertThat(expected) + .hasMessageThat() .contains( "TestObject{group=1, item=2} [group 1, item 2] must be equivalent to " + "TestObject{group=1, item=3} [group 1, item 3]"); @@ -158,7 +159,8 @@ public void testTest_inequivalence() { try { tester.addEquivalenceGroup(group1Item1).addEquivalenceGroup(group2Item1).test(); } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()) + assertThat(expected) + .hasMessageThat() .contains( "TestObject{group=1, item=1} [group 1, item 1] must not be equivalent to " + "TestObject{group=2, item=1} [group 2, item 1]"); @@ -236,8 +238,8 @@ void expectHash(Object object, int hash) { void replay() { checkRecording(); - equivalentExpectations = equivalentExpectationsBuilder.build(); - hashExpectations = hashExpectationsBuilder.build(); + equivalentExpectations = equivalentExpectationsBuilder.buildOrThrow(); + hashExpectations = hashExpectationsBuilder.buildOrThrow(); } @Override diff --git a/android/guava-testlib/test/com/google/common/testing/FakeTickerTest.java b/android/guava-testlib/test/com/google/common/testing/FakeTickerTest.java index 654304b1902b..83c463425d3a 100644 --- a/android/guava-testlib/test/com/google/common/testing/FakeTickerTest.java +++ b/android/guava-testlib/test/com/google/common/testing/FakeTickerTest.java @@ -16,23 +16,33 @@ package com.google.common.testing; +import static com.google.common.testing.ReflectionFreeAssertThrows.assertThrows; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import java.util.EnumSet; +import java.time.Duration; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link FakeTicker}. * * @author Jige Yu */ -@GwtCompatible(emulated = true) +@GwtCompatible +// We also want to test the TimeUnit overload (especially under GWT, where it's the only option). +@SuppressWarnings("SetAutoIncrementStep_Nanos") +@NullUnmarked public class FakeTickerTest extends TestCase { @GwtIncompatible // NullPointerTester @@ -41,48 +51,61 @@ public void testNullPointerExceptions() { tester.testAllPublicInstanceMethods(new FakeTicker()); } + @GwtIncompatible // java.time.Duration + @IgnoreJRERequirement // TODO: b/288085449 - Remove this once we use library-desugaring scents. public void testAdvance() { FakeTicker ticker = new FakeTicker(); assertEquals(0, ticker.read()); assertSame(ticker, ticker.advance(10)); assertEquals(10, ticker.read()); - ticker.advance(1, TimeUnit.MILLISECONDS); + ticker.advance(1, MILLISECONDS); assertEquals(1000010L, ticker.read()); + ticker.advance(Duration.ofMillis(1)); + assertEquals(2000010L, ticker.read()); } public void testAutoIncrementStep_returnsSameInstance() { FakeTicker ticker = new FakeTicker(); - assertSame(ticker, ticker.setAutoIncrementStep(10, TimeUnit.NANOSECONDS)); + assertSame(ticker, ticker.setAutoIncrementStep(10, NANOSECONDS)); } public void testAutoIncrementStep_nanos() { - FakeTicker ticker = new FakeTicker().setAutoIncrementStep(10, TimeUnit.NANOSECONDS); + FakeTicker ticker = new FakeTicker().setAutoIncrementStep(10, NANOSECONDS); assertEquals(0, ticker.read()); assertEquals(10, ticker.read()); assertEquals(20, ticker.read()); } public void testAutoIncrementStep_millis() { - FakeTicker ticker = new FakeTicker().setAutoIncrementStep(1, TimeUnit.MILLISECONDS); + FakeTicker ticker = new FakeTicker().setAutoIncrementStep(1, MILLISECONDS); assertEquals(0, ticker.read()); assertEquals(1000000, ticker.read()); assertEquals(2000000, ticker.read()); } public void testAutoIncrementStep_seconds() { - FakeTicker ticker = new FakeTicker().setAutoIncrementStep(3, TimeUnit.SECONDS); + FakeTicker ticker = new FakeTicker().setAutoIncrementStep(3, SECONDS); assertEquals(0, ticker.read()); assertEquals(3000000000L, ticker.read()); assertEquals(6000000000L, ticker.read()); } + @GwtIncompatible // java.time.Duration + @IgnoreJRERequirement // TODO: b/288085449 - Remove this once we use library-desugaring scents. + public void testAutoIncrementStep_duration() { + FakeTicker ticker = new FakeTicker().setAutoIncrementStep(Duration.ofMillis(1)); + assertEquals(0, ticker.read()); + assertEquals(1000000, ticker.read()); + assertEquals(2000000, ticker.read()); + } + public void testAutoIncrementStep_resetToZero() { - FakeTicker ticker = new FakeTicker().setAutoIncrementStep(10, TimeUnit.NANOSECONDS); + FakeTicker ticker = new FakeTicker().setAutoIncrementStep(10, NANOSECONDS); assertEquals(0, ticker.read()); assertEquals(10, ticker.read()); assertEquals(20, ticker.read()); - for (TimeUnit timeUnit : EnumSet.allOf(TimeUnit.class)) { + for (TimeUnit timeUnit : TimeUnit.values()) { ticker.setAutoIncrementStep(0, timeUnit); assertEquals( "Expected no auto-increment when setting autoIncrementStep to 0 " + timeUnit, @@ -93,24 +116,21 @@ public void testAutoIncrementStep_resetToZero() { public void testAutoIncrement_negative() { FakeTicker ticker = new FakeTicker(); - try { - ticker.setAutoIncrementStep(-1, TimeUnit.NANOSECONDS); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> ticker.setAutoIncrementStep(-1, NANOSECONDS)); } @GwtIncompatible // concurrency public void testConcurrentAdvance() throws Exception { - final FakeTicker ticker = new FakeTicker(); + FakeTicker ticker = new FakeTicker(); int numberOfThreads = 64; runConcurrentTest( numberOfThreads, - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { // adds two nanoseconds to the ticker ticker.advance(1L); Thread.sleep(10); @@ -126,16 +146,15 @@ public Void call() throws Exception { public void testConcurrentAutoIncrementStep() throws Exception { int incrementByNanos = 3; - final FakeTicker ticker = - new FakeTicker().setAutoIncrementStep(incrementByNanos, TimeUnit.NANOSECONDS); + FakeTicker ticker = new FakeTicker().setAutoIncrementStep(incrementByNanos, NANOSECONDS); int numberOfThreads = 64; runConcurrentTest( numberOfThreads, - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { - ticker.read(); + public @Nullable Void call() throws Exception { + long unused = ticker.read(); return null; } }); @@ -145,18 +164,18 @@ public Void call() throws Exception { /** Runs {@code callable} concurrently {@code numberOfThreads} times. */ @GwtIncompatible // concurrency - private void runConcurrentTest(int numberOfThreads, final Callable callable) + private void runConcurrentTest(int numberOfThreads, Callable<@Nullable Void> callable) throws Exception { - ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); - final CountDownLatch startLatch = new CountDownLatch(numberOfThreads); - final CountDownLatch doneLatch = new CountDownLatch(numberOfThreads); + ExecutorService executorService = newFixedThreadPool(numberOfThreads); + CountDownLatch startLatch = new CountDownLatch(numberOfThreads); + CountDownLatch doneLatch = new CountDownLatch(numberOfThreads); for (int i = numberOfThreads; i > 0; i--) { @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError = executorService.submit( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { startLatch.countDown(); startLatch.await(); callable.call(); diff --git a/android/guava-testlib/test/com/google/common/testing/FreshValueGeneratorTest.java b/android/guava-testlib/test/com/google/common/testing/FreshValueGeneratorTest.java index b20517cadb15..36fa857d2af7 100644 --- a/android/guava-testlib/test/com/google/common/testing/FreshValueGeneratorTest.java +++ b/android/guava-testlib/test/com/google/common/testing/FreshValueGeneratorTest.java @@ -16,6 +16,8 @@ package com.google.common.testing; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.base.CharMatcher; import com.google.common.base.Equivalence; import com.google.common.base.Function; @@ -106,12 +108,14 @@ import java.util.regex.MatchResult; import java.util.regex.Pattern; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link FreshValueGenerator}. * * @author Ben Yu */ +@NullUnmarked public class FreshValueGeneratorTest extends TestCase { @AndroidIncompatible // problem with equality of Type objects? @@ -473,9 +477,9 @@ public void testAddSampleInstances_noInstance() { public void testFreshCurrency() { FreshValueGenerator generator = new FreshValueGenerator(); // repeat a few times to make sure we don't stumble upon a bad Locale - assertNotNull(generator.generateFresh(Currency.class)); - assertNotNull(generator.generateFresh(Currency.class)); - assertNotNull(generator.generateFresh(Currency.class)); + assertThat(generator.generateFresh(Currency.class)).isNotNull(); + assertThat(generator.generateFresh(Currency.class)).isNotNull(); + assertThat(generator.generateFresh(Currency.class)).isNotNull(); } public void testNulls() throws Exception { @@ -527,11 +531,11 @@ private enum TwoConstantEnum { private static void assertCanGenerateOnly(TypeToken type, Object expected) { FreshValueGenerator generator = new FreshValueGenerator(); assertValueAndTypeEquals(expected, generator.generateFresh(type)); - assertNull(generator.generateFresh(type)); + assertThat(generator.generateFresh(type)).isNull(); } private static void assertNotInstantiable(TypeToken type) { - assertNull(new FreshValueGenerator().generateFresh(type)); + assertThat(new FreshValueGenerator().generateFresh(type)).isNull(); } private static void assertValueAndTypeEquals(Object expected, Object actual) { diff --git a/android/guava-testlib/test/com/google/common/testing/GcFinalizationTest.java b/android/guava-testlib/test/com/google/common/testing/GcFinalizationTest.java index 164ac5a0c0a5..379c8bfda11e 100644 --- a/android/guava-testlib/test/com/google/common/testing/GcFinalizationTest.java +++ b/android/guava-testlib/test/com/google/common/testing/GcFinalizationTest.java @@ -17,6 +17,7 @@ package com.google.common.testing; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.testing.GcFinalization.FinalizationPredicate; import com.google.common.util.concurrent.SettableFuture; @@ -25,6 +26,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link GcFinalization}. @@ -32,68 +35,74 @@ * @author Martin Buchholz * @author mike nonemacher */ +@AndroidIncompatible // depends on details of gc +@NullUnmarked public class GcFinalizationTest extends TestCase { // ---------------------------------------------------------------- // Ordinary tests of successful method execution // ---------------------------------------------------------------- - public void testAwait_CountDownLatch() { - final CountDownLatch latch = new CountDownLatch(1); - Object x = + public void testAwait_countDownLatch() { + CountDownLatch latch = new CountDownLatch(1); + Object unused = new Object() { + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 @Override protected void finalize() { latch.countDown(); } }; - x = null; // Hint to the JIT that x is unreachable + unused = null; // Hint to the JIT that unused is unreachable GcFinalization.await(latch); assertEquals(0, latch.getCount()); } - public void testAwaitDone_Future() { - final SettableFuture future = SettableFuture.create(); - Object x = + public void testAwaitDone_future() { + SettableFuture<@Nullable Void> future = SettableFuture.create(); + Object unused = new Object() { + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 @Override protected void finalize() { future.set(null); } }; - x = null; // Hint to the JIT that x is unreachable + unused = null; // Hint to the JIT that unused is unreachable GcFinalization.awaitDone(future); assertTrue(future.isDone()); assertFalse(future.isCancelled()); } - public void testAwaitDone_Future_Cancel() { - final SettableFuture future = SettableFuture.create(); - Object x = + public void testAwaitDone_future_cancel() { + SettableFuture<@Nullable Void> future = SettableFuture.create(); + Object unused = new Object() { + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 @Override protected void finalize() { future.cancel(false); } }; - x = null; // Hint to the JIT that x is unreachable + unused = null; // Hint to the JIT that unused is unreachable GcFinalization.awaitDone(future); assertTrue(future.isDone()); assertTrue(future.isCancelled()); } public void testAwaitClear() { - final WeakReference ref = new WeakReference<>(new Object()); + WeakReference ref = new WeakReference<>(new Object()); GcFinalization.awaitClear(ref); - assertNull(ref.get()); + assertThat(ref.get()).isNull(); } - public void testAwaitDone_FinalizationPredicate() { - final WeakHashMap map = new WeakHashMap<>(); + public void testAwaitDone_finalizationPredicate() { + WeakHashMap map = new WeakHashMap<>(); map.put(new Object(), Boolean.TRUE); GcFinalization.awaitDone( new FinalizationPredicate() { + @Override public boolean isDone() { return map.isEmpty(); } @@ -109,13 +118,15 @@ public boolean isDone() { class Interruptenator extends Thread { final AtomicBoolean shutdown; - Interruptenator(final Thread interruptee) { + Interruptenator(Thread interruptee) { this(interruptee, new AtomicBoolean(false)); } - Interruptenator(final Thread interruptee, final AtomicBoolean shutdown) { + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. + Interruptenator(Thread interruptee, AtomicBoolean shutdown) { super( new Runnable() { + @Override public void run() { while (!shutdown.get()) { interruptee.interrupt(); @@ -127,6 +138,7 @@ public void run() { start(); } + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. void shutdown() { shutdown.set(true); while (this.isAlive()) { @@ -140,68 +152,60 @@ void assertWrapsInterruptedException(RuntimeException e) { assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class); } - public void testAwait_CountDownLatch_Interrupted() { + public void testAwait_countDownLatch_interrupted() { Interruptenator interruptenator = new Interruptenator(Thread.currentThread()); try { - final CountDownLatch latch = new CountDownLatch(1); - try { - GcFinalization.await(latch); - fail("should throw"); - } catch (RuntimeException expected) { - assertWrapsInterruptedException(expected); - } + CountDownLatch latch = new CountDownLatch(1); + RuntimeException expected = + assertThrows(RuntimeException.class, () -> GcFinalization.await(latch)); + assertWrapsInterruptedException(expected); } finally { interruptenator.shutdown(); Thread.interrupted(); } } - public void testAwaitDone_Future_Interrupted_Interrupted() { + public void testAwaitDone_future_interrupted_interrupted() { Interruptenator interruptenator = new Interruptenator(Thread.currentThread()); try { - final SettableFuture future = SettableFuture.create(); - try { - GcFinalization.awaitDone(future); - fail("should throw"); - } catch (RuntimeException expected) { - assertWrapsInterruptedException(expected); - } + SettableFuture<@Nullable Void> future = SettableFuture.create(); + RuntimeException expected = + assertThrows(RuntimeException.class, () -> GcFinalization.awaitDone(future)); + assertWrapsInterruptedException(expected); } finally { interruptenator.shutdown(); Thread.interrupted(); } } - public void testAwaitClear_Interrupted() { + public void testAwaitClear_interrupted() { Interruptenator interruptenator = new Interruptenator(Thread.currentThread()); try { - final WeakReference ref = new WeakReference(Boolean.TRUE); - try { - GcFinalization.awaitClear(ref); - fail("should throw"); - } catch (RuntimeException expected) { - assertWrapsInterruptedException(expected); - } + WeakReference ref = new WeakReference(Boolean.TRUE); + RuntimeException expected = + assertThrows(RuntimeException.class, () -> GcFinalization.awaitClear(ref)); + assertWrapsInterruptedException(expected); } finally { interruptenator.shutdown(); Thread.interrupted(); } } - public void testAwaitDone_FinalizationPredicate_Interrupted() { + public void testAwaitDone_finalizationPredicate_interrupted() { Interruptenator interruptenator = new Interruptenator(Thread.currentThread()); try { - try { - GcFinalization.awaitDone( - new FinalizationPredicate() { - public boolean isDone() { - return false; - } - }); - fail("should throw"); - } catch (RuntimeException expected) { - assertWrapsInterruptedException(expected); - } + RuntimeException expected = + assertThrows( + RuntimeException.class, + () -> + GcFinalization.awaitDone( + new FinalizationPredicate() { + @Override + public boolean isDone() { + return false; + } + })); + assertWrapsInterruptedException(expected); } finally { interruptenator.shutdown(); Thread.interrupted(); @@ -214,10 +218,11 @@ public boolean isDone() { * this test. (And if it isn't, we'd like to know about it first!) */ public void testAwaitFullGc() { - final CountDownLatch finalizerRan = new CountDownLatch(1); - final WeakReference ref = + CountDownLatch finalizerRan = new CountDownLatch(1); + WeakReference ref = new WeakReference( new Object() { + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 @Override protected void finalize() { finalizerRan.countDown(); @@ -228,10 +233,10 @@ protected void finalize() { // Use e.g. awaitClear or await(CountDownLatch) instead. GcFinalization.awaitFullGc(); - // If this test turns out to be flaky, add a second call to awaitFullGc() - // GcFinalization.awaitFullGc(); + // Attempt to help with some flakiness that we've seen: b/387521512. + GcFinalization.awaitFullGc(); assertEquals(0, finalizerRan.getCount()); - assertNull(ref.get()); + assertThat(ref.get()).isNull(); } } diff --git a/android/guava-testlib/test/com/google/common/testing/NullPointerTesterTest.java b/android/guava-testlib/test/com/google/common/testing/NullPointerTesterTest.java deleted file mode 100644 index d1dc49bf165b..000000000000 --- a/android/guava-testlib/test/com/google/common/testing/NullPointerTesterTest.java +++ /dev/null @@ -1,1434 +0,0 @@ -/* - * Copyright (C) 2005 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.testing; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.base.Converter; -import com.google.common.base.Function; -import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableMultiset; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Multiset; -import com.google.common.collect.Table; -import com.google.common.reflect.TypeToken; -import com.google.common.testing.NullPointerTester.Visibility; -import com.google.common.testing.anotherpackage.SomeClassThatDoesNotUseNullable; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import junit.framework.AssertionFailedError; -import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; - -/** - * Unit test for {@link NullPointerTester}. - * - * @author Kevin Bourrillion - * @author Mick Killianey - */ -@SuppressWarnings("CheckReturnValue") -public class NullPointerTesterTest extends TestCase { - - /** Non-NPE RuntimeException. */ - public static class FooException extends RuntimeException { - private static final long serialVersionUID = 1L; - } - - /** - * Class for testing all permutations of static/non-static one-argument methods using - * methodParameter(). - */ - @SuppressWarnings("unused") // used by reflection - public static class OneArg { - - public static void staticOneArgCorrectlyThrowsNpe(String s) { - checkNotNull(s); // expect NPE here on null - } - - public static void staticOneArgThrowsOtherThanNpe(String s) { - throw new FooException(); // should catch as failure - } - - public static void staticOneArgShouldThrowNpeButDoesnt(String s) { - // should catch as failure - } - - public static void staticOneArgCheckForNullCorrectlyDoesNotThrowNPE( - @javax.annotation.CheckForNull String s) { - // null? no problem - } - - public static void staticOneArgJsr305NullableCorrectlyDoesNotThrowNPE(@NullableDecl String s) { - // null? no problem - } - - public static void staticOneArgNullableCorrectlyDoesNotThrowNPE(@NullableDecl String s) { - // null? no problem - } - - public static void staticOneArgCheckForNullCorrectlyThrowsOtherThanNPE( - @javax.annotation.CheckForNull String s) { - throw new FooException(); // ok, as long as it's not NullPointerException - } - - public static void staticOneArgNullableCorrectlyThrowsOtherThanNPE(@NullableDecl String s) { - throw new FooException(); // ok, as long as it's not NullPointerException - } - - public static void staticOneArgCheckForNullThrowsNPE(@javax.annotation.CheckForNull String s) { - checkNotNull(s); // doesn't check if you said you'd accept null, but you don't - } - - public static void staticOneArgNullableThrowsNPE(@NullableDecl String s) { - checkNotNull(s); // doesn't check if you said you'd accept null, but you don't - } - - public void oneArgCorrectlyThrowsNpe(String s) { - checkNotNull(s); // expect NPE here on null - } - - public void oneArgThrowsOtherThanNpe(String s) { - throw new FooException(); // should catch as failure - } - - public void oneArgShouldThrowNpeButDoesnt(String s) { - // should catch as failure - } - - public void oneArgCheckForNullCorrectlyDoesNotThrowNPE( - @javax.annotation.CheckForNull String s) { - // null? no problem - } - - public void oneArgNullableCorrectlyDoesNotThrowNPE(@NullableDecl String s) { - // null? no problem - } - - public void oneArgCheckForNullCorrectlyThrowsOtherThanNPE( - @javax.annotation.CheckForNull String s) { - throw new FooException(); // ok, as long as it's not NullPointerException - } - - public void oneArgNullableCorrectlyThrowsOtherThanNPE(@NullableDecl String s) { - throw new FooException(); // ok, as long as it's not NullPointerException - } - - public void oneArgCheckForNullThrowsNPE(@javax.annotation.CheckForNull String s) { - checkNotNull(s); // doesn't check if you said you'd accept null, but you don't - } - - public void oneArgNullableThrowsNPE(@NullableDecl String s) { - checkNotNull(s); // doesn't check if you said you'd accept null, but you don't - } - } - - private static final String[] STATIC_ONE_ARG_METHODS_SHOULD_PASS = { - "staticOneArgCorrectlyThrowsNpe", - "staticOneArgCheckForNullCorrectlyDoesNotThrowNPE", - "staticOneArgCheckForNullCorrectlyThrowsOtherThanNPE", - "staticOneArgCheckForNullThrowsNPE", - "staticOneArgNullableCorrectlyDoesNotThrowNPE", - "staticOneArgNullableCorrectlyThrowsOtherThanNPE", - "staticOneArgNullableThrowsNPE", - }; - private static final String[] STATIC_ONE_ARG_METHODS_SHOULD_FAIL = { - "staticOneArgThrowsOtherThanNpe", "staticOneArgShouldThrowNpeButDoesnt", - }; - private static final String[] NONSTATIC_ONE_ARG_METHODS_SHOULD_PASS = { - "oneArgCorrectlyThrowsNpe", - "oneArgCheckForNullCorrectlyDoesNotThrowNPE", - "oneArgCheckForNullCorrectlyThrowsOtherThanNPE", - "oneArgCheckForNullThrowsNPE", - "oneArgNullableCorrectlyDoesNotThrowNPE", - "oneArgNullableCorrectlyThrowsOtherThanNPE", - "oneArgNullableThrowsNPE", - }; - private static final String[] NONSTATIC_ONE_ARG_METHODS_SHOULD_FAIL = { - "oneArgThrowsOtherThanNpe", "oneArgShouldThrowNpeButDoesnt", - }; - - private static class ThrowsIae { - public static void christenPoodle(String name) { - checkArgument(name != null); - } - } - - private static class ThrowsNpe { - public static void christenPoodle(String name) { - checkNotNull(name); - } - } - - private static class ThrowsUoe { - public static void christenPoodle(String name) { - throw new UnsupportedOperationException(); - } - } - - private static class ThrowsSomethingElse { - public static void christenPoodle(String name) { - throw new RuntimeException(); - } - } - - public void testDontAcceptIae() { - NullPointerTester tester = new NullPointerTester(); - tester.testAllPublicStaticMethods(ThrowsNpe.class); - tester.testAllPublicStaticMethods(ThrowsUoe.class); - try { - tester.testAllPublicStaticMethods(ThrowsIae.class); - } catch (AssertionFailedError expected) { - return; - } - fail(); - } - - public void testStaticOneArgMethodsThatShouldPass() throws Exception { - for (String methodName : STATIC_ONE_ARG_METHODS_SHOULD_PASS) { - Method method = OneArg.class.getMethod(methodName, String.class); - try { - new NullPointerTester().testMethodParameter(new OneArg(), method, 0); - } catch (AssertionFailedError unexpected) { - fail("Should not have flagged method " + methodName); - } - } - } - - public void testStaticOneArgMethodsThatShouldFail() throws Exception { - for (String methodName : STATIC_ONE_ARG_METHODS_SHOULD_FAIL) { - Method method = OneArg.class.getMethod(methodName, String.class); - boolean foundProblem = false; - try { - new NullPointerTester().testMethodParameter(new OneArg(), method, 0); - } catch (AssertionFailedError expected) { - foundProblem = true; - } - assertTrue("Should report error in method " + methodName, foundProblem); - } - } - - public void testNonStaticOneArgMethodsThatShouldPass() throws Exception { - OneArg foo = new OneArg(); - for (String methodName : NONSTATIC_ONE_ARG_METHODS_SHOULD_PASS) { - Method method = OneArg.class.getMethod(methodName, String.class); - try { - new NullPointerTester().testMethodParameter(foo, method, 0); - } catch (AssertionFailedError unexpected) { - fail("Should not have flagged method " + methodName); - } - } - } - - public void testNonStaticOneArgMethodsThatShouldFail() throws Exception { - OneArg foo = new OneArg(); - for (String methodName : NONSTATIC_ONE_ARG_METHODS_SHOULD_FAIL) { - Method method = OneArg.class.getMethod(methodName, String.class); - boolean foundProblem = false; - try { - new NullPointerTester().testMethodParameter(foo, method, 0); - } catch (AssertionFailedError expected) { - foundProblem = true; - } - assertTrue("Should report error in method " + methodName, foundProblem); - } - } - - public void testMessageOtherException() throws Exception { - Method method = OneArg.class.getMethod("staticOneArgThrowsOtherThanNpe", String.class); - boolean foundProblem = false; - try { - new NullPointerTester().testMethodParameter(new OneArg(), method, 0); - } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()).contains("index 0"); - assertThat(expected.getMessage()).contains("[null]"); - foundProblem = true; - } - assertTrue("Should report error when different exception is thrown", foundProblem); - } - - public void testMessageNoException() throws Exception { - Method method = OneArg.class.getMethod("staticOneArgShouldThrowNpeButDoesnt", String.class); - boolean foundProblem = false; - try { - new NullPointerTester().testMethodParameter(new OneArg(), method, 0); - } catch (AssertionFailedError expected) { - assertThat(expected.getMessage()).contains("index 0"); - assertThat(expected.getMessage()).contains("[null]"); - foundProblem = true; - } - assertTrue("Should report error when no exception is thrown", foundProblem); - } - - /** - * Class for testing all permutations of nullable/non-nullable two-argument methods using - * testMethod(). - * - *
      - *
    • normalNormal: two params, neither is Nullable - *
    • nullableNormal: only first param is Nullable - *
    • normalNullable: only second param is Nullable - *
    • nullableNullable: both params are Nullable - *
    - */ - public static class TwoArg { - /** Action to take on a null param. */ - public enum Action { - THROW_A_NPE { - @Override - public void act() { - throw new NullPointerException(); - } - }, - THROW_OTHER { - @Override - public void act() { - throw new FooException(); - } - }, - JUST_RETURN { - @Override - public void act() {} - }; - - public abstract void act(); - } - - Action actionWhenFirstParamIsNull; - Action actionWhenSecondParamIsNull; - - public TwoArg(Action actionWhenFirstParamIsNull, Action actionWhenSecondParamIsNull) { - this.actionWhenFirstParamIsNull = actionWhenFirstParamIsNull; - this.actionWhenSecondParamIsNull = actionWhenSecondParamIsNull; - } - - /** Method that decides how to react to parameters. */ - public void reactToNullParameters(Object first, Object second) { - if (first == null) { - actionWhenFirstParamIsNull.act(); - } - if (second == null) { - actionWhenSecondParamIsNull.act(); - } - } - - /** Two-arg method with no Nullable params. */ - @SuppressWarnings("GoodTime") // false positive; b/122617528 - public void normalNormal(String first, Integer second) { - reactToNullParameters(first, second); - } - - /** Two-arg method with the second param Nullable. */ - @SuppressWarnings("GoodTime") // false positive; b/122617528 - public void normalNullable(String first, @NullableDecl Integer second) { - reactToNullParameters(first, second); - } - - /** Two-arg method with the first param Nullable. */ - @SuppressWarnings("GoodTime") // false positive; b/122617528 - public void nullableNormal(@NullableDecl String first, Integer second) { - reactToNullParameters(first, second); - } - - /** Two-arg method with the both params Nullable. */ - @SuppressWarnings("GoodTime") // false positive; b/122617528 - public void nullableNullable(@NullableDecl String first, @NullableDecl Integer second) { - reactToNullParameters(first, second); - } - - /** To provide sanity during debugging. */ - @Override - public String toString() { - return rootLocaleFormat( - "Bar(%s, %s)", actionWhenFirstParamIsNull, actionWhenSecondParamIsNull); - } - } - - public void verifyBarPass(Method method, TwoArg bar) { - try { - new NullPointerTester().testMethod(bar, method); - } catch (AssertionFailedError incorrectError) { - String errorMessage = - rootLocaleFormat("Should not have flagged method %s for %s", method.getName(), bar); - assertNull(errorMessage, incorrectError); - } - } - - public void verifyBarFail(Method method, TwoArg bar) { - try { - new NullPointerTester().testMethod(bar, method); - } catch (AssertionFailedError expected) { - return; // good...we wanted a failure - } - String errorMessage = - rootLocaleFormat("Should have flagged method %s for %s", method.getName(), bar); - fail(errorMessage); - } - - public void testTwoArgNormalNormal() throws Exception { - Method method = TwoArg.class.getMethod("normalNormal", String.class, Integer.class); - for (TwoArg.Action first : TwoArg.Action.values()) { - for (TwoArg.Action second : TwoArg.Action.values()) { - TwoArg bar = new TwoArg(first, second); - if (first.equals(TwoArg.Action.THROW_A_NPE) && second.equals(TwoArg.Action.THROW_A_NPE)) { - verifyBarPass(method, bar); // require both params to throw NPE - } else { - verifyBarFail(method, bar); - } - } - } - } - - public void testTwoArgNormalNullable() throws Exception { - Method method = TwoArg.class.getMethod("normalNullable", String.class, Integer.class); - for (TwoArg.Action first : TwoArg.Action.values()) { - for (TwoArg.Action second : TwoArg.Action.values()) { - TwoArg bar = new TwoArg(first, second); - if (first.equals(TwoArg.Action.THROW_A_NPE)) { - verifyBarPass(method, bar); // only pass if 1st param throws NPE - } else { - verifyBarFail(method, bar); - } - } - } - } - - public void testTwoArgNullableNormal() throws Exception { - Method method = TwoArg.class.getMethod("nullableNormal", String.class, Integer.class); - for (TwoArg.Action first : TwoArg.Action.values()) { - for (TwoArg.Action second : TwoArg.Action.values()) { - TwoArg bar = new TwoArg(first, second); - if (second.equals(TwoArg.Action.THROW_A_NPE)) { - verifyBarPass(method, bar); // only pass if 2nd param throws NPE - } else { - verifyBarFail(method, bar); - } - } - } - } - - public void testTwoArgNullableNullable() throws Exception { - Method method = TwoArg.class.getMethod("nullableNullable", String.class, Integer.class); - for (TwoArg.Action first : TwoArg.Action.values()) { - for (TwoArg.Action second : TwoArg.Action.values()) { - TwoArg bar = new TwoArg(first, second); - verifyBarPass(method, bar); // All args nullable: anything goes! - } - } - } - - /* - * This next part consists of several sample classes that provide - * demonstrations of conditions that cause NullPointerTester - * to succeed/fail. - */ - - /** Lots of well-behaved methods. */ - @SuppressWarnings("unused") // used by reflection - private static class PassObject extends SomeClassThatDoesNotUseNullable { - public static void doThrow(Object arg) { - if (arg == null) { - throw new FooException(); - } - } - - public void noArg() {} - - public void oneArg(String s) { - checkNotNull(s); - } - - void packagePrivateOneArg(String s) { - checkNotNull(s); - } - - protected void protectedOneArg(String s) { - checkNotNull(s); - } - - public void oneNullableArg(@NullableDecl String s) {} - - public void oneNullableArgThrows(@NullableDecl String s) { - doThrow(s); - } - - public void twoArg(String s, Integer i) { - checkNotNull(s); - i.intValue(); - } - - public void twoMixedArgs(String s, @NullableDecl Integer i) { - checkNotNull(s); - } - - public void twoMixedArgs(@NullableDecl Integer i, String s) { - checkNotNull(s); - } - - public void twoMixedArgsThrows(String s, @NullableDecl Integer i) { - checkNotNull(s); - doThrow(i); - } - - public void twoMixedArgsThrows(@NullableDecl Integer i, String s) { - checkNotNull(s); - doThrow(i); - } - - public void twoNullableArgs(@NullableDecl String s, @NullableDecl Integer i) {} - - public void twoNullableArgsThrowsFirstArg(@NullableDecl String s, @NullableDecl Integer i) { - doThrow(s); - } - - public void twoNullableArgsThrowsSecondArg(@NullableDecl String s, @NullableDecl Integer i) { - doThrow(i); - } - - public static void staticOneArg(String s) { - checkNotNull(s); - } - - public static void staticOneNullableArg(@NullableDecl String s) {} - - public static void staticOneNullableArgThrows(@NullableDecl String s) { - doThrow(s); - } - } - - public void testGoodClass() { - shouldPass(new PassObject()); - } - - private static class FailOneArgDoesntThrowNPE extends PassObject { - @Override - public void oneArg(String s) { - // Fail: missing NPE for s - } - } - - public void testFailOneArgDoesntThrowNpe() { - shouldFail(new FailOneArgDoesntThrowNPE()); - } - - private static class FailOneArgThrowsWrongType extends PassObject { - @Override - public void oneArg(String s) { - doThrow(s); // Fail: throwing non-NPE exception for null s - } - } - - public void testFailOneArgThrowsWrongType() { - shouldFail(new FailOneArgThrowsWrongType()); - } - - private static class PassOneNullableArgThrowsNPE extends PassObject { - @Override - public void oneNullableArg(@NullableDecl String s) { - checkNotNull(s); // ok to throw NPE - } - } - - public void testPassOneNullableArgThrowsNPE() { - shouldPass(new PassOneNullableArgThrowsNPE()); - } - - private static class FailTwoArgsFirstArgDoesntThrowNPE extends PassObject { - @Override - public void twoArg(String s, Integer i) { - // Fail: missing NPE for s - i.intValue(); - } - } - - public void testFailTwoArgsFirstArgDoesntThrowNPE() { - shouldFail(new FailTwoArgsFirstArgDoesntThrowNPE()); - } - - private static class FailTwoArgsFirstArgThrowsWrongType extends PassObject { - @Override - public void twoArg(String s, Integer i) { - doThrow(s); // Fail: throwing non-NPE exception for null s - i.intValue(); - } - } - - public void testFailTwoArgsFirstArgThrowsWrongType() { - shouldFail(new FailTwoArgsFirstArgThrowsWrongType()); - } - - private static class FailTwoArgsSecondArgDoesntThrowNPE extends PassObject { - @Override - public void twoArg(String s, Integer i) { - checkNotNull(s); - // Fail: missing NPE for i - } - } - - public void testFailTwoArgsSecondArgDoesntThrowNPE() { - shouldFail(new FailTwoArgsSecondArgDoesntThrowNPE()); - } - - private static class FailTwoArgsSecondArgThrowsWrongType extends PassObject { - @Override - public void twoArg(String s, Integer i) { - checkNotNull(s); - doThrow(i); // Fail: throwing non-NPE exception for null i - } - } - - public void testFailTwoArgsSecondArgThrowsWrongType() { - shouldFail(new FailTwoArgsSecondArgThrowsWrongType()); - } - - private static class FailTwoMixedArgsFirstArgDoesntThrowNPE extends PassObject { - @Override - public void twoMixedArgs(String s, @NullableDecl Integer i) { - // Fail: missing NPE for s - } - } - - public void testFailTwoMixedArgsFirstArgDoesntThrowNPE() { - shouldFail(new FailTwoMixedArgsFirstArgDoesntThrowNPE()); - } - - private static class FailTwoMixedArgsFirstArgThrowsWrongType extends PassObject { - @Override - public void twoMixedArgs(String s, @NullableDecl Integer i) { - doThrow(s); // Fail: throwing non-NPE exception for null s - } - } - - public void testFailTwoMixedArgsFirstArgThrowsWrongType() { - shouldFail(new FailTwoMixedArgsFirstArgThrowsWrongType()); - } - - private static class PassTwoMixedArgsNullableArgThrowsNPE extends PassObject { - @Override - public void twoMixedArgs(String s, @NullableDecl Integer i) { - checkNotNull(s); - i.intValue(); // ok to throw NPE? - } - } - - public void testPassTwoMixedArgsNullableArgThrowsNPE() { - shouldPass(new PassTwoMixedArgsNullableArgThrowsNPE()); - } - - private static class PassTwoMixedArgSecondNullableArgThrowsOther extends PassObject { - @Override - public void twoMixedArgs(String s, @NullableDecl Integer i) { - checkNotNull(s); - doThrow(i); // ok to throw non-NPE exception for null i - } - } - - public void testPassTwoMixedArgSecondNullableArgThrowsOther() { - shouldPass(new PassTwoMixedArgSecondNullableArgThrowsOther()); - } - - private static class FailTwoMixedArgsSecondArgDoesntThrowNPE extends PassObject { - @Override - public void twoMixedArgs(@NullableDecl Integer i, String s) { - // Fail: missing NPE for null s - } - } - - public void testFailTwoMixedArgsSecondArgDoesntThrowNPE() { - shouldFail(new FailTwoMixedArgsSecondArgDoesntThrowNPE()); - } - - private static class FailTwoMixedArgsSecondArgThrowsWrongType extends PassObject { - @Override - public void twoMixedArgs(@NullableDecl Integer i, String s) { - doThrow(s); // Fail: throwing non-NPE exception for null s - } - } - - public void testFailTwoMixedArgsSecondArgThrowsWrongType() { - shouldFail(new FailTwoMixedArgsSecondArgThrowsWrongType()); - } - - private static class PassTwoNullableArgsFirstThrowsNPE extends PassObject { - @Override - public void twoNullableArgs(@NullableDecl String s, @NullableDecl Integer i) { - checkNotNull(s); // ok to throw NPE? - } - } - - public void testPassTwoNullableArgsFirstThrowsNPE() { - shouldPass(new PassTwoNullableArgsFirstThrowsNPE()); - } - - private static class PassTwoNullableArgsFirstThrowsOther extends PassObject { - @Override - public void twoNullableArgs(@NullableDecl String s, @NullableDecl Integer i) { - doThrow(s); // ok to throw non-NPE exception for null s - } - } - - public void testPassTwoNullableArgsFirstThrowsOther() { - shouldPass(new PassTwoNullableArgsFirstThrowsOther()); - } - - private static class PassTwoNullableArgsSecondThrowsNPE extends PassObject { - @Override - public void twoNullableArgs(@NullableDecl String s, @NullableDecl Integer i) { - i.intValue(); // ok to throw NPE? - } - } - - public void testPassTwoNullableArgsSecondThrowsNPE() { - shouldPass(new PassTwoNullableArgsSecondThrowsNPE()); - } - - private static class PassTwoNullableArgsSecondThrowsOther extends PassObject { - @Override - public void twoNullableArgs(@NullableDecl String s, @NullableDecl Integer i) { - doThrow(i); // ok to throw non-NPE exception for null i - } - } - - public void testPassTwoNullableArgsSecondThrowsOther() { - shouldPass(new PassTwoNullableArgsSecondThrowsOther()); - } - - private static class PassTwoNullableArgsNeitherThrowsAnything extends PassObject { - @Override - public void twoNullableArgs(@NullableDecl String s, @NullableDecl Integer i) { - // ok to do nothing - } - } - - public void testPassTwoNullableArgsNeitherThrowsAnything() { - shouldPass(new PassTwoNullableArgsNeitherThrowsAnything()); - } - - @SuppressWarnings("unused") // for NullPointerTester - private abstract static class BaseClassThatFailsToThrow { - public void oneArg(String s) {} - } - - private static class SubclassWithBadSuperclass extends BaseClassThatFailsToThrow {} - - public void testSubclassWithBadSuperclass() { - shouldFail(new SubclassWithBadSuperclass()); - } - - @SuppressWarnings("unused") // for NullPointerTester - private abstract static class BaseClassThatFailsToThrowForPackagePrivate { - void packagePrivateOneArg(String s) {} - } - - private static class SubclassWithBadSuperclassForPackagePrivate - extends BaseClassThatFailsToThrowForPackagePrivate {} - - public void testSubclassWithBadSuperclassForPackagePrivateMethod() { - shouldFail(new SubclassWithBadSuperclassForPackagePrivate(), Visibility.PACKAGE); - } - - @SuppressWarnings("unused") // for NullPointerTester - private abstract static class BaseClassThatFailsToThrowForProtected { - protected void protectedOneArg(String s) {} - } - - private static class SubclassWithBadSuperclassForProtected - extends BaseClassThatFailsToThrowForProtected {} - - public void testSubclassWithBadSuperclassForPackageProtectedMethod() { - shouldFail(new SubclassWithBadSuperclassForProtected(), Visibility.PROTECTED); - } - - private static class SubclassThatOverridesBadSuperclassMethod extends BaseClassThatFailsToThrow { - @Override - public void oneArg(@NullableDecl String s) {} - } - - public void testSubclassThatOverridesBadSuperclassMethod() { - shouldPass(new SubclassThatOverridesBadSuperclassMethod()); - } - - @SuppressWarnings("unused") // for NullPointerTester - private static class SubclassOverridesTheWrongMethod extends BaseClassThatFailsToThrow { - public void oneArg(@NullableDecl CharSequence s) {} - } - - public void testSubclassOverridesTheWrongMethod() { - shouldFail(new SubclassOverridesTheWrongMethod()); - } - - @SuppressWarnings("unused") // for NullPointerTester - private static class ClassThatFailsToThrowForStatic { - static void staticOneArg(String s) {} - } - - public void testClassThatFailsToThrowForStatic() { - shouldFail(ClassThatFailsToThrowForStatic.class); - } - - private static class SubclassThatFailsToThrowForStatic extends ClassThatFailsToThrowForStatic {} - - public void testSubclassThatFailsToThrowForStatic() { - shouldFail(SubclassThatFailsToThrowForStatic.class); - } - - private static class SubclassThatTriesToOverrideBadStaticMethod - extends ClassThatFailsToThrowForStatic { - static void staticOneArg(@NullableDecl String s) {} - } - - public void testSubclassThatTriesToOverrideBadStaticMethod() { - shouldFail(SubclassThatTriesToOverrideBadStaticMethod.class); - } - - private static final class HardToCreate { - private HardToCreate(HardToCreate x) {} - } - - @SuppressWarnings("unused") // used by reflection - private static class CanCreateDefault { - public void foo(@NullableDecl HardToCreate ignored, String required) { - checkNotNull(required); - } - } - - public void testCanCreateDefault() { - shouldPass(new CanCreateDefault()); - } - - @SuppressWarnings("unused") // used by reflection - private static class CannotCreateDefault { - public void foo(HardToCreate ignored, String required) { - checkNotNull(ignored); - checkNotNull(required); - } - } - - public void testCannotCreateDefault() { - shouldFail(new CannotCreateDefault()); - } - - private static void shouldPass(Object instance, Visibility visibility) { - new NullPointerTester().testInstanceMethods(instance, visibility); - } - - private static void shouldPass(Object instance) { - shouldPass(instance, Visibility.PACKAGE); - shouldPass(instance, Visibility.PROTECTED); - shouldPass(instance, Visibility.PUBLIC); - } - - // TODO(cpovirk): eliminate surprising Object/Class overloading of shouldFail - - private static void shouldFail(Object instance, Visibility visibility) { - try { - new NullPointerTester().testInstanceMethods(instance, visibility); - } catch (AssertionFailedError expected) { - return; - } - fail("Should detect problem in " + instance.getClass().getSimpleName()); - } - - private static void shouldFail(Object instance) { - shouldFail(instance, Visibility.PACKAGE); - shouldFail(instance, Visibility.PROTECTED); - shouldFail(instance, Visibility.PUBLIC); - } - - private static void shouldFail(Class cls, Visibility visibility) { - try { - new NullPointerTester().testStaticMethods(cls, visibility); - } catch (AssertionFailedError expected) { - return; - } - fail("Should detect problem in " + cls.getSimpleName()); - } - - private static void shouldFail(Class cls) { - shouldFail(cls, Visibility.PACKAGE); - } - - @SuppressWarnings("unused") // used by reflection - private static class PrivateClassWithPrivateConstructor { - private PrivateClassWithPrivateConstructor(@NullableDecl Integer argument) {} - } - - public void testPrivateClass() { - NullPointerTester tester = new NullPointerTester(); - for (Constructor constructor : - PrivateClassWithPrivateConstructor.class.getDeclaredConstructors()) { - tester.testConstructor(constructor); - } - } - - private interface Foo { - void doSomething(T bar, Integer baz); - } - - private static class StringFoo implements Foo { - - @Override - public void doSomething(String bar, Integer baz) { - checkNotNull(bar); - checkNotNull(baz); - } - } - - public void testBridgeMethodIgnored() { - new NullPointerTester().testAllPublicInstanceMethods(new StringFoo()); - } - - private abstract static class DefaultValueChecker { - - private final Map arguments = Maps.newHashMap(); - - final DefaultValueChecker runTester() { - new NullPointerTester().testInstanceMethods(this, Visibility.PACKAGE); - return this; - } - - final void assertNonNullValues(Object... expectedValues) { - assertEquals(expectedValues.length, arguments.size()); - for (int i = 0; i < expectedValues.length; i++) { - assertEquals("Default value for parameter #" + i, expectedValues[i], arguments.get(i)); - } - } - - final Object getDefaultParameterValue(int position) { - return arguments.get(position); - } - - final void calledWith(Object... args) { - for (int i = 0; i < args.length; i++) { - if (args[i] != null) { - arguments.put(i, args[i]); - } - } - for (Object arg : args) { - checkNotNull(arg); // to fulfill null check - } - } - } - - private enum Gender { - MALE, - FEMALE - } - - private static class AllDefaultValuesChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkDefaultValuesForTheseTypes( - Gender gender, - Integer integer, - int i, - String string, - CharSequence charSequence, - List list, - ImmutableList immutableList, - Map map, - ImmutableMap immutableMap, - Set set, - ImmutableSet immutableSet, - SortedSet sortedSet, - ImmutableSortedSet immutableSortedSet, - Multiset multiset, - ImmutableMultiset immutableMultiset, - Multimap multimap, - ImmutableMultimap immutableMultimap, - Table table, - ImmutableTable immutableTable) { - calledWith( - gender, - integer, - i, - string, - charSequence, - list, - immutableList, - map, - immutableMap, - set, - immutableSet, - sortedSet, - immutableSortedSet, - multiset, - immutableMultiset, - multimap, - immutableMultimap, - table, - immutableTable); - } - - final void check() { - runTester() - .assertNonNullValues( - Gender.MALE, - Integer.valueOf(0), - 0, - "", - "", - ImmutableList.of(), - ImmutableList.of(), - ImmutableMap.of(), - ImmutableMap.of(), - ImmutableSet.of(), - ImmutableSet.of(), - ImmutableSortedSet.of(), - ImmutableSortedSet.of(), - ImmutableMultiset.of(), - ImmutableMultiset.of(), - ImmutableMultimap.of(), - ImmutableMultimap.of(), - ImmutableTable.of(), - ImmutableTable.of()); - } - } - - public void testDefaultValues() { - new AllDefaultValuesChecker().check(); - } - - private static class ObjectArrayDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(Object[] array, String s) { - calledWith(array, s); - } - - void check() { - runTester(); - Object[] defaultArray = (Object[]) getDefaultParameterValue(0); - assertThat(defaultArray).isEmpty(); - } - } - - public void testObjectArrayDefaultValue() { - new ObjectArrayDefaultValueChecker().check(); - } - - private static class StringArrayDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(String[] array, String s) { - calledWith(array, s); - } - - void check() { - runTester(); - String[] defaultArray = (String[]) getDefaultParameterValue(0); - assertThat(defaultArray).isEmpty(); - } - } - - public void testStringArrayDefaultValue() { - new StringArrayDefaultValueChecker().check(); - } - - private static class IntArrayDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(int[] array, String s) { - calledWith(array, s); - } - - void check() { - runTester(); - int[] defaultArray = (int[]) getDefaultParameterValue(0); - assertEquals(0, defaultArray.length); - } - } - - public void testIntArrayDefaultValue() { - new IntArrayDefaultValueChecker().check(); - } - - private enum EmptyEnum {} - - private static class EmptyEnumDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(EmptyEnum object, String s) { - calledWith(object, s); - } - - void check() { - try { - runTester(); - } catch (AssertionFailedError expected) { - return; - } - fail("Should have failed because enum has no constant"); - } - } - - public void testEmptyEnumDefaultValue() { - new EmptyEnumDefaultValueChecker().check(); - } - - private static class GenericClassTypeDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(Class> cls, String s) { - calledWith(cls, s); - } - - void check() { - runTester(); - Class defaultClass = (Class) getDefaultParameterValue(0); - assertEquals(List.class, defaultClass); - } - } - - public void testGenericClassDefaultValue() { - new GenericClassTypeDefaultValueChecker().check(); - } - - private static class NonGenericClassTypeDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(@SuppressWarnings("rawtypes") Class cls, String s) { - calledWith(cls, s); - } - - void check() { - runTester(); - Class defaultClass = (Class) getDefaultParameterValue(0); - assertEquals(Object.class, defaultClass); - } - } - - public void testNonGenericClassDefaultValue() { - new NonGenericClassTypeDefaultValueChecker().check(); - } - - private static class GenericTypeTokenDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(TypeToken> type, String s) { - calledWith(type, s); - } - - void check() { - runTester(); - TypeToken defaultType = (TypeToken) getDefaultParameterValue(0); - assertTrue(new TypeToken>() {}.isSupertypeOf(defaultType)); - } - } - - public void testGenericTypeTokenDefaultValue() { - new GenericTypeTokenDefaultValueChecker().check(); - } - - private static class NonGenericTypeTokenDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(@SuppressWarnings("rawtypes") TypeToken type, String s) { - calledWith(type, s); - } - - void check() { - runTester(); - TypeToken defaultType = (TypeToken) getDefaultParameterValue(0); - assertEquals(new TypeToken() {}, defaultType); - } - } - - public void testNonGenericTypeTokenDefaultValue() { - new NonGenericTypeTokenDefaultValueChecker().check(); - } - - private interface FromTo extends Function {} - - private static class GenericInterfaceDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(FromTo f, String s) { - calledWith(f, s); - } - - void check() { - runTester(); - FromTo defaultFunction = (FromTo) getDefaultParameterValue(0); - assertEquals(0, defaultFunction.apply(null)); - } - } - - public void testGenericInterfaceDefaultValue() { - new GenericInterfaceDefaultValueChecker().check(); - } - - private interface NullRejectingFromTo extends Function { - @Override - public abstract T apply(F from); - } - - private static class NullRejectingInterfaceDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(NullRejectingFromTo f, String s) { - calledWith(f, s); - } - - void check() { - runTester(); - NullRejectingFromTo defaultFunction = - (NullRejectingFromTo) getDefaultParameterValue(0); - assertNotNull(defaultFunction); - try { - defaultFunction.apply(null); - fail("Proxy Should have rejected null"); - } catch (NullPointerException expected) { - } - } - } - - public void testNullRejectingInterfaceDefaultValue() { - new NullRejectingInterfaceDefaultValueChecker().check(); - } - - private static class MultipleInterfacesDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public & Supplier> void checkArray(T f, String s) { - calledWith(f, s); - } - - void check() { - runTester(); - FromTo defaultFunction = (FromTo) getDefaultParameterValue(0); - assertEquals(0, defaultFunction.apply(null)); - Supplier defaultSupplier = (Supplier) defaultFunction; - assertEquals(Long.valueOf(0), defaultSupplier.get()); - } - } - - public void testMultipleInterfacesDefaultValue() { - new MultipleInterfacesDefaultValueChecker().check(); - } - - private static class GenericInterface2DefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(FromTo> f, String s) { - calledWith(f, s); - } - - void check() { - runTester(); - FromTo defaultFunction = (FromTo) getDefaultParameterValue(0); - FromTo returnValue = (FromTo) defaultFunction.apply(null); - assertEquals("", returnValue.apply(null)); - } - } - - public void testGenericInterfaceReturnedByGenericMethod() { - new GenericInterface2DefaultValueChecker().check(); - } - - private abstract static class AbstractGenericDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkGeneric(T value, String s) { - calledWith(value, s); - } - } - - private static class GenericDefaultValueResolvedToStringChecker - extends AbstractGenericDefaultValueChecker { - void check() { - runTester(); - assertEquals("", getDefaultParameterValue(0)); - } - } - - public void testGenericTypeResolvedForDefaultValue() { - new GenericDefaultValueResolvedToStringChecker().check(); - } - - private abstract static class AbstractGenericDefaultValueForPackagePrivateMethodChecker - extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - void checkGeneric(T value, String s) { - calledWith(value, s); - } - } - - private static class DefaultValueForPackagePrivateMethodResolvedToStringChecker - extends AbstractGenericDefaultValueForPackagePrivateMethodChecker { - void check() { - runTester(); - assertEquals("", getDefaultParameterValue(0)); - } - } - - public void testDefaultValueResolvedForPackagePrivateMethod() { - new DefaultValueForPackagePrivateMethodResolvedToStringChecker().check(); - } - - private static class ConverterDefaultValueChecker extends DefaultValueChecker { - - @SuppressWarnings("unused") // called by NullPointerTester - public void checkArray(Converter c, String s) { - calledWith(c, s); - } - - void check() { - runTester(); - @SuppressWarnings("unchecked") // We are checking it anyway - Converter defaultConverter = - (Converter) getDefaultParameterValue(0); - assertEquals(Integer.valueOf(0), defaultConverter.convert("anything")); - assertEquals("", defaultConverter.reverse().convert(123)); - assertNull(defaultConverter.convert(null)); - assertNull(defaultConverter.reverse().convert(null)); - } - } - - public void testConverterDefaultValue() { - new ConverterDefaultValueChecker().check(); - } - - private static class VisibilityMethods { - - @SuppressWarnings("unused") // Called by reflection - private void privateMethod() {} - - @SuppressWarnings("unused") // Called by reflection - void packagePrivateMethod() {} - - @SuppressWarnings("unused") // Called by reflection - protected void protectedMethod() {} - - @SuppressWarnings("unused") // Called by reflection - public void publicMethod() {} - } - - public void testVisibility_public() throws Exception { - assertFalse( - Visibility.PUBLIC.isVisible(VisibilityMethods.class.getDeclaredMethod("privateMethod"))); - assertFalse( - Visibility.PUBLIC.isVisible( - VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod"))); - assertFalse( - Visibility.PUBLIC.isVisible(VisibilityMethods.class.getDeclaredMethod("protectedMethod"))); - assertTrue( - Visibility.PUBLIC.isVisible(VisibilityMethods.class.getDeclaredMethod("publicMethod"))); - } - - public void testVisibility_protected() throws Exception { - assertFalse( - Visibility.PROTECTED.isVisible(VisibilityMethods.class.getDeclaredMethod("privateMethod"))); - assertFalse( - Visibility.PROTECTED.isVisible( - VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod"))); - assertTrue( - Visibility.PROTECTED.isVisible( - VisibilityMethods.class.getDeclaredMethod("protectedMethod"))); - assertTrue( - Visibility.PROTECTED.isVisible(VisibilityMethods.class.getDeclaredMethod("publicMethod"))); - } - - public void testVisibility_package() throws Exception { - assertFalse( - Visibility.PACKAGE.isVisible(VisibilityMethods.class.getDeclaredMethod("privateMethod"))); - assertTrue( - Visibility.PACKAGE.isVisible( - VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod"))); - assertTrue( - Visibility.PACKAGE.isVisible(VisibilityMethods.class.getDeclaredMethod("protectedMethod"))); - assertTrue( - Visibility.PACKAGE.isVisible(VisibilityMethods.class.getDeclaredMethod("publicMethod"))); - } - - private class Inner { - public Inner(String s) { - checkNotNull(s); - } - } - - public void testNonStaticInnerClass() { - try { - new NullPointerTester().testAllPublicConstructors(Inner.class); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()).contains("inner class"); - } - } - - private static String rootLocaleFormat(String format, Object... args) { - return String.format(Locale.ROOT, format, args); - } - - static class OverridesEquals { - @SuppressWarnings("EqualsHashCode") - @Override - public boolean equals(Object o) { - return true; - } - } - - static class DoesNotOverrideEquals { - public boolean equals(Object a, Object b) { - return true; - } - } - - public void testEqualsMethod() { - shouldPass(new OverridesEquals()); - shouldFail(new DoesNotOverrideEquals()); - } - - private static final class FailOnOneOfTwoConstructors { - @SuppressWarnings("unused") // Called by reflection - public FailOnOneOfTwoConstructors(String s) {} - - @SuppressWarnings("unused") // Called by reflection - public FailOnOneOfTwoConstructors(Object o) { - checkNotNull(o); - } - } - - public void testConstructor_Ignored_ShouldPass() throws Exception { - new NullPointerTester() - .ignore(FailOnOneOfTwoConstructors.class.getDeclaredConstructor(String.class)) - .testAllPublicConstructors(FailOnOneOfTwoConstructors.class); - } - - public void testConstructor_ShouldFail() throws Exception { - try { - new NullPointerTester().testAllPublicConstructors(FailOnOneOfTwoConstructors.class); - } catch (AssertionFailedError expected) { - return; - } - fail("Should detect problem in " + FailOnOneOfTwoConstructors.class.getSimpleName()); - } -} diff --git a/android/guava-testlib/test/com/google/common/testing/PackageSanityTests.java b/android/guava-testlib/test/com/google/common/testing/PackageSanityTests.java index d5483d36f092..1770e3376685 100644 --- a/android/guava-testlib/test/com/google/common/testing/PackageSanityTests.java +++ b/android/guava-testlib/test/com/google/common/testing/PackageSanityTests.java @@ -16,7 +16,9 @@ package com.google.common.testing; +import org.jspecify.annotations.NullUnmarked; /** Test nulls for the entire package. */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests {} diff --git a/android/guava-testlib/test/com/google/common/testing/ReflectionFreeAssertThrows.java b/android/guava-testlib/test/com/google/common/testing/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..8e302ef9c4ed --- /dev/null +++ b/android/guava-testlib/test/com/google/common/testing/ReflectionFreeAssertThrows.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.testing; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-testlib/test/com/google/common/testing/RelationshipTesterTest.java b/android/guava-testlib/test/com/google/common/testing/RelationshipTesterTest.java index 943c2951e3a0..4233bc834d9d 100644 --- a/android/guava-testlib/test/com/google/common/testing/RelationshipTesterTest.java +++ b/android/guava-testlib/test/com/google/common/testing/RelationshipTesterTest.java @@ -16,19 +16,21 @@ package com.google.common.testing; +import com.google.common.testing.RelationshipTester.Item; import com.google.common.testing.RelationshipTester.ItemReporter; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link RelationshipTester}. * * @author Ben Yu */ +@NullUnmarked public class RelationshipTesterTest extends TestCase { - public void testNulls() { new ClassSanityTester() - .setDefault(ItemReporter.class, new ItemReporter()) + .setDefault(ItemReporter.class, /* itemReporter */ Item::toString) .testNulls(RelationshipTester.class); } } diff --git a/android/guava-testlib/test/com/google/common/testing/SerializableTesterTest.java b/android/guava-testlib/test/com/google/common/testing/SerializableTesterTest.java index 753c4ab63118..67f11de2e6ed 100644 --- a/android/guava-testlib/test/com/google/common/testing/SerializableTesterTest.java +++ b/android/guava-testlib/test/com/google/common/testing/SerializableTesterTest.java @@ -19,12 +19,15 @@ import java.io.Serializable; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link SerializableTester}. * * @author Nick Kralevich */ +@NullUnmarked public class SerializableTesterTest extends TestCase { public void testStringAssertions() { String original = "hello world"; @@ -82,7 +85,7 @@ private static class ClassWhichIsAlwaysEqualButHasDifferentHashcodes implements @SuppressWarnings("EqualsHashCode") @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { return (other instanceof ClassWhichIsAlwaysEqualButHasDifferentHashcodes); } } @@ -91,7 +94,7 @@ private static class ObjectWhichIsEqualButChangesClass implements Serializable { private static final long serialVersionUID = 1L; @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { return (other instanceof ObjectWhichIsEqualButChangesClass || other instanceof OtherForm); } @@ -106,7 +109,7 @@ private Object writeReplace() { private static class OtherForm implements Serializable { @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { return (other instanceof ObjectWhichIsEqualButChangesClass || other instanceof OtherForm); } diff --git a/android/guava-testlib/test/com/google/common/testing/TearDownStackTest.java b/android/guava-testlib/test/com/google/common/testing/TearDownStackTest.java index 5a4f9ede4861..7345fbd6fe23 100644 --- a/android/guava-testlib/test/com/google/common/testing/TearDownStackTest.java +++ b/android/guava-testlib/test/com/google/common/testing/TearDownStackTest.java @@ -16,21 +16,28 @@ package com.google.common.testing; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; -/** @author Luiz-Otavio "Z" Zorzella */ +/** + * @author Luiz-Otavio "Z" Zorzella + */ @GwtCompatible +@NullUnmarked public class TearDownStackTest extends TestCase { - private TearDownStack tearDownStack = new TearDownStack(); + private final TearDownStack tearDownStack = new TearDownStack(); public void testSingleTearDown() throws Exception { - final TearDownStack stack = buildTearDownStack(); + TearDownStack stack = buildTearDownStack(); - final SimpleTearDown tearDown = new SimpleTearDown(); + SimpleTearDown tearDown = new SimpleTearDown(); stack.addTearDown(tearDown); assertEquals(false, tearDown.ran); @@ -41,12 +48,12 @@ public void testSingleTearDown() throws Exception { } public void testMultipleTearDownsHappenInOrder() throws Exception { - final TearDownStack stack = buildTearDownStack(); + TearDownStack stack = buildTearDownStack(); - final SimpleTearDown tearDownOne = new SimpleTearDown(); + SimpleTearDown tearDownOne = new SimpleTearDown(); stack.addTearDown(tearDownOne); - final Callback callback = + Callback callback = new Callback() { @Override public void run() { @@ -55,7 +62,7 @@ public void run() { } }; - final SimpleTearDown tearDownTwo = new SimpleTearDown(callback); + SimpleTearDown tearDownTwo = new SimpleTearDown(callback); stack.addTearDown(tearDownTwo); assertEquals(false, tearDownOne.ran); @@ -68,12 +75,12 @@ public void run() { } public void testThrowingTearDown() throws Exception { - final TearDownStack stack = buildTearDownStack(); + TearDownStack stack = buildTearDownStack(); - final ThrowingTearDown tearDownOne = new ThrowingTearDown("one"); + ThrowingTearDown tearDownOne = new ThrowingTearDown("one"); stack.addTearDown(tearDownOne); - final ThrowingTearDown tearDownTwo = new ThrowingTearDown("two"); + ThrowingTearDown tearDownTwo = new ThrowingTearDown("two"); stack.addTearDown(tearDownTwo); assertEquals(false, tearDownOne.ran); @@ -82,11 +89,11 @@ public void testThrowingTearDown() throws Exception { try { stack.runTearDown(); fail("runTearDown should have thrown an exception"); - } catch (ClusterException expected) { - assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("two"); - } catch (RuntimeException e) { - throw new RuntimeException( - "A ClusterException should have been thrown, rather than a " + e.getClass().getName(), e); + } catch (RuntimeException expected) { + assertThat(expected).hasMessageThat().isEqualTo("two"); + assertThat(getOnlyElement(asList(expected.getSuppressed()))) + .hasMessageThat() + .isEqualTo("one"); } assertEquals(true, tearDownOne.ran); @@ -110,13 +117,13 @@ protected void tearDown() { /** Builds a {@link TearDownStack} that makes sure it's clear by the end of this test. */ private TearDownStack buildTearDownStack() { - final TearDownStack result = new TearDownStack(); + TearDownStack result = new TearDownStack(); tearDownStack.addTearDown( new TearDown() { @Override public void tearDown() throws Exception { - synchronized (result.stack) { + synchronized (result.lock) { assertEquals( "The test should have cleared the stack (say, by virtue of running runTearDown)", 0, @@ -146,11 +153,11 @@ public void tearDown() throws Exception { private static final class SimpleTearDown implements TearDown { boolean ran = false; - Callback callback = null; + @Nullable Callback callback = null; - public SimpleTearDown() {} + SimpleTearDown() {} - public SimpleTearDown(Callback callback) { + SimpleTearDown(Callback callback) { this.callback = callback; } diff --git a/android/guava-testlib/test/com/google/common/testing/TestLogHandlerTest.java b/android/guava-testlib/test/com/google/common/testing/TestLogHandlerTest.java index 212f6af9f0e9..bd329ddef50e 100644 --- a/android/guava-testlib/test/com/google/common/testing/TestLogHandlerTest.java +++ b/android/guava-testlib/test/com/google/common/testing/TestLogHandlerTest.java @@ -20,16 +20,18 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link TestLogHandler}. * * @author kevinb */ +@NullUnmarked public class TestLogHandlerTest extends TestCase { private TestLogHandler handler; - private TearDownStack stack = new TearDownStack(); + private final TearDownStack stack = new TearDownStack(); @Override protected void setUp() throws Exception { @@ -89,11 +91,13 @@ protected void tearDown() { static final Exception EXCEPTION = new Exception(); - static class ExampleClassUnderTest { + static final class ExampleClassUnderTest { static final Logger logger = Logger.getLogger(ExampleClassUnderTest.class.getName()); static void foo() { logger.log(Level.INFO, "message", EXCEPTION); } + + private ExampleClassUnderTest() {} } } diff --git a/android/guava-testlib/test/com/google/common/testing/anotherpackage/ForwardingWrapperTesterTest.java b/android/guava-testlib/test/com/google/common/testing/anotherpackage/ForwardingWrapperTesterTest.java index 6e3bf2397dee..a28a59c88a7a 100644 --- a/android/guava-testlib/test/com/google/common/testing/anotherpackage/ForwardingWrapperTesterTest.java +++ b/android/guava-testlib/test/com/google/common/testing/anotherpackage/ForwardingWrapperTesterTest.java @@ -17,6 +17,7 @@ package com.google.common.testing.anotherpackage; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.base.Equivalence; import com.google.common.base.Function; @@ -28,12 +29,14 @@ import com.google.common.primitives.UnsignedLong; import com.google.common.testing.ForwardingWrapperTester; import com.google.common.testing.NullPointerTester; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.InputStream; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ForwardingWrapperTester}. Live in a different package to detect reflection @@ -69,7 +72,7 @@ public void testVoidMethodForwarding() { Runnable.class, new Function() { @Override - public Runnable apply(final Runnable runnable) { + public Runnable apply(Runnable runnable) { return new ForwardingRunnable(runnable); } }); @@ -80,7 +83,7 @@ public void testToStringForwarding() { Runnable.class, new Function() { @Override - public Runnable apply(final Runnable runnable) { + public Runnable apply(Runnable runnable) { return new ForwardingRunnable(runnable) { @Override public String toString() { @@ -96,7 +99,7 @@ public void testFailsToForwardToString() { Runnable.class, new Function() { @Override - public Runnable apply(final Runnable runnable) { + public Runnable apply(Runnable runnable) { return new ForwardingRunnable(runnable) { @Override public String toString() { @@ -114,12 +117,12 @@ public void testFailsToForwardHashCode() { Runnable.class, new Function() { @Override - public Runnable apply(final Runnable runnable) { + public Runnable apply(Runnable runnable) { return new ForwardingRunnable(runnable) { @SuppressWarnings("EqualsHashCode") @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof ForwardingRunnable) { ForwardingRunnable that = (ForwardingRunnable) o; return runnable.equals(that.runnable); @@ -138,10 +141,10 @@ public void testEqualsAndHashCodeForwarded() { Runnable.class, new Function() { @Override - public Runnable apply(final Runnable runnable) { + public Runnable apply(Runnable runnable) { return new ForwardingRunnable(runnable) { @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof ForwardingRunnable) { ForwardingRunnable that = (ForwardingRunnable) o; return runnable.equals(that.runnable); @@ -164,7 +167,7 @@ public void testFailsToForwardEquals() { Runnable.class, new Function() { @Override - public Runnable apply(final Runnable runnable) { + public Runnable apply(Runnable runnable) { return new ForwardingRunnable(runnable) { @Override public int hashCode() { @@ -197,7 +200,7 @@ public void testRedundantForwarding() { Runnable.class, new Function() { @Override - public Runnable apply(final Runnable runnable) { + public Runnable apply(Runnable runnable) { return new Runnable() { @Override public void run() { @@ -255,7 +258,7 @@ public void testFailsToPropagateException() { new Function() { @Override public Adder apply(Adder adder) { - return new FailsToPropagageException(adder); + return new FailsToPropagateException(adder); } }, "add(", @@ -263,11 +266,11 @@ public Adder apply(Adder adder) { } public void testNotInterfaceType() { - try { - new ForwardingWrapperTester().testForwarding(String.class, Functions.identity()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + new ForwardingWrapperTester() + .testForwarding(String.class, Functions.identity())); } public void testNulls() { @@ -284,7 +287,7 @@ private void assertFailure( tester.testForwarding(interfaceType, wrapperFunction); } catch (AssertionFailedError expected) { for (String message : expectedMessages) { - assertThat(expected.getMessage()).contains(message); + assertThat(expected).hasMessageThat().contains(message); } return; } @@ -317,7 +320,7 @@ private interface Adder { private static class ForwardingArithmetic implements Arithmetic { private final Arithmetic arithmetic; - public ForwardingArithmetic(Arithmetic arithmetic) { + ForwardingArithmetic(Arithmetic arithmetic) { this.arithmetic = arithmetic; } @@ -373,18 +376,19 @@ public String toString() { } } - private static class FailsToPropagageException implements Adder { + private static class FailsToPropagateException implements Adder { private final Adder adder; - FailsToPropagageException(Adder adder) { + FailsToPropagateException(Adder adder) { this.adder = adder; } @Override + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception public int add(int a, int b) { try { return adder.add(a, b); - } catch (Exception e) { + } catch (Exception e) { // sneaky checked exception // swallow! return 0; } @@ -451,7 +455,7 @@ void foo( private static class ParameterTypesDifferentForwarder implements ParameterTypesDifferent { private final ParameterTypesDifferent delegate; - public ParameterTypesDifferentForwarder(ParameterTypesDifferent delegate) { + ParameterTypesDifferentForwarder(ParameterTypesDifferent delegate) { this.delegate = delegate; } @@ -530,7 +534,7 @@ public String toString() { private interface Equals { @Override - boolean equals(Object obj); + boolean equals(@Nullable Object obj); @Override int hashCode(); @@ -541,7 +545,7 @@ private interface Equals { private static class NoDelegateToEquals implements Equals { - private static Function WRAPPER = + private static final Function WRAPPER = new Function() { @Override public NoDelegateToEquals apply(Equals delegate) { @@ -579,7 +583,9 @@ public void testExplicitEqualsAndHashCodeDelegatedWhenExplicitlyAsked() { /** An interface for the 2 ways that a chaining call might be defined. */ private interface ChainingCalls { // A method that is defined to 'return this' + @CanIgnoreReturnValue ChainingCalls chainingCall(); + // A method that just happens to return a ChainingCalls object ChainingCalls nonChainingCall(); } @@ -591,6 +597,7 @@ private static class ForwardingChainingCalls implements ChainingCalls { this.delegate = delegate; } + @CanIgnoreReturnValue @Override public ForwardingChainingCalls chainingCall() { delegate.chainingCall(); diff --git a/android/guava-testlib/test/com/google/common/util/concurrent/testing/TestingExecutorsTest.java b/android/guava-testlib/test/com/google/common/util/concurrent/testing/TestingExecutorsTest.java index 66ad78491c9c..1944dfda3e63 100644 --- a/android/guava-testlib/test/com/google/common/util/concurrent/testing/TestingExecutorsTest.java +++ b/android/guava-testlib/test/com/google/common/util/concurrent/testing/TestingExecutorsTest.java @@ -16,6 +16,9 @@ package com.google.common.util.concurrent.testing; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertThrows; + import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListeningScheduledExecutorService; import java.util.List; @@ -24,7 +27,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import junit.framework.TestCase; /** @@ -45,7 +47,7 @@ public void run() { } }; ScheduledFuture future = - TestingExecutors.noOpScheduledExecutor().schedule(task, 10, TimeUnit.MILLISECONDS); + TestingExecutors.noOpScheduledExecutor().schedule(task, 10, MILLISECONDS); Thread.sleep(20); assertFalse(taskDone); assertFalse(future.isDone()); @@ -71,17 +73,11 @@ public Boolean call() { return taskDone; } }; - List> futureList = - executor.invokeAll(ImmutableList.of(task), 10, TimeUnit.MILLISECONDS); + List> futureList = executor.invokeAll(ImmutableList.of(task), 10, MILLISECONDS); Future future = futureList.get(0); assertFalse(taskDone); assertTrue(future.isDone()); - try { - future.get(); - fail(); - } catch (CancellationException e) { - // pass - } + assertThrows(CancellationException.class, () -> future.get()); } public void testSameThreadScheduledExecutor() throws ExecutionException, InterruptedException { @@ -95,7 +91,7 @@ public Integer call() { } }; Future future = - TestingExecutors.sameThreadScheduledExecutor().schedule(task, 10000, TimeUnit.MILLISECONDS); + TestingExecutors.sameThreadScheduledExecutor().schedule(task, 10000, MILLISECONDS); assertTrue("Should run callable immediately", taskDone); assertEquals(6, (int) future.get()); } @@ -110,11 +106,6 @@ public void run() { }; Future future = TestingExecutors.sameThreadScheduledExecutor().submit(runnable); - try { - future.get(); - fail("Should have thrown exception"); - } catch (ExecutionException e) { - // pass - } + assertThrows(ExecutionException.class, () -> future.get()); } } diff --git a/android/guava-tests/benchmark/com/google/common/base/AsciiBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/AsciiBenchmark.java index 6ed8006ee903..2727d3723fe6 100644 --- a/android/guava-tests/benchmark/com/google/common/base/AsciiBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/AsciiBenchmark.java @@ -25,12 +25,14 @@ import java.util.List; import java.util.Locale; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the ASCII class. * * @author Kevin Bourrillion */ +@NullUnmarked public class AsciiBenchmark { private static final String ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static final String NONALPHA = "0123456789`~-_=+[]{}|;:',.<>/?!@#$%^&*()\"\\"; diff --git a/android/guava-tests/benchmark/com/google/common/base/CharMatcherBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/CharMatcherBenchmark.java index 9dee99b7ab6d..64085530dfa0 100644 --- a/android/guava-tests/benchmark/com/google/common/base/CharMatcherBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/CharMatcherBenchmark.java @@ -20,11 +20,12 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import com.google.common.base.BenchmarkHelpers.SampleMatcherConfig; -import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.List; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmark for the {@link CharMatcher} class. @@ -33,6 +34,7 @@ * @author Kevin Bourrillion * @author David Richter */ +@NullUnmarked public class CharMatcherBenchmark { // Caliper injects params automatically @@ -78,7 +80,6 @@ void setUp() { if (size == Size.SMALL) { BitSet tmp = new BitSet(); matcher.setBits(tmp); - int matchedCharCount = tmp.cardinality(); this.matcher = SmallCharMatcher.from(tmp, ""); } this.string = checkString(length, percent, config.matchingChars, new Random(), forceSlow, web); @@ -120,7 +121,7 @@ private static String checkString( } // Use a shuffled index array to ensure constant percentage of matching // characters - List list = Lists.newArrayList(); + List list = new ArrayList<>(); for (int i = 0; i < length; i++) { list.add(i); } @@ -130,9 +131,9 @@ private static String checkString( list.set(list.indexOf(0), list.get(0)); list.set(0, 0); } - // Get threshold in the range [0, length], rounding up to ensure that non - // zero percent values result in a non-zero threshold (so we always have at - // least one matching character). + // Get threshold in the range [0, length], rounding up to ensure that + // non-zero percent values result in a non-zero threshold (so we always + // have at least one matching character). int threshold = ((percent * length) + 99) / 100; StringBuilder builder = new StringBuilder(length); for (int n = 0; n < length; n++) { @@ -200,7 +201,7 @@ public int nextCodePoint() { } } - private int sum = 69552218; + private final int sum = 69552218; private static int[] prob; private static void populateProb1() { diff --git a/android/guava-tests/benchmark/com/google/common/base/EnumsBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/EnumsBenchmark.java index 73cda9af8629..79143409d672 100644 --- a/android/guava-tests/benchmark/com/google/common/base/EnumsBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/EnumsBenchmark.java @@ -22,8 +22,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.NullUnmarked; @SuppressWarnings("unused") // Nested enums used reflectively in setUp. +@NullUnmarked public class EnumsBenchmark { @Param({"Small", "Medium", "Large"}) @@ -32,17 +34,20 @@ public class EnumsBenchmark { @Param({"0.2", "0.8"}) float hitRate; + // We could avoid the raw type here by initializing this with a ternary (? SmallEnum.class : ...). + // However, we end up needing a raw type in getIfPresent, as discussed there. + @SuppressWarnings("rawtypes") private Class enumType; + private String[] sampleData; @BeforeExperiment - @SuppressWarnings("unchecked") void setUp() throws ClassNotFoundException { Preconditions.checkArgument(hitRate >= 0 && hitRate <= 1, "hitRate must be in the range [0,1]"); enumType = - (Class) - Class.forName(EnumsBenchmark.class.getCanonicalName() + "$" + enumSize + "Enum"); + Class.forName(EnumsBenchmark.class.getCanonicalName() + "$" + enumSize + "Enum") + .asSubclass(Enum.class); Enum[] allConstants = enumType.getEnumConstants(); List hits = new ArrayList<>(); @@ -64,6 +69,8 @@ void setUp() throws ClassNotFoundException { sampleData = sampleDataList.toArray(new String[sampleDataList.size()]); } + // Since we can't pass a concrete SomeEnum.class directly, we need to use a raw type. + @SuppressWarnings("unchecked") @Benchmark boolean getIfPresent(int repetitions) { boolean retVal = false; diff --git a/android/guava-tests/benchmark/com/google/common/base/JoinerBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/JoinerBenchmark.java index 99728327e9bb..1d53f79f00af 100644 --- a/android/guava-tests/benchmark/com/google/common/base/JoinerBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/JoinerBenchmark.java @@ -21,12 +21,14 @@ import com.google.caliper.Param; import java.util.Arrays; import java.util.Iterator; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks {@link Joiner} against some common implementations of delimiter-based string joining. * * @author Adomas Paltanavicius */ +@NullUnmarked public class JoinerBenchmark { private static final String DELIMITER_STRING = ","; @@ -44,6 +46,7 @@ public class JoinerBenchmark { private Iterable components; @BeforeExperiment + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 void setUp() { String component = Strings.repeat("a", componentLength); String[] raw = new String[count]; diff --git a/android/guava-tests/benchmark/com/google/common/base/LazyStackTraceBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/LazyStackTraceBenchmark.java index 5f79d0c42366..f4843bddcaef 100644 --- a/android/guava-tests/benchmark/com/google/common/base/LazyStackTraceBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/LazyStackTraceBenchmark.java @@ -24,11 +24,13 @@ import com.google.caliper.Param; import com.google.caliper.api.SkipThisScenarioException; import java.util.List; +import org.jspecify.annotations.NullUnmarked; /** * Quick and dirty benchmark of {@link Throwables#lazyStackTrace(Throwable)}. We benchmark a "caller * finder" implementation that might be used in a logging framework. */ +@NullUnmarked public class LazyStackTraceBenchmark { @Param({"20", "200", "2000"}) int stackDepth; diff --git a/android/guava-tests/benchmark/com/google/common/base/ObjectsBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/ObjectsBenchmark.java index 561f76dca98b..82385daa3725 100644 --- a/android/guava-tests/benchmark/com/google/common/base/ObjectsBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/ObjectsBenchmark.java @@ -17,12 +17,14 @@ package com.google.common.base; import com.google.caliper.Benchmark; +import org.jspecify.annotations.NullUnmarked; /** * Some microbenchmarks for the {@link com.google.common.base.Objects} class. * * @author Ben L. Titzer */ +@NullUnmarked public class ObjectsBenchmark { private static final Integer I0 = -45; diff --git a/android/guava-tests/benchmark/com/google/common/base/SplitterBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/SplitterBenchmark.java index 935951994eb4..f8a53e45e9b1 100644 --- a/android/guava-tests/benchmark/com/google/common/base/SplitterBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/SplitterBenchmark.java @@ -20,16 +20,19 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import com.google.common.collect.Iterables; +import org.jspecify.annotations.NullUnmarked; /** * Microbenchmark for {@link Splitter#on} with char vs String with length == 1. * * @author Paul Lindner */ +@NullUnmarked public class SplitterBenchmark { // overall size of string @Param({"1", "10", "100", "1000"}) int length; + // Number of matching strings @Param({"xxxx", "xxXx", "xXxX", "XXXX"}) String text; @@ -40,25 +43,30 @@ public class SplitterBenchmark { private static final Splitter STRING_SPLITTER = Splitter.on("X"); @BeforeExperiment + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 void setUp() { input = Strings.repeat(text, length); } @Benchmark - void charSplitter(int reps) { + int charSplitter(int reps) { int total = 0; for (int i = 0; i < reps; i++) { total += Iterables.size(CHAR_SPLITTER.split(input)); } + + return total; } @Benchmark - void stringSplitter(int reps) { + int stringSplitter(int reps) { int total = 0; for (int i = 0; i < reps; i++) { total += Iterables.size(STRING_SPLITTER.split(input)); } + + return total; } } diff --git a/android/guava-tests/benchmark/com/google/common/base/StopwatchBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/StopwatchBenchmark.java index 692ed365a230..34ea0992b4bf 100644 --- a/android/guava-tests/benchmark/com/google/common/base/StopwatchBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/StopwatchBenchmark.java @@ -16,8 +16,10 @@ package com.google.common.base; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import com.google.caliper.Benchmark; -import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.NullUnmarked; /** * Simple benchmark: create, start, read. This does not currently report the most useful result @@ -25,6 +27,7 @@ * * @author Kevin Bourrillion */ +@NullUnmarked public class StopwatchBenchmark { @Benchmark long stopwatch(int reps) { @@ -32,7 +35,7 @@ long stopwatch(int reps) { for (int i = 0; i < reps; i++) { Stopwatch s = Stopwatch.createStarted(); // here is where you would do something - total += s.elapsed(TimeUnit.NANOSECONDS); + total += s.elapsed(NANOSECONDS); } return total; } @@ -43,7 +46,7 @@ long manual(int reps) { for (int i = 0; i < reps; i++) { long start = System.nanoTime(); // here is where you would do something - total += (System.nanoTime() - start); + total += System.nanoTime() - start; } return total; } diff --git a/android/guava-tests/benchmark/com/google/common/base/StringsRepeatBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/StringsRepeatBenchmark.java index 30261da5d8ff..df1bb294566a 100644 --- a/android/guava-tests/benchmark/com/google/common/base/StringsRepeatBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/StringsRepeatBenchmark.java @@ -19,12 +19,14 @@ import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; +import org.jspecify.annotations.NullUnmarked; /** * Microbenchmark for {@link com.google.common.base.Strings#repeat} * * @author Mike Cripps */ +@NullUnmarked public class StringsRepeatBenchmark { @Param({"1", "5", "25", "125"}) int count; @@ -35,12 +37,13 @@ public class StringsRepeatBenchmark { private String originalString; @BeforeExperiment + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 void setUp() { originalString = Strings.repeat("x", length); } @Benchmark - void oldRepeat(int reps) { + void oldRepeat(long reps) { for (int i = 0; i < reps; i++) { String x = oldRepeat(originalString, count); if (x.length() != (originalString.length() * count)) { @@ -52,8 +55,8 @@ void oldRepeat(int reps) { private static String oldRepeat(String string, int count) { // If this multiplication overflows, a NegativeArraySizeException or // OutOfMemoryError is not far behind - final int len = string.length(); - final int size = len * count; + int len = string.length(); + int size = len * count; char[] array = new char[size]; for (int i = 0; i < size; i += len) { string.getChars(0, len, array, i); @@ -62,7 +65,7 @@ private static String oldRepeat(String string, int count) { } @Benchmark - void mikeRepeat(int reps) { + void mikeRepeat(long reps) { for (int i = 0; i < reps; i++) { String x = mikeRepeat(originalString, count); if (x.length() != (originalString.length() * count)) { @@ -72,7 +75,7 @@ void mikeRepeat(int reps) { } private static String mikeRepeat(String string, int count) { - final int len = string.length(); + int len = string.length(); char[] strCopy = new char[len * Integer.highestOneBit(count)]; string.getChars(0, len, strCopy, 0); @@ -95,7 +98,7 @@ private static String mikeRepeat(String string, int count) { } @Benchmark - void martinRepeat(int reps) { + void martinRepeat(long reps) { for (int i = 0; i < reps; i++) { String x = martinRepeat(originalString, count); if (x.length() != (originalString.length() * count)) { @@ -105,9 +108,9 @@ void martinRepeat(int reps) { } private static String martinRepeat(String string, int count) { - final int len = string.length(); - final int size = len * count; - final char[] array = new char[size]; + int len = string.length(); + int size = len * count; + char[] array = new char[size]; string.getChars(0, len, array, 0); int n; for (n = len; n < size - n; n <<= 1) { diff --git a/android/guava-tests/benchmark/com/google/common/base/ToStringHelperBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/ToStringHelperBenchmark.java index 2d7a447272b1..c0241db44f9b 100644 --- a/android/guava-tests/benchmark/com/google/common/base/ToStringHelperBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/ToStringHelperBenchmark.java @@ -20,12 +20,14 @@ import com.google.caliper.Param; import java.util.Arrays; import java.util.Collections; +import org.jspecify.annotations.NullUnmarked; /** * Some microbenchmarks for the {@link MoreObjects.ToStringHelper} class. * * @author Osvaldo Doederlein */ +@NullUnmarked public class ToStringHelperBenchmark { @Param({"0", "1", "5"}) @@ -36,6 +38,7 @@ public class ToStringHelperBenchmark { enum Dataset { SMALL { + @Override void addEntries(MoreObjects.ToStringHelper helper) { helper .add(SHORT_NAME, 10) @@ -47,6 +50,7 @@ void addEntries(MoreObjects.ToStringHelper helper) { } }, CONDITIONAL { + @Override void addEntries(MoreObjects.ToStringHelper helper) { helper .add(SHORT_NAME, "x") @@ -82,6 +86,7 @@ void addEntries(MoreObjects.ToStringHelper helper) { } }, UNCONDITIONAL { + @Override void addEntries(MoreObjects.ToStringHelper helper) { helper .add(SHORT_NAME, false) @@ -136,4 +141,6 @@ int toString(int reps) { } return dummy; } + + // When omitEmptyValues() is released, remove this method and add a new @Param "omitEmptyValues". } diff --git a/android/guava-tests/benchmark/com/google/common/base/WhitespaceMatcherBenchmark.java b/android/guava-tests/benchmark/com/google/common/base/WhitespaceMatcherBenchmark.java index fd3c9c4dc255..63578443a652 100644 --- a/android/guava-tests/benchmark/com/google/common/base/WhitespaceMatcherBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/base/WhitespaceMatcherBenchmark.java @@ -21,8 +21,10 @@ import com.google.caliper.Param; import java.util.BitSet; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** Benchmark for the {@link CharMatcher#whitespace} implementation. */ +@NullUnmarked public class WhitespaceMatcherBenchmark { private static final int STRING_LENGTH = 10000; @@ -85,7 +87,7 @@ public int collapseFrom(int reps) { } private static String allMatchingChars(BitSet bitSet) { - final char[] result = new char[bitSet.cardinality()]; + char[] result = new char[bitSet.cardinality()]; for (int j = 0, c = bitSet.nextSetBit(0); j < result.length; ++j) { result[j] = (char) c; c = bitSet.nextSetBit(c + 1); @@ -94,8 +96,8 @@ private static String allMatchingChars(BitSet bitSet) { } private static String newTestString(Random random, BitSet bitSet, int percentMatching) { - final String allMatchingChars = allMatchingChars(bitSet); - final char[] result = new char[STRING_LENGTH]; + String allMatchingChars = allMatchingChars(bitSet); + char[] result = new char[STRING_LENGTH]; // Fill with matching chars. for (int i = 0; i < result.length; i++) { result[i] = allMatchingChars.charAt(random.nextInt(allMatchingChars.length())); @@ -103,9 +105,9 @@ private static String newTestString(Random random, BitSet bitSet, int percentMat // Replace some of chars by non-matching. int remaining = (int) ((100 - percentMatching) * result.length / 100.0 + 0.5); while (remaining > 0) { - final char c = (char) random.nextInt(); + char c = (char) random.nextInt(); if (bitSet.get(c)) { - final int pos = random.nextInt(result.length); + int pos = random.nextInt(result.length); if (bitSet.get(result[pos])) { result[pos] = c; remaining--; diff --git a/android/guava-tests/benchmark/com/google/common/cache/ChainBenchmark.java b/android/guava-tests/benchmark/com/google/common/cache/ChainBenchmark.java index 43fc75cff318..01d47a6e47f6 100644 --- a/android/guava-tests/benchmark/com/google/common/cache/ChainBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/cache/ChainBenchmark.java @@ -20,12 +20,16 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import com.google.common.cache.LocalCache.Segment; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Benchmark for {@code LocalCache.Segment.removeEntryFromChain}. * * @author Charles Fry */ +@SuppressWarnings("CheckReturnValue") +@NullUnmarked public class ChainBenchmark { @Param({"1", "2", "3", "4", "5", "6"}) @@ -33,7 +37,7 @@ public class ChainBenchmark { private Segment segment; private ReferenceEntry head; - private ReferenceEntry chain; + private @Nullable ReferenceEntry chain; @SuppressWarnings("GuardedBy") @BeforeExperiment diff --git a/android/guava-tests/benchmark/com/google/common/cache/LoadingCacheSingleThreadBenchmark.java b/android/guava-tests/benchmark/com/google/common/cache/LoadingCacheSingleThreadBenchmark.java index b15de8f8892e..3383b407b448 100644 --- a/android/guava-tests/benchmark/com/google/common/cache/LoadingCacheSingleThreadBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/cache/LoadingCacheSingleThreadBenchmark.java @@ -23,12 +23,14 @@ import com.google.common.primitives.Ints; import java.util.Random; import java.util.concurrent.atomic.AtomicLong; +import org.jspecify.annotations.NullUnmarked; /** * Single-threaded benchmark for {@link LoadingCache}. * * @author Charles Fry */ +@NullUnmarked public class LoadingCacheSingleThreadBenchmark { @Param({"1000", "2000"}) int maximumSize; diff --git a/android/guava-tests/benchmark/com/google/common/cache/MapMakerComparisonBenchmark.java b/android/guava-tests/benchmark/com/google/common/cache/MapMakerComparisonBenchmark.java index 5fa6cbc94d14..5b8fc509acfe 100644 --- a/android/guava-tests/benchmark/com/google/common/cache/MapMakerComparisonBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/cache/MapMakerComparisonBenchmark.java @@ -20,12 +20,14 @@ import com.google.caliper.Benchmark; import com.google.common.collect.MapMaker; import java.util.Map; +import org.jspecify.annotations.NullUnmarked; /** * Compare CacheBuilder and MapMaker performance, ensuring that they remain on par with each other. * * @author Nikita Sidorov */ +@NullUnmarked public class MapMakerComparisonBenchmark { private static final String TEST_KEY = "test key"; private static final String TEST_VALUE = "test value"; diff --git a/android/guava-tests/benchmark/com/google/common/cache/SegmentBenchmark.java b/android/guava-tests/benchmark/com/google/common/cache/SegmentBenchmark.java index a473d75e585d..e50da889645e 100644 --- a/android/guava-tests/benchmark/com/google/common/cache/SegmentBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/cache/SegmentBenchmark.java @@ -23,12 +23,14 @@ import com.google.caliper.Param; import com.google.common.cache.LocalCache.Segment; import java.util.concurrent.atomic.AtomicReferenceArray; +import org.jspecify.annotations.NullUnmarked; /** * Benchmark for {@code LocalCache.Segment.expand()}. * * @author Charles Fry */ +@NullUnmarked public class SegmentBenchmark { @Param({"16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192"}) diff --git a/android/guava-tests/benchmark/com/google/common/collect/BinaryTreeTraverserBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/BinaryTreeTraverserBenchmark.java deleted file mode 100644 index 75fc1f90d2ef..000000000000 --- a/android/guava-tests/benchmark/com/google/common/collect/BinaryTreeTraverserBenchmark.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2012 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package com.google.common.collect; - -import com.google.caliper.BeforeExperiment; -import com.google.caliper.Benchmark; -import com.google.caliper.Param; -import com.google.common.base.Optional; -import com.google.common.primitives.Ints; -import java.util.List; -import java.util.Random; - -/** - * Benchmarks for the {@code TreeTraverser} operations on binary trees. - * - * @author Louis Wasserman - */ -public class BinaryTreeTraverserBenchmark { - private static class BinaryNode { - final int x; - final Optional left; - final Optional right; - - BinaryNode(int x, Optional left, Optional right) { - this.x = x; - this.left = left; - this.right = right; - } - } - - enum Topology { - BALANCED { - @Override - Optional createTree(int size, Random rng) { - if (size == 0) { - return Optional.absent(); - } else { - int leftChildSize = (size - 1) / 2; - int rightChildSize = size - 1 - leftChildSize; - return Optional.of( - new BinaryNode( - rng.nextInt(), createTree(leftChildSize, rng), createTree(rightChildSize, rng))); - } - } - }, - ALL_LEFT { - @Override - Optional createTree(int size, Random rng) { - Optional root = Optional.absent(); - for (int i = 0; i < size; i++) { - root = Optional.of(new BinaryNode(rng.nextInt(), root, Optional.absent())); - } - return root; - } - }, - ALL_RIGHT { - @Override - Optional createTree(int size, Random rng) { - Optional root = Optional.absent(); - for (int i = 0; i < size; i++) { - root = Optional.of(new BinaryNode(rng.nextInt(), Optional.absent(), root)); - } - return root; - } - }, - RANDOM { - /** - * Generates a tree with topology selected uniformly at random from the topologies of binary - * trees of the specified size. - */ - @Override - Optional createTree(int size, Random rng) { - int[] keys = new int[size]; - for (int i = 0; i < size; i++) { - keys[i] = rng.nextInt(); - } - return createTreap(Ints.asList(keys)); - } - - // See http://en.wikipedia.org/wiki/Treap for details on the algorithm. - private Optional createTreap(List keys) { - if (keys.isEmpty()) { - return Optional.absent(); - } - int minIndex = 0; - for (int i = 1; i < keys.size(); i++) { - if (keys.get(i) < keys.get(minIndex)) { - minIndex = i; - } - } - Optional leftChild = createTreap(keys.subList(0, minIndex)); - Optional rightChild = createTreap(keys.subList(minIndex + 1, keys.size())); - return Optional.of(new BinaryNode(keys.get(minIndex), leftChild, rightChild)); - } - }; - - abstract Optional createTree(int size, Random rng); - } - - private static final TreeTraverser VIEWER = - new TreeTraverser() { - @Override - public Iterable children(BinaryNode root) { - return Optional.presentInstances(ImmutableList.of(root.left, root.right)); - } - }; - - enum Traversal { - PRE_ORDER { - @Override - Iterable view(T root, TreeTraverser viewer) { - return viewer.preOrderTraversal(root); - } - }, - POST_ORDER { - @Override - Iterable view(T root, TreeTraverser viewer) { - return viewer.postOrderTraversal(root); - } - }, - BREADTH_FIRST { - @Override - Iterable view(T root, TreeTraverser viewer) { - return viewer.breadthFirstTraversal(root); - } - }; - - abstract Iterable view(T root, TreeTraverser viewer); - } - - private Iterable view; - - @Param Topology topology; - - @Param({"1", "100", "10000", "1000000"}) - int size; - - @Param Traversal traversal; - - @Param({"1234"}) - SpecialRandom rng; - - @BeforeExperiment - void setUp() { - this.view = traversal.view(topology.createTree(size, rng).get(), VIEWER); - } - - @Benchmark - int traversal(int reps) { - int tmp = 0; - - for (int i = 0; i < reps; i++) { - for (BinaryNode node : view) { - tmp += node.x; - } - } - return tmp; - } -} diff --git a/android/guava-tests/benchmark/com/google/common/collect/ComparatorDelegationOverheadBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/ComparatorDelegationOverheadBenchmark.java index 856600013e91..9d618b5fb9d3 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/ComparatorDelegationOverheadBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/ComparatorDelegationOverheadBenchmark.java @@ -14,12 +14,15 @@ package com.google.common.collect; +import static java.util.Arrays.sort; + import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.util.Arrays; import java.util.Comparator; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * A benchmark to determine the overhead of sorting with {@link Ordering#from(Comparator)}, or with @@ -28,6 +31,7 @@ * * @author Louis Wasserman */ +@NullUnmarked public class ComparatorDelegationOverheadBenchmark { private final Integer[][] inputArrays = new Integer[0x100][]; @@ -51,7 +55,7 @@ int arraysSortNoComparator(int reps) { int tmp = 0; for (int i = 0; i < reps; i++) { Integer[] copy = inputArrays[i & 0xFF].clone(); - Arrays.sort(copy); + sort(copy); tmp += copy[0]; } return tmp; @@ -62,7 +66,7 @@ int arraysSortOrderingNatural(int reps) { int tmp = 0; for (int i = 0; i < reps; i++) { Integer[] copy = inputArrays[i & 0xFF].clone(); - Arrays.sort(copy, Ordering.natural()); + sort(copy, Ordering.natural()); tmp += copy[0]; } return tmp; @@ -81,7 +85,7 @@ int arraysSortOrderingFromNatural(int reps) { int tmp = 0; for (int i = 0; i < reps; i++) { Integer[] copy = inputArrays[i & 0xFF].clone(); - Arrays.sort(copy, Ordering.from(NATURAL_INTEGER)); + sort(copy, Ordering.from(NATURAL_INTEGER)); tmp += copy[0]; } return tmp; diff --git a/android/guava-tests/benchmark/com/google/common/collect/ConcurrentHashMultisetBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/ConcurrentHashMultisetBenchmark.java index 5b713f26b64f..c94673a67996 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/ConcurrentHashMultisetBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/ConcurrentHashMultisetBenchmark.java @@ -18,6 +18,8 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.Lists.newArrayListWithExpectedSize; +import static java.util.concurrent.Executors.newFixedThreadPool; import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; @@ -35,15 +37,16 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Benchmarks for {@link ConcurrentHashMultiset}. * * @author mike nonemacher */ +@NullUnmarked public class ConcurrentHashMultisetBenchmark { @Param({"1", "2", "4", "8"}) int threads; @@ -65,12 +68,11 @@ void setUp() throws Exception { builder.add(i); } keys = builder.build(); - threadPool = - Executors.newFixedThreadPool(threads, new ThreadFactoryBuilder().setDaemon(true).build()); + threadPool = newFixedThreadPool(threads, new ThreadFactoryBuilder().setDaemon(true).build()); } @Benchmark - long add(final int reps) throws ExecutionException, InterruptedException { + long add(int reps) throws ExecutionException, InterruptedException { return doMultithreadedLoop( new Callable() { @Override @@ -81,7 +83,7 @@ public Long call() { } @Benchmark - long addRemove(final int reps) throws ExecutionException, InterruptedException { + long addRemove(int reps) throws ExecutionException, InterruptedException { return doMultithreadedLoop( new Callable() { @Override @@ -173,7 +175,7 @@ private static final class OldConcurrentHashMultiset extends AbstractMultiset * Creates a new, empty {@code OldConcurrentHashMultiset} using the default initial capacity, * load factor, and concurrency settings. */ - public static OldConcurrentHashMultiset create() { + static OldConcurrentHashMultiset create() { return new OldConcurrentHashMultiset(new ConcurrentHashMap()); } @@ -192,7 +194,7 @@ public static OldConcurrentHashMultiset create() { * @return the nonnegative number of occurrences of the element */ @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { try { return unbox(countMap.get(element)); } catch (NullPointerException | ClassCastException e) { @@ -235,7 +237,7 @@ public T[] toArray(T[] array) { * either of these would recurse back to us again! */ private List snapshot() { - List list = Lists.newArrayListWithExpectedSize(size()); + List list = newArrayListWithExpectedSize(size()); for (Multiset.Entry entry : entrySet()) { E element = entry.getElement(); for (int i = entry.getCount(); i > 0; i--) { @@ -295,7 +297,7 @@ public int add(E element, int occurrences) { * @throws IllegalArgumentException if {@code occurrences} is negative */ @Override - public int remove(@NullableDecl Object element, int occurrences) { + public int remove(@Nullable Object element, int occurrences) { if (occurrences == 0) { return count(element); } @@ -330,7 +332,7 @@ public int remove(@NullableDecl Object element, int occurrences) { * @param element the element whose occurrences should all be removed * @return the number of occurrences successfully removed, possibly zero */ - private int removeAllOccurrences(@NullableDecl Object element) { + private int removeAllOccurrences(@Nullable Object element) { try { return unbox(countMap.remove(element)); } catch (NullPointerException | ClassCastException e) { @@ -349,7 +351,7 @@ private int removeAllOccurrences(@NullableDecl Object element) { * @param occurrences the number of occurrences of {@code element} to remove * @return {@code true} if the removal was possible (including if {@code occurrences} is zero) */ - public boolean removeExactly(@NullableDecl Object element, int occurrences) { + boolean removeExactly(@Nullable Object element, int occurrences) { if (occurrences == 0) { return true; } @@ -420,7 +422,7 @@ public boolean setCount(E element, int oldCount, int newCount) { @Override Set createElementSet() { - final Set delegate = countMap.keySet(); + Set delegate = countMap.keySet(); return new ForwardingSet() { @Override protected Set delegate() { @@ -466,7 +468,7 @@ public boolean isEmpty() { @Override Iterator> entryIterator() { - final Iterator> backingIterator = countMap.entrySet().iterator(); + Iterator> backingIterator = countMap.entrySet().iterator(); return new Iterator>() { @Override public boolean hasNext() { @@ -518,7 +520,7 @@ public T[] toArray(T[] array) { } private List> snapshot() { - List> list = Lists.newArrayListWithExpectedSize(size()); + List> list = newArrayListWithExpectedSize(size()); // not Iterables.addAll(list, this), because that'll forward back here Iterators.addAll(list, iterator()); return list; @@ -543,7 +545,7 @@ public int hashCode() { } /** We use a special form of unboxing that treats null as zero. */ - private static int unbox(@NullableDecl Integer i) { + private static int unbox(@Nullable Integer i) { return (i == null) ? 0 : i; } } diff --git a/android/guava-tests/benchmark/com/google/common/collect/HashMultisetAddPresentBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/HashMultisetAddPresentBenchmark.java index a109cc2ed324..f48d27cf5cc2 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/HashMultisetAddPresentBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/HashMultisetAddPresentBenchmark.java @@ -19,12 +19,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmark for HashMultiset.add for an already-present element. * * @author Louis Wasserman */ +@NullUnmarked public class HashMultisetAddPresentBenchmark { private static final int ARRAY_MASK = 0x0ffff; private static final int ARRAY_SIZE = 0x10000; diff --git a/android/guava-tests/benchmark/com/google/common/collect/ImmutableListCreationBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/ImmutableListCreationBenchmark.java index ce0e24abb616..20b1b0d31489 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/ImmutableListCreationBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/ImmutableListCreationBenchmark.java @@ -18,13 +18,16 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; +import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.NullUnmarked; /** * Benchmark for various ways to create an {@code ImmutableList}. * * @author Louis Wasserman */ +@NullUnmarked public class ImmutableListCreationBenchmark { @Param({"10", "1000", "1000000"}) @@ -65,7 +68,7 @@ int copyArrayList(int reps) { int size = this.size; int dummy = 0; for (int rep = 0; rep < reps; rep++) { - List builder = Lists.newArrayList(); + List builder = new ArrayList<>(); for (int i = 0; i < size; i++) { builder.add(OBJECT); } diff --git a/android/guava-tests/benchmark/com/google/common/collect/InternersBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/InternersBenchmark.java index 2b7ef36c700a..b709e7def074 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/InternersBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/InternersBenchmark.java @@ -17,31 +17,37 @@ package com.google.common.collect; import com.google.caliper.Benchmark; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarking interners. * * @author Dimitris Andreou */ +@NullUnmarked public class InternersBenchmark { + @CanIgnoreReturnValue @Benchmark int weakInterner(int reps) { Interner interner = Interners.newWeakInterner(); for (int i = 0; i < reps; i++) { - interner.intern(Double.toHexString(Math.random())); + String unused = interner.intern(Double.toHexString(Math.random())); } return reps; } + @CanIgnoreReturnValue @Benchmark int strongInterner(int reps) { Interner interner = Interners.newStrongInterner(); for (int i = 0; i < reps; i++) { - interner.intern(Double.toHexString(Math.random())); + String unused = interner.intern(Double.toHexString(Math.random())); } return reps; } + @CanIgnoreReturnValue @Benchmark int stringIntern(int reps) { for (int i = 0; i < reps; i++) { diff --git a/android/guava-tests/benchmark/com/google/common/collect/IteratorBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/IteratorBenchmark.java index 20e832dd882e..a0e96853b196 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/IteratorBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/IteratorBenchmark.java @@ -21,12 +21,14 @@ import com.google.caliper.Param; import java.util.ArrayList; import java.util.LinkedList; +import org.jspecify.annotations.NullUnmarked; /** * Tests the speed of iteration of different iteration methods for collections. * * @author David Richter */ +@NullUnmarked public class IteratorBenchmark { @Param({"0", "1", "16", "256", "4096", "65536"}) int size; @@ -40,7 +42,7 @@ public class IteratorBenchmark { void setUp() { array = new Object[size]; arrayList = Lists.newArrayListWithCapacity(size); - linkedList = Lists.newLinkedList(); + linkedList = new LinkedList<>(); for (int i = 0; i < size; i++) { Object value = new Object(); diff --git a/android/guava-tests/benchmark/com/google/common/collect/MapBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/MapBenchmark.java index f10b94ea6e10..b50935bc2824 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/MapBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/MapBenchmark.java @@ -16,18 +16,23 @@ package com.google.common.collect; -import static com.google.common.collect.Lists.newArrayList; +import static java.util.Collections.sort; +import static java.util.Collections.unmodifiableMap; import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; import com.google.common.collect.CollectionBenchmarkSampleData.Element; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; +import org.jspecify.annotations.NullUnmarked; /** * A microbenchmark that tests the performance of get() and iteration on various map @@ -35,6 +40,7 @@ * * @author Nicholaus Shupe */ +@NullUnmarked public class MapBenchmark { @Param({"Hash", "LinkedHM", "MapMaker1", "Immutable"}) private Impl impl; @@ -43,7 +49,7 @@ public enum Impl { Hash { @Override Map create(Collection keys) { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); for (Element element : keys) { map.put(element, element); } @@ -53,7 +59,7 @@ Map create(Collection keys) { LinkedHM { @Override Map create(Collection keys) { - Map map = Maps.newLinkedHashMap(); + Map map = new LinkedHashMap<>(); for (Element element : keys) { map.put(element, element); } @@ -63,7 +69,7 @@ Map create(Collection keys) { UnmodHM { @Override Map create(Collection keys) { - return Collections.unmodifiableMap(Hash.create(keys)); + return unmodifiableMap(Hash.create(keys)); } }, SyncHM { @@ -139,7 +145,7 @@ Map create(Collection keys) { for (Element element : keys) { builder.put(element, element); } - return builder.build(); + return builder.buildOrThrow(); } }, ImmutableSorted { @@ -185,8 +191,8 @@ void setUp() { new CollectionBenchmarkSampleData(isUserTypeFast, random, hitRate, size); if (sortedData) { - List valueList = newArrayList(sampleData.getValuesInSet()); - Collections.sort(valueList); + List valueList = new ArrayList<>(sampleData.getValuesInSet()); + sort(valueList); values = valueList; } else { values = sampleData.getValuesInSet(); diff --git a/android/guava-tests/benchmark/com/google/common/collect/MapsMemoryBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/MapsMemoryBenchmark.java index b8348464d4e0..68d201e3af14 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/MapsMemoryBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/MapsMemoryBenchmark.java @@ -18,6 +18,7 @@ import static com.google.common.base.Functions.toStringFunction; import static com.google.common.collect.Maps.uniqueIndex; +import static java.util.Arrays.asList; import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; @@ -28,17 +29,17 @@ import com.google.common.collect.BenchmarkHelpers.MapsImplEnum; import com.google.common.collect.BenchmarkHelpers.SortedMapImpl; import com.google.common.collect.CollectionBenchmarkSampleData.Element; -import java.util.Arrays; +import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.NullUnmarked; /** Benchmarks for memory consumption of map implementations. */ +@NullUnmarked public class MapsMemoryBenchmark { static final Map mapEnums = uniqueIndex( Iterables.concat( - Arrays.asList(MapImpl.values()), - Arrays.asList(SortedMapImpl.values()), - Arrays.asList(BiMapImpl.values())), + asList(MapImpl.values()), asList(SortedMapImpl.values()), asList(BiMapImpl.values())), toStringFunction()); @Param({ @@ -80,7 +81,7 @@ public class MapsMemoryBenchmark { public void prepareContents() throws Exception { mapsImpl = mapEnums.get(implName); elems = new CollectionBenchmarkSampleData(elements); - contents = Maps.newHashMap(); + contents = new HashMap<>(); for (Element key : elems.getValuesInSet()) { contents.put(key, key); } diff --git a/android/guava-tests/benchmark/com/google/common/collect/MinMaxPriorityQueueBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/MinMaxPriorityQueueBenchmark.java index 239a033f7e4d..04496c0bfa35 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/MinMaxPriorityQueueBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/MinMaxPriorityQueueBenchmark.java @@ -25,12 +25,15 @@ import java.util.PriorityQueue; import java.util.Queue; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Benchmarks to compare performance of MinMaxPriorityQueue and PriorityQueue. * * @author Sverre Sundsdal */ +@NullUnmarked public class MinMaxPriorityQueueBenchmark { @Param private ComparatorType comparator; @@ -90,7 +93,7 @@ protected Queue delegate() { } @Override - public T poll() { + public @Nullable T poll() { return mmHeap.pollLast(); } } diff --git a/android/guava-tests/benchmark/com/google/common/collect/MultipleSetContainsBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/MultipleSetContainsBenchmark.java index b87d9641dcd7..e21eb4734698 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/MultipleSetContainsBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/MultipleSetContainsBenchmark.java @@ -21,8 +21,10 @@ import com.google.caliper.Param; import com.google.caliper.api.SkipThisScenarioException; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** A benchmark that tries invoking {@code Set.contains} on many different sets. */ +@NullUnmarked public class MultipleSetContainsBenchmark { @Param({"0.0", "0.1", "0.7", "1.0"}) @@ -37,8 +39,7 @@ public class MultipleSetContainsBenchmark { static final Object PRESENT = new Object(); static final Object ABSENT = new Object(); - @SuppressWarnings("unchecked") - private final ImmutableSet[] sets = new ImmutableSet[0x1000]; + private final ImmutableSet[] sets = new ImmutableSet[0x1000]; private final Object[] queries = new Object[0x1000]; @@ -69,7 +70,7 @@ void setUp() { @Benchmark public boolean contains(int reps) { - ImmutableSet[] sets = this.sets; + ImmutableSet[] sets = this.sets; Object[] queries = this.queries; boolean result = false; for (int i = 0; i < reps; i++) { diff --git a/android/guava-tests/benchmark/com/google/common/collect/MultisetIteratorBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/MultisetIteratorBenchmark.java index f8700a4357f2..552743d69abe 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/MultisetIteratorBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/MultisetIteratorBenchmark.java @@ -16,17 +16,21 @@ package com.google.common.collect; +import static java.lang.Math.min; + import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; import com.google.common.base.Preconditions; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Tests the speed of iteration of different iteration methods for collections. * * @author David Richter */ +@NullUnmarked public class MultisetIteratorBenchmark { @Param({"0", "1", "16", "256", "4096", "65536"}) int size; @@ -48,10 +52,10 @@ void setUp() { int sizeRemaining = size; // TODO(kevinb): generate better test contents for multisets - for (int i = 0; sizeRemaining > 0; i++) { + while (sizeRemaining > 0) { // The JVM will return interned values for small ints. Integer value = random.nextInt(1000) + 128; - int count = Math.min(random.nextInt(10) + 1, sizeRemaining); + int count = min(random.nextInt(10) + 1, sizeRemaining); sizeRemaining -= count; hashMultiset.add(value, count); linkedHashMultiset.add(value, count); diff --git a/android/guava-tests/benchmark/com/google/common/collect/PowerSetBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/PowerSetBenchmark.java index e775b8b28acf..bf6cd36a5142 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/PowerSetBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/PowerSetBenchmark.java @@ -22,12 +22,14 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; /** * Very simple powerSet iteration benchmark. * * @author Kevin Bourrillion */ +@NullUnmarked public class PowerSetBenchmark { @Param({"2", "4", "8", "16"}) int elements; diff --git a/android/guava-tests/benchmark/com/google/common/collect/SetContainsBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/SetContainsBenchmark.java index c2a21b124460..88665d667521 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/SetContainsBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/SetContainsBenchmark.java @@ -22,12 +22,14 @@ import com.google.common.collect.BenchmarkHelpers.SetImpl; import com.google.common.collect.CollectionBenchmarkSampleData.Element; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; /** * A microbenchmark that tests the performance of contains() on various Set implementations. * * @author Kevin Bourrillion */ +@NullUnmarked public class SetContainsBenchmark { // Start at 4.88 then multiply by 2*2^phi - The goal is be uniform // yet visit a variety of "values-relative-to-the-next-power-of-2" diff --git a/android/guava-tests/benchmark/com/google/common/collect/SetCreationBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/SetCreationBenchmark.java index be114e1539e3..00887ba70904 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/SetCreationBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/SetCreationBenchmark.java @@ -20,6 +20,7 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import com.google.common.collect.BenchmarkHelpers.SetImpl; +import org.jspecify.annotations.NullUnmarked; /** * This is meant to be used with {@code --measureMemory} to measure the memory usage of various @@ -27,6 +28,7 @@ * * @author Christopher Swenson */ +@NullUnmarked public class SetCreationBenchmark { @Param({ "3", "6", "11", "23", "45", "91", "181", "362", "724", "1448", "2896", "5793", "11585", "23170", diff --git a/android/guava-tests/benchmark/com/google/common/collect/SetIterationBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/SetIterationBenchmark.java index 796acefabfe6..958fc0c19817 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/SetIterationBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/SetIterationBenchmark.java @@ -22,12 +22,14 @@ import com.google.common.collect.BenchmarkHelpers.SetImpl; import com.google.common.collect.CollectionBenchmarkSampleData.Element; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; /** * Test iteration speed at various size for {@link Set} instances. * * @author Christopher Swenson */ +@NullUnmarked public class SetIterationBenchmark { @Param({ "3", "6", "11", "23", "45", "91", "181", "362", "724", "1448", "2896", "5793", "11585", "23170", diff --git a/android/guava-tests/benchmark/com/google/common/collect/SortedCopyBenchmark.java b/android/guava-tests/benchmark/com/google/common/collect/SortedCopyBenchmark.java index 891e4af393d0..3353ed96dc04 100644 --- a/android/guava-tests/benchmark/com/google/common/collect/SortedCopyBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/collect/SortedCopyBenchmark.java @@ -15,6 +15,7 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Collections.sort; import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; @@ -26,6 +27,7 @@ import java.util.Random; import java.util.Set; import java.util.TreeSet; +import org.jspecify.annotations.NullUnmarked; /** * Provides supporting data for performance notes in the documentation of {@link @@ -33,6 +35,7 @@ * suggestions. * */ +@NullUnmarked public class SortedCopyBenchmark { @Param({"1", "10", "1000", "1000000"}) int size; // logarithmic triangular @@ -45,13 +48,13 @@ enum InputOrder { SORTED { @Override void arrange(List list) { - Collections.sort(list); + sort(list); } }, ALMOST_SORTED { @Override void arrange(List list) { - Collections.sort(list); + sort(list); if (list.size() > 1) { int i = (list.size() - 1) / 2; Collections.swap(list, i, i + 1); @@ -89,13 +92,13 @@ int collections(int reps) { if (mutable) { for (int i = 0; i < reps; i++) { List copy = new ArrayList<>(input); - Collections.sort(copy); + sort(copy); dummy += copy.get(0); } } else { for (int i = 0; i < reps; i++) { List copy = new ArrayList<>(input); - Collections.sort(copy); + sort(copy); dummy += ImmutableList.copyOf(copy).get(0); } } diff --git a/android/guava-tests/benchmark/com/google/common/eventbus/EventBusBenchmark.java b/android/guava-tests/benchmark/com/google/common/eventbus/EventBusBenchmark.java index 53ddfb7cc0c7..0c4095efbb62 100644 --- a/android/guava-tests/benchmark/com/google/common/eventbus/EventBusBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/eventbus/EventBusBenchmark.java @@ -18,12 +18,14 @@ import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; +import org.jspecify.annotations.NullUnmarked; /** * Benchmark for {@link EventBus}. * * @author Eric Fellheimer */ +@NullUnmarked public class EventBusBenchmark { private EventBus eventBus; diff --git a/android/guava-tests/benchmark/com/google/common/hash/ChecksumBenchmark.java b/android/guava-tests/benchmark/com/google/common/hash/ChecksumBenchmark.java deleted file mode 100644 index 0fa3e2a88ebd..000000000000 --- a/android/guava-tests/benchmark/com/google/common/hash/ChecksumBenchmark.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2012 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.hash; - -import com.google.caliper.BeforeExperiment; -import com.google.caliper.Benchmark; -import com.google.caliper.Param; -import java.util.Random; -import java.util.zip.Adler32; -import java.util.zip.CRC32; -import java.util.zip.Checksum; - -/** - * Benchmarks for comparing {@link Checksum}s and {@link HashFunction}s that wrap {@link Checksum}s. - * - *

    Parameters for the benchmark are: - * - *

      - *
    • size: The length of the byte array to hash. - *
    - * - * @author Colin Decker - */ -public class ChecksumBenchmark { - - // Use a constant seed for all of the benchmarks to ensure apples to apples comparisons. - private static final int RANDOM_SEED = new Random().nextInt(); - - @Param({"10", "1000", "100000", "1000000"}) - private int size; - - private byte[] testBytes; - - @BeforeExperiment - void setUp() { - testBytes = new byte[size]; - new Random(RANDOM_SEED).nextBytes(testBytes); - } - - // CRC32 - - @Benchmark - byte crc32HashFunction(int reps) { - return runHashFunction(reps, Hashing.crc32()); - } - - @Benchmark - byte crc32Checksum(int reps) throws Exception { - byte result = 0x01; - for (int i = 0; i < reps; i++) { - CRC32 checksum = new CRC32(); - checksum.update(testBytes); - result = (byte) (result ^ checksum.getValue()); - } - return result; - } - - // Adler32 - - @Benchmark - byte adler32HashFunction(int reps) { - return runHashFunction(reps, Hashing.adler32()); - } - - @Benchmark - byte adler32Checksum(int reps) throws Exception { - byte result = 0x01; - for (int i = 0; i < reps; i++) { - Adler32 checksum = new Adler32(); - checksum.update(testBytes); - result = (byte) (result ^ checksum.getValue()); - } - return result; - } - - // Helpers + main - - private byte runHashFunction(int reps, HashFunction hashFunction) { - byte result = 0x01; - // Trick the JVM to prevent it from using the hash function non-polymorphically - result ^= Hashing.crc32().hashInt(reps).asBytes()[0]; - result ^= Hashing.adler32().hashInt(reps).asBytes()[0]; - for (int i = 0; i < reps; i++) { - result ^= hashFunction.hashBytes(testBytes).asBytes()[0]; - } - return result; - } -} diff --git a/android/guava-tests/benchmark/com/google/common/hash/HashCodeBenchmark.java b/android/guava-tests/benchmark/com/google/common/hash/HashCodeBenchmark.java index 3e3ec70fafa1..17a1e86d7b45 100644 --- a/android/guava-tests/benchmark/com/google/common/hash/HashCodeBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/hash/HashCodeBenchmark.java @@ -22,6 +22,7 @@ import java.security.MessageDigest; import java.util.Arrays; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for comparing the various {@link HashCode#equals} methods. @@ -41,6 +42,7 @@ * * @author Kurt Alfred Kluever */ +@NullUnmarked public class HashCodeBenchmark { // Use a statically configured random instance for all of the benchmarks @@ -68,7 +70,7 @@ boolean doEquals(byte[] a, byte[] b) { } boolean areEqual = true; for (int i = 0; i < a.length; i++) { - areEqual &= (a[i] == b[i]); + areEqual &= a[i] == b[i]; } return areEqual; } @@ -83,7 +85,7 @@ boolean doEquals(byte[] a, byte[] b) { for (int i = 0; i < a.length; i++) { result = (byte) (result | a[i] ^ b[i]); } - return (result == 0); + return result == 0; } }, XORING_TO_INT { @@ -96,7 +98,7 @@ boolean doEquals(byte[] a, byte[] b) { for (int i = 0; i < a.length; i++) { result |= a[i] ^ b[i]; } - return (result == 0); + return result == 0; } }, MESSAGE_DIGEST_IS_EQUAL { diff --git a/android/guava-tests/benchmark/com/google/common/hash/HashFunctionBenchmark.java b/android/guava-tests/benchmark/com/google/common/hash/HashFunctionBenchmark.java index 8a807f9b1ba1..69ece875f195 100644 --- a/android/guava-tests/benchmark/com/google/common/hash/HashFunctionBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/hash/HashFunctionBenchmark.java @@ -20,6 +20,7 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for comparing the various {@link HashFunction functions} that we provide. @@ -33,6 +34,7 @@ * * @author Kurt Alfred Kluever */ +@NullUnmarked public class HashFunctionBenchmark { // Use a statically configured random instance for all of the benchmarks diff --git a/android/guava-tests/benchmark/com/google/common/hash/HashStringBenchmark.java b/android/guava-tests/benchmark/com/google/common/hash/HashStringBenchmark.java index e7f9ffdc7b37..76ff514a5186 100644 --- a/android/guava-tests/benchmark/com/google/common/hash/HashStringBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/hash/HashStringBenchmark.java @@ -16,13 +16,16 @@ package com.google.common.hash; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; -import java.nio.charset.StandardCharsets; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** Benchmarks for the hashing of UTF-8 strings. */ +@NullUnmarked public class HashStringBenchmark { static class MaxCodePoint { final int value; @@ -95,8 +98,8 @@ public MaxCodePoint(String userFriendly) { */ @BeforeExperiment void setUp() { - final long seed = 99; - final Random rnd = new Random(seed); + long seed = 99; + Random rnd = new Random(seed); strings = new String[SAMPLES]; for (int i = 0; i < SAMPLES; i++) { StringBuilder sb = new StringBuilder(); @@ -118,9 +121,7 @@ int hashUtf8(int reps) { for (int i = 0; i < reps; i++) { res += System.identityHashCode( - hashFunctionEnum - .getHashFunction() - .hashString(strings[i & SAMPLE_MASK], StandardCharsets.UTF_8)); + hashFunctionEnum.getHashFunction().hashString(strings[i & SAMPLE_MASK], UTF_8)); } return res; } @@ -134,7 +135,7 @@ int hashUtf8Hasher(int reps) { hashFunctionEnum .getHashFunction() .newHasher() - .putString(strings[i & SAMPLE_MASK], StandardCharsets.UTF_8) + .putString(strings[i & SAMPLE_MASK], UTF_8) .hash()); } return res; @@ -148,7 +149,7 @@ int hashUtf8GetBytes(int reps) { System.identityHashCode( hashFunctionEnum .getHashFunction() - .hashBytes(strings[i & SAMPLE_MASK].getBytes(StandardCharsets.UTF_8))); + .hashBytes(strings[i & SAMPLE_MASK].getBytes(UTF_8))); } return res; } @@ -162,7 +163,7 @@ int hashUtf8GetBytesHasher(int reps) { hashFunctionEnum .getHashFunction() .newHasher() - .putBytes(strings[i & SAMPLE_MASK].getBytes(StandardCharsets.UTF_8)) + .putBytes(strings[i & SAMPLE_MASK].getBytes(UTF_8)) .hash()); } return res; diff --git a/android/guava-tests/benchmark/com/google/common/hash/MessageDigestAlgorithmBenchmark.java b/android/guava-tests/benchmark/com/google/common/hash/MessageDigestAlgorithmBenchmark.java index 2e252d127b68..b7a752c2852b 100644 --- a/android/guava-tests/benchmark/com/google/common/hash/MessageDigestAlgorithmBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/hash/MessageDigestAlgorithmBenchmark.java @@ -22,6 +22,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for comparing {@link MessageDigest}s and {@link com.google.common.hash.HashFunction}s @@ -37,6 +38,7 @@ * * @author Kurt Alfred Kluever */ +@NullUnmarked public class MessageDigestAlgorithmBenchmark { @Param({"10", "1000", "100000", "1000000"}) int size; @@ -67,7 +69,7 @@ public byte[] hash(Algorithm algorithm, byte[] input) { }; ; - public abstract byte[] hash(Algorithm algorithm, byte[] input); + abstract byte[] hash(Algorithm algorithm, byte[] input); } private enum Algorithm { @@ -85,7 +87,7 @@ private enum Algorithm { this.hashFn = hashFn; } - public MessageDigest getMessageDigest() { + MessageDigest getMessageDigest() { try { return MessageDigest.getInstance(algorithmName); } catch (NoSuchAlgorithmException e) { @@ -93,7 +95,7 @@ public MessageDigest getMessageDigest() { } } - public HashFunction getHashFunction() { + HashFunction getHashFunction() { return hashFn; } } diff --git a/android/guava-tests/benchmark/com/google/common/hash/MessageDigestCreationBenchmark.java b/android/guava-tests/benchmark/com/google/common/hash/MessageDigestCreationBenchmark.java index bc2dc94102f2..3990b467633a 100644 --- a/android/guava-tests/benchmark/com/google/common/hash/MessageDigestCreationBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/hash/MessageDigestCreationBenchmark.java @@ -20,12 +20,14 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.security.MessageDigest; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for comparing instance creation of {@link MessageDigest}s. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class MessageDigestCreationBenchmark { @Param({"MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512"}) diff --git a/android/guava-tests/benchmark/com/google/common/io/BaseEncodingBenchmark.java b/android/guava-tests/benchmark/com/google/common/io/BaseEncodingBenchmark.java index a098542a7130..4cd211110c3d 100644 --- a/android/guava-tests/benchmark/com/google/common/io/BaseEncodingBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/io/BaseEncodingBenchmark.java @@ -23,8 +23,10 @@ import java.io.StringReader; import java.io.StringWriter; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** Benchmark for {@code BaseEncoding} performance. */ +@NullUnmarked public class BaseEncodingBenchmark { private static final int INPUTS_COUNT = 0x1000; private static final int INPUTS_MASK = 0xFFF; diff --git a/android/guava-tests/benchmark/com/google/common/io/ByteSourceAsCharSourceReadBenchmark.java b/android/guava-tests/benchmark/com/google/common/io/ByteSourceAsCharSourceReadBenchmark.java index ee81ea1259b2..a25de3bc9ec1 100644 --- a/android/guava-tests/benchmark/com/google/common/io/ByteSourceAsCharSourceReadBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/io/ByteSourceAsCharSourceReadBenchmark.java @@ -23,12 +23,14 @@ import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for various potential implementations of {@code ByteSource.asCharSource(...).read()}. */ // These benchmarks allocate a lot of data so use a large heap @VmOptions({"-Xms12g", "-Xmx12g", "-d64"}) +@NullUnmarked public class ByteSourceAsCharSourceReadBenchmark { enum ReadStrategy { TO_BYTE_ARRAY_NEW_STRING { @@ -78,7 +80,7 @@ String read(ByteSource byteSource, Charset cs) throws IOException { return new String(buffer, 0, bufIndex); } // otherwise we got the size wrong. This can happen if the size changes between when - // we called sizeIfKnown and when we started reading the file (or i guess if + // we called sizeIfKnown and when we started reading the file (or I guess if // maxCharsPerByte is wrong) // Fallback to an incremental approach StringBuilder builder = new StringBuilder(bufIndex + 32); @@ -126,9 +128,9 @@ public void setUp() { @Benchmark public int timeCopy(int reps) throws IOException { int r = 0; - final Charset localCharset = charset; - final ByteSource localData = data; - final ReadStrategy localStrategy = strategy; + Charset localCharset = charset; + ByteSource localData = data; + ReadStrategy localStrategy = strategy; for (int i = 0; i < reps; i++) { r += localStrategy.read(localData, localCharset).hashCode(); } diff --git a/android/guava-tests/benchmark/com/google/common/io/CharStreamsCopyBenchmark.java b/android/guava-tests/benchmark/com/google/common/io/CharStreamsCopyBenchmark.java index 52532bb61ac2..38dda721db27 100644 --- a/android/guava-tests/benchmark/com/google/common/io/CharStreamsCopyBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/io/CharStreamsCopyBenchmark.java @@ -21,8 +21,10 @@ import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; +import java.nio.Buffer; import java.nio.CharBuffer; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for {@link CharStreams#copy}. @@ -32,6 +34,7 @@ */ // These benchmarks allocate a lot of data so use a large heap @VmOptions({"-Xms12g", "-Xmx12g", "-d64"}) +@NullUnmarked public class CharStreamsCopyBenchmark { enum CopyStrategy { OLD { @@ -40,10 +43,10 @@ long copy(Readable from, Appendable to) throws IOException { CharBuffer buf = CharStreams.createBuffer(); long total = 0; while (from.read(buf) != -1) { - buf.flip(); + ((Buffer) buf).flip(); to.append(buf); total += buf.remaining(); - buf.clear(); + ((Buffer) buf).clear(); } return total; } @@ -99,9 +102,9 @@ public void setUp() { @Benchmark public long timeCopy(int reps) throws IOException { long r = 0; - final String localData = data; - final TargetSupplier localTarget = target; - final CopyStrategy localStrategy = strategy; + String localData = data; + TargetSupplier localTarget = target; + CopyStrategy localStrategy = strategy; for (int i = 0; i < reps; i++) { Appendable appendable = localTarget.get(localData.length()); r += localStrategy.copy(new StringReader(localData), appendable); diff --git a/android/guava-tests/benchmark/com/google/common/math/ApacheBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/ApacheBenchmark.java index afcf95c4285b..abdfcd518803 100644 --- a/android/guava-tests/benchmark/com/google/common/math/ApacheBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/ApacheBenchmark.java @@ -25,6 +25,7 @@ import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks against the Apache Commons Math utilities. @@ -33,6 +34,7 @@ * * @author Louis Wasserman */ +@NullUnmarked public class ApacheBenchmark { private enum Impl { GUAVA { @@ -97,21 +99,21 @@ public boolean noMulOverflow(long a, long b) { } }; - public abstract double factorialDouble(int n); + abstract double factorialDouble(int n); - public abstract long binomialCoefficient(int n, int k); + abstract long binomialCoefficient(int n, int k); - public abstract int gcdInt(int a, int b); + abstract int gcdInt(int a, int b); - public abstract long gcdLong(long a, long b); + abstract long gcdLong(long a, long b); - public abstract boolean noAddOverflow(int a, int b); + abstract boolean noAddOverflow(int a, int b); - public abstract boolean noAddOverflow(long a, long b); + abstract boolean noAddOverflow(long a, long b); - public abstract boolean noMulOverflow(int a, int b); + abstract boolean noMulOverflow(int a, int b); - public abstract boolean noMulOverflow(long a, long b); + abstract boolean noMulOverflow(long a, long b); } private final int[] factorials = new int[ARRAY_SIZE]; diff --git a/android/guava-tests/benchmark/com/google/common/math/BigIntegerMathBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/BigIntegerMathBenchmark.java index 73118543e55d..d719e4adac75 100644 --- a/android/guava-tests/benchmark/com/google/common/math/BigIntegerMathBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/BigIntegerMathBenchmark.java @@ -25,12 +25,14 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.math.BigInteger; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the non-rounding methods of {@code BigIntegerMath}. * * @author Louis Wasserman */ +@NullUnmarked public class BigIntegerMathBenchmark { private static final int[] factorials = new int[ARRAY_SIZE]; private static final int[] slowFactorials = new int[ARRAY_SIZE]; @@ -59,6 +61,7 @@ private static BigInteger oldSlowFactorial(int n) { } /** Returns the product of {@code n1} exclusive through {@code n2} inclusive. */ + @SuppressWarnings("UseCorrectAssertInTests") // TODO(b/345814817): Remove or convert assertion. private static BigInteger oldSlowFactorial(int n1, int n2) { assert n1 <= n2; if (IntMath.log2(n2, CEILING) * (n2 - n1) < Long.SIZE - 1) { diff --git a/android/guava-tests/benchmark/com/google/common/math/BigIntegerMathRoundingBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/BigIntegerMathRoundingBenchmark.java index be387f792459..e33aca162dd3 100644 --- a/android/guava-tests/benchmark/com/google/common/math/BigIntegerMathRoundingBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/BigIntegerMathRoundingBenchmark.java @@ -26,12 +26,14 @@ import com.google.caliper.Param; import java.math.BigInteger; import java.math.RoundingMode; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the rounding methods of {@code BigIntegerMath}. * * @author Louis Wasserman */ +@NullUnmarked public class BigIntegerMathRoundingBenchmark { private static final BigInteger[] nonzero1 = new BigInteger[ARRAY_SIZE]; private static final BigInteger[] nonzero2 = new BigInteger[ARRAY_SIZE]; diff --git a/android/guava-tests/benchmark/com/google/common/math/DoubleMathBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/DoubleMathBenchmark.java index 937c94c98337..b012e729f40d 100644 --- a/android/guava-tests/benchmark/com/google/common/math/DoubleMathBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/DoubleMathBenchmark.java @@ -24,12 +24,14 @@ import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the non-rounding methods of {@code DoubleMath}. * * @author Louis Wasserman */ +@NullUnmarked public class DoubleMathBenchmark { private static final double[] positiveDoubles = new double[ARRAY_SIZE]; private static final int[] factorials = new int[ARRAY_SIZE]; diff --git a/android/guava-tests/benchmark/com/google/common/math/DoubleMathRoundingBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/DoubleMathRoundingBenchmark.java index 7ab80b15cd6b..6ba25a1994ec 100644 --- a/android/guava-tests/benchmark/com/google/common/math/DoubleMathRoundingBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/DoubleMathRoundingBenchmark.java @@ -25,12 +25,14 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.math.RoundingMode; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the rounding methods of {@code DoubleMath}. * * @author Louis Wasserman */ +@NullUnmarked public class DoubleMathRoundingBenchmark { private static final double[] doubleInIntRange = new double[ARRAY_SIZE]; private static final double[] doubleInLongRange = new double[ARRAY_SIZE]; diff --git a/android/guava-tests/benchmark/com/google/common/math/IntMathBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/IntMathBenchmark.java index 58b0bb64e0e9..2f9076416ac5 100644 --- a/android/guava-tests/benchmark/com/google/common/math/IntMathBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/IntMathBenchmark.java @@ -25,16 +25,18 @@ import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the non-rounding methods of {@code IntMath}. * * @author Louis Wasserman */ +@NullUnmarked public class IntMathBenchmark { - private static int[] exponent = new int[ARRAY_SIZE]; - private static int[] factorial = new int[ARRAY_SIZE]; - private static int[] binomial = new int[ARRAY_SIZE]; + private static final int[] exponent = new int[ARRAY_SIZE]; + private static final int[] factorial = new int[ARRAY_SIZE]; + private static final int[] binomial = new int[ARRAY_SIZE]; private static final int[] positive = new int[ARRAY_SIZE]; private static final int[] nonnegative = new int[ARRAY_SIZE]; private static final int[] ints = new int[ARRAY_SIZE]; diff --git a/android/guava-tests/benchmark/com/google/common/math/IntMathRoundingBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/IntMathRoundingBenchmark.java index cfa3d7393cd1..b1902b9f6a4f 100644 --- a/android/guava-tests/benchmark/com/google/common/math/IntMathRoundingBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/IntMathRoundingBenchmark.java @@ -26,12 +26,14 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.math.RoundingMode; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the rounding methods of {@code IntMath}. * * @author Louis Wasserman */ +@NullUnmarked public class IntMathRoundingBenchmark { private static final int[] positive = new int[ARRAY_SIZE]; private static final int[] nonzero = new int[ARRAY_SIZE]; diff --git a/android/guava-tests/benchmark/com/google/common/math/LessThanBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/LessThanBenchmark.java index a63dd6b16b0a..de7a3e55c247 100644 --- a/android/guava-tests/benchmark/com/google/common/math/LessThanBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/LessThanBenchmark.java @@ -20,12 +20,14 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for various ways of writing the expression {@code foo + ((bar < baz) ? 1 : 0)}. * * @author Louis Wasserman */ +@NullUnmarked public class LessThanBenchmark { static final int SAMPLE_SIZE = 0x1000; static final int SAMPLE_MASK = 0x0FFF; diff --git a/android/guava-tests/benchmark/com/google/common/math/LongMathBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/LongMathBenchmark.java index 6b0407cd5787..7934a776c71f 100644 --- a/android/guava-tests/benchmark/com/google/common/math/LongMathBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/LongMathBenchmark.java @@ -25,12 +25,14 @@ import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the non-rounding methods of {@code LongMath}. * * @author Louis Wasserman */ +@NullUnmarked public class LongMathBenchmark { private static final int[] exponents = new int[ARRAY_SIZE]; private static final int[] factorialArguments = new int[ARRAY_SIZE]; diff --git a/android/guava-tests/benchmark/com/google/common/math/LongMathRoundingBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/LongMathRoundingBenchmark.java index 5f2ac40550c3..868f2d5d2d04 100644 --- a/android/guava-tests/benchmark/com/google/common/math/LongMathRoundingBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/LongMathRoundingBenchmark.java @@ -26,12 +26,14 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import java.math.RoundingMode; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for the rounding methods of {@code LongMath}. * * @author Louis Wasserman */ +@NullUnmarked public class LongMathRoundingBenchmark { @Param({"DOWN", "UP", "FLOOR", "CEILING", "HALF_EVEN", "HALF_UP", "HALF_DOWN"}) RoundingMode mode; diff --git a/android/guava-tests/benchmark/com/google/common/math/QuantilesBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/QuantilesBenchmark.java index 6830482c2a7b..9bd9b59cd925 100644 --- a/android/guava-tests/benchmark/com/google/common/math/QuantilesBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/QuantilesBenchmark.java @@ -24,8 +24,10 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Range; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** Benchmarks some algorithms providing the same functionality as {@link Quantiles}. */ +@NullUnmarked public class QuantilesBenchmark { private static final ContiguousSet ALL_DECILE_INDEXES = @@ -36,7 +38,7 @@ public class QuantilesBenchmark { @Param QuantilesAlgorithm algorithm; - private double[][] datasets = new double[0x100][]; + private final double[][] datasets = new double[0x100][]; @BeforeExperiment void setUp() { @@ -50,7 +52,7 @@ void setUp() { } private double[] dataset(int i) { - // We must test on a fresh clone of the dataset each time. Doing sorts and quickselects on an + // We must test on a fresh clone of the dataset each time. Doing sorts and quickselects on a // dataset which is already sorted or partially sorted is cheating. return datasets[i & 0xFF].clone(); } diff --git a/android/guava-tests/benchmark/com/google/common/math/StatsBenchmark.java b/android/guava-tests/benchmark/com/google/common/math/StatsBenchmark.java index 7e50249162af..ce1e7d24ab9e 100644 --- a/android/guava-tests/benchmark/com/google/common/math/StatsBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/math/StatsBenchmark.java @@ -20,14 +20,15 @@ import com.google.caliper.Benchmark; import com.google.caliper.Param; import com.google.caliper.api.SkipThisScenarioException; -import com.google.common.primitives.Doubles; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for various algorithms for computing the mean and/or variance. * * @author Louis Wasserman */ +@NullUnmarked public class StatsBenchmark { enum MeanAlgorithm { @@ -80,7 +81,7 @@ static class MeanAndVariance { @Override public int hashCode() { - return Doubles.hashCode(mean) * 31 + Doubles.hashCode(variance); + return Double.hashCode(mean) * 31 + Double.hashCode(variance); } } @@ -146,7 +147,7 @@ MeanAndVariance variance(double[] values, MeanAlgorithm meanAlgorithm) { @Param MeanAlgorithm meanAlgorithm; @Param VarianceAlgorithm varianceAlgorithm; - private double[][] values = new double[0x100][]; + private final double[][] values = new double[0x100][]; @BeforeExperiment void setUp() { diff --git a/android/guava-tests/benchmark/com/google/common/primitives/UnsignedBytesBenchmark.java b/android/guava-tests/benchmark/com/google/common/primitives/UnsignedBytesBenchmark.java index 71ea279bd057..3b4fda5a7f28 100644 --- a/android/guava-tests/benchmark/com/google/common/primitives/UnsignedBytesBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/primitives/UnsignedBytesBenchmark.java @@ -22,12 +22,14 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Microbenchmark for {@link UnsignedBytes}. * * @author Hiroshi Yamauchi */ +@NullUnmarked public class UnsignedBytesBenchmark { private byte[] ba1; diff --git a/android/guava-tests/benchmark/com/google/common/primitives/UnsignedLongsBenchmark.java b/android/guava-tests/benchmark/com/google/common/primitives/UnsignedLongsBenchmark.java index 288aa0c5c33c..d7531ee71875 100644 --- a/android/guava-tests/benchmark/com/google/common/primitives/UnsignedLongsBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/primitives/UnsignedLongsBenchmark.java @@ -19,16 +19,18 @@ import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for certain methods of {@code UnsignedLongs}. * * @author Eamonn McManus */ +@NullUnmarked public class UnsignedLongsBenchmark { private static final int ARRAY_SIZE = 0x10000; private static final int ARRAY_MASK = 0x0ffff; - private static final Random RANDOM_SOURCE = new Random(314159265358979L); + private static final Random randomSource = new Random(314159265358979L); private static final long[] longs = new long[ARRAY_SIZE]; private static final long[] divisors = new long[ARRAY_SIZE]; private static final String[] decimalStrings = new String[ARRAY_SIZE]; @@ -120,7 +122,7 @@ int toString(int reps) { } private static long random() { - return RANDOM_SOURCE.nextLong(); + return randomSource.nextLong(); } // A random value that cannot be 0 and that is unsigned-less-than or equal @@ -129,7 +131,7 @@ private static long random() { // Using remainder here does not give us a uniform distribution but it should // not have a big impact on the measurement. private static long randomDivisor(long dividend) { - long r = RANDOM_SOURCE.nextLong(); + long r = randomSource.nextLong(); if (dividend == -1) { return r; } else { diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/AbstractFutureFootprintBenchmark.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/AbstractFutureFootprintBenchmark.java index 9a2d87140dff..5472602a5755 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/AbstractFutureFootprintBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/AbstractFutureFootprintBenchmark.java @@ -27,8 +27,10 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.Executor; +import org.jspecify.annotations.NullUnmarked; /** Measures the size of AbstractFuture implementations. */ +@NullUnmarked public class AbstractFutureFootprintBenchmark { enum State { @@ -65,7 +67,7 @@ public Object measureSize() { thread.interrupt(); } blockedThreads.clear(); - final Facade f = impl.newFacade(); + Facade f = impl.newFacade(); for (int i = 0; i < numThreads; i++) { Thread thread = new Thread() { @@ -98,8 +100,6 @@ public void run() { case FAILED: f.setException(new Exception()); break; - default: - throw new AssertionError(); } return f; } diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/CycleDetectingLockFactoryBenchmark.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/CycleDetectingLockFactoryBenchmark.java index ce674e14e8ce..ec28f78abab8 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/CycleDetectingLockFactoryBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/CycleDetectingLockFactoryBenchmark.java @@ -21,12 +21,14 @@ import com.google.caliper.Param; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for {@link CycleDetectingLockFactory}. * * @author Darick Tong */ +@NullUnmarked public class CycleDetectingLockFactoryBenchmark { @Param({"2", "3", "4", "5", "10"}) diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/ExecutionListBenchmark.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/ExecutionListBenchmark.java index c9df7748336e..99b8842c3fd7 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/ExecutionListBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/ExecutionListBenchmark.java @@ -17,6 +17,7 @@ package com.google.common.util.concurrent; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.caliper.AfterExperiment; import com.google.caliper.BeforeExperiment; @@ -25,23 +26,24 @@ import com.google.caliper.api.Footprint; import com.google.caliper.api.VmOptions; import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.AbstractFutureBenchmarks.OldAbstractFuture; import com.google.errorprone.annotations.concurrent.GuardedBy; +import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** Benchmarks for {@link ExecutionList}. */ @VmOptions({"-Xms8g", "-Xmx8g"}) +@NullUnmarked public class ExecutionListBenchmark { private static final int NUM_THREADS = 10; // make a param? @@ -50,6 +52,7 @@ interface ExecutionListWrapper { void add(Runnable runnable, Executor executor); void execute(); + /** Returns the underlying implementation, useful for the Footprint benchmark. */ Object getImpl(); } @@ -78,29 +81,6 @@ public Object getImpl() { }; } }, - NEW_WITH_CAS { - @Override - ExecutionListWrapper newExecutionList() { - return new ExecutionListWrapper() { - final ExecutionListCAS list = new ExecutionListCAS(); - - @Override - public void add(Runnable runnable, Executor executor) { - list.add(runnable, executor); - } - - @Override - public void execute() { - list.execute(); - } - - @Override - public Object getImpl() { - return list; - } - }; - } - }, NEW_WITH_QUEUE { @Override ExecutionListWrapper newExecutionList() { @@ -246,10 +226,10 @@ void setUp() throws Exception { NUM_THREADS, NUM_THREADS, Long.MAX_VALUE, - TimeUnit.SECONDS, + SECONDS, new ArrayBlockingQueue(1000)); executorService.prestartAllCoreThreads(); - final AtomicInteger integer = new AtomicInteger(); + AtomicInteger integer = new AtomicInteger(); // Execute a bunch of tasks to ensure that our threads are allocated and hot for (int i = 0; i < NUM_THREADS * 10; i++) { @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored @@ -319,7 +299,7 @@ public void run() { }; @Benchmark - int addThenExecute_multiThreaded(final int reps) throws InterruptedException { + int addThenExecute_multiThreaded(int reps) throws InterruptedException { Runnable addTask = new Runnable() { @Override @@ -346,7 +326,7 @@ public void run() { } @Benchmark - int executeThenAdd_multiThreaded(final int reps) throws InterruptedException { + int executeThenAdd_multiThreaded(int reps) throws InterruptedException { Runnable addTask = new Runnable() { @Override @@ -375,10 +355,10 @@ public void run() { // This is the old implementation of ExecutionList using a LinkedList. private static final class OldExecutionList { static final Logger log = Logger.getLogger(OldExecutionList.class.getName()); - final Queue runnables = Lists.newLinkedList(); + final Queue runnables = new LinkedList<>(); boolean executed = false; - public void add(Runnable runnable, Executor executor) { + void add(Runnable runnable, Executor executor) { Preconditions.checkNotNull(runnable, "Runnable was null."); Preconditions.checkNotNull(executor, "Executor was null."); @@ -397,7 +377,7 @@ public void add(Runnable runnable, Executor executor) { } } - public void execute() { + void execute() { synchronized (runnables) { if (executed) { return; @@ -440,12 +420,12 @@ private static final class NewExecutionListWithoutReverse { static final Logger log = Logger.getLogger(NewExecutionListWithoutReverse.class.getName()); @GuardedBy("this") - private RunnableExecutorPair runnables; + private @Nullable RunnableExecutorPair runnables; @GuardedBy("this") private boolean executed; - public void add(Runnable runnable, Executor executor) { + void add(Runnable runnable, Executor executor) { Preconditions.checkNotNull(runnable, "Runnable was null."); Preconditions.checkNotNull(executor, "Executor was null."); @@ -458,7 +438,7 @@ public void add(Runnable runnable, Executor executor) { executeListener(runnable, executor); } - public void execute() { + void execute() { RunnableExecutorPair list; synchronized (this) { if (executed) { @@ -488,7 +468,7 @@ private static void executeListener(Runnable runnable, Executor executor) { private static final class RunnableExecutorPair { final Runnable runnable; final Executor executor; - @NullableDecl RunnableExecutorPair next; + @Nullable final RunnableExecutorPair next; RunnableExecutorPair(Runnable runnable, Executor executor, RunnableExecutorPair next) { this.runnable = runnable; @@ -504,15 +484,15 @@ private static final class NewExecutionListQueue { static final Logger log = Logger.getLogger(NewExecutionListQueue.class.getName()); @GuardedBy("this") - private RunnableExecutorPair head; + private @Nullable RunnableExecutorPair head; @GuardedBy("this") - private RunnableExecutorPair tail; + private @Nullable RunnableExecutorPair tail; @GuardedBy("this") private boolean executed; - public void add(Runnable runnable, Executor executor) { + void add(Runnable runnable, Executor executor) { Preconditions.checkNotNull(runnable, "Runnable was null."); Preconditions.checkNotNull(executor, "Executor was null."); @@ -532,7 +512,7 @@ public void add(Runnable runnable, Executor executor) { executeListener(runnable, executor); } - public void execute() { + void execute() { RunnableExecutorPair list; synchronized (this) { if (executed) { @@ -561,134 +541,14 @@ private static void executeListener(Runnable runnable, Executor executor) { } private static final class RunnableExecutorPair { - Runnable runnable; - Executor executor; - @NullableDecl RunnableExecutorPair next; - - RunnableExecutorPair(Runnable runnable, Executor executor) { - this.runnable = runnable; - this.executor = executor; - } - } - } - - // A version of the list that uses compare and swap to manage the stack without locks. - private static final class ExecutionListCAS { - static final Logger log = Logger.getLogger(ExecutionListCAS.class.getName()); - - private static final sun.misc.Unsafe UNSAFE; - private static final long HEAD_OFFSET; - - /** - * A special instance of {@link RunnableExecutorPair} that is used as a sentinel value for the - * bottom of the stack. - */ - private static final RunnableExecutorPair NULL_PAIR = new RunnableExecutorPair(null, null); - - static { - try { - UNSAFE = getUnsafe(); - HEAD_OFFSET = UNSAFE.objectFieldOffset(ExecutionListCAS.class.getDeclaredField("head")); - } catch (Exception ex) { - throw new Error(ex); - } - } - - /** TODO(lukes): This was copied verbatim from Striped64.java... standardize this? */ - private static sun.misc.Unsafe getUnsafe() { - try { - return sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) { - } - try { - return java.security.AccessController.doPrivileged( - new java.security.PrivilegedExceptionAction() { - @Override - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) return k.cast(x); - } - throw new NoSuchFieldError("the Unsafe"); - } - }); - } catch (java.security.PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", e.getCause()); - } - } - - private volatile RunnableExecutorPair head = NULL_PAIR; - - public void add(Runnable runnable, Executor executor) { - Preconditions.checkNotNull(runnable, "Runnable was null."); - Preconditions.checkNotNull(executor, "Executor was null."); - - RunnableExecutorPair newHead = new RunnableExecutorPair(runnable, executor); - RunnableExecutorPair oldHead; - do { - oldHead = head; - if (oldHead == null) { - // If runnables == null then execute() has been called so we should just execute our - // listener immediately. - newHead.execute(); - return; - } - // Try to make newHead the new head of the stack at runnables. - newHead.next = oldHead; - } while (!UNSAFE.compareAndSwapObject(this, HEAD_OFFSET, oldHead, newHead)); - } - - public void execute() { - RunnableExecutorPair stack; - do { - stack = head; - if (stack == null) { - // If head == null then execute() has been called so we should just return - return; - } - // try to swap null into head. - } while (!UNSAFE.compareAndSwapObject(this, HEAD_OFFSET, stack, null)); - - RunnableExecutorPair reversedStack = null; - while (stack != NULL_PAIR) { - RunnableExecutorPair head = stack; - stack = stack.next; - head.next = reversedStack; - reversedStack = head; - } - stack = reversedStack; - while (stack != null) { - stack.execute(); - stack = stack.next; - } - } - - private static class RunnableExecutorPair { final Runnable runnable; final Executor executor; - // Volatile because this is written on one thread and read on another with no synchronization. - @NullableDecl volatile RunnableExecutorPair next; + @Nullable RunnableExecutorPair next; RunnableExecutorPair(Runnable runnable, Executor executor) { this.runnable = runnable; this.executor = executor; } - - void execute() { - try { - executor.execute(runnable); - } catch (RuntimeException e) { - log.log( - Level.SEVERE, - "RuntimeException while executing runnable " - + runnable - + " with executor " - + executor, - e); - } - } } } } diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/FuturesGetCheckedBenchmark.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/FuturesGetCheckedBenchmark.java index dd1883bcb053..a599d578f622 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/FuturesGetCheckedBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/FuturesGetCheckedBenchmark.java @@ -17,11 +17,9 @@ package com.google.common.util.concurrent; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Lists.newArrayList; import static com.google.common.util.concurrent.Futures.immediateFailedFuture; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.FuturesGetChecked.checkExceptionClassValidity; -import static com.google.common.util.concurrent.FuturesGetChecked.classValueValidator; import static com.google.common.util.concurrent.FuturesGetChecked.getChecked; import static com.google.common.util.concurrent.FuturesGetChecked.isCheckedException; import static com.google.common.util.concurrent.FuturesGetChecked.weakSetValidator; @@ -34,7 +32,8 @@ import java.io.IOException; import java.net.URISyntaxException; import java.security.GeneralSecurityException; -import java.security.acl.NotOwnerException; +import java.security.KeyException; +import java.util.ArrayList; import java.util.List; import java.util.TooManyListenersException; import java.util.concurrent.BrokenBarrierException; @@ -45,14 +44,16 @@ import java.util.prefs.InvalidPreferencesFormatException; import java.util.zip.DataFormatException; import javax.security.auth.RefreshFailedException; +import org.jspecify.annotations.NullUnmarked; /** Microbenchmark for {@link Futures#getChecked}. */ +@NullUnmarked public class FuturesGetCheckedBenchmark { private enum Validator { NON_CACHING_WITH_CONSTRUCTOR_CHECK(nonCachingWithConstructorCheckValidator()), NON_CACHING_WITHOUT_CONSTRUCTOR_CHECK(nonCachingWithoutConstructorCheckValidator()), WEAK_SET(weakSetValidator()), - CLASS_VALUE(classValueValidator()); + ; final GetCheckedTypeValidator validator; @@ -92,7 +93,7 @@ private enum ExceptionType { ExecutionException.class, GeneralSecurityException.class, InvalidPreferencesFormatException.class, - NotOwnerException.class, + KeyException.class, RefreshFailedException.class, TimeoutException.class, TooManyListenersException.class, @@ -101,6 +102,7 @@ private enum ExceptionType { @Param Validator validator; @Param Result result; @Param ExceptionType exceptionType; + /** * The number of other exception types in the cache of known-good exceptions and the number of * other {@code ClassValue} entries for the exception type to be tested. This lets us evaluate @@ -111,7 +113,7 @@ private enum ExceptionType { @Param({"0", "1", "12"}) int otherEntriesInDataStructure; - final List> retainedReferencesToOtherClassValues = newArrayList(); + final List> retainedReferencesToOtherClassValues = new ArrayList<>(); @BeforeExperiment void addOtherEntries() throws Exception { diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBasedArrayBlockingQueue.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBasedArrayBlockingQueue.java index 7998a729f3b9..65ac14a286b5 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBasedArrayBlockingQueue.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBasedArrayBlockingQueue.java @@ -25,7 +25,8 @@ import java.util.NoSuchElementException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * A bounded {@linkplain BlockingQueue blocking queue} backed by an array. This queue orders @@ -36,12 +37,12 @@ * *

    This is a classic "bounded buffer", in which a fixed-sized array holds elements * inserted by producers and extracted by consumers. Once created, the capacity cannot be increased. - * Attempts to put an element into a full queue will result in the operation blocking; - * attempts to take an element from an empty queue will similarly block. + * Attempts to {@code put} an element into a full queue will result in the operation blocking; + * attempts to {@code take} an element from an empty queue will similarly block. * *

    This class supports an optional fairness policy for ordering waiting producer and consumer * threads. By default, this ordering is not guaranteed. However, a queue constructed with fairness - * set to true grants threads access in FIFO order. Fairness generally decreases throughput + * set to {@code true} grants threads access in FIFO order. Fairness generally decreases throughput * but reduces variability and avoids starvation. * *

    This class and its iterator implement all of the optional methods of the {@link @@ -51,7 +52,8 @@ * @author Justin T. Sampson * @param the type of elements held in this collection */ -@CanIgnoreReturnValue +// TODO(kak): consider removing some of the @CanIgnoreReturnValue annotations as appropriate +@NullUnmarked public class MonitorBasedArrayBlockingQueue extends AbstractQueue implements BlockingQueue { @@ -60,10 +62,13 @@ public class MonitorBasedArrayBlockingQueue extends AbstractQueue /** The queued items */ final E[] items; + /** items index for next take, poll or remove */ int takeIndex; + /** items index for next put, offer, or add. */ int putIndex; + /** Number of items in the queue */ private int count; @@ -103,7 +108,7 @@ private void insert(E x) { * monitor. */ private E extract() { - final E[] items = this.items; + E[] items = this.items; E x = items[takeIndex]; items[takeIndex] = null; takeIndex = inc(takeIndex); @@ -116,7 +121,7 @@ private E extract() { * monitor. */ void removeAt(int i) { - final E[] items = this.items; + E[] items = this.items; // if removing front item, just advance if (i == takeIndex) { items[takeIndex] = null; @@ -139,24 +144,24 @@ void removeAt(int i) { } /** - * Creates an MonitorBasedArrayBlockingQueue with the given (fixed) capacity and default + * Creates an {@code MonitorBasedArrayBlockingQueue} with the given (fixed) capacity and default * access policy. * * @param capacity the capacity of this queue - * @throws IllegalArgumentException if capacity is less than 1 + * @throws IllegalArgumentException if {@code capacity} is less than 1 */ public MonitorBasedArrayBlockingQueue(int capacity) { this(capacity, false); } /** - * Creates an MonitorBasedArrayBlockingQueue with the given (fixed) capacity and the + * Creates an {@code MonitorBasedArrayBlockingQueue} with the given (fixed) capacity and the * specified access policy. * * @param capacity the capacity of this queue - * @param fair if true then queue accesses for threads blocked on insertion or removal, - * are processed in FIFO order; if false the access order is unspecified. - * @throws IllegalArgumentException if capacity is less than 1 + * @param fair if {@code true} then queue accesses for threads blocked on insertion or removal, + * are processed in FIFO order; if {@code false} the access order is unspecified. + * @throws IllegalArgumentException if {@code capacity} is less than 1 */ public MonitorBasedArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); @@ -179,15 +184,15 @@ public boolean isSatisfied() { } /** - * Creates an MonitorBasedArrayBlockingQueue with the given (fixed) capacity, the + * Creates an {@code MonitorBasedArrayBlockingQueue} with the given (fixed) capacity, the * specified access policy and initially containing the elements of the given collection, added in * traversal order of the collection's iterator. * * @param capacity the capacity of this queue - * @param fair if true then queue accesses for threads blocked on insertion or removal, - * are processed in FIFO order; if false the access order is unspecified. + * @param fair if {@code true} then queue accesses for threads blocked on insertion or removal, + * are processed in FIFO order; if {@code false} the access order is unspecified. * @param c the collection of elements to initially contain - * @throws IllegalArgumentException if capacity is less than c.size(), or less + * @throws IllegalArgumentException if {@code capacity} is less than {@code c.size()}, or less * than 1. * @throws NullPointerException if the specified collection or any of its elements are null */ @@ -205,14 +210,15 @@ private static E[] newEArray(int capacity) { /** * Inserts the specified element at the tail of this queue if it is possible to do so immediately - * without exceeding the queue's capacity, returning true upon success and throwing an - * IllegalStateException if this queue is full. + * without exceeding the queue's capacity, returning {@code true} upon success and throwing an + * {@code IllegalStateException} if this queue is full. * * @param e the element to add - * @return true (as specified by {@link Collection#add}) + * @return {@code true} (as specified by {@link Collection#add}) * @throws IllegalStateException if this queue is full * @throws NullPointerException if the specified element is null */ + @CanIgnoreReturnValue @Override public boolean add(E e) { return super.add(e); @@ -220,16 +226,17 @@ public boolean add(E e) { /** * Inserts the specified element at the tail of this queue if it is possible to do so immediately - * without exceeding the queue's capacity, returning true upon success and false + * without exceeding the queue's capacity, returning {@code true} upon success and {@code false} * if this queue is full. This method is generally preferable to method {@link #add}, which can * fail to insert an element only by throwing an exception. * * @throws NullPointerException if the specified element is null */ + @CanIgnoreReturnValue @Override public boolean offer(E e) { if (e == null) throw new NullPointerException(); - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; if (monitor.enterIf(notFull)) { try { insert(e); @@ -249,11 +256,12 @@ public boolean offer(E e) { * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ + @CanIgnoreReturnValue @Override public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { if (e == null) throw new NullPointerException(); - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; if (monitor.enterWhen(notFull, timeout, unit)) { try { insert(e); @@ -276,7 +284,7 @@ public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedExcepti @Override public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enterWhen(notFull); try { insert(e); @@ -285,9 +293,10 @@ public void put(E e) throws InterruptedException { } } + @CanIgnoreReturnValue @Override - public E poll() { - final Monitor monitor = this.monitor; + public @Nullable E poll() { + Monitor monitor = this.monitor; if (monitor.enterIf(notEmpty)) { try { return extract(); @@ -299,9 +308,10 @@ public E poll() { } } + @CanIgnoreReturnValue @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { - final Monitor monitor = this.monitor; + public @Nullable E poll(long timeout, TimeUnit unit) throws InterruptedException { + Monitor monitor = this.monitor; if (monitor.enterWhen(notEmpty, timeout, unit)) { try { return extract(); @@ -313,9 +323,10 @@ public E poll(long timeout, TimeUnit unit) throws InterruptedException { } } + @CanIgnoreReturnValue @Override public E take() throws InterruptedException { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enterWhen(notEmpty); try { return extract(); @@ -324,9 +335,10 @@ public E take() throws InterruptedException { } } + @CanIgnoreReturnValue @Override - public E peek() { - final Monitor monitor = this.monitor; + public @Nullable E peek() { + Monitor monitor = this.monitor; if (monitor.enterIf(notEmpty)) { try { return items[takeIndex]; @@ -345,9 +357,10 @@ public E peek() { * * @return the number of elements in this queue */ + @CanIgnoreReturnValue @Override public int size() { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { return count; @@ -361,15 +374,16 @@ public int size() { /** * Returns the number of additional elements that this queue can ideally (in the absence of memory * or resource constraints) accept without blocking. This is always equal to the initial capacity - * of this queue less the current size of this queue. + * of this queue less the current {@code size} of this queue. * *

    Note that you cannot always tell if an attempt to insert an element will succeed by - * inspecting remainingCapacity because it may be the case that another thread is about - * to insert or remove an element. + * inspecting {@code remainingCapacity} because it may be the case that another thread is about to + * insert or remove an element. */ + @CanIgnoreReturnValue @Override public int remainingCapacity() { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { return items.length - count; @@ -380,18 +394,19 @@ public int remainingCapacity() { /** * Removes a single instance of the specified element from this queue, if it is present. More - * formally, removes an element e such that o.equals(e), if this queue contains - * one or more such elements. Returns true if this queue contained the specified element + * formally, removes an element {@code e} such that {@code o.equals(e)}, if this queue contains + * one or more such elements. Returns {@code true} if this queue contained the specified element * (or equivalently, if this queue changed as a result of the call). * * @param o element to be removed from this queue, if present - * @return true if this queue changed as a result of the call + * @return {@code true} if this queue changed as a result of the call */ + @CanIgnoreReturnValue @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { if (o == null) return false; - final E[] items = this.items; - final Monitor monitor = this.monitor; + E[] items = this.items; + Monitor monitor = this.monitor; monitor.enter(); try { int i = takeIndex; @@ -410,18 +425,19 @@ public boolean remove(@NullableDecl Object o) { } /** - * Returns true if this queue contains the specified element. More formally, returns - * true if and only if this queue contains at least one element e such that - * o.equals(e). + * Returns {@code true} if this queue contains the specified element. More formally, returns + * {@code true} if and only if this queue contains at least one element {@code e} such that {@code + * o.equals(e)}. * * @param o object to be checked for containment in this queue - * @return true if this queue contains the specified element + * @return {@code true} if this queue contains the specified element */ + @CanIgnoreReturnValue @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { if (o == null) return false; - final E[] items = this.items; - final Monitor monitor = this.monitor; + E[] items = this.items; + Monitor monitor = this.monitor; monitor.enter(); try { int i = takeIndex; @@ -447,10 +463,11 @@ public boolean contains(@NullableDecl Object o) { * * @return an array containing all of the elements in this queue */ + @CanIgnoreReturnValue @Override public Object[] toArray() { - final E[] items = this.items; - final Monitor monitor = this.monitor; + E[] items = this.items; + Monitor monitor = this.monitor; monitor.enter(); try { Object[] a = new Object[count]; @@ -474,19 +491,19 @@ public Object[] toArray() { * *

    If this queue fits in the specified array with room to spare (i.e., the array has more * elements than this queue), the element in the array immediately following the end of the queue - * is set to null. + * is set to {@code null}. * *

    Like the {@link #toArray()} method, this method acts as bridge between array-based and * collection-based APIs. Further, this method allows precise control over the runtime type of the * output array, and may, under certain circumstances, be used to save allocation costs. * - *

    Suppose x is a queue known to contain only strings. The following code can be used - * to dump the queue into a newly allocated array of String: + *

    Suppose {@code x} is a queue known to contain only strings. The following code can be used + * to dump the queue into a newly allocated array of {@code String}: * *

        *     String[] y = x.toArray(new String[0]);
    * - *

    Note that toArray(new Object[0]) is identical in function to toArray(). + *

    Note that {@code toArray(new Object[0])} is identical in function to {@code toArray()}. * * @param a the array into which the elements of the queue are to be stored, if it is big enough; * otherwise, a new array of the same runtime type is allocated for this purpose @@ -495,10 +512,11 @@ public Object[] toArray() { * the runtime type of every element in this queue * @throws NullPointerException if the specified array is null */ + @CanIgnoreReturnValue @Override public T[] toArray(T[] a) { - final E[] items = this.items; - final Monitor monitor = this.monitor; + E[] items = this.items; + Monitor monitor = this.monitor; monitor.enter(); try { if (a.length < count) a = ObjectArrays.newArray(a, count); @@ -522,9 +540,10 @@ public T[] toArray(T[] a) { } } + @CanIgnoreReturnValue @Override public String toString() { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { return super.toString(); @@ -539,8 +558,8 @@ public String toString() { */ @Override public void clear() { - final E[] items = this.items; - final Monitor monitor = this.monitor; + E[] items = this.items; + Monitor monitor = this.monitor; monitor.enter(); try { int i = takeIndex; @@ -563,12 +582,13 @@ public void clear() { * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ + @CanIgnoreReturnValue @Override public int drainTo(Collection c) { if (c == null) throw new NullPointerException(); if (c == this) throw new IllegalArgumentException(); - final E[] items = this.items; - final Monitor monitor = this.monitor; + E[] items = this.items; + Monitor monitor = this.monitor; monitor.enter(); try { int i = takeIndex; @@ -597,13 +617,14 @@ public int drainTo(Collection c) { * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ + @CanIgnoreReturnValue @Override public int drainTo(Collection c, int maxElements) { if (c == null) throw new NullPointerException(); if (c == this) throw new IllegalArgumentException(); if (maxElements <= 0) return 0; - final E[] items = this.items; - final Monitor monitor = this.monitor; + E[] items = this.items; + Monitor monitor = this.monitor; monitor.enter(); try { int i = takeIndex; @@ -626,17 +647,18 @@ public int drainTo(Collection c, int maxElements) { } /** - * Returns an iterator over the elements in this queue in proper sequence. The returned - * Iterator is a "weakly consistent" iterator that will never throw {@link + * Returns an iterator over the elements in this queue in proper sequence. The returned {@code + * Iterator} is a "weakly consistent" iterator that will never throw {@link * ConcurrentModificationException}, and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) reflect any modifications * subsequent to construction. * * @return an iterator over the elements in this queue in proper sequence */ + @CanIgnoreReturnValue @Override public Iterator iterator() { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { return new Itr(); @@ -655,7 +677,7 @@ private class Itr implements Iterator { * we must return it in the following next() call even if it was in the process of being removed * when hasNext() was called. */ - private E nextItem; + private @Nullable E nextItem; /** * Index of element returned by most recent call to next. Reset to -1 if this element is deleted @@ -698,7 +720,7 @@ private void checkNext() { @Override public E next() { - final Monitor monitor = MonitorBasedArrayBlockingQueue.this.monitor; + Monitor monitor = MonitorBasedArrayBlockingQueue.this.monitor; monitor.enter(); try { if (nextIndex < 0) throw new NoSuchElementException(); @@ -714,7 +736,7 @@ public E next() { @Override public void remove() { - final Monitor monitor = MonitorBasedArrayBlockingQueue.this.monitor; + Monitor monitor = MonitorBasedArrayBlockingQueue.this.monitor; monitor.enter(); try { int i = lastRet; diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBasedPriorityBlockingQueue.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBasedPriorityBlockingQueue.java index 715a9c815097..2ae67cf38331 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBasedPriorityBlockingQueue.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBasedPriorityBlockingQueue.java @@ -30,53 +30,61 @@ import java.util.SortedSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * An unbounded {@linkplain BlockingQueue blocking queue} that uses the same ordering rules as class * {@link PriorityQueue} and supplies blocking retrieval operations. While this queue is logically - * unbounded, attempted additions may fail due to resource exhaustion (causing - * OutOfMemoryError). This class does not permit null elements. A priority queue - * relying on {@linkplain Comparable natural ordering} also does not permit insertion of - * non-comparable objects (doing so results in ClassCastException). + * unbounded, attempted additions may fail due to resource exhaustion (causing {@code + * OutOfMemoryError}). This class does not permit {@code null} elements. A priority queue relying on + * {@linkplain Comparable natural ordering} also does not permit insertion of non-comparable objects + * (doing so results in {@code ClassCastException}). * *

    This class and its iterator implement all of the optional methods of the {@link * Collection} and {@link Iterator} interfaces. The Iterator provided in method {@link #iterator()} * is not guaranteed to traverse the elements of the MonitorBasedPriorityBlockingQueue in - * any particular order. If you need ordered traversal, consider using - * Arrays.sort(pq.toArray()). Also, method drainTo can be used to remove - * some or all elements in priority order and place them in another collection. + * any particular order. If you need ordered traversal, consider using {@code + * Arrays.sort(pq.toArray())}. Also, method {@code drainTo} can be used to remove some or + * all elements in priority order and place them in another collection. * *

    Operations on this class make no guarantees about the ordering of elements with equal * priority. If you need to enforce an ordering, you can define custom classes or comparators that * use a secondary key to break ties in primary priority values. For example, here is a class that * applies first-in-first-out tie-breaking to comparable elements. To use it, you would insert a - * new FIFOEntry(anEntry) instead of a plain entry object. + * {@code new FIFOEntry(anEntry)} instead of a plain entry object. + * + *

    {@code
    + * class FIFOEntry> implements Comparable> {
    + *   static final AtomicLong seq = new AtomicLong();
      *
    - * 
    - * class FIFOEntry<E extends Comparable<? super E>>
    - *     implements Comparable<FIFOEntry<E>> {
    - *   final static AtomicLong seq = new AtomicLong();
      *   final long seqNum;
      *   final E entry;
    + *
      *   public FIFOEntry(E entry) {
      *     seqNum = seq.getAndIncrement();
      *     this.entry = entry;
      *   }
    - *   public E getEntry() { return entry; }
    - *   public int compareTo(FIFOEntry<E> other) {
    + *
    + *   public E getEntry() {
    + *     return entry;
    + *   }
    + *
    + *   public int compareTo(FIFOEntry other) {
      *     int res = entry.compareTo(other.entry);
    - *     if (res == 0 && other.entry != this.entry)
    - *       res = (seqNum < other.seqNum ? -1 : 1);
    + *     if (res == 0 && other.entry != this.entry) {
    + *       res = (seqNum < other.seqNum ? -1 : 1);
    + *     }
      *     return res;
      *   }
    + * }
      * }
    * * @author Doug Lea * @author Justin T. Sampson * @param the type of elements held in this collection */ -@CanIgnoreReturnValue // TODO(cpovirk): Consider being more strict. +@NullUnmarked public class MonitorBasedPriorityBlockingQueue extends AbstractQueue implements BlockingQueue { @@ -96,40 +104,40 @@ public boolean isSatisfied() { }; /** - * Creates a MonitorBasedPriorityBlockingQueue with the default initial capacity (11) - * that orders its elements according to their {@linkplain Comparable natural ordering}. + * Creates a {@code MonitorBasedPriorityBlockingQueue} with the default initial capacity (11) that + * orders its elements according to their {@linkplain Comparable natural ordering}. */ public MonitorBasedPriorityBlockingQueue() { q = new PriorityQueue(); } /** - * Creates a MonitorBasedPriorityBlockingQueue with the specified initial capacity that + * Creates a {@code MonitorBasedPriorityBlockingQueue} with the specified initial capacity that * orders its elements according to their {@linkplain Comparable natural ordering}. * * @param initialCapacity the initial capacity for this priority queue - * @throws IllegalArgumentException if initialCapacity is less than 1 + * @throws IllegalArgumentException if {@code initialCapacity} is less than 1 */ public MonitorBasedPriorityBlockingQueue(int initialCapacity) { q = new PriorityQueue(initialCapacity, null); } /** - * Creates a MonitorBasedPriorityBlockingQueue with the specified initial capacity that + * Creates a {@code MonitorBasedPriorityBlockingQueue} with the specified initial capacity that * orders its elements according to the specified comparator. * * @param initialCapacity the initial capacity for this priority queue * @param comparator the comparator that will be used to order this priority queue. If {@code * null}, the {@linkplain Comparable natural ordering} of the elements will be used. - * @throws IllegalArgumentException if initialCapacity is less than 1 + * @throws IllegalArgumentException if {@code initialCapacity} is less than 1 */ public MonitorBasedPriorityBlockingQueue( - int initialCapacity, @NullableDecl Comparator comparator) { + int initialCapacity, @Nullable Comparator comparator) { q = new PriorityQueue(initialCapacity, comparator); } /** - * Creates a MonitorBasedPriorityBlockingQueue containing the elements in the specified + * Creates a {@code MonitorBasedPriorityBlockingQueue} containing the elements in the specified * collection. If the specified collection is a {@link SortedSet} or a {@link PriorityQueue}, this * priority queue will be ordered according to the same ordering. Otherwise, this priority queue * will be ordered according to the {@linkplain Comparable natural ordering} of its elements. @@ -147,11 +155,12 @@ public MonitorBasedPriorityBlockingQueue(Collection c) { * Inserts the specified element into this priority queue. * * @param e the element to add - * @return true (as specified by {@link Collection#add}) + * @return {@code true} (as specified by {@link Collection#add}) * @throws ClassCastException if the specified element cannot be compared with elements currently * in the priority queue according to the priority queue's ordering * @throws NullPointerException if the specified element is null */ + @CanIgnoreReturnValue // pushed down from class to method @Override public boolean add(E e) { return offer(e); @@ -161,14 +170,15 @@ public boolean add(E e) { * Inserts the specified element into this priority queue. * * @param e the element to add - * @return true (as specified by {@link Queue#offer}) + * @return {@code true} (as specified by {@link Queue#offer}) * @throws ClassCastException if the specified element cannot be compared with elements currently * in the priority queue according to the priority queue's ordering * @throws NullPointerException if the specified element is null */ + @CanIgnoreReturnValue // pushed down from class to method @Override public boolean offer(E e) { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { boolean ok = q.offer(e); @@ -188,11 +198,12 @@ public boolean offer(E e) { * @param e the element to add * @param timeout This parameter is ignored as the method never blocks * @param unit This parameter is ignored as the method never blocks - * @return true + * @return {@code true} * @throws ClassCastException if the specified element cannot be compared with elements currently * in the priority queue according to the priority queue's ordering * @throws NullPointerException if the specified element is null */ + @CanIgnoreReturnValue // pushed down from class to method @Override public boolean offer(E e, long timeout, TimeUnit unit) { checkNotNull(unit); @@ -213,9 +224,10 @@ public void put(E e) { offer(e); // never need to block } + @CanIgnoreReturnValue // pushed down from class to method @Override - public E poll() { - final Monitor monitor = this.monitor; + public @Nullable E poll() { + Monitor monitor = this.monitor; monitor.enter(); try { return q.poll(); @@ -224,9 +236,10 @@ public E poll() { } } + @CanIgnoreReturnValue // pushed down from class to method @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { - final Monitor monitor = this.monitor; + public @Nullable E poll(long timeout, TimeUnit unit) throws InterruptedException { + Monitor monitor = this.monitor; if (monitor.enterWhen(notEmpty, timeout, unit)) { try { return q.poll(); @@ -238,9 +251,10 @@ public E poll(long timeout, TimeUnit unit) throws InterruptedException { } } + @CanIgnoreReturnValue // pushed down from class to method @Override public E take() throws InterruptedException { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enterWhen(notEmpty); try { return q.poll(); @@ -249,9 +263,10 @@ public E take() throws InterruptedException { } } + @CanIgnoreReturnValue // pushed down from class to method @Override - public E peek() { - final Monitor monitor = this.monitor; + public @Nullable E peek() { + Monitor monitor = this.monitor; monitor.enter(); try { return q.peek(); @@ -261,19 +276,21 @@ public E peek() { } /** - * Returns the comparator used to order the elements in this queue, or null if this queue + * Returns the comparator used to order the elements in this queue, or {@code null} if this queue * uses the {@linkplain Comparable natural ordering} of its elements. * - * @return the comparator used to order the elements in this queue, or null if this queue + * @return the comparator used to order the elements in this queue, or {@code null} if this queue * uses the natural ordering of its elements */ + @CanIgnoreReturnValue // pushed down from class to method public Comparator comparator() { return q.comparator(); } + @CanIgnoreReturnValue // pushed down from class to method @Override public int size() { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { return q.size(); @@ -283,11 +300,12 @@ public int size() { } /** - * Always returns Integer.MAX_VALUE because a MonitorBasedPriorityBlockingQueue - * is not capacity constrained. + * Always returns {@code Integer.MAX_VALUE} because a {@code MonitorBasedPriorityBlockingQueue} is + * not capacity constrained. * - * @return Integer.MAX_VALUE + * @return {@code Integer.MAX_VALUE} */ + @CanIgnoreReturnValue // pushed down from class to method @Override public int remainingCapacity() { return Integer.MAX_VALUE; @@ -300,11 +318,12 @@ public int remainingCapacity() { * specified element (or equivalently, if this queue changed as a result of the call). * * @param o element to be removed from this queue, if present - * @return true if this queue changed as a result of the call + * @return {@code true} if this queue changed as a result of the call */ + @CanIgnoreReturnValue // pushed down from class to method @Override - public boolean remove(@NullableDecl Object o) { - final Monitor monitor = this.monitor; + public boolean remove(@Nullable Object o) { + Monitor monitor = this.monitor; monitor.enter(); try { return q.remove(o); @@ -319,11 +338,12 @@ public boolean remove(@NullableDecl Object o) { * o.equals(e)}. * * @param o object to be checked for containment in this queue - * @return true if this queue contains the specified element + * @return {@code true} if this queue contains the specified element */ + @CanIgnoreReturnValue // pushed down from class to method @Override - public boolean contains(@NullableDecl Object o) { - final Monitor monitor = this.monitor; + public boolean contains(@Nullable Object o) { + Monitor monitor = this.monitor; monitor.enter(); try { return q.contains(o); @@ -344,9 +364,10 @@ public boolean contains(@NullableDecl Object o) { * * @return an array containing all of the elements in this queue */ + @CanIgnoreReturnValue // pushed down from class to method @Override public Object[] toArray() { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { return q.toArray(); @@ -363,19 +384,19 @@ public Object[] toArray() { * *

    If this queue fits in the specified array with room to spare (i.e., the array has more * elements than this queue), the element in the array immediately following the end of the queue - * is set to null. + * is set to {@code null}. * *

    Like the {@link #toArray()} method, this method acts as bridge between array-based and * collection-based APIs. Further, this method allows precise control over the runtime type of the * output array, and may, under certain circumstances, be used to save allocation costs. * - *

    Suppose x is a queue known to contain only strings. The following code can be used - * to dump the queue into a newly allocated array of String: + *

    Suppose {@code x} is a queue known to contain only strings. The following code can be used + * to dump the queue into a newly allocated array of {@code String}: * *

        *     String[] y = x.toArray(new String[0]);
    * - *

    Note that toArray(new Object[0]) is identical in function to toArray(). + *

    Note that {@code toArray(new Object[0])} is identical in function to {@code toArray()}. * * @param a the array into which the elements of the queue are to be stored, if it is big enough; * otherwise, a new array of the same runtime type is allocated for this purpose @@ -384,9 +405,10 @@ public Object[] toArray() { * the runtime type of every element in this queue * @throws NullPointerException if the specified array is null */ + @CanIgnoreReturnValue // pushed down from class to method @Override public T[] toArray(T[] a) { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { return q.toArray(a); @@ -395,9 +417,10 @@ public T[] toArray(T[] a) { } } + @CanIgnoreReturnValue // pushed down from class to method @Override public String toString() { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { return q.toString(); @@ -412,11 +435,12 @@ public String toString() { * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ + @CanIgnoreReturnValue // pushed down from class to method @Override public int drainTo(Collection c) { if (c == null) throw new NullPointerException(); if (c == this) throw new IllegalArgumentException(); - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { int n = 0; @@ -437,12 +461,13 @@ public int drainTo(Collection c) { * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ + @CanIgnoreReturnValue // pushed down from class to method @Override public int drainTo(Collection c, int maxElements) { if (c == null) throw new NullPointerException(); if (c == this) throw new IllegalArgumentException(); if (maxElements <= 0) return 0; - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { int n = 0; @@ -463,7 +488,7 @@ public int drainTo(Collection c, int maxElements) { */ @Override public void clear() { - final Monitor monitor = this.monitor; + Monitor monitor = this.monitor; monitor.enter(); try { q.clear(); @@ -474,13 +499,14 @@ public void clear() { /** * Returns an iterator over the elements in this queue. The iterator does not return the elements - * in any particular order. The returned Iterator is a "weakly consistent" iterator that + * in any particular order. The returned {@code Iterator} is a "weakly consistent" iterator that * will never throw {@link ConcurrentModificationException}, and guarantees to traverse elements * as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect * any modifications subsequent to construction. * * @return an iterator over the elements in this queue */ + @CanIgnoreReturnValue // pushed down from class to method @Override public Iterator iterator() { return new Itr(toArray()); @@ -497,11 +523,13 @@ private class Itr implements Iterator { this.array = array; } + @CanIgnoreReturnValue // pushed down from class to method @Override public boolean hasNext() { return cursor < array.length; } + @CanIgnoreReturnValue // pushed down from class to method @Override public E next() { if (cursor >= array.length) throw new NoSuchElementException(); @@ -533,19 +561,4 @@ public void remove() { } } } - - /** - * Saves the state to a stream (that is, serializes it). This merely wraps default serialization - * within the monitor. The serialization strategy for items is left to underlying Queue. Note that - * locking is not needed on deserialization, so readObject is not defined, just relying on - * default. - */ - private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { - monitor.enter(); - try { - s.defaultWriteObject(); - } finally { - monitor.leave(); - } - } } diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBenchmark.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBenchmark.java index 692017d786c2..adef4f5cd42b 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/MonitorBenchmark.java @@ -21,12 +21,14 @@ import com.google.caliper.Param; import java.lang.reflect.Constructor; import java.util.concurrent.BlockingQueue; +import org.jspecify.annotations.NullUnmarked; /** * Benchmarks for {@link Monitor}. * * @author Justin T. Sampson */ +@NullUnmarked public class MonitorBenchmark { @Param({"10", "100", "1000"}) @@ -44,7 +46,7 @@ public class MonitorBenchmark { @SuppressWarnings("unchecked") void setUp() throws Exception { String prefix = - (useMonitor ? "com.google.common.util.concurrent.MonitorBased" : "java.util.concurrent."); + useMonitor ? "com.google.common.util.concurrent.MonitorBased" : "java.util.concurrent."; String className = prefix + queueType + "BlockingQueue"; Constructor constructor = Class.forName(className).getConstructor(int.class); queue = (BlockingQueue) constructor.newInstance(capacity); diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/MoreExecutorsDirectExecutorBenchmark.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/MoreExecutorsDirectExecutorBenchmark.java index f64ae389eefe..ef5ea2756269 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/MoreExecutorsDirectExecutorBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/MoreExecutorsDirectExecutorBenchmark.java @@ -29,12 +29,14 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import org.jspecify.annotations.NullUnmarked; /** * A benchmark comparing the {@link MoreExecutors#newDirectExecutorService()} to {@link * MoreExecutors#directExecutor}. */ @VmOptions({"-Xms12g", "-Xmx12g", "-d64"}) +@NullUnmarked public class MoreExecutorsDirectExecutorBenchmark { enum Impl { EXECUTOR_SERVICE { @@ -103,8 +105,8 @@ Object measureSize() { @Benchmark int timeUncontendedExecute(int reps) { - final Executor executor = this.executor; - final CountingRunnable countingRunnable = this.countingRunnable; + Executor executor = this.executor; + CountingRunnable countingRunnable = this.countingRunnable; for (int i = 0; i < reps; i++) { executor.execute(countingRunnable); } @@ -113,13 +115,13 @@ int timeUncontendedExecute(int reps) { @Benchmark int timeContendedExecute(int reps) { - final Executor executor = this.executor; + Executor executor = this.executor; for (Thread thread : threads) { if (!thread.isAlive()) { thread.start(); } } - final CountingRunnable countingRunnable = this.countingRunnable; + CountingRunnable countingRunnable = this.countingRunnable; for (int i = 0; i < reps; i++) { executor.execute(countingRunnable); } diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/SingleThreadAbstractFutureBenchmark.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/SingleThreadAbstractFutureBenchmark.java index a9b0ae5eac51..3893e486ef56 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/SingleThreadAbstractFutureBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/SingleThreadAbstractFutureBenchmark.java @@ -16,6 +16,9 @@ package com.google.common.util.concurrent; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; @@ -26,11 +29,12 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.NullUnmarked; /** A benchmark that times how long it takes to add a given number of */ @VmOptions({"-Xms8g", "-Xmx8g"}) +@NullUnmarked public class SingleThreadAbstractFutureBenchmark { @Param Impl impl; @@ -47,7 +51,7 @@ public long timeComplete_Normal(int reps) throws Exception { long r = 0; List> list = new ArrayList<>(reps); for (int i = 0; i < reps; i++) { - final Facade localFuture = impl.newFacade(); + Facade localFuture = impl.newFacade(); list.add(localFuture); localFuture.set(i); } @@ -62,7 +66,7 @@ public long timeComplete_Failure(int reps) throws Exception { long r = 0; List> list = new ArrayList<>(reps); for (int i = 0; i < reps; i++) { - final Facade localFuture = impl.newFacade(); + Facade localFuture = impl.newFacade(); list.add(localFuture); localFuture.setException(exception); } @@ -83,7 +87,7 @@ public long timeComplete_Cancel(int reps) throws Exception { long r = 0; List> list = new ArrayList<>(reps); for (int i = 0; i < reps; i++) { - final Facade localFuture = impl.newFacade(); + Facade localFuture = impl.newFacade(); list.add(localFuture); localFuture.cancel(false); } @@ -105,7 +109,7 @@ public long timeGetWith0Timeout(long reps) throws Exception { long r = 0; for (int i = 0; i < reps; i++) { try { - f.get(0, TimeUnit.SECONDS); + f.get(0, SECONDS); r += 1; } catch (TimeoutException e) { r += 2; @@ -120,7 +124,7 @@ public long timeGetWithSmallTimeout(long reps) throws Exception { long r = 0; for (int i = 0; i < reps; i++) { try { - f.get(500, TimeUnit.NANOSECONDS); + f.get(500, NANOSECONDS); r += 1; } catch (TimeoutException e) { r += 2; diff --git a/android/guava-tests/benchmark/com/google/common/util/concurrent/StripedBenchmark.java b/android/guava-tests/benchmark/com/google/common/util/concurrent/StripedBenchmark.java index 03c90d31dd89..3c1401e901c8 100644 --- a/android/guava-tests/benchmark/com/google/common/util/concurrent/StripedBenchmark.java +++ b/android/guava-tests/benchmark/com/google/common/util/concurrent/StripedBenchmark.java @@ -33,9 +33,11 @@ import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.jspecify.annotations.NullUnmarked; /** A benchmark comparing the various striped implementations. */ @VmOptions({"-Xms12g", "-Xmx12g", "-d64"}) +@NullUnmarked public class StripedBenchmark { private static final Supplier LOCK_SUPPLIER = new Supplier() { diff --git a/android/guava-tests/pom.xml b/android/guava-tests/pom.xml index 5f56f4b3a0bd..ad5835cb303a 100644 --- a/android/guava-tests/pom.xml +++ b/android/guava-tests/pom.xml @@ -5,7 +5,7 @@ com.google.guava guava-parent - HEAD-android-SNAPSHOT + 999.0.0-HEAD-android-SNAPSHOT guava-tests Guava Unit Tests @@ -22,12 +22,8 @@ test - com.google.code.findbugs - jsr305 - - - org.checkerframework - checker-compat-qual + org.jspecify + jspecify com.google.errorprone @@ -36,30 +32,43 @@ junit junit - - - org.easymock - easymock + 4.13.2 + test org.mockito mockito-core + 4.11.0 + test com.google.truth truth + ${truth.version} + test com.google.jimfs jimfs + 1.3.1 + test com.google.caliper caliper + 1.0-beta-3 + test + + org.mvnsearch + toolchains-maven-plugin + + + maven-toolchains-plugin + maven-compiler-plugin @@ -79,13 +88,6 @@ maven-jar-plugin - - default-jar - jar - - true - - create-test-jar test-jar @@ -93,15 +95,18 @@ - maven-deploy-plugin - 2.8.2 + org.sonatype.central + central-publishing-maven-plugin - true + true org.codehaus.mojo animal-sniffer-maven-plugin + + false + org.codehaus.mojo diff --git a/android/guava-tests/test/com/google/common/base/AbstractIteratorTest.java b/android/guava-tests/test/com/google/common/base/AbstractIteratorTest.java index da732bf54946..d063650639ad 100644 --- a/android/guava-tests/test/com/google/common/base/AbstractIteratorTest.java +++ b/android/guava-tests/test/com/google/common/base/AbstractIteratorTest.java @@ -16,20 +16,29 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.base.SneakyThrows.sneakyThrow; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.TestExceptions.SomeCheckedException; +import com.google.common.base.TestExceptions.SomeUncheckedException; import com.google.common.testing.GcFinalization; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code AbstractIterator}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class AbstractIteratorTest extends TestCase { public void testDefaultBehaviorOfNextAndHasNext() { @@ -41,7 +50,7 @@ public void testDefaultBehaviorOfNextAndHasNext() { private int rep; @Override - public Integer computeNext() { + public @Nullable Integer computeNext() { switch (rep++) { case 0: return 0; @@ -50,8 +59,7 @@ public Integer computeNext() { case 2: return endOfData(); default: - fail("Should not have been invoked again"); - return null; + throw new AssertionError("Should not have been invoked again"); } } }; @@ -70,11 +78,7 @@ public Integer computeNext() { // Make sure computeNext() doesn't get invoked again assertFalse(iter.hasNext()); - try { - iter.next(); - fail("no exception thrown"); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, iter::next); } public void testSneakyThrow() throws Exception { @@ -85,35 +89,22 @@ public void testSneakyThrow() throws Exception { @Override public Integer computeNext() { if (haveBeenCalled) { - fail("Should not have been called again"); + throw new AssertionError("Should not have been called again"); } else { haveBeenCalled = true; - sneakyThrow(new SomeCheckedException()); + throw sneakyThrow(new SomeCheckedException()); } - return null; // never reached } }; // The first time, the sneakily-thrown exception comes out - try { - iter.hasNext(); - fail("No exception thrown"); - } catch (Exception e) { - if (!(e instanceof SomeCheckedException)) { - throw e; - } - } - + assertThrows(SomeCheckedException.class, iter::hasNext); // But the second time, AbstractIterator itself throws an ISE - try { - iter.hasNext(); - fail("No exception thrown"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, iter::hasNext); } public void testException() { - final SomeUncheckedException exception = new SomeUncheckedException(); + SomeUncheckedException exception = new SomeUncheckedException(); Iterator iter = new AbstractIterator() { @Override @@ -123,12 +114,8 @@ public Integer computeNext() { }; // It should pass through untouched - try { - iter.hasNext(); - fail("No exception thrown"); - } catch (SomeUncheckedException e) { - assertSame(exception, e); - } + SomeUncheckedException e = assertThrows(SomeUncheckedException.class, iter::hasNext); + assertSame(exception, e); } public void testExceptionAfterEndOfData() { @@ -140,11 +127,7 @@ public Integer computeNext() { throw new SomeUncheckedException(); } }; - try { - iter.hasNext(); - fail("No exception thrown"); - } catch (SomeUncheckedException expected) { - } + assertThrows(SomeUncheckedException.class, iter::hasNext); } public void testCantRemove() { @@ -164,15 +147,13 @@ public Integer computeNext() { assertEquals(0, (int) iter.next()); - try { - iter.remove(); - fail("No exception thrown"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, iter::remove); } @GwtIncompatible // weak references + @J2ktIncompatible + @AndroidIncompatible // depends on details of GC public void testFreesNextReference() { Iterator itr = new AbstractIterator() { @@ -191,32 +172,13 @@ public void testReentrantHasNext() { @Override protected Integer computeNext() { boolean unused = hasNext(); - return null; + throw new AssertionError(); } }; - try { - iter.hasNext(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, iter::hasNext); } // Technically we should test other reentrant scenarios (4 combinations of // hasNext/next), but we'll cop out for now, knowing that // next() both start by invoking hasNext() anyway. - - /** Throws a undeclared checked exception. */ - private static void sneakyThrow(Throwable t) { - class SneakyThrower { - @SuppressWarnings("unchecked") // intentionally unsafe for test - void throwIt(Throwable t) throws T { - throw (T) t; - } - } - new SneakyThrower().throwIt(t); - } - - private static class SomeCheckedException extends Exception {} - - private static class SomeUncheckedException extends RuntimeException {} } diff --git a/android/guava-tests/test/com/google/common/base/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/base/AndroidIncompatible.java index 5e190a3e1871..9ed987a26292 100644 --- a/android/guava-tests/test/com/google/common/base/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/base/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    Why use a custom annotation instead of {@code android.test.suitebuilder.annotation.Suppress}? * I'm not completely sure that this is the right choice, but it has various advantages: diff --git a/android/guava-tests/test/com/google/common/base/AsciiTest.java b/android/guava-tests/test/com/google/common/base/AsciiTest.java index d3a1f8f6597e..6faf81046006 100644 --- a/android/guava-tests/test/com/google/common/base/AsciiTest.java +++ b/android/guava-tests/test/com/google/common/base/AsciiTest.java @@ -16,9 +16,12 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Ascii}. @@ -26,6 +29,7 @@ * @author Craig Berry */ @GwtCompatible +@NullUnmarked public class AsciiTest extends TestCase { /** @@ -54,8 +58,8 @@ public void testToUpperCase() { public void testCharsIgnored() { for (char c : IGNORED.toCharArray()) { String str = String.valueOf(c); - assertTrue(str, c == Ascii.toLowerCase(c)); - assertTrue(str, c == Ascii.toUpperCase(c)); + assertEquals(str, c, Ascii.toLowerCase(c)); + assertEquals(str, c, Ascii.toUpperCase(c)); assertFalse(str, Ascii.isLowerCase(c)); assertFalse(str, Ascii.isUpperCase(c)); } @@ -98,30 +102,13 @@ public void testTruncate() { } public void testTruncateIllegalArguments() { - String truncated = null; - try { - truncated = Ascii.truncate("foobar", 2, "..."); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Ascii.truncate("foobar", 2, "...")); - try { - truncated = Ascii.truncate("foobar", 8, "1234567890"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Ascii.truncate("foobar", 8, "1234567890")); - try { - truncated = Ascii.truncate("foobar", -1, "..."); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Ascii.truncate("foobar", -1, "...")); - try { - truncated = Ascii.truncate("foobar", -1, ""); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Ascii.truncate("foobar", -1, "")); } public void testEqualsIgnoreCase() { diff --git a/android/guava-tests/test/com/google/common/base/BenchmarkHelpers.java b/android/guava-tests/test/com/google/common/base/BenchmarkHelpers.java index eda9074b5418..c97fd966c958 100644 --- a/android/guava-tests/test/com/google/common/base/BenchmarkHelpers.java +++ b/android/guava-tests/test/com/google/common/base/BenchmarkHelpers.java @@ -14,13 +14,16 @@ package com.google.common.base; +import org.jspecify.annotations.NullUnmarked; + /** * Common benchmarking utilities. * * @author Christopher Swenson * @author Louis Wasserman */ -class BenchmarkHelpers { +@NullUnmarked +final class BenchmarkHelpers { private static final String WHITESPACE_CHARACTERS = "\u00a0\u180e\u202f\t\n\013\f\r \u0085" + "\u1680\u2028\u2029\u205f\u3000\u2000\u2001\u2002\u2003\u2004\u2005" @@ -85,4 +88,6 @@ public enum SampleMatcherConfig { this.matchingChars = matchingChars; } } + + private BenchmarkHelpers() {} } diff --git a/android/guava-tests/test/com/google/common/base/CaseFormatTest.java b/android/guava-tests/test/com/google/common/base/CaseFormatTest.java index f08d9f93692c..ab78284747d0 100644 --- a/android/guava-tests/test/com/google/common/base/CaseFormatTest.java +++ b/android/guava-tests/test/com/google/common/base/CaseFormatTest.java @@ -21,31 +21,37 @@ import static com.google.common.base.CaseFormat.LOWER_UNDERSCORE; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link CaseFormat}. * * @author Mike Bostock */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class CaseFormatTest extends TestCase { public void testIdentity() { for (CaseFormat from : CaseFormat.values()) { - assertSame(from + " to " + from, "foo", from.to(from, "foo")); + assertWithMessage("%s to %s", from, from).that(from.to(from, "foo")).isSameInstanceAs("foo"); for (CaseFormat to : CaseFormat.values()) { - assertEquals(from + " to " + to, "", from.to(to, "")); - assertEquals(from + " to " + to, " ", from.to(to, " ")); + assertWithMessage("%s to %s", from, to).that(from.to(to, "")).isEmpty(); + assertWithMessage("%s to %s", from, to).that(from.to(to, " ")).isEqualTo(" "); } } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullArguments() { NullPointerTester tester = new NullPointerTester(); @@ -56,163 +62,167 @@ public void testNullArguments() { } public void testLowerHyphenToLowerHyphen() { - assertEquals("foo", LOWER_HYPHEN.to(LOWER_HYPHEN, "foo")); - assertEquals("foo-bar", LOWER_HYPHEN.to(LOWER_HYPHEN, "foo-bar")); + assertThat(LOWER_HYPHEN.to(LOWER_HYPHEN, "foo")).isEqualTo("foo"); + assertThat(LOWER_HYPHEN.to(LOWER_HYPHEN, "foo-bar")).isEqualTo("foo-bar"); } public void testLowerHyphenToLowerUnderscore() { - assertEquals("foo", LOWER_HYPHEN.to(LOWER_UNDERSCORE, "foo")); - assertEquals("foo_bar", LOWER_HYPHEN.to(LOWER_UNDERSCORE, "foo-bar")); + assertThat(LOWER_HYPHEN.to(LOWER_UNDERSCORE, "foo")).isEqualTo("foo"); + assertThat(LOWER_HYPHEN.to(LOWER_UNDERSCORE, "foo-bar")).isEqualTo("foo_bar"); } public void testLowerHyphenToLowerCamel() { - assertEquals("foo", LOWER_HYPHEN.to(LOWER_CAMEL, "foo")); - assertEquals("fooBar", LOWER_HYPHEN.to(LOWER_CAMEL, "foo-bar")); + assertThat(LOWER_HYPHEN.to(LOWER_CAMEL, "foo")).isEqualTo("foo"); + assertThat(LOWER_HYPHEN.to(LOWER_CAMEL, "foo-bar")).isEqualTo("fooBar"); } public void testLowerHyphenToUpperCamel() { - assertEquals("Foo", LOWER_HYPHEN.to(UPPER_CAMEL, "foo")); - assertEquals("FooBar", LOWER_HYPHEN.to(UPPER_CAMEL, "foo-bar")); + assertThat(LOWER_HYPHEN.to(UPPER_CAMEL, "foo")).isEqualTo("Foo"); + assertThat(LOWER_HYPHEN.to(UPPER_CAMEL, "foo-bar")).isEqualTo("FooBar"); } public void testLowerHyphenToUpperUnderscore() { - assertEquals("FOO", LOWER_HYPHEN.to(UPPER_UNDERSCORE, "foo")); - assertEquals("FOO_BAR", LOWER_HYPHEN.to(UPPER_UNDERSCORE, "foo-bar")); + assertThat(LOWER_HYPHEN.to(UPPER_UNDERSCORE, "foo")).isEqualTo("FOO"); + assertThat(LOWER_HYPHEN.to(UPPER_UNDERSCORE, "foo-bar")).isEqualTo("FOO_BAR"); } public void testLowerUnderscoreToLowerHyphen() { - assertEquals("foo", LOWER_UNDERSCORE.to(LOWER_HYPHEN, "foo")); - assertEquals("foo-bar", LOWER_UNDERSCORE.to(LOWER_HYPHEN, "foo_bar")); + assertThat(LOWER_UNDERSCORE.to(LOWER_HYPHEN, "foo")).isEqualTo("foo"); + assertThat(LOWER_UNDERSCORE.to(LOWER_HYPHEN, "foo_bar")).isEqualTo("foo-bar"); } public void testLowerUnderscoreToLowerUnderscore() { - assertEquals("foo", LOWER_UNDERSCORE.to(LOWER_UNDERSCORE, "foo")); - assertEquals("foo_bar", LOWER_UNDERSCORE.to(LOWER_UNDERSCORE, "foo_bar")); + assertThat(LOWER_UNDERSCORE.to(LOWER_UNDERSCORE, "foo")).isEqualTo("foo"); + assertThat(LOWER_UNDERSCORE.to(LOWER_UNDERSCORE, "foo_bar")).isEqualTo("foo_bar"); } public void testLowerUnderscoreToLowerCamel() { - assertEquals("foo", LOWER_UNDERSCORE.to(LOWER_CAMEL, "foo")); - assertEquals("fooBar", LOWER_UNDERSCORE.to(LOWER_CAMEL, "foo_bar")); + assertThat(LOWER_UNDERSCORE.to(LOWER_CAMEL, "foo")).isEqualTo("foo"); + assertThat(LOWER_UNDERSCORE.to(LOWER_CAMEL, "foo_bar")).isEqualTo("fooBar"); } public void testLowerUnderscoreToUpperCamel() { - assertEquals("Foo", LOWER_UNDERSCORE.to(UPPER_CAMEL, "foo")); - assertEquals("FooBar", LOWER_UNDERSCORE.to(UPPER_CAMEL, "foo_bar")); + assertThat(LOWER_UNDERSCORE.to(UPPER_CAMEL, "foo")).isEqualTo("Foo"); + assertThat(LOWER_UNDERSCORE.to(UPPER_CAMEL, "foo_bar")).isEqualTo("FooBar"); } public void testLowerUnderscoreToUpperUnderscore() { - assertEquals("FOO", LOWER_UNDERSCORE.to(UPPER_UNDERSCORE, "foo")); - assertEquals("FOO_BAR", LOWER_UNDERSCORE.to(UPPER_UNDERSCORE, "foo_bar")); + assertThat(LOWER_UNDERSCORE.to(UPPER_UNDERSCORE, "foo")).isEqualTo("FOO"); + assertThat(LOWER_UNDERSCORE.to(UPPER_UNDERSCORE, "foo_bar")).isEqualTo("FOO_BAR"); } public void testLowerCamelToLowerHyphen() { - assertEquals("foo", LOWER_CAMEL.to(LOWER_HYPHEN, "foo")); - assertEquals("foo-bar", LOWER_CAMEL.to(LOWER_HYPHEN, "fooBar")); - assertEquals("h-t-t-p", LOWER_CAMEL.to(LOWER_HYPHEN, "HTTP")); + assertThat(LOWER_CAMEL.to(LOWER_HYPHEN, "foo")).isEqualTo("foo"); + assertThat(LOWER_CAMEL.to(LOWER_HYPHEN, "fooBar")).isEqualTo("foo-bar"); + assertThat(LOWER_CAMEL.to(LOWER_HYPHEN, "HTTP")).isEqualTo("h-t-t-p"); } public void testLowerCamelToLowerUnderscore() { - assertEquals("foo", LOWER_CAMEL.to(LOWER_UNDERSCORE, "foo")); - assertEquals("foo_bar", LOWER_CAMEL.to(LOWER_UNDERSCORE, "fooBar")); - assertEquals("h_t_t_p", LOWER_CAMEL.to(LOWER_UNDERSCORE, "hTTP")); + assertThat(LOWER_CAMEL.to(LOWER_UNDERSCORE, "foo")).isEqualTo("foo"); + assertThat(LOWER_CAMEL.to(LOWER_UNDERSCORE, "fooBar")).isEqualTo("foo_bar"); + assertThat(LOWER_CAMEL.to(LOWER_UNDERSCORE, "hTTP")).isEqualTo("h_t_t_p"); } public void testLowerCamelToLowerCamel() { - assertEquals("foo", LOWER_CAMEL.to(LOWER_CAMEL, "foo")); - assertEquals("fooBar", LOWER_CAMEL.to(LOWER_CAMEL, "fooBar")); + assertThat(LOWER_CAMEL.to(LOWER_CAMEL, "foo")).isEqualTo("foo"); + assertThat(LOWER_CAMEL.to(LOWER_CAMEL, "fooBar")).isEqualTo("fooBar"); } public void testLowerCamelToUpperCamel() { - assertEquals("Foo", LOWER_CAMEL.to(UPPER_CAMEL, "foo")); - assertEquals("FooBar", LOWER_CAMEL.to(UPPER_CAMEL, "fooBar")); - assertEquals("HTTP", LOWER_CAMEL.to(UPPER_CAMEL, "hTTP")); + assertThat(LOWER_CAMEL.to(UPPER_CAMEL, "foo")).isEqualTo("Foo"); + assertThat(LOWER_CAMEL.to(UPPER_CAMEL, "fooBar")).isEqualTo("FooBar"); + assertThat(LOWER_CAMEL.to(UPPER_CAMEL, "hTTP")).isEqualTo("HTTP"); } public void testLowerCamelToUpperUnderscore() { - assertEquals("FOO", LOWER_CAMEL.to(UPPER_UNDERSCORE, "foo")); - assertEquals("FOO_BAR", LOWER_CAMEL.to(UPPER_UNDERSCORE, "fooBar")); + assertThat(LOWER_CAMEL.to(UPPER_UNDERSCORE, "foo")).isEqualTo("FOO"); + assertThat(LOWER_CAMEL.to(UPPER_UNDERSCORE, "fooBar")).isEqualTo("FOO_BAR"); } public void testUpperCamelToLowerHyphen() { - assertEquals("foo", UPPER_CAMEL.to(LOWER_HYPHEN, "Foo")); - assertEquals("foo-bar", UPPER_CAMEL.to(LOWER_HYPHEN, "FooBar")); + assertThat(UPPER_CAMEL.to(LOWER_HYPHEN, "Foo")).isEqualTo("foo"); + assertThat(UPPER_CAMEL.to(LOWER_HYPHEN, "FooBar")).isEqualTo("foo-bar"); } public void testUpperCamelToLowerUnderscore() { - assertEquals("foo", UPPER_CAMEL.to(LOWER_UNDERSCORE, "Foo")); - assertEquals("foo_bar", UPPER_CAMEL.to(LOWER_UNDERSCORE, "FooBar")); + assertThat(UPPER_CAMEL.to(LOWER_UNDERSCORE, "Foo")).isEqualTo("foo"); + assertThat(UPPER_CAMEL.to(LOWER_UNDERSCORE, "FooBar")).isEqualTo("foo_bar"); } public void testUpperCamelToLowerCamel() { - assertEquals("foo", UPPER_CAMEL.to(LOWER_CAMEL, "Foo")); - assertEquals("fooBar", UPPER_CAMEL.to(LOWER_CAMEL, "FooBar")); - assertEquals("hTTP", UPPER_CAMEL.to(LOWER_CAMEL, "HTTP")); + assertThat(UPPER_CAMEL.to(LOWER_CAMEL, "Foo")).isEqualTo("foo"); + assertThat(UPPER_CAMEL.to(LOWER_CAMEL, "FooBar")).isEqualTo("fooBar"); + assertThat(UPPER_CAMEL.to(LOWER_CAMEL, "HTTP")).isEqualTo("hTTP"); } public void testUpperCamelToUpperCamel() { - assertEquals("Foo", UPPER_CAMEL.to(UPPER_CAMEL, "Foo")); - assertEquals("FooBar", UPPER_CAMEL.to(UPPER_CAMEL, "FooBar")); + assertThat(UPPER_CAMEL.to(UPPER_CAMEL, "Foo")).isEqualTo("Foo"); + assertThat(UPPER_CAMEL.to(UPPER_CAMEL, "FooBar")).isEqualTo("FooBar"); } public void testUpperCamelToUpperUnderscore() { - assertEquals("FOO", UPPER_CAMEL.to(UPPER_UNDERSCORE, "Foo")); - assertEquals("FOO_BAR", UPPER_CAMEL.to(UPPER_UNDERSCORE, "FooBar")); - assertEquals("H_T_T_P", UPPER_CAMEL.to(UPPER_UNDERSCORE, "HTTP")); - assertEquals("H__T__T__P", UPPER_CAMEL.to(UPPER_UNDERSCORE, "H_T_T_P")); + assertThat(UPPER_CAMEL.to(UPPER_UNDERSCORE, "Foo")).isEqualTo("FOO"); + assertThat(UPPER_CAMEL.to(UPPER_UNDERSCORE, "FooBar")).isEqualTo("FOO_BAR"); + assertThat(UPPER_CAMEL.to(UPPER_UNDERSCORE, "HTTP")).isEqualTo("H_T_T_P"); + assertThat(UPPER_CAMEL.to(UPPER_UNDERSCORE, "H_T_T_P")).isEqualTo("H__T__T__P"); } public void testUpperUnderscoreToLowerHyphen() { - assertEquals("foo", UPPER_UNDERSCORE.to(LOWER_HYPHEN, "FOO")); - assertEquals("foo-bar", UPPER_UNDERSCORE.to(LOWER_HYPHEN, "FOO_BAR")); + assertThat(UPPER_UNDERSCORE.to(LOWER_HYPHEN, "FOO")).isEqualTo("foo"); + assertThat(UPPER_UNDERSCORE.to(LOWER_HYPHEN, "FOO_BAR")).isEqualTo("foo-bar"); } public void testUpperUnderscoreToLowerUnderscore() { - assertEquals("foo", UPPER_UNDERSCORE.to(LOWER_UNDERSCORE, "FOO")); - assertEquals("foo_bar", UPPER_UNDERSCORE.to(LOWER_UNDERSCORE, "FOO_BAR")); + assertThat(UPPER_UNDERSCORE.to(LOWER_UNDERSCORE, "FOO")).isEqualTo("foo"); + assertThat(UPPER_UNDERSCORE.to(LOWER_UNDERSCORE, "FOO_BAR")).isEqualTo("foo_bar"); } public void testUpperUnderscoreToLowerCamel() { - assertEquals("foo", UPPER_UNDERSCORE.to(LOWER_CAMEL, "FOO")); - assertEquals("fooBar", UPPER_UNDERSCORE.to(LOWER_CAMEL, "FOO_BAR")); + assertThat(UPPER_UNDERSCORE.to(LOWER_CAMEL, "FOO")).isEqualTo("foo"); + assertThat(UPPER_UNDERSCORE.to(LOWER_CAMEL, "FOO_BAR")).isEqualTo("fooBar"); } public void testUpperUnderscoreToUpperCamel() { - assertEquals("Foo", UPPER_UNDERSCORE.to(UPPER_CAMEL, "FOO")); - assertEquals("FooBar", UPPER_UNDERSCORE.to(UPPER_CAMEL, "FOO_BAR")); - assertEquals("HTTP", UPPER_UNDERSCORE.to(UPPER_CAMEL, "H_T_T_P")); + assertThat(UPPER_UNDERSCORE.to(UPPER_CAMEL, "FOO")).isEqualTo("Foo"); + assertThat(UPPER_UNDERSCORE.to(UPPER_CAMEL, "FOO_BAR")).isEqualTo("FooBar"); + assertThat(UPPER_UNDERSCORE.to(UPPER_CAMEL, "H_T_T_P")).isEqualTo("HTTP"); } public void testUpperUnderscoreToUpperUnderscore() { - assertEquals("FOO", UPPER_UNDERSCORE.to(UPPER_UNDERSCORE, "FOO")); - assertEquals("FOO_BAR", UPPER_UNDERSCORE.to(UPPER_UNDERSCORE, "FOO_BAR")); + assertThat(UPPER_UNDERSCORE.to(UPPER_UNDERSCORE, "FOO")).isEqualTo("FOO"); + assertThat(UPPER_UNDERSCORE.to(UPPER_UNDERSCORE, "FOO_BAR")).isEqualTo("FOO_BAR"); } public void testConverterToForward() { - assertEquals("FooBar", UPPER_UNDERSCORE.converterTo(UPPER_CAMEL).convert("FOO_BAR")); - assertEquals("fooBar", UPPER_UNDERSCORE.converterTo(LOWER_CAMEL).convert("FOO_BAR")); - assertEquals("FOO_BAR", UPPER_CAMEL.converterTo(UPPER_UNDERSCORE).convert("FooBar")); - assertEquals("FOO_BAR", LOWER_CAMEL.converterTo(UPPER_UNDERSCORE).convert("fooBar")); + assertThat(UPPER_UNDERSCORE.converterTo(UPPER_CAMEL).convert("FOO_BAR")).isEqualTo("FooBar"); + assertThat(UPPER_UNDERSCORE.converterTo(LOWER_CAMEL).convert("FOO_BAR")).isEqualTo("fooBar"); + assertThat(UPPER_CAMEL.converterTo(UPPER_UNDERSCORE).convert("FooBar")).isEqualTo("FOO_BAR"); + assertThat(LOWER_CAMEL.converterTo(UPPER_UNDERSCORE).convert("fooBar")).isEqualTo("FOO_BAR"); } public void testConverterToBackward() { - assertEquals("FOO_BAR", UPPER_UNDERSCORE.converterTo(UPPER_CAMEL).reverse().convert("FooBar")); - assertEquals("FOO_BAR", UPPER_UNDERSCORE.converterTo(LOWER_CAMEL).reverse().convert("fooBar")); - assertEquals("FooBar", UPPER_CAMEL.converterTo(UPPER_UNDERSCORE).reverse().convert("FOO_BAR")); - assertEquals("fooBar", LOWER_CAMEL.converterTo(UPPER_UNDERSCORE).reverse().convert("FOO_BAR")); + assertThat(UPPER_UNDERSCORE.converterTo(UPPER_CAMEL).reverse().convert("FooBar")) + .isEqualTo("FOO_BAR"); + assertThat(UPPER_UNDERSCORE.converterTo(LOWER_CAMEL).reverse().convert("fooBar")) + .isEqualTo("FOO_BAR"); + assertThat(UPPER_CAMEL.converterTo(UPPER_UNDERSCORE).reverse().convert("FOO_BAR")) + .isEqualTo("FooBar"); + assertThat(LOWER_CAMEL.converterTo(UPPER_UNDERSCORE).reverse().convert("FOO_BAR")) + .isEqualTo("fooBar"); } public void testConverter_nullConversions() { for (CaseFormat outer : CaseFormat.values()) { for (CaseFormat inner : CaseFormat.values()) { - assertNull(outer.converterTo(inner).convert(null)); - assertNull(outer.converterTo(inner).reverse().convert(null)); + assertThat(outer.converterTo(inner).convert(null)).isNull(); + assertThat(outer.converterTo(inner).reverse().convert(null)).isNull(); } } } public void testConverter_toString() { - assertEquals( - "LOWER_HYPHEN.converterTo(UPPER_CAMEL)", LOWER_HYPHEN.converterTo(UPPER_CAMEL).toString()); + assertThat(LOWER_HYPHEN.converterTo(UPPER_CAMEL).toString()) + .isEqualTo("LOWER_HYPHEN.converterTo(UPPER_CAMEL)"); } public void testConverter_serialization() { diff --git a/android/guava-tests/test/com/google/common/base/CharMatcherTest.java b/android/guava-tests/test/com/google/common/base/CharMatcherTest.java index 5412882c5641..bd8cea6bfc41 100644 --- a/android/guava-tests/test/com/google/common/base/CharMatcherTest.java +++ b/android/guava-tests/test/com/google/common/base/CharMatcherTest.java @@ -24,9 +24,11 @@ import static com.google.common.base.CharMatcher.isNot; import static com.google.common.base.CharMatcher.noneOf; import static com.google.common.base.CharMatcher.whitespace; +import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Sets; import com.google.common.testing.NullPointerTester; import java.util.Arrays; @@ -36,15 +38,18 @@ import java.util.Set; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link CharMatcher}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class CharMatcherTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testStaticNullPointers() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -90,6 +95,7 @@ public void testWhitespaceBreakingWhitespaceSubset() throws Exception { // The next tests require ICU4J and have, at least for now, been sliced out // of the open-source view of the tests. + @J2ktIncompatible @GwtIncompatible // Character.isISOControl public void testJavaIsoControl() { for (int c = 0; c <= Character.MAX_VALUE; c++) { @@ -151,6 +157,7 @@ public void testEmpty() throws Exception { doTestEmpty(forPredicate(Predicates.equalTo('c'))); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNull() throws Exception { doTestNull(CharMatcher.any()); @@ -196,6 +203,7 @@ private void reallyTestEmpty(CharMatcher matcher) throws Exception { assertEquals(0, matcher.countIn("")); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester private static void doTestNull(CharMatcher matcher) throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -286,6 +294,7 @@ private void reallyTestNoMatches(CharMatcher matcher, CharSequence s) { assertEquals(0, matcher.countIn(s)); } + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 private void reallyTestAllMatches(CharMatcher matcher, CharSequence s) { assertTrue(matcher.matches(s.charAt(0))); assertEquals(0, matcher.indexIn(s)); @@ -303,6 +312,8 @@ private void reallyTestAllMatches(CharMatcher matcher, CharSequence s) { assertEquals(s.length(), matcher.countIn(s)); } + // Kotlin subSequence()/replace() always return new strings, violating expectations of this test + @J2ktIncompatible public void testGeneral() { doTestGeneral(is('a'), 'a', 'b'); doTestGeneral(isNot('a'), 'b', 'a'); @@ -352,7 +363,11 @@ private void doTestNoMatchThenMatch(CharMatcher matcher, String s) { reallyTestMatchThenNoMatch(matcher.precomputed().negate(), s); } - @SuppressWarnings("deprecation") // intentionally testing apply() method + // intentionally testing apply() method + @SuppressWarnings({ + "deprecation", + "InlineMeInliner", + }) private void reallyTestOneCharMatch(CharMatcher matcher, String s) { assertTrue(matcher.matches(s.charAt(0))); assertTrue(matcher.apply(s.charAt(0))); @@ -370,7 +385,11 @@ private void reallyTestOneCharMatch(CharMatcher matcher, String s) { assertEquals(1, matcher.countIn(s)); } - @SuppressWarnings("deprecation") // intentionally testing apply() method + // intentionally testing apply() method + @SuppressWarnings({ + "deprecation", + "InlineMeInliner", + }) private void reallyTestOneCharNoMatch(CharMatcher matcher, String s) { assertFalse(matcher.matches(s.charAt(0))); assertFalse(matcher.apply(s.charAt(0))); @@ -386,7 +405,7 @@ private void reallyTestOneCharNoMatch(CharMatcher matcher, String s) { assertSame(s, matcher.replaceFrom(s, 'z')); assertSame(s, matcher.replaceFrom(s, "ZZ")); assertSame(s, matcher.trimFrom(s)); - assertSame(0, matcher.countIn(s)); + assertEquals(0, matcher.countIn(s)); } private void reallyTestMatchThenNoMatch(CharMatcher matcher, String s) { @@ -644,6 +663,14 @@ public void testReplaceFrom() { assertEquals("12 > 5", is('>').replaceFrom("12 > 5", ">")); } + public void testRetainFrom() { + assertEquals("aaa", is('a').retainFrom("bazaar")); + assertEquals("z", is('z').retainFrom("bazaar")); + assertEquals("!", is('!').retainFrom("!@#$%^&*()-=")); + assertEquals("", is('x').retainFrom("bazaar")); + assertEquals("", is('a').retainFrom("")); + } + public void testPrecomputedOptimizations() { // These are testing behavior that's never promised by the API. // Some matchers are so efficient that it is a waste of effort to @@ -719,7 +746,7 @@ static void checkExactMatches(CharMatcher m, char[] chars) { positive.add(c); } for (int c = 0; c <= Character.MAX_VALUE; c++) { - assertFalse(positive.contains(new Character((char) c)) ^ m.matches((char) c)); + assertFalse(positive.contains(Character.valueOf((char) c)) ^ m.matches((char) c)); } } @@ -742,19 +769,46 @@ static char[] randomChars(Random rand, int size) { } public void testToString() { - assertToStringWorks("CharMatcher.none()", CharMatcher.anyOf("")); - assertToStringWorks("CharMatcher.is('\\u0031')", CharMatcher.anyOf("1")); - assertToStringWorks("CharMatcher.isNot('\\u0031')", CharMatcher.isNot('1')); - assertToStringWorks("CharMatcher.anyOf(\"\\u0031\\u0032\")", CharMatcher.anyOf("12")); - assertToStringWorks("CharMatcher.anyOf(\"\\u0031\\u0032\\u0033\")", CharMatcher.anyOf("321")); - assertToStringWorks("CharMatcher.inRange('\\u0031', '\\u0033')", CharMatcher.inRange('1', '3')); - } - - private static void assertToStringWorks(String expected, CharMatcher matcher) { - assertEquals(expected, matcher.toString()); - assertEquals(expected, matcher.precomputed().toString()); - assertEquals(expected, matcher.negate().negate().toString()); - assertEquals(expected, matcher.negate().precomputed().negate().toString()); - assertEquals(expected, matcher.negate().precomputed().negate().precomputed().toString()); + assertToStringWorks(CharMatcher.anyOf(""), "CharMatcher.none()"); + assertToStringWorks(CharMatcher.anyOf("1"), "CharMatcher.is('\\u0031')"); + assertToStringWorks(CharMatcher.isNot('1'), "CharMatcher.isNot('\\u0031')"); + assertToStringWorks(CharMatcher.anyOf("12"), "CharMatcher.anyOf(\"\\u0031\\u0032\")"); + assertToStringWorks(CharMatcher.anyOf("321"), "CharMatcher.anyOf(\"\\u0031\\u0032\\u0033\")"); + assertToStringWorks(CharMatcher.inRange('1', '3'), "CharMatcher.inRange('\\u0031', '\\u0033')"); + assertToStringWorks( + CharMatcher.is('0').or(is('1')), + /* expectedNormal= */ "CharMatcher.is('\\u0030').or(CharMatcher.is('\\u0031'))", + // .precomputed() optimizes + /* expectedPrecomputed= */ "CharMatcher.anyOf(\"\\u0030\\u0031\")"); + assertToStringWorks( + CharMatcher.digit().and(CharMatcher.ascii()), + "CharMatcher.digit().and(CharMatcher.ascii())"); + assertToStringWorks( + CharMatcher.inRange('a', 'z') + .or(CharMatcher.inRange('A', 'Z')) + .or(CharMatcher.inRange('0', '9')) + .or(CharMatcher.is('_')), + "CharMatcher.inRange('\\u0061', '\\u007A')" + + ".or(CharMatcher.inRange('\\u0041', '\\u005A'))" + + ".or(CharMatcher.inRange('\\u0030', '\\u0039'))" + + ".or(CharMatcher.is('\\u005F'))"); + } + + private static void assertToStringWorks(CharMatcher matcher, String expected) { + assertToStringWorks(matcher, expected, expected); + } + + private static void assertToStringWorks( + CharMatcher matcher, String expectedNormal, String expectedPrecomputed) { + assertThat(matcher.toString()).isEqualTo(expectedNormal); + assertThat(matcher.negate().negate().toString()).isEqualTo(expectedNormal); + + // The precomputed form is different on regular platforms but the same on j2cl and j2kt. + // Hence isAnyOf here. + assertThat(matcher.precomputed().toString()).isAnyOf(expectedNormal, expectedPrecomputed); + assertThat(matcher.negate().precomputed().negate().toString()) + .isAnyOf(expectedNormal, expectedPrecomputed); + assertThat(matcher.negate().precomputed().negate().precomputed().toString()) + .isAnyOf(expectedNormal, expectedPrecomputed); } } diff --git a/android/guava-tests/test/com/google/common/base/CharsetsTest.java b/android/guava-tests/test/com/google/common/base/CharsetsTest.java index c968c8d39746..ad252e87997a 100644 --- a/android/guava-tests/test/com/google/common/base/CharsetsTest.java +++ b/android/guava-tests/test/com/google/common/base/CharsetsTest.java @@ -16,25 +16,31 @@ package com.google.common.base; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.nio.charset.Charset; -import java.util.Arrays; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Charsets}. * * @author Mike Bostock */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class CharsetsTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // Non-UTF-8 Charset public void testUsAscii() { assertEquals(Charset.forName("US-ASCII"), Charsets.US_ASCII); } + @J2ktIncompatible @GwtIncompatible // Non-UTF-8 Charset public void testIso88591() { assertEquals(Charset.forName("ISO-8859-1"), Charsets.ISO_8859_1); @@ -44,21 +50,25 @@ public void testUtf8() { assertEquals(Charset.forName("UTF-8"), Charsets.UTF_8); } + @J2ktIncompatible @GwtIncompatible // Non-UTF-8 Charset public void testUtf16be() { assertEquals(Charset.forName("UTF-16BE"), Charsets.UTF_16BE); } + @J2ktIncompatible @GwtIncompatible // Non-UTF-8 Charset public void testUtf16le() { assertEquals(Charset.forName("UTF-16LE"), Charsets.UTF_16LE); } + @J2ktIncompatible @GwtIncompatible // Non-UTF-8 Charset public void testUtf16() { assertEquals(Charset.forName("UTF-16"), Charsets.UTF_16); } + @J2ktIncompatible @GwtIncompatible // Non-UTF-8 Charset public void testWhyUsAsciiIsDangerous() { byte[] b1 = "朝日新聞".getBytes(Charsets.US_ASCII); @@ -67,9 +77,9 @@ public void testWhyUsAsciiIsDangerous() { byte[] b4 = "ニュース".getBytes(Charsets.US_ASCII); byte[] b5 = "スューー".getBytes(Charsets.US_ASCII); // Assert they are all equal (using the transitive property) - assertTrue(Arrays.equals(b1, b2)); - assertTrue(Arrays.equals(b2, b3)); - assertTrue(Arrays.equals(b3, b4)); - assertTrue(Arrays.equals(b4, b5)); + assertThat(b1).isEqualTo(b2); + assertThat(b2).isEqualTo(b3); + assertThat(b3).isEqualTo(b4); + assertThat(b4).isEqualTo(b5); } } diff --git a/android/guava-tests/test/com/google/common/base/ConverterTest.java b/android/guava-tests/test/com/google/common/base/ConverterTest.java index c787ef004c40..f66e8542efa8 100644 --- a/android/guava-tests/test/com/google/common/base/ConverterTest.java +++ b/android/guava-tests/test/com/google/common/base/ConverterTest.java @@ -17,8 +17,11 @@ package com.google.common.base; import static com.google.common.base.Functions.toStringFunction; +import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.primitives.Longs; @@ -27,9 +30,11 @@ import java.util.Iterator; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link Converter}. */ @GwtCompatible +@NullUnmarked public class ConverterTest extends TestCase { private static final Converter STR_TO_LONG = @@ -99,6 +104,8 @@ public void testReverseReverse() { assertEquals(converter, converter.reverse().reverse()); } + // We need to test that apply() does in fact behave like convert(). + @SuppressWarnings("InlineMeInliner") public void testApply() { assertEquals(LONG_VAL, STR_TO_LONG.apply(STR_VAL)); } @@ -106,11 +113,12 @@ public void testApply() { private static class StringWrapper { private final String value; - public StringWrapper(String value) { + StringWrapper(String value) { this.value = value; } } + @GwtIncompatible // J2CL generics problem public void testAndThen() { Converter first = new Converter() { @@ -137,9 +145,12 @@ public String toString() { assertEquals("StringWrapper.andThen(string2long)", converter.toString()); - assertEquals(first.andThen(STR_TO_LONG), first.andThen(STR_TO_LONG)); + new EqualsTester() + .addEqualityGroup(first.andThen(STR_TO_LONG), first.andThen(STR_TO_LONG)) + .testEquals(); } + @GwtIncompatible // J2CL generics problem public void testIdentityConverter() { Converter stringIdentityConverter = Converter.identity(); @@ -166,13 +177,15 @@ public Integer apply(String input) { Converter converter = Converter.from(forward, backward); - assertNull(converter.convert(null)); - assertNull(converter.reverse().convert(null)); + assertThat(converter.convert(null)).isNull(); + assertThat(converter.reverse().convert(null)).isNull(); assertEquals((Integer) 5, converter.convert("5")); assertEquals("5", converter.reverse().convert(5)); } + // Null-passthrough violates our nullness annotations, so we don't support it under J2KT. + @J2ktIncompatible public void testNullIsPassedThrough() { Converter nullsArePassed = sillyConverter(false); assertEquals("forward", nullsArePassed.convert("foo")); @@ -189,7 +202,7 @@ public void testNullIsNotPassedThrough() { assertEquals(null, nullsAreHandled.reverse().convert(null)); } - private static Converter sillyConverter(final boolean handleNullAutomatically) { + private static Converter sillyConverter(boolean handleNullAutomatically) { return new Converter(handleNullAutomatically) { @Override protected String doForward(String string) { @@ -213,6 +226,7 @@ public void testSerialization_reverse() { SerializableTester.reserializeAndAssert(reverseConverter); } + @GwtIncompatible // J2CL generics problem public void testSerialization_andThen() { Converter converterA = Longs.stringConverter(); Converter reverseConverter = Longs.stringConverter().reverse(); diff --git a/android/guava-tests/test/com/google/common/base/DefaultsTest.java b/android/guava-tests/test/com/google/common/base/DefaultsTest.java index 7b990ba5239a..8923a23884b8 100644 --- a/android/guava-tests/test/com/google/common/base/DefaultsTest.java +++ b/android/guava-tests/test/com/google/common/base/DefaultsTest.java @@ -16,13 +16,19 @@ package com.google.common.base; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.annotations.GwtIncompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Defaults}. * * @author Jige Yu */ +@GwtIncompatible +@NullUnmarked public class DefaultsTest extends TestCase { public void testGetDefaultValue() { assertEquals(false, Defaults.defaultValue(boolean.class).booleanValue()); @@ -32,8 +38,8 @@ public void testGetDefaultValue() { assertEquals(0, Defaults.defaultValue(int.class).intValue()); assertEquals(0, Defaults.defaultValue(long.class).longValue()); assertEquals(0.0f, Defaults.defaultValue(float.class).floatValue()); - assertEquals(0.0d, Defaults.defaultValue(double.class).doubleValue()); - assertNull(Defaults.defaultValue(void.class)); - assertNull(Defaults.defaultValue(String.class)); + assertThat(Defaults.defaultValue(double.class).doubleValue()).isEqualTo(0.0d); + assertThat(Defaults.defaultValue(void.class)).isNull(); + assertThat(Defaults.defaultValue(String.class)).isNull(); } } diff --git a/android/guava-tests/test/com/google/common/base/EnumsTest.java b/android/guava-tests/test/com/google/common/base/EnumsTest.java index d8b13af75423..b469aec9b2f5 100644 --- a/android/guava-tests/test/com/google/common/base/EnumsTest.java +++ b/android/guava-tests/test/com/google/common/base/EnumsTest.java @@ -19,9 +19,10 @@ import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH; import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; -import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.testing.GcFinalization; @@ -38,13 +39,16 @@ import java.util.HashSet; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Enums}. * * @author Steve McKay */ -@GwtCompatible(emulated = true) +@GwtIncompatible +@J2ktIncompatible +@NullUnmarked public class EnumsTest extends TestCase { private enum TestEnum { @@ -53,8 +57,6 @@ private enum TestEnum { POODLE, } - private enum OtherEnum {} - public void testGetIfPresent() { assertThat(Enums.getIfPresent(TestEnum.class, "CHEETO")).hasValue(TestEnum.CHEETO); assertThat(Enums.getIfPresent(TestEnum.class, "HONDA")).hasValue(TestEnum.HONDA); @@ -80,7 +82,8 @@ public void testGetIfPresent_whenNoMatchingConstant() { } - @GwtIncompatible // weak references + @J2ktIncompatible + @AndroidIncompatible // depends on details of GC and classloading public void testGetIfPresent_doesNotPreventClassUnloading() throws Exception { WeakReference shadowLoaderReference = doTestClassUnloading(); GcFinalization.awaitClear(shadowLoaderReference); @@ -91,7 +94,7 @@ public void testGetIfPresent_doesNotPreventClassUnloading() throws Exception { // new ClassLoader. If Enums.getIfPresent does caching that prevents the shadow TestEnum // (and therefore its ClassLoader) from being unloaded, then this WeakReference will never be // cleared. - @GwtIncompatible // weak references + @J2ktIncompatible private WeakReference doTestClassUnloading() throws Exception { URLClassLoader shadowLoader = new URLClassLoader(getClassPathUrls(), null); @SuppressWarnings("unchecked") @@ -117,17 +120,13 @@ public void testStringConverter_convert() { assertEquals(TestEnum.CHEETO, converter.convert("CHEETO")); assertEquals(TestEnum.HONDA, converter.convert("HONDA")); assertEquals(TestEnum.POODLE, converter.convert("POODLE")); - assertNull(converter.convert(null)); - assertNull(converter.reverse().convert(null)); + assertThat(converter.convert(null)).isNull(); + assertThat(converter.reverse().convert(null)).isNull(); } public void testStringConverter_convertError() { Converter converter = Enums.stringConverter(TestEnum.class); - try { - converter.convert("xxx"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> converter.convert("xxx")); } public void testStringConverter_reverse() { @@ -137,7 +136,7 @@ public void testStringConverter_reverse() { assertEquals("POODLE", converter.reverse().convert(TestEnum.POODLE)); } - @GwtIncompatible // NullPointerTester + @J2ktIncompatible public void testStringConverter_nullPointerTester() throws Exception { Converter converter = Enums.stringConverter(TestEnum.class); NullPointerTester tester = new NullPointerTester(); @@ -146,11 +145,11 @@ public void testStringConverter_nullPointerTester() throws Exception { public void testStringConverter_nullConversions() { Converter converter = Enums.stringConverter(TestEnum.class); - assertNull(converter.convert(null)); - assertNull(converter.reverse().convert(null)); + assertThat(converter.convert(null)).isNull(); + assertThat(converter.reverse().convert(null)).isNull(); } - @GwtIncompatible // Class.getName() + @J2ktIncompatible public void testStringConverter_toString() { assertEquals( "Enums.stringConverter(com.google.common.base.EnumsTest$TestEnum.class)", @@ -161,7 +160,7 @@ public void testStringConverter_serialization() { SerializableTester.reserializeAndAssert(Enums.stringConverter(TestEnum.class)); } - @GwtIncompatible // NullPointerTester + @J2ktIncompatible public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicStaticMethods(Enums.class); @@ -176,7 +175,7 @@ private enum AnEnum { BAR } - @GwtIncompatible // reflection + @J2ktIncompatible public void testGetField() { Field foo = Enums.getField(AnEnum.FOO); assertEquals("FOO", foo.getName()); @@ -187,7 +186,7 @@ public void testGetField() { assertFalse(bar.isAnnotationPresent(ExampleAnnotation.class)); } - @GwtIncompatible // Class.getClassLoader() + @J2ktIncompatible private URL[] getClassPathUrls() { ClassLoader classLoader = getClass().getClassLoader(); return classLoader instanceof URLClassLoader @@ -200,7 +199,7 @@ private URL[] getClassPathUrls() { * System#getProperty system property}. */ // TODO(b/65488446): Make this a public API. - @GwtIncompatible + @J2ktIncompatible private static ImmutableList parseJavaClassPath() { ImmutableList.Builder urls = ImmutableList.builder(); for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) { @@ -211,9 +210,7 @@ private static ImmutableList parseJavaClassPath() { urls.add(new URL("file", null, new File(entry).getAbsolutePath())); } } catch (MalformedURLException e) { - AssertionError error = new AssertionError("malformed class path entry: " + entry); - error.initCause(e); - throw error; + throw new AssertionError("malformed class path entry: " + entry, e); } } return urls.build(); diff --git a/android/guava-tests/test/com/google/common/base/EquivalenceTest.java b/android/guava-tests/test/com/google/common/base/EquivalenceTest.java index 07c86eae8270..2b1f602c73fa 100644 --- a/android/guava-tests/test/com/google/common/base/EquivalenceTest.java +++ b/android/guava-tests/test/com/google/common/base/EquivalenceTest.java @@ -18,6 +18,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.ImmutableList; import com.google.common.testing.EqualsTester; @@ -25,15 +26,17 @@ import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Equivalence}. * * @author Jige Yu */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class EquivalenceTest extends TestCase { - @SuppressWarnings("unchecked") // varargs public void testPairwiseEquivalent() { EquivalenceTester.of(Equivalence.equals().pairwise()) .addEquivalenceGroup(ImmutableList.of()) @@ -69,9 +72,11 @@ public void testWrap() { LENGTH_EQUIVALENCE.wrap("hello"), LENGTH_EQUIVALENCE.wrap("world")) .addEqualityGroup(LENGTH_EQUIVALENCE.wrap("hi"), LENGTH_EQUIVALENCE.wrap("yo")) - .addEqualityGroup(LENGTH_EQUIVALENCE.wrap(null), LENGTH_EQUIVALENCE.wrap(null)) + .addEqualityGroup( + LENGTH_EQUIVALENCE.<@Nullable String>wrap(null), + LENGTH_EQUIVALENCE.<@Nullable String>wrap(null)) .addEqualityGroup(Equivalence.equals().wrap("hello")) - .addEqualityGroup(Equivalence.equals().wrap(null)) + .addEqualityGroup(Equivalence.equals().<@Nullable Object>wrap(null)) .testEquals(); } @@ -81,6 +86,7 @@ public void testWrap_get() { assertSame(test, wrapper.get()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { SerializableTester.reserializeAndAssert(LENGTH_EQUIVALENCE.wrap("hello")); @@ -119,11 +125,11 @@ public void testOnResultOf_equals() { } public void testEquivalentTo() { - Predicate equalTo1 = Equivalence.equals().equivalentTo("1"); + Predicate<@Nullable Object> equalTo1 = Equivalence.equals().equivalentTo("1"); assertTrue(equalTo1.apply("1")); assertFalse(equalTo1.apply("2")); assertFalse(equalTo1.apply(null)); - Predicate isNull = Equivalence.equals().equivalentTo(null); + Predicate<@Nullable Object> isNull = Equivalence.equals().equivalentTo(null); assertFalse(isNull.apply("1")); assertFalse(isNull.apply("2")); assertTrue(isNull.apply(null)); @@ -135,17 +141,25 @@ public void testEquivalentTo() { .testEquals(); } + /* + * We use large numbers to avoid the integer cache. Normally, we'd accomplish that merely by using + * `new Integer` (as we do) instead of `Integer.valueOf`. However, under J2KT, `new Integer` + * gets translated back to `Integer.valueOf` because that is the only thing J2KT can support. And + * anyway, it's nice to avoid `Integer.valueOf` because the Android toolchain optimizes multiple + * `Integer.valueOf` calls into one! So we stick with the deprecated `Integer` constructor. + */ + public void testEqualsEquivalent() { EquivalenceTester.of(Equivalence.equals()) - .addEquivalenceGroup(new Integer(42), 42) + .addEquivalenceGroup(new Integer(42_000_000), 42_000_000) .addEquivalenceGroup("a") .test(); } public void testIdentityEquivalent() { EquivalenceTester.of(Equivalence.identity()) - .addEquivalenceGroup(new Integer(42)) - .addEquivalenceGroup(new Integer(42)) + .addEquivalenceGroup(new Integer(42_000_000)) + .addEquivalenceGroup(new Integer(42_000_000)) .addEquivalenceGroup("a") .test(); } @@ -157,10 +171,16 @@ public void testEquals() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester - public void testNulls() { - new NullPointerTester().testAllPublicStaticMethods(Equivalence.class); - new NullPointerTester().testAllPublicInstanceMethods(Equivalence.equals()); - new NullPointerTester().testAllPublicInstanceMethods(Equivalence.identity()); + public void testNulls() throws NoSuchMethodException { + NullPointerTester tester = new NullPointerTester(); + // Necessary until JDK15: + // https://bugs.openjdk.org/browse/JDK-8202469 + tester.ignore(Equivalence.class.getMethod("wrap", Object.class)); + + tester.testAllPublicStaticMethods(Equivalence.class); + tester.testAllPublicInstanceMethods(Equivalence.equals()); + tester.testAllPublicInstanceMethods(Equivalence.identity()); } } diff --git a/android/guava-tests/test/com/google/common/base/FinalizableReferenceQueueClassLoaderUnloadingTest.java b/android/guava-tests/test/com/google/common/base/FinalizableReferenceQueueClassLoaderUnloadingTest.java index ae35c16f6482..2fcfe832e399 100644 --- a/android/guava-tests/test/com/google/common/base/FinalizableReferenceQueueClassLoaderUnloadingTest.java +++ b/android/guava-tests/test/com/google/common/base/FinalizableReferenceQueueClassLoaderUnloadingTest.java @@ -17,12 +17,11 @@ package com.google.common.base; import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH; -import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR; +import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; import com.google.common.testing.GcFinalization; -import java.io.Closeable; import java.io.File; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; @@ -30,14 +29,11 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; -import java.security.Permission; -import java.security.Policy; -import java.security.ProtectionDomain; -import java.util.concurrent.Callable; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; /** * Tests that the {@code ClassLoader} of {@link FinalizableReferenceQueue} can be unloaded. These @@ -46,9 +42,10 @@ * * @author Eamonn McManus */ - - -public class FinalizableReferenceQueueClassLoaderUnloadingTest extends TestCase { +@AndroidIncompatible +@RunWith(JUnit4.class) +@NullUnmarked +public class FinalizableReferenceQueueClassLoaderUnloadingTest { /* * The following tests check that the use of FinalizableReferenceQueue does not prevent the @@ -76,15 +73,8 @@ public MyFinalizableWeakReference(Object x, FinalizableReferenceQueue queue) { public void finalizeReferent() {} } - private static class PermissivePolicy extends Policy { - @Override - public boolean implies(ProtectionDomain pd, Permission perm) { - return true; - } - } - private WeakReference useFrqInSeparateLoader() throws Exception { - final ClassLoader myLoader = getClass().getClassLoader(); + ClassLoader myLoader = getClass().getClassLoader(); URLClassLoader sepLoader = new URLClassLoader(getClassPathUrls(), myLoader.getParent()); // sepLoader is the loader that we will use to load the parallel FinalizableReferenceQueue (FRQ) // and friends, and that we will eventually expect to see garbage-collected. The assumption @@ -94,7 +84,7 @@ private WeakReference useFrqInSeparateLoader() throws Exception { Class frqC = FinalizableReferenceQueue.class; Class sepFrqC = sepLoader.loadClass(frqC.getName()); - assertNotSame(frqC, sepFrqC); + assertThat(frqC).isNotSameInstanceAs(sepFrqC); // Check the assumptions above. // FRQ tries to load the Finalizer class (for the reference-collecting thread) in a few ways. @@ -117,13 +107,13 @@ private WeakReference useFrqInSeparateLoader() throws Exception { Constructor sepFwrCons = sepFwrC.getConstructor(Object.class, sepFrqC); // The object that we will wrap in FinalizableWeakReference is a Stopwatch. Class sepStopwatchC = sepLoader.loadClass(Stopwatch.class.getName()); - assertSame(sepLoader, sepStopwatchC.getClassLoader()); + assertThat(sepLoader).isSameInstanceAs(sepStopwatchC.getClassLoader()); AtomicReference sepStopwatchA = new AtomicReference(sepStopwatchC.getMethod("createUnstarted").invoke(null)); AtomicReference> sepStopwatchRef = new AtomicReference>( (WeakReference) sepFwrCons.newInstance(sepStopwatchA.get(), sepFrqA.get())); - assertNotNull(sepStopwatchA.get()); + assertThat(sepStopwatchA.get()).isNotNull(); // Clear all references to the Stopwatch and wait for it to be gc'd. sepStopwatchA.set(null); GcFinalization.awaitClear(sepStopwatchRef.get()); @@ -132,131 +122,14 @@ private WeakReference useFrqInSeparateLoader() throws Exception { return new WeakReference(sepLoader); } - private void doTestUnloadable() throws Exception { - WeakReference loaderRef = useFrqInSeparateLoader(); - GcFinalization.awaitClear(loaderRef); - } - /** * Tests that the use of a {@link FinalizableReferenceQueue} does not subsequently prevent the * loader of that class from being garbage-collected. */ - public void testUnloadableWithoutSecurityManager() throws Exception { - if (isJdk9OrHigher()) { - return; - } - SecurityManager oldSecurityManager = System.getSecurityManager(); - try { - System.setSecurityManager(null); - doTestUnloadable(); - } finally { - System.setSecurityManager(oldSecurityManager); - } - } - - /** - * Tests that the use of a {@link FinalizableReferenceQueue} does not subsequently prevent the - * loader of that class from being garbage-collected even if there is a {@link SecurityManager}. - * The {@link SecurityManager} environment makes such leaks more likely because when you create a - * {@link URLClassLoader} with a {@link SecurityManager}, the creating code's {@link - * java.security.AccessControlContext} is captured, and that references the creating code's {@link - * ClassLoader}. - */ - public void testUnloadableWithSecurityManager() throws Exception { - if (isJdk9OrHigher()) { - return; - } - Policy oldPolicy = Policy.getPolicy(); - SecurityManager oldSecurityManager = System.getSecurityManager(); - try { - Policy.setPolicy(new PermissivePolicy()); - System.setSecurityManager(new SecurityManager()); - doTestUnloadable(); - } finally { - System.setSecurityManager(oldSecurityManager); - Policy.setPolicy(oldPolicy); - } - } - - public static class FrqUser implements Callable> { - public static FinalizableReferenceQueue frq = new FinalizableReferenceQueue(); - public static final Semaphore finalized = new Semaphore(0); - - @Override - public WeakReference call() { - WeakReference wr = - new FinalizableWeakReference(new Integer(23), frq) { - @Override - public void finalizeReferent() { - finalized.release(); - } - }; - return wr; - } - } - - public void testUnloadableInStaticFieldIfClosed() throws Exception { - if (isJdk9OrHigher()) { - return; - } - Policy oldPolicy = Policy.getPolicy(); - SecurityManager oldSecurityManager = System.getSecurityManager(); - try { - Policy.setPolicy(new PermissivePolicy()); - System.setSecurityManager(new SecurityManager()); - WeakReference loaderRef = doTestUnloadableInStaticFieldIfClosed(); - GcFinalization.awaitClear(loaderRef); - } finally { - System.setSecurityManager(oldSecurityManager); - Policy.setPolicy(oldPolicy); - } - } - - // If you have a FinalizableReferenceQueue that is a static field of one of the classes of your - // app (like the FrqUser class above), then the app's ClassLoader will never be gc'd. The reason - // is that we attempt to run a thread in a separate ClassLoader that will detect when the FRQ - // is no longer referenced, meaning that the app's ClassLoader has been gc'd, and when that - // happens. But the thread's supposedly separate ClassLoader actually has a reference to the app's - // ClasLoader via its AccessControlContext. It does not seem to be possible to make a - // URLClassLoader without capturing this reference, and it probably would not be desirable for - // security reasons anyway. Therefore, the FRQ.close() method provides a way to stop the thread - // explicitly. This test checks that calling that method does allow an app's ClassLoader to be - // gc'd even if there is a still a FinalizableReferenceQueue in a static field. (Setting the field - // to null would also work, but only if there are no references to the FRQ anywhere else.) - private WeakReference doTestUnloadableInStaticFieldIfClosed() throws Exception { - final ClassLoader myLoader = getClass().getClassLoader(); - URLClassLoader sepLoader = new URLClassLoader(getClassPathUrls(), myLoader.getParent()); - - Class frqC = FinalizableReferenceQueue.class; - Class sepFrqC = sepLoader.loadClass(frqC.getName()); - assertNotSame(frqC, sepFrqC); - - Class sepFrqSystemLoaderC = - sepLoader.loadClass(FinalizableReferenceQueue.SystemLoader.class.getName()); - Field disabled = sepFrqSystemLoaderC.getDeclaredField("disabled"); - disabled.setAccessible(true); - disabled.set(null, true); - - Class frqUserC = FrqUser.class; - Class sepFrqUserC = sepLoader.loadClass(frqUserC.getName()); - assertNotSame(frqUserC, sepFrqUserC); - assertSame(sepLoader, sepFrqUserC.getClassLoader()); - - Callable sepFrqUser = (Callable) sepFrqUserC.getDeclaredConstructor().newInstance(); - WeakReference finalizableWeakReference = (WeakReference) sepFrqUser.call(); - - GcFinalization.awaitClear(finalizableWeakReference); - - Field sepFrqUserFinalizedF = sepFrqUserC.getField("finalized"); - Semaphore finalizeCount = (Semaphore) sepFrqUserFinalizedF.get(null); - boolean finalized = finalizeCount.tryAcquire(5, TimeUnit.SECONDS); - assertTrue(finalized); - - Field sepFrqUserFrqF = sepFrqUserC.getField("frq"); - Closeable frq = (Closeable) sepFrqUserFrqF.get(null); - frq.close(); - - return new WeakReference(sepLoader); + @Test + public void testUnloadable() throws Exception { + WeakReference loaderRef = useFrqInSeparateLoader(); + GcFinalization.awaitClear(loaderRef); } private URL[] getClassPathUrls() { @@ -281,21 +154,9 @@ private static ImmutableList parseJavaClassPath() { urls.add(new URL("file", null, new File(entry).getAbsolutePath())); } } catch (MalformedURLException e) { - AssertionError error = new AssertionError("malformed class path entry: " + entry); - error.initCause(e); - throw error; + throw new AssertionError("malformed class path entry: " + entry, e); } } return urls.build(); } - - /** - * These tests fail in JDK 9 and JDK 10 for an unknown reason. It might be the test; it might be - * the underlying functionality. Fixing this is not a high priority; if you need it to be fixed, - * please comment on issue 3086. - */ - private static boolean isJdk9OrHigher() { - return JAVA_SPECIFICATION_VERSION.value().startsWith("9") - || JAVA_SPECIFICATION_VERSION.value().startsWith("10"); - } } diff --git a/android/guava-tests/test/com/google/common/base/FinalizableReferenceQueueTest.java b/android/guava-tests/test/com/google/common/base/FinalizableReferenceQueueTest.java index 3e9912280b4b..8846d46bd184 100644 --- a/android/guava-tests/test/com/google/common/base/FinalizableReferenceQueueTest.java +++ b/android/guava-tests/test/com/google/common/base/FinalizableReferenceQueueTest.java @@ -16,40 +16,58 @@ package com.google.common.base; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.annotations.GwtIncompatible; import com.google.common.base.internal.Finalizer; +import com.google.common.collect.Sets; import com.google.common.testing.GcFinalization; +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.ref.Cleaner; +import java.lang.ref.Cleaner.Cleanable; +import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.net.ServerSocket; import java.net.URL; import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.Collections; -import junit.framework.TestCase; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; /** * Unit test for {@link FinalizableReferenceQueue}. * * @author Bob Lee */ -public class FinalizableReferenceQueueTest extends TestCase { +// - depends on details of GC and classloading +// - .class files aren't available +// - possibly no real concept of separate ClassLoaders? +@AndroidIncompatible +@GwtIncompatible +@RunWith(JUnit4.class) +@NullUnmarked +public class FinalizableReferenceQueueTest { - private FinalizableReferenceQueue frq; + private @Nullable FinalizableReferenceQueue frq; - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { frq = null; } - + @Test public void testFinalizeReferentCalled() { - final MockReference reference = new MockReference(frq = new FinalizableReferenceQueue()); + MockReference reference = new MockReference(frq = new FinalizableReferenceQueue()); - GcFinalization.awaitDone( - new GcFinalization.FinalizationPredicate() { - public boolean isDone() { - return reference.finalizeReferentCalled; - } - }); + GcFinalization.awaitDone(() -> reference.finalizeReferentCalled); } static class MockReference extends FinalizableWeakReference { @@ -72,14 +90,14 @@ public void finalizeReferent() { */ private WeakReference> queueReference; - + @Test public void testThatFinalizerStops() { weaklyReferenceQueue(); GcFinalization.awaitClear(queueReference); } /** If we don't keep a strong reference to the reference object, it won't be enqueued. */ - FinalizableWeakReference reference; + @Nullable FinalizableWeakReference reference; /** Create the FRQ in a method that goes out of scope so that we're sure it will be reclaimed. */ private void weaklyReferenceQueue() { @@ -101,7 +119,7 @@ public void finalizeReferent() { }; } - @AndroidIncompatible // no concept of separate ClassLoaders + @Test public void testDecoupledLoader() { FinalizableReferenceQueue.DecoupledLoader decoupledLoader = new FinalizableReferenceQueue.DecoupledLoader() { @@ -113,10 +131,10 @@ URLClassLoader newLoader(URL base) { Class finalizerCopy = decoupledLoader.loadFinalizer(); - assertNotNull(finalizerCopy); - assertNotSame(Finalizer.class, finalizerCopy); + assertThat(finalizerCopy).isNotNull(); + assertThat(finalizerCopy).isNotSameInstanceAs(Finalizer.class); - assertNotNull(FinalizableReferenceQueue.getStartFinalizer(finalizerCopy)); + assertThat(FinalizableReferenceQueue.getStartFinalizer(finalizerCopy)).isNotNull(); } static class DecoupledClassLoader extends URLClassLoader { @@ -141,14 +159,120 @@ protected synchronized Class loadClass(String name, boolean resolve) } } - @AndroidIncompatible // TODO(cpovirk): How significant is this failure? + @Test public void testGetFinalizerUrl() { - assertNotNull(getClass().getResource("internal/Finalizer.class")); + assertThat(getClass().getResource("internal/Finalizer.class")).isNotNull(); } + @Test public void testFinalizeClassHasNoNestedClasses() throws Exception { // Ensure that the Finalizer class has no nested classes. - // See https://code.google.com/p/guava-libraries/issues/detail?id=1505 - assertEquals(Collections.emptyList(), Arrays.asList(Finalizer.class.getDeclaredClasses())); + // See https://github.com/google/guava/issues/1505 + assertThat(Finalizer.class.getDeclaredClasses()).isEmpty(); + } + + static class MyServerExampleWithFrq implements Closeable { + private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue(); + + private static final Set> references = Sets.newConcurrentHashSet(); + + private final ServerSocket serverSocket; + + private MyServerExampleWithFrq() throws IOException { + this.serverSocket = new ServerSocket(0); + } + + static MyServerExampleWithFrq create(AtomicBoolean finalizeReferentRan) throws IOException { + MyServerExampleWithFrq myServer = new MyServerExampleWithFrq(); + ServerSocket serverSocket = myServer.serverSocket; + Reference reference = + new FinalizablePhantomReference(myServer, frq) { + @Override + public void finalizeReferent() { + references.remove(this); + if (!serverSocket.isClosed()) { + try { + serverSocket.close(); + finalizeReferentRan.set(true); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + }; + references.add(reference); + return myServer; + } + + @Override + public void close() throws IOException { + serverSocket.close(); + } + } + + private ServerSocket makeMyServerExampleWithFrq(AtomicBoolean finalizeReferentRan) + throws IOException { + MyServerExampleWithFrq myServer = MyServerExampleWithFrq.create(finalizeReferentRan); + assertThat(myServer.serverSocket.isClosed()).isFalse(); + return myServer.serverSocket; + } + + @Test + public void testMyServerExampleWithFrq() throws Exception { + AtomicBoolean finalizeReferentRan = new AtomicBoolean(false); + ServerSocket serverSocket = makeMyServerExampleWithFrq(finalizeReferentRan); + GcFinalization.awaitDone(finalizeReferentRan::get); + assertThat(serverSocket.isClosed()).isTrue(); + } + + @SuppressWarnings("Java8ApiChecker") + static class MyServerExampleWithCleaner implements AutoCloseable { + private static final Cleaner cleaner = Cleaner.create(); + + private static Runnable closeServerSocketRunnable( + ServerSocket serverSocket, AtomicBoolean cleanerRan) { + return () -> { + try { + serverSocket.close(); + cleanerRan.set(true); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + } + + private final ServerSocket serverSocket; + private final Cleanable cleanable; + + MyServerExampleWithCleaner(AtomicBoolean cleanerRan) throws IOException { + this.serverSocket = new ServerSocket(0); + this.cleanable = cleaner.register(this, closeServerSocketRunnable(serverSocket, cleanerRan)); + } + + @Override + public void close() { + cleanable.clean(); + } + } + + @SuppressWarnings("Java8ApiChecker") + private ServerSocket makeMyServerExampleWithCleaner(AtomicBoolean cleanerRan) throws IOException { + MyServerExampleWithCleaner myServer = new MyServerExampleWithCleaner(cleanerRan); + assertThat(myServer.serverSocket.isClosed()).isFalse(); + return myServer.serverSocket; + } + + @SuppressWarnings("Java8ApiChecker") + @Test + public void testMyServerExampleWithCleaner() throws Exception { + try { + Class.forName("java.lang.ref.Cleaner"); + } catch (ClassNotFoundException beforeJava9) { + return; + } + AtomicBoolean cleanerRan = new AtomicBoolean(false); + ServerSocket serverSocket = makeMyServerExampleWithCleaner(cleanerRan); + GcFinalization.awaitDone(cleanerRan::get); + assertThat(serverSocket.isClosed()).isTrue(); } } diff --git a/android/guava-tests/test/com/google/common/base/FunctionsTest.java b/android/guava-tests/test/com/google/common/base/FunctionsTest.java index 1411c192b551..af7d18fdbaed 100644 --- a/android/guava-tests/test/com/google/common/base/FunctionsTest.java +++ b/android/guava-tests/test/com/google/common/base/FunctionsTest.java @@ -16,17 +16,23 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.google.common.testing.ClassSanityTester; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.io.Serializable; +import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Functions}. @@ -34,12 +40,13 @@ * @author Mike Bostock * @author Vlad Patryshev */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class FunctionsTest extends TestCase { public void testIdentity_same() { - Function identity = Functions.identity(); - assertNull(identity.apply(null)); + Function<@Nullable String, @Nullable String> identity = Functions.identity(); + assertThat(identity.apply(null)).isNull(); assertSame("foo", identity.apply("foo")); } @@ -48,6 +55,7 @@ public void testIdentity_notSame() { assertNotSame(new Long(135135L), identity.apply(new Long(135135L))); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testIdentitySerializable() { checkCanReserializeSingleton(Functions.identity()); @@ -66,18 +74,16 @@ public String toString() { return "I'm a string"; } })); - try { - Functions.toStringFunction().apply(null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Functions.toStringFunction().apply(null)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testToStringFunctionSerializable() { checkCanReserializeSingleton(Functions.toStringFunction()); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -85,21 +91,17 @@ public void testNullPointerExceptions() { } public void testForMapWithoutDefault() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("One", 1); map.put("Three", 3); map.put("Null", null); - Function function = Functions.forMap(map); + Function function = Functions.forMap(map); assertEquals(1, function.apply("One").intValue()); assertEquals(3, function.apply("Three").intValue()); - assertNull(function.apply("Null")); + assertThat(function.apply("Null")).isNull(); - try { - function.apply("Two"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> function.apply("Two")); new EqualsTester() .addEqualityGroup(function, Functions.forMap(map)) @@ -107,22 +109,23 @@ public void testForMapWithoutDefault() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapWithoutDefaultSerializable() { checkCanReserialize(Functions.forMap(ImmutableMap.of(1, 2))); } public void testForMapWithDefault() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("One", 1); map.put("Three", 3); map.put("Null", null); - Function function = Functions.forMap(map, 42); + Function function = Functions.forMap(map, 42); assertEquals(1, function.apply("One").intValue()); assertEquals(42, function.apply("Two").intValue()); assertEquals(3, function.apply("Three").intValue()); - assertNull(function.apply("Null")); + assertThat(function.apply("Null")).isNull(); new EqualsTester() .addEqualityGroup(function, Functions.forMap(map, 42)) @@ -132,9 +135,10 @@ public void testForMapWithDefault() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapWithDefault_includeSerializable() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("One", 1); map.put("Three", 3); Function function = Functions.forMap(map, 42); @@ -152,6 +156,7 @@ public void testForMapWithDefault_includeSerializable() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapWithDefaultSerializable() { checkCanReserialize(Functions.forMap(ImmutableMap.of(1, 2), 3)); @@ -159,10 +164,10 @@ public void testForMapWithDefaultSerializable() { public void testForMapWithDefault_null() { ImmutableMap map = ImmutableMap.of("One", 1); - Function function = Functions.forMap(map, null); + Function function = Functions.forMap(map, null); assertEquals((Integer) 1, function.apply("One")); - assertNull(function.apply("Two")); + assertThat(function.apply("Two")).isNull(); // check basic sanity of equals and hashCode new EqualsTester() @@ -171,13 +176,14 @@ public void testForMapWithDefault_null() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapWithDefault_null_compareWithSerializable() { ImmutableMap map = ImmutableMap.of("One", 1); Function function = Functions.forMap(map, null); assertEquals((Integer) 1, function.apply("One")); - assertNull(function.apply("Two")); + assertThat(function.apply("Two")).isNull(); // check basic sanity of equals and hashCode new EqualsTester() @@ -187,7 +193,7 @@ public void testForMapWithDefault_null_compareWithSerializable() { } public void testForMapWildCardWithDefault() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("One", 1); map.put("Three", 3); Number number = Double.valueOf(42); @@ -199,13 +205,13 @@ public void testForMapWildCardWithDefault() { } public void testComposition() { - Map mJapaneseToInteger = Maps.newHashMap(); + Map mJapaneseToInteger = new HashMap<>(); mJapaneseToInteger.put("Ichi", 1); mJapaneseToInteger.put("Ni", 2); mJapaneseToInteger.put("San", 3); Function japaneseToInteger = Functions.forMap(mJapaneseToInteger); - Map mIntegerToSpanish = Maps.newHashMap(); + Map mIntegerToSpanish = new HashMap<>(); mIntegerToSpanish.put(1, "Uno"); mIntegerToSpanish.put(3, "Tres"); mIntegerToSpanish.put(4, "Cuatro"); @@ -215,17 +221,9 @@ public void testComposition() { Functions.compose(integerToSpanish, japaneseToInteger); assertEquals("Uno", japaneseToSpanish.apply("Ichi")); - try { - japaneseToSpanish.apply("Ni"); - fail(); - } catch (IllegalArgumentException e) { - } + assertThrows(IllegalArgumentException.class, () -> japaneseToSpanish.apply("Ni")); assertEquals("Tres", japaneseToSpanish.apply("San")); - try { - japaneseToSpanish.apply("Shi"); - fail(); - } catch (IllegalArgumentException e) { - } + assertThrows(IllegalArgumentException.class, () -> japaneseToSpanish.apply("Shi")); new EqualsTester() .addEqualityGroup(japaneseToSpanish, Functions.compose(integerToSpanish, japaneseToInteger)) @@ -235,15 +233,16 @@ public void testComposition() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testComposition_includeReserializabled() { - Map mJapaneseToInteger = Maps.newHashMap(); + Map mJapaneseToInteger = new HashMap<>(); mJapaneseToInteger.put("Ichi", 1); mJapaneseToInteger.put("Ni", 2); mJapaneseToInteger.put("San", 3); Function japaneseToInteger = Functions.forMap(mJapaneseToInteger); - Map mIntegerToSpanish = Maps.newHashMap(); + Map mIntegerToSpanish = new HashMap<>(); mIntegerToSpanish.put(1, "Uno"); mIntegerToSpanish.put(3, "Tres"); mIntegerToSpanish.put(4, "Cuatro"); @@ -264,18 +263,18 @@ public void testComposition_includeReserializabled() { } public void testCompositionWildcard() { - Map mapJapaneseToInteger = Maps.newHashMap(); + Map mapJapaneseToInteger = new HashMap<>(); Function japaneseToInteger = Functions.forMap(mapJapaneseToInteger); Function numberToSpanish = Functions.constant("Yo no se"); - Function japaneseToSpanish = + Function unusedJapaneseToSpanish = Functions.compose(numberToSpanish, japaneseToInteger); } - private static class HashCodeFunction implements Function { + private static class HashCodeFunction implements Function<@Nullable Object, Integer> { @Override - public Integer apply(Object o) { + public Integer apply(@Nullable Object o) { return (o == null) ? 0 : o.hashCode(); } } @@ -332,17 +331,18 @@ public void testForPredicate() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForPredicateSerializable() { checkCanReserialize(Functions.forPredicate(Predicates.equalTo(5))); } public void testConstant() { - Function f = Functions.constant("correct"); + Function<@Nullable Object, Object> f = Functions.constant("correct"); assertEquals("correct", f.apply(new Object())); assertEquals("correct", f.apply(null)); - Function g = Functions.constant(null); + Function<@Nullable Object, @Nullable String> g = Functions.constant(null); assertEquals(null, g.apply(2)); assertEquals(null, g.apply(null)); @@ -354,13 +354,14 @@ public void testConstant() { .testEquals(); new EqualsTester() - .addEqualityGroup(g, Functions.constant(null)) + .addEqualityGroup(g, Functions.<@Nullable Object>constant(null)) .addEqualityGroup(Functions.constant("incorrect")) .addEqualityGroup(Functions.toStringFunction()) .addEqualityGroup(f) .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testConstantSerializable() { checkCanReserialize(Functions.constant(5)); @@ -368,7 +369,7 @@ public void testConstantSerializable() { private static class CountingSupplier implements Supplier, Serializable { - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; private int value; @@ -378,7 +379,7 @@ public Integer get() { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof CountingSupplier) { return this.value == ((CountingSupplier) obj).value; } @@ -393,7 +394,7 @@ public int hashCode() { public void testForSupplier() { Supplier supplier = new CountingSupplier(); - Function function = Functions.forSupplier(supplier); + Function<@Nullable Object, Integer> function = Functions.forSupplier(supplier); assertEquals(1, (int) function.apply(null)); assertEquals(2, (int) function.apply("foo")); @@ -406,16 +407,19 @@ public void testForSupplier() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForSupplierSerializable() { checkCanReserialize(Functions.forSupplier(new CountingSupplier())); } + @J2ktIncompatible @GwtIncompatible // reflection public void testNulls() throws Exception { new ClassSanityTester().forAllPublicStaticMethods(Functions.class).testNulls(); } + @J2ktIncompatible @GwtIncompatible // reflection @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException: com.google.common.base.Function // (I suspect that this and the other similar failures happen with ArbitraryInstances proxies.) @@ -423,6 +427,7 @@ public void testEqualsAndSerializable() throws Exception { new ClassSanityTester().forAllPublicStaticMethods(Functions.class).testEqualsAndSerializable(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester private static void checkCanReserialize(Function f) { Function g = SerializableTester.reserializeAndAssert(f); @@ -443,6 +448,7 @@ private static void checkCanReserialize(Function f) { } } + @J2ktIncompatible @GwtIncompatible // SerializableTester private static void checkCanReserializeSingleton(Function f) { Function g = SerializableTester.reserializeAndAssert(f); diff --git a/android/guava-tests/test/com/google/common/base/JoinerTest.java b/android/guava-tests/test/com/google/common/base/JoinerTest.java index d9ed3472184c..a08189373cb8 100644 --- a/android/guava-tests/test/com/google/common/base/JoinerTest.java +++ b/android/guava-tests/test/com/google/common/base/JoinerTest.java @@ -16,116 +16,158 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Lists.newArrayList; +import static java.util.Collections.unmodifiableList; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Joiner.MapJoiner; +import com.google.common.collect.ForwardingList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.testing.NullPointerTester; import java.io.IOException; import java.util.Arrays; -import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Joiner}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class JoinerTest extends TestCase { private static final Joiner J = Joiner.on("-"); // needed to prevent warning :( - private static final Iterable ITERABLE_ = Arrays.asList(); - private static final Iterable ITERABLE_1 = Arrays.asList(1); - private static final Iterable ITERABLE_12 = Arrays.asList(1, 2); - private static final Iterable ITERABLE_123 = Arrays.asList(1, 2, 3); - private static final Iterable ITERABLE_NULL = Arrays.asList((Integer) null); - private static final Iterable ITERABLE_NULL_NULL = Arrays.asList((Integer) null, null); - private static final Iterable ITERABLE_NULL_1 = Arrays.asList(null, 1); - private static final Iterable ITERABLE_1_NULL = Arrays.asList(1, null); - private static final Iterable ITERABLE_1_NULL_2 = Arrays.asList(1, null, 2); - private static final Iterable ITERABLE_FOUR_NULLS = + private static final Iterable iterable = Arrays.asList(); + private static final Iterable iterable1 = Arrays.asList(1); + private static final Iterable iterable12 = Arrays.asList(1, 2); + private static final Iterable iterable123 = Arrays.asList(1, 2, 3); + private static final Iterable<@Nullable Integer> iterableNull = Arrays.asList((Integer) null); + private static final Iterable<@Nullable Integer> iterableNullNull = + Arrays.asList((Integer) null, null); + private static final Iterable<@Nullable Integer> iterableNull1 = Arrays.asList(null, 1); + private static final Iterable<@Nullable Integer> iterable1Null = Arrays.asList(1, null); + private static final Iterable<@Nullable Integer> iterable1Null2 = Arrays.asList(1, null, 2); + private static final Iterable<@Nullable Integer> iterableFourNulls = Arrays.asList((Integer) null, null, null, null); - public void testNoSpecialNullBehavior() { - checkNoOutput(J, ITERABLE_); - checkResult(J, ITERABLE_1, "1"); - checkResult(J, ITERABLE_12, "1-2"); - checkResult(J, ITERABLE_123, "1-2-3"); + /* + * Both of these fields *are* immutable/constant. They don't use the type ImmutableList because + * they need to behave slightly differently. + */ + @SuppressWarnings("ConstantCaseForConstants") + private static final List UNDERREPORTING_SIZE_LIST; - try { - J.join(ITERABLE_NULL); - fail(); - } catch (NullPointerException expected) { - } - try { - J.join(ITERABLE_1_NULL_2); - fail(); - } catch (NullPointerException expected) { + @SuppressWarnings("ConstantCaseForConstants") + private static final List OVERREPORTING_SIZE_LIST; + + static { + List collection123 = Arrays.asList(1, 2, 3); + UNDERREPORTING_SIZE_LIST = unmodifiableList(new MisleadingSizeList<>(collection123, -1)); + OVERREPORTING_SIZE_LIST = unmodifiableList(new MisleadingSizeList<>(collection123, 1)); + } + + /* + * c.g.c.collect.testing.Helpers.misleadingSizeList has a broken Iterator, so we can't use it. (I + * mean, ideally we'd fix it....) Also, we specifically need a List so that we trigger the fast + * path in join(Iterable). + */ + private static final class MisleadingSizeList + extends ForwardingList { + final List delegate; + final int delta; + + MisleadingSizeList(List delegate, int delta) { + this.delegate = delegate; + this.delta = delta; } - try { - J.join(ITERABLE_NULL.iterator()); - fail(); - } catch (NullPointerException expected) { + @Override + protected List delegate() { + return delegate; } - try { - J.join(ITERABLE_1_NULL_2.iterator()); - fail(); - } catch (NullPointerException expected) { + + @Override + public int size() { + return delegate.size() + delta; } } + @SuppressWarnings("JoinIterableIterator") // explicitly testing iterator overload, too + public void testNoSpecialNullBehavior() { + checkNoOutput(J, iterable); + checkResult(J, iterable1, "1"); + checkResult(J, iterable12, "1-2"); + checkResult(J, iterable123, "1-2-3"); + checkResult(J, UNDERREPORTING_SIZE_LIST, "1-2-3"); + checkResult(J, OVERREPORTING_SIZE_LIST, "1-2-3"); + + assertThrows(NullPointerException.class, () -> J.join(iterableNull)); + assertThrows(NullPointerException.class, () -> J.join(iterable1Null2)); + + assertThrows(NullPointerException.class, () -> J.join(iterableNull.iterator())); + assertThrows(NullPointerException.class, () -> J.join(iterable1Null2.iterator())); + } + public void testOnCharOverride() { Joiner onChar = Joiner.on('-'); - checkNoOutput(onChar, ITERABLE_); - checkResult(onChar, ITERABLE_1, "1"); - checkResult(onChar, ITERABLE_12, "1-2"); - checkResult(onChar, ITERABLE_123, "1-2-3"); + checkNoOutput(onChar, iterable); + checkResult(onChar, iterable1, "1"); + checkResult(onChar, iterable12, "1-2"); + checkResult(onChar, iterable123, "1-2-3"); + checkResult(J, UNDERREPORTING_SIZE_LIST, "1-2-3"); + checkResult(J, OVERREPORTING_SIZE_LIST, "1-2-3"); } public void testSkipNulls() { Joiner skipNulls = J.skipNulls(); - checkNoOutput(skipNulls, ITERABLE_); - checkNoOutput(skipNulls, ITERABLE_NULL); - checkNoOutput(skipNulls, ITERABLE_NULL_NULL); - checkNoOutput(skipNulls, ITERABLE_FOUR_NULLS); - checkResult(skipNulls, ITERABLE_1, "1"); - checkResult(skipNulls, ITERABLE_12, "1-2"); - checkResult(skipNulls, ITERABLE_123, "1-2-3"); - checkResult(skipNulls, ITERABLE_NULL_1, "1"); - checkResult(skipNulls, ITERABLE_1_NULL, "1"); - checkResult(skipNulls, ITERABLE_1_NULL_2, "1-2"); + checkNoOutput(skipNulls, iterable); + checkNoOutput(skipNulls, iterableNull); + checkNoOutput(skipNulls, iterableNullNull); + checkNoOutput(skipNulls, iterableFourNulls); + checkResult(skipNulls, iterable1, "1"); + checkResult(skipNulls, iterable12, "1-2"); + checkResult(skipNulls, iterable123, "1-2-3"); + checkResult(J, UNDERREPORTING_SIZE_LIST, "1-2-3"); + checkResult(J, OVERREPORTING_SIZE_LIST, "1-2-3"); + checkResult(skipNulls, iterableNull1, "1"); + checkResult(skipNulls, iterable1Null, "1"); + checkResult(skipNulls, iterable1Null2, "1-2"); } public void testUseForNull() { Joiner zeroForNull = J.useForNull("0"); - checkNoOutput(zeroForNull, ITERABLE_); - checkResult(zeroForNull, ITERABLE_1, "1"); - checkResult(zeroForNull, ITERABLE_12, "1-2"); - checkResult(zeroForNull, ITERABLE_123, "1-2-3"); - checkResult(zeroForNull, ITERABLE_NULL, "0"); - checkResult(zeroForNull, ITERABLE_NULL_NULL, "0-0"); - checkResult(zeroForNull, ITERABLE_NULL_1, "0-1"); - checkResult(zeroForNull, ITERABLE_1_NULL, "1-0"); - checkResult(zeroForNull, ITERABLE_1_NULL_2, "1-0-2"); - checkResult(zeroForNull, ITERABLE_FOUR_NULLS, "0-0-0-0"); + checkNoOutput(zeroForNull, iterable); + checkResult(zeroForNull, iterable1, "1"); + checkResult(zeroForNull, iterable12, "1-2"); + checkResult(zeroForNull, iterable123, "1-2-3"); + checkResult(J, UNDERREPORTING_SIZE_LIST, "1-2-3"); + checkResult(J, OVERREPORTING_SIZE_LIST, "1-2-3"); + checkResult(zeroForNull, iterableNull, "0"); + checkResult(zeroForNull, iterableNullNull, "0-0"); + checkResult(zeroForNull, iterableNull1, "0-1"); + checkResult(zeroForNull, iterable1Null, "1-0"); + checkResult(zeroForNull, iterable1Null2, "1-0-2"); + checkResult(zeroForNull, iterableFourNulls, "0-0-0-0"); } private static void checkNoOutput(Joiner joiner, Iterable set) { assertEquals("", joiner.join(set)); assertEquals("", joiner.join(set.iterator())); - Object[] array = Lists.newArrayList(set).toArray(new Integer[0]); + Object[] array = newArrayList(set).toArray(new Integer[0]); assertEquals("", joiner.join(array)); StringBuilder sb1FromIterable = new StringBuilder(); @@ -162,12 +204,13 @@ private static void checkNoOutput(Joiner joiner, Iterable set) { private static final Appendable NASTY_APPENDABLE = new Appendable() { @Override - public Appendable append(CharSequence csq) throws IOException { + public Appendable append(@Nullable CharSequence csq) throws IOException { throw new IOException(); } @Override - public Appendable append(CharSequence csq, int start, int end) throws IOException { + public Appendable append(@Nullable CharSequence csq, int start, int end) + throws IOException { throw new IOException(); } @@ -189,7 +232,8 @@ private static void checkResult(Joiner joiner, Iterable parts, String e joiner.appendTo(sb1FromIterator, parts.iterator()); assertEquals("x" + expected, sb1FromIterator.toString()); - Integer[] partsArray = Lists.newArrayList(parts).toArray(new Integer[0]); + // The use of iterator() works around J2KT b/381065164. + Integer[] partsArray = newArrayList(parts.iterator()).toArray(new Integer[0]); assertEquals(expected, joiner.join(partsArray)); StringBuilder sb2 = new StringBuilder().append('x'); @@ -213,29 +257,17 @@ private static void checkResult(Joiner joiner, Iterable parts, String e public void test_useForNull_skipNulls() { Joiner j = Joiner.on("x").useForNull("y"); - try { - j = j.skipNulls(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, j::skipNulls); } public void test_skipNulls_useForNull() { Joiner j = Joiner.on("x").skipNulls(); - try { - j = j.useForNull("y"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> j.useForNull("y")); } public void test_useForNull_twice() { Joiner j = Joiner.on("x").useForNull("y"); - try { - j = j.useForNull("y"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> j.useForNull("y")); } public void testMap() { @@ -243,15 +275,11 @@ public void testMap() { assertEquals("", j.join(ImmutableMap.of())); assertEquals(":", j.join(ImmutableMap.of("", ""))); - Map mapWithNulls = Maps.newLinkedHashMap(); + Map<@Nullable String, @Nullable String> mapWithNulls = new LinkedHashMap<>(); mapWithNulls.put("a", null); mapWithNulls.put(null, "b"); - try { - j.join(mapWithNulls); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> j.join(mapWithNulls)); assertEquals("a:00;00:b", j.useForNull("00").join(mapWithNulls)); @@ -269,22 +297,14 @@ public void testEntries() { assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries())); assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries().iterator())); - Map mapWithNulls = Maps.newLinkedHashMap(); + Map<@Nullable String, @Nullable String> mapWithNulls = new LinkedHashMap<>(); mapWithNulls.put("a", null); mapWithNulls.put(null, "b"); Set> entriesWithNulls = mapWithNulls.entrySet(); - try { - j.join(entriesWithNulls); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> j.join(entriesWithNulls)); - try { - j.join(entriesWithNulls.iterator()); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> j.join(entriesWithNulls.iterator())); assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls)); assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls.iterator())); @@ -300,73 +320,10 @@ public void testEntries() { public void test_skipNulls_onMap() { Joiner j = Joiner.on(",").skipNulls(); - try { - j.withKeyValueSeparator("/"); - fail(); - } catch (UnsupportedOperationException expected) { - } - } - - private static class DontStringMeBro implements CharSequence { - @Override - public int length() { - return 3; - } - - @Override - public char charAt(int index) { - return "foo".charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) { - return "foo".subSequence(start, end); - } - - @Override - public String toString() { - throw new AssertionFailedError("shouldn't be invoked"); - } - } - - // Don't do this. - private static class IterableIterator implements Iterable, Iterator { - private static final ImmutableSet INTEGERS = ImmutableSet.of(1, 2, 3, 4); - private final Iterator iterator; - - public IterableIterator() { - this.iterator = iterator(); - } - - @Override - public Iterator iterator() { - return INTEGERS.iterator(); - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public Integer next() { - return iterator.next(); - } - - @Override - public void remove() { - iterator.remove(); - } - } - - @GwtIncompatible // StringBuilder.append in GWT invokes Object.toString(), unlike the JRE version. - public void testDontConvertCharSequenceToString() { - assertEquals("foo,foo", Joiner.on(",").join(new DontStringMeBro(), new DontStringMeBro())); - assertEquals( - "foo,bar,foo", - Joiner.on(",").useForNull("bar").join(new DontStringMeBro(), null, new DontStringMeBro())); + assertThrows(UnsupportedOperationException.class, () -> j.withKeyValueSeparator("/")); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/base/MoreObjectsTest.java b/android/guava-tests/test/com/google/common/base/MoreObjectsTest.java new file mode 100644 index 000000000000..aca45848b300 --- /dev/null +++ b/android/guava-tests/test/com/google/common/base/MoreObjectsTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.testing.NullPointerTester; +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +/** Tests for {@link MoreObjects}. */ +@GwtCompatible +@NullUnmarked +public class MoreObjectsTest extends TestCase { + public void testFirstNonNull_withNonNull() { + String s1 = "foo"; + String s2 = MoreObjects.firstNonNull(s1, "bar"); + assertSame(s1, s2); + + Long n1 = 42L; + Long n2 = MoreObjects.firstNonNull(null, n1); + assertSame(n1, n2); + + Boolean b1 = true; + Boolean b2 = MoreObjects.firstNonNull(b1, null); + assertSame(b1, b2); + } + + public void testFirstNonNull_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> MoreObjects.firstNonNull(null, null)); + } + + // ToStringHelper's tests are in ToStringHelperTest + + @J2ktIncompatible + @GwtIncompatible("NullPointerTester") + public void testNulls() throws Exception { + NullPointerTester tester = new NullPointerTester(); + tester.ignore(MoreObjects.class.getMethod("firstNonNull", Object.class, Object.class)); + tester.testAllPublicStaticMethods(MoreObjects.class); + tester.testAllPublicInstanceMethods(MoreObjects.toStringHelper(new TestClass())); + } + + /** Test class for testing formatting of inner classes. */ + private static class TestClass {} +} diff --git a/android/guava-tests/test/com/google/common/base/ObjectsTest.java b/android/guava-tests/test/com/google/common/base/ObjectsTest.java index 03881402a335..f546032e8b7c 100644 --- a/android/guava-tests/test/com/google/common/base/ObjectsTest.java +++ b/android/guava-tests/test/com/google/common/base/ObjectsTest.java @@ -18,17 +18,28 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Objects}. * * @author Laurence Gonsalves */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class ObjectsTest extends TestCase { + @SuppressWarnings({ + "ObjectEqualsForPrimitives", // test of a trivial call + "EqualsInteger", // test of a trivial call + "EqualsLong", // b/273939864 + "EqualsDouble", // b/273939864 + "EqualsFloat", // b/273939864 + "YodaCondition", // test of reversed call + }) public void testEqual() throws Exception { assertTrue(Objects.equal(1, 1)); assertTrue(Objects.equal(null, null)); @@ -46,7 +57,7 @@ public void testEqual() throws Exception { public void testHashCode() throws Exception { int h1 = Objects.hashCode(1, "two", 3.0); - int h2 = Objects.hashCode(new Integer(1), new String("two"), new Double(3.0)); + int h2 = Objects.hashCode(Integer.valueOf(1), new String("two"), Double.valueOf(3.0)); // repeatable assertEquals(h1, h2); @@ -58,6 +69,7 @@ public void testHashCode() throws Exception { assertTrue(Objects.hashCode(1, 2, 3) != Objects.hashCode(2, 3, 1)); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/base/OptionalTest.java b/android/guava-tests/test/com/google/common/base/OptionalTest.java index 35de2d485670..1c289ab7189e 100644 --- a/android/guava-tests/test/com/google/common/base/OptionalTest.java +++ b/android/guava-tests/test/com/google/common/base/OptionalTest.java @@ -16,11 +16,13 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.testing.SerializableTester.reserialize; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.testing.EqualsTester; @@ -29,14 +31,36 @@ import java.util.List; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Optional}. * * @author Kurt Alfred Kluever */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public final class OptionalTest extends TestCase { + @SuppressWarnings("NullOptional") + public void testToJavaUtil_static() { + assertThat(Optional.toJavaUtil(null)).isNull(); + assertEquals(java.util.Optional.empty(), Optional.toJavaUtil(Optional.absent())); + assertEquals(java.util.Optional.of("abc"), Optional.toJavaUtil(Optional.of("abc"))); + } + + public void testToJavaUtil_instance() { + assertEquals(java.util.Optional.empty(), Optional.absent().toJavaUtil()); + assertEquals(java.util.Optional.of("abc"), Optional.of("abc").toJavaUtil()); + } + + @SuppressWarnings("NullOptional") + public void testFromJavaUtil() { + assertThat(Optional.fromJavaUtil(null)).isNull(); + assertEquals(Optional.absent(), Optional.fromJavaUtil(java.util.Optional.empty())); + assertEquals(Optional.of("abc"), Optional.fromJavaUtil(java.util.Optional.of("abc"))); + } + public void testAbsent() { Optional optionalName = Optional.absent(); assertFalse(optionalName.isPresent()); @@ -47,11 +71,7 @@ public void testOf() { } public void testOf_null() { - try { - Optional.of(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Optional.of(null)); } public void testFromNullable() { @@ -68,31 +88,30 @@ public void testIsPresent_no() { assertFalse(Optional.absent().isPresent()); } + @SuppressWarnings("OptionalOfRedundantMethod") // Unit tests for Optional public void testIsPresent_yes() { assertTrue(Optional.of("training").isPresent()); } public void testGet_absent() { Optional optional = Optional.absent(); - try { - optional.get(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, optional::get); } public void testGet_present() { assertEquals("training", Optional.of("training").get()); } - public void testOr_T_present() { + @SuppressWarnings("OptionalOfRedundantMethod") // Unit tests for Optional + public void testOr_t_present() { assertEquals("a", Optional.of("a").or("default")); } - public void testOr_T_absent() { + public void testOr_t_absent() { assertEquals("default", Optional.absent().or("default")); } + @SuppressWarnings("OptionalOfRedundantMethod") // Unit tests for Optional public void testOr_supplier_present() { assertEquals("a", Optional.of("a").or(Suppliers.ofInstance("fallback"))); } @@ -102,34 +121,33 @@ public void testOr_supplier_absent() { } public void testOr_nullSupplier_absent() { - Supplier nullSupplier = Suppliers.ofInstance(null); + Supplier nullSupplier = (Supplier) Suppliers.<@Nullable Object>ofInstance(null); Optional absentOptional = Optional.absent(); - try { - absentOptional.or(nullSupplier); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> absentOptional.or(nullSupplier)); } + @SuppressWarnings("OptionalOfRedundantMethod") // Unit tests for Optional public void testOr_nullSupplier_present() { - Supplier nullSupplier = Suppliers.ofInstance(null); + Supplier nullSupplier = (Supplier) Suppliers.<@Nullable String>ofInstance(null); assertEquals("a", Optional.of("a").or(nullSupplier)); } - public void testOr_Optional_present() { + @SuppressWarnings("OptionalOfRedundantMethod") // Unit tests for Optional + public void testOr_optional_present() { assertEquals(Optional.of("a"), Optional.of("a").or(Optional.of("fallback"))); } - public void testOr_Optional_absent() { + public void testOr_optional_absent() { assertEquals(Optional.of("fallback"), Optional.absent().or(Optional.of("fallback"))); } + @SuppressWarnings("OptionalOfRedundantMethod") // Unit tests for Optional public void testOrNull_present() { assertEquals("a", Optional.of("a").orNull()); } public void testOrNull_absent() { - assertNull(Optional.absent().orNull()); + assertThat(Optional.absent().orNull()).isNull(); } public void testAsSet_present() { @@ -143,20 +161,12 @@ public void testAsSet_absent() { public void testAsSet_presentIsImmutable() { Set presentAsSet = Optional.of("a").asSet(); - try { - presentAsSet.add("b"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> presentAsSet.add("b")); } public void testAsSet_absentIsImmutable() { Set absentAsSet = Optional.absent().asSet(); - try { - absentAsSet.add("foo"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> absentAsSet.add("foo")); } public void testTransform_absent() { @@ -173,39 +183,18 @@ public void testTransform_presentToString() { } public void testTransform_present_functionReturnsNull() { - try { - Optional unused = - Optional.of("a") - .transform( - new Function() { - @Override - public String apply(String input) { - return null; - } - }); - fail("Should throw if Function returns null."); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Optional.of("a").transform(input -> null)); } public void testTransform_absent_functionReturnsNull() { - assertEquals( - Optional.absent(), - Optional.absent() - .transform( - new Function() { - @Override - public Object apply(Object input) { - return null; - } - })); + assertEquals(Optional.absent(), Optional.absent().transform(input -> null)); } public void testEqualsAndHashCode() { new EqualsTester() .addEqualityGroup(Optional.absent(), reserialize(Optional.absent())) - .addEqualityGroup(Optional.of(new Long(5)), reserialize(Optional.of(new Long(5)))) - .addEqualityGroup(Optional.of(new Long(42)), reserialize(Optional.of(new Long(42)))) + .addEqualityGroup(Optional.of(Long.valueOf(5)), reserialize(Optional.of(Long.valueOf(5)))) + .addEqualityGroup(Optional.of(Long.valueOf(42)), reserialize(Optional.of(Long.valueOf(42)))) .testEquals(); } @@ -292,6 +281,7 @@ public void testSampleCodeFine2() { Number value = first.or(0.5); // fine } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester npTester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/base/PackageSanityTests.java b/android/guava-tests/test/com/google/common/base/PackageSanityTests.java index f524fbb6ccae..7b77c80c7f63 100644 --- a/android/guava-tests/test/com/google/common/base/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/base/PackageSanityTests.java @@ -16,10 +16,14 @@ package com.google.common.base; +import com.google.common.annotations.GwtIncompatible; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** Basic sanity tests for classes in {@code common.base}. */ +@GwtIncompatible +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() { // package private classes like FunctionalEquivalence are tested through the public API. diff --git a/android/guava-tests/test/com/google/common/base/PreconditionsTest.java b/android/guava-tests/test/com/google/common/base/PreconditionsTest.java index 072649f99da1..1bbd4695130c 100644 --- a/android/guava-tests/test/com/google/common/base/PreconditionsTest.java +++ b/android/guava-tests/test/com/google/common/base/PreconditionsTest.java @@ -16,15 +16,22 @@ package com.google.common.base; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.testing.ArbitraryInstances; -import com.google.common.testing.NullPointerTester; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -32,6 +39,8 @@ import java.util.List; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Preconditions}. @@ -39,352 +48,257 @@ * @author Kevin Bourrillion * @author Jared Levy */ -@GwtCompatible(emulated = true) +@NullMarked +@SuppressWarnings("LenientFormatStringValidation") // Intentional for testing +@GwtCompatible public class PreconditionsTest extends TestCase { public void testCheckArgument_simple_success() { - Preconditions.checkArgument(true); + checkArgument(true); } public void testCheckArgument_simple_failure() { - try { - Preconditions.checkArgument(false); - fail("no exception thrown"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> checkArgument(false)); } public void testCheckArgument_simpleMessage_success() { - Preconditions.checkArgument(true, IGNORE_ME); + checkArgument(true, IGNORE_ME); } public void testCheckArgument_simpleMessage_failure() { - try { - Preconditions.checkArgument(false, new Message()); - fail("no exception thrown"); - } catch (IllegalArgumentException expected) { - verifySimpleMessage(expected); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> checkArgument(false, new Message())); + verifySimpleMessage(expected); } public void testCheckArgument_nullMessage_failure() { - try { - Preconditions.checkArgument(false, null); - fail("no exception thrown"); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("null"); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> checkArgument(false, null)); + assertThat(expected).hasMessageThat().isEqualTo("null"); } public void testCheckArgument_nullMessageWithArgs_failure() { - try { - Preconditions.checkArgument(false, null, "b", "d"); - fail("no exception thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().isEqualTo("null [b, d]"); - } + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> checkArgument(false, null, "b", "d")); + assertThat(e).hasMessageThat().isEqualTo("null [b, d]"); } public void testCheckArgument_nullArgs_failure() { - try { - Preconditions.checkArgument(false, "A %s C %s E", null, null); - fail("no exception thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().isEqualTo("A null C null E"); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> checkArgument(false, "A %s C %s E", null, null)); + assertThat(e).hasMessageThat().isEqualTo("A null C null E"); } public void testCheckArgument_notEnoughArgs_failure() { - try { - Preconditions.checkArgument(false, "A %s C %s E", "b"); - fail("no exception thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().isEqualTo("A b C %s E"); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> checkArgument(false, "A %s C %s E", "b")); + assertThat(e).hasMessageThat().isEqualTo("A b C %s E"); } public void testCheckArgument_tooManyArgs_failure() { - try { - Preconditions.checkArgument(false, "A %s C %s E", "b", "d", "f"); - fail("no exception thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().isEqualTo("A b C d E [f]"); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> checkArgument(false, "A %s C %s E", "b", "d", "f")); + assertThat(e).hasMessageThat().isEqualTo("A b C d E [f]"); } public void testCheckArgument_singleNullArg_failure() { - try { - Preconditions.checkArgument(false, "A %s C", (Object) null); - fail("no exception thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().isEqualTo("A null C"); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> checkArgument(false, "A %s C", (Object) null)); + assertThat(e).hasMessageThat().isEqualTo("A null C"); } + @J2ktIncompatible // TODO(b/319404022): Allow passing null array as varargs public void testCheckArgument_singleNullArray_failure() { - try { - Preconditions.checkArgument(false, "A %s C", (Object[]) null); - fail("no exception thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().isEqualTo("A (Object[])null C"); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> checkArgument(false, "A %s C", (Object[]) null)); + assertThat(e).hasMessageThat().isEqualTo("A (Object[])null C"); } public void testCheckArgument_complexMessage_success() { - Preconditions.checkArgument(true, "%s", IGNORE_ME); + checkArgument(true, "%s", IGNORE_ME); } public void testCheckArgument_complexMessage_failure() { - try { - Preconditions.checkArgument(false, FORMAT, 5); - fail("no exception thrown"); - } catch (IllegalArgumentException expected) { - verifyComplexMessage(expected); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> checkArgument(false, FORMAT, 5)); + verifyComplexMessage(expected); } public void testCheckState_simple_success() { - Preconditions.checkState(true); + checkState(true); } public void testCheckState_simple_failure() { - try { - Preconditions.checkState(false); - fail("no exception thrown"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> checkState(false)); } public void testCheckState_simpleMessage_success() { - Preconditions.checkState(true, IGNORE_ME); + checkState(true, IGNORE_ME); } public void testCheckState_simpleMessage_failure() { - try { - Preconditions.checkState(false, new Message()); - fail("no exception thrown"); - } catch (IllegalStateException expected) { - verifySimpleMessage(expected); - } + IllegalStateException expected = + assertThrows(IllegalStateException.class, () -> checkState(false, new Message())); + verifySimpleMessage(expected); } public void testCheckState_nullMessage_failure() { - try { - Preconditions.checkState(false, null); - fail("no exception thrown"); - } catch (IllegalStateException expected) { - assertThat(expected).hasMessageThat().isEqualTo("null"); - } + IllegalStateException expected = + assertThrows(IllegalStateException.class, () -> checkState(false, null)); + assertThat(expected).hasMessageThat().isEqualTo("null"); } public void testCheckState_complexMessage_success() { - Preconditions.checkState(true, "%s", IGNORE_ME); + checkState(true, "%s", IGNORE_ME); } public void testCheckState_complexMessage_failure() { - try { - Preconditions.checkState(false, FORMAT, 5); - fail("no exception thrown"); - } catch (IllegalStateException expected) { - verifyComplexMessage(expected); - } + IllegalStateException expected = + assertThrows(IllegalStateException.class, () -> checkState(false, FORMAT, 5)); + verifyComplexMessage(expected); } private static final String NON_NULL_STRING = "foo"; public void testCheckNotNull_simple_success() { - String result = Preconditions.checkNotNull(NON_NULL_STRING); + String result = checkNotNull(NON_NULL_STRING); assertSame(NON_NULL_STRING, result); } public void testCheckNotNull_simple_failure() { - try { - Preconditions.checkNotNull(null); - fail("no exception thrown"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> checkNotNull(null)); } public void testCheckNotNull_simpleMessage_success() { - String result = Preconditions.checkNotNull(NON_NULL_STRING, IGNORE_ME); + String result = checkNotNull(NON_NULL_STRING, IGNORE_ME); assertSame(NON_NULL_STRING, result); } public void testCheckNotNull_simpleMessage_failure() { - try { - Preconditions.checkNotNull(null, new Message()); - fail("no exception thrown"); - } catch (NullPointerException expected) { - verifySimpleMessage(expected); - } + NullPointerException expected = + assertThrows(NullPointerException.class, () -> checkNotNull(null, new Message())); + verifySimpleMessage(expected); } public void testCheckNotNull_complexMessage_success() { - String result = Preconditions.checkNotNull(NON_NULL_STRING, "%s", IGNORE_ME); + String result = checkNotNull(NON_NULL_STRING, "%s", IGNORE_ME); assertSame(NON_NULL_STRING, result); } public void testCheckNotNull_complexMessage_failure() { - try { - Preconditions.checkNotNull(null, FORMAT, 5); - fail("no exception thrown"); - } catch (NullPointerException expected) { - verifyComplexMessage(expected); - } + NullPointerException expected = + assertThrows(NullPointerException.class, () -> checkNotNull(null, FORMAT, 5)); + verifyComplexMessage(expected); } public void testCheckElementIndex_ok() { - assertEquals(0, Preconditions.checkElementIndex(0, 1)); - assertEquals(0, Preconditions.checkElementIndex(0, 2)); - assertEquals(1, Preconditions.checkElementIndex(1, 2)); + assertEquals(0, checkElementIndex(0, 1)); + assertEquals(0, checkElementIndex(0, 2)); + assertEquals(1, checkElementIndex(1, 2)); } public void testCheckElementIndex_badSize() { - try { - Preconditions.checkElementIndex(1, -1); - fail(); - } catch (IllegalArgumentException expected) { - // don't care what the message text is, as this is an invalid usage of - // the Preconditions class, unlike all the other exceptions it throws - } + assertThrows(IllegalArgumentException.class, () -> checkElementIndex(1, -1)); } public void testCheckElementIndex_negative() { - try { - Preconditions.checkElementIndex(-1, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected).hasMessageThat().isEqualTo("index (-1) must not be negative"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkElementIndex(-1, 1)); + assertThat(expected).hasMessageThat().isEqualTo("index (-1) must not be negative"); } public void testCheckElementIndex_tooHigh() { - try { - Preconditions.checkElementIndex(1, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected).hasMessageThat().isEqualTo("index (1) must be less than size (1)"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkElementIndex(1, 1)); + assertThat(expected).hasMessageThat().isEqualTo("index (1) must be less than size (1)"); } public void testCheckElementIndex_withDesc_negative() { - try { - Preconditions.checkElementIndex(-1, 1, "foo"); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected).hasMessageThat().isEqualTo("foo (-1) must not be negative"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkElementIndex(-1, 1, "foo")); + assertThat(expected).hasMessageThat().isEqualTo("foo (-1) must not be negative"); } public void testCheckElementIndex_withDesc_tooHigh() { - try { - Preconditions.checkElementIndex(1, 1, "foo"); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected).hasMessageThat().isEqualTo("foo (1) must be less than size (1)"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkElementIndex(1, 1, "foo")); + assertThat(expected).hasMessageThat().isEqualTo("foo (1) must be less than size (1)"); } public void testCheckPositionIndex_ok() { - assertEquals(0, Preconditions.checkPositionIndex(0, 0)); - assertEquals(0, Preconditions.checkPositionIndex(0, 1)); - assertEquals(1, Preconditions.checkPositionIndex(1, 1)); + assertEquals(0, checkPositionIndex(0, 0)); + assertEquals(0, checkPositionIndex(0, 1)); + assertEquals(1, checkPositionIndex(1, 1)); } public void testCheckPositionIndex_badSize() { - try { - Preconditions.checkPositionIndex(1, -1); - fail(); - } catch (IllegalArgumentException expected) { - // don't care what the message text is, as this is an invalid usage of - // the Preconditions class, unlike all the other exceptions it throws - } + assertThrows(IllegalArgumentException.class, () -> checkPositionIndex(1, -1)); } public void testCheckPositionIndex_negative() { - try { - Preconditions.checkPositionIndex(-1, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected).hasMessageThat().isEqualTo("index (-1) must not be negative"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkPositionIndex(-1, 1)); + assertThat(expected).hasMessageThat().isEqualTo("index (-1) must not be negative"); } public void testCheckPositionIndex_tooHigh() { - try { - Preconditions.checkPositionIndex(2, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("index (2) must not be greater than size (1)"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkPositionIndex(2, 1)); + assertThat(expected).hasMessageThat().isEqualTo("index (2) must not be greater than size (1)"); } public void testCheckPositionIndex_withDesc_negative() { - try { - Preconditions.checkPositionIndex(-1, 1, "foo"); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected).hasMessageThat().isEqualTo("foo (-1) must not be negative"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkPositionIndex(-1, 1, "foo")); + assertThat(expected).hasMessageThat().isEqualTo("foo (-1) must not be negative"); } public void testCheckPositionIndex_withDesc_tooHigh() { - try { - Preconditions.checkPositionIndex(2, 1, "foo"); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected).hasMessageThat().isEqualTo("foo (2) must not be greater than size (1)"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkPositionIndex(2, 1, "foo")); + assertThat(expected).hasMessageThat().isEqualTo("foo (2) must not be greater than size (1)"); } public void testCheckPositionIndexes_ok() { - Preconditions.checkPositionIndexes(0, 0, 0); - Preconditions.checkPositionIndexes(0, 0, 1); - Preconditions.checkPositionIndexes(0, 1, 1); - Preconditions.checkPositionIndexes(1, 1, 1); + checkPositionIndexes(0, 0, 0); + checkPositionIndexes(0, 0, 1); + checkPositionIndexes(0, 1, 1); + checkPositionIndexes(1, 1, 1); } public void testCheckPositionIndexes_badSize() { - try { - Preconditions.checkPositionIndexes(1, 1, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> checkPositionIndexes(1, 1, -1)); } public void testCheckPositionIndex_startNegative() { - try { - Preconditions.checkPositionIndexes(-1, 1, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected).hasMessageThat().isEqualTo("start index (-1) must not be negative"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkPositionIndexes(-1, 1, 1)); + assertThat(expected).hasMessageThat().isEqualTo("start index (-1) must not be negative"); } public void testCheckPositionIndexes_endTooHigh() { - try { - Preconditions.checkPositionIndexes(0, 2, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("end index (2) must not be greater than size (1)"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkPositionIndexes(0, 2, 1)); + assertThat(expected) + .hasMessageThat() + .isEqualTo("end index (2) must not be greater than size (1)"); } public void testCheckPositionIndexes_reversed() { - try { - Preconditions.checkPositionIndexes(1, 0, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("end index (0) must not be less than start index (1)"); - } + IndexOutOfBoundsException expected = + assertThrows(IndexOutOfBoundsException.class, () -> checkPositionIndexes(1, 0, 1)); + assertThat(expected) + .hasMessageThat() + .isEqualTo("end index (0) must not be less than start index (1)"); } @GwtIncompatible("Reflection") + @J2ktIncompatible public void testAllOverloads_checkArgument() throws Exception { for (ImmutableList> sig : allSignatures(boolean.class)) { Method checkArgumentMethod = @@ -392,16 +306,16 @@ public void testAllOverloads_checkArgument() throws Exception { checkArgumentMethod.invoke(null /* static method */, getParametersForSignature(true, sig)); Object[] failingParams = getParametersForSignature(false, sig); - try { - checkArgumentMethod.invoke(null /* static method */, failingParams); - fail(); - } catch (InvocationTargetException ite) { - assertFailureCause(ite.getCause(), IllegalArgumentException.class, failingParams); - } + InvocationTargetException ite = + assertThrows( + InvocationTargetException.class, + () -> checkArgumentMethod.invoke(null /* static method */, failingParams)); + assertFailureCause(ite.getCause(), IllegalArgumentException.class, failingParams); } } @GwtIncompatible("Reflection") + @J2ktIncompatible public void testAllOverloads_checkState() throws Exception { for (ImmutableList> sig : allSignatures(boolean.class)) { Method checkArgumentMethod = @@ -409,16 +323,16 @@ public void testAllOverloads_checkState() throws Exception { checkArgumentMethod.invoke(null /* static method */, getParametersForSignature(true, sig)); Object[] failingParams = getParametersForSignature(false, sig); - try { - checkArgumentMethod.invoke(null /* static method */, failingParams); - fail(); - } catch (InvocationTargetException ite) { - assertFailureCause(ite.getCause(), IllegalStateException.class, failingParams); - } + InvocationTargetException ite = + assertThrows( + InvocationTargetException.class, + () -> checkArgumentMethod.invoke(null /* static method */, failingParams)); + assertFailureCause(ite.getCause(), IllegalStateException.class, failingParams); } } @GwtIncompatible("Reflection") + @J2ktIncompatible public void testAllOverloads_checkNotNull() throws Exception { for (ImmutableList> sig : allSignatures(Object.class)) { Method checkArgumentMethod = @@ -427,12 +341,11 @@ public void testAllOverloads_checkNotNull() throws Exception { null /* static method */, getParametersForSignature(new Object(), sig)); Object[] failingParams = getParametersForSignature(null, sig); - try { - checkArgumentMethod.invoke(null /* static method */, failingParams); - fail(); - } catch (InvocationTargetException ite) { - assertFailureCause(ite.getCause(), NullPointerException.class, failingParams); - } + InvocationTargetException ite = + assertThrows( + InvocationTargetException.class, + () -> checkArgumentMethod.invoke(null /* static method */, failingParams)); + assertFailureCause(ite.getCause(), NullPointerException.class, failingParams); } } @@ -462,7 +375,9 @@ private void assertFailureCause( * @param sig The method signature */ @GwtIncompatible("ArbitraryInstances") - private Object[] getParametersForSignature(Object firstParam, ImmutableList> sig) { + @J2ktIncompatible + private Object[] getParametersForSignature( + @Nullable Object firstParam, ImmutableList> sig) { Object[] params = new Object[sig.size()]; params[0] = firstParam; if (params.length > 1) { @@ -477,7 +392,7 @@ private Object[] getParametersForSignature(Object firstParam, ImmutableList> possibleParamTypes = + private static final ImmutableList> POSSIBLE_PARAM_TYPES = ImmutableList.of(char.class, int.class, long.class, Object.class); /** @@ -495,7 +410,7 @@ private static ImmutableList>> allSignatures(Class pre List>> typesLists = new ArrayList<>(); for (int i = 0; i < 2; i++) { - typesLists.add(possibleParamTypes); + typesLists.add(POSSIBLE_PARAM_TYPES); for (List> curr : Lists.cartesianProduct(typesLists)) { allOverloads.add( ImmutableList.>builder() @@ -521,26 +436,41 @@ public void overloadSelection() { int anInt = 1; // With a boxed predicate, no overloads can be selected in phase 1 // ambiguous without the call to .booleanValue to unbox the Boolean - Preconditions.checkState(boxedBoolean.booleanValue(), "", 1); + checkState(boxedBoolean.booleanValue(), "", 1); // ambiguous without the cast to Object because the boxed predicate prevents any overload from // being selected in phase 1 - Preconditions.checkState(boxedBoolean, "", (Object) boxedLong); + checkState(boxedBoolean, "", (Object) boxedLong); // ternaries introduce their own problems. because of the ternary (which requires a boxing // operation) no overload can be selected in phase 1. and in phase 2 it is ambiguous since it // matches with the second parameter being boxed and without it being boxed. The cast to Object // avoids this. - Preconditions.checkState(aBoolean, "", aBoolean ? "" : anInt, (Object) anInt); + checkState(aBoolean, "", aBoolean ? "" : anInt, (Object) anInt); // ambiguous without the .booleanValue() call since the boxing forces us into phase 2 resolution short s = 2; - Preconditions.checkState(boxedBoolean.booleanValue(), "", s); + checkState(boxedBoolean.booleanValue(), "", s); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { - NullPointerTester tester = new NullPointerTester(); - tester.testAllPublicStaticMethods(Preconditions.class); + /* + * Don't bother testing: Preconditions defines a bunch of methods that accept a template (or + * even entire message) that simultaneously: + * + * - _shouldn't_ be null, so we don't annotate it with @Nullable + * + * - _can_ be null without causing a runtime failure (because we don't want the interesting + * details of precondition failure to be hidden by an exception we throw about an unexpectedly + * null _failure message_) + * + * That combination upsets NullPointerTester, which wants any call that passes null for a + * non-@Nullable parameter to trigger a NullPointerException. + * + * (We still define this empty method to keep PackageSanityTests from generating its own + * automated nullness tests, which would fail.) + */ } private static final Object IGNORE_ME = diff --git a/android/guava-tests/test/com/google/common/base/PredicatesTest.java b/android/guava-tests/test/com/google/common/base/PredicatesTest.java index 8f8647f4d190..bf4c7374e2c4 100644 --- a/android/guava-tests/test/com/google/common/base/PredicatesTest.java +++ b/android/guava-tests/test/com/google/common/base/PredicatesTest.java @@ -17,10 +17,13 @@ package com.google.common.base; import static com.google.common.base.CharMatcher.whitespace; -import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.testing.ClassSanityTester; import com.google.common.testing.EqualsTester; @@ -30,36 +33,38 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Predicates}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class PredicatesTest extends TestCase { - private static final Predicate TRUE = Predicates.alwaysTrue(); - private static final Predicate FALSE = Predicates.alwaysFalse(); - private static final Predicate NEVER_REACHED = - new Predicate() { + private static final Predicate<@Nullable Integer> TRUE = Predicates.alwaysTrue(); + private static final Predicate<@Nullable Integer> FALSE = Predicates.alwaysFalse(); + private static final Predicate<@Nullable Integer> NEVER_REACHED = + new Predicate<@Nullable Integer>() { @Override - public boolean apply(Integer i) { + public boolean apply(@Nullable Integer i) { throw new AssertionFailedError("This predicate should never have been evaluated"); } }; /** Instantiable predicate with reasonable hashCode() and equals() methods. */ - static class IsOdd implements Predicate, Serializable { - private static final long serialVersionUID = 0x150ddL; + static class IsOdd implements Predicate<@Nullable Integer>, Serializable { + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0x150ddL; @Override - public boolean apply(Integer i) { + public boolean apply(@Nullable Integer i) { return (i.intValue() & 1) == 1; } @@ -69,7 +74,7 @@ public int hashCode() { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return obj instanceof IsOdd; } @@ -105,6 +110,7 @@ public void testAlwaysTrue_equality() throws Exception { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testAlwaysTrue_serialization() { checkSerialization(Predicates.alwaysTrue()); @@ -126,6 +132,7 @@ public void testAlwaysFalse_equality() throws Exception { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testAlwaysFalse_serialization() { checkSerialization(Predicates.alwaysFalse()); @@ -175,6 +182,7 @@ public void testNot_equalityForNotOfKnownValues() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNot_serialization() { checkSerialization(Predicates.not(isOdd())); @@ -184,12 +192,10 @@ public void testNot_serialization() { * Tests for all the different flavors of Predicates.and(). */ - @SuppressWarnings("unchecked") // varargs public void testAnd_applyNoArgs() { assertEvalsToTrue(Predicates.and()); } - @SuppressWarnings("unchecked") // varargs public void testAnd_equalityNoArgs() { new EqualsTester() .addEqualityGroup(Predicates.and(), Predicates.and()) @@ -198,18 +204,16 @@ public void testAnd_equalityNoArgs() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester - @SuppressWarnings("unchecked") // varargs public void testAnd_serializationNoArgs() { checkSerialization(Predicates.and()); } - @SuppressWarnings("unchecked") // varargs public void testAnd_applyOneArg() { assertEvalsLikeOdd(Predicates.and(isOdd())); } - @SuppressWarnings("unchecked") // varargs public void testAnd_equalityOneArg() { Object[] notEqualObjects = {Predicates.and(NEVER_REACHED, FALSE)}; new EqualsTester() @@ -221,8 +225,8 @@ public void testAnd_equalityOneArg() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester - @SuppressWarnings("unchecked") // varargs public void testAnd_serializationOneArg() { checkSerialization(Predicates.and(isOdd())); } @@ -233,7 +237,6 @@ public void testAnd_applyBinary() { assertEvalsToFalse(Predicates.and(FALSE, NEVER_REACHED)); } - @SuppressWarnings("unchecked") // varargs public void testAnd_equalityBinary() { new EqualsTester() .addEqualityGroup(Predicates.and(TRUE, NEVER_REACHED), Predicates.and(TRUE, NEVER_REACHED)) @@ -243,12 +246,12 @@ public void testAnd_equalityBinary() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testAnd_serializationBinary() { checkSerialization(Predicates.and(TRUE, isOdd())); } - @SuppressWarnings("unchecked") // varargs public void testAnd_applyTernary() { assertEvalsLikeOdd(Predicates.and(isOdd(), TRUE, TRUE)); assertEvalsLikeOdd(Predicates.and(TRUE, isOdd(), TRUE)); @@ -256,7 +259,6 @@ public void testAnd_applyTernary() { assertEvalsToFalse(Predicates.and(TRUE, FALSE, NEVER_REACHED)); } - @SuppressWarnings("unchecked") // varargs public void testAnd_equalityTernary() { new EqualsTester() .addEqualityGroup( @@ -268,22 +270,20 @@ public void testAnd_equalityTernary() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester - @SuppressWarnings("unchecked") // varargs public void testAnd_serializationTernary() { checkSerialization(Predicates.and(TRUE, isOdd(), FALSE)); } - @SuppressWarnings("unchecked") // varargs public void testAnd_applyIterable() { - Collection> empty = Arrays.asList(); + Collection> empty = Arrays.asList(); assertEvalsToTrue(Predicates.and(empty)); assertEvalsLikeOdd(Predicates.and(Arrays.asList(isOdd()))); assertEvalsLikeOdd(Predicates.and(Arrays.asList(TRUE, isOdd()))); assertEvalsToFalse(Predicates.and(Arrays.asList(FALSE, NEVER_REACHED))); } - @SuppressWarnings("unchecked") // varargs public void testAnd_equalityIterable() { new EqualsTester() .addEqualityGroup( @@ -295,15 +295,15 @@ public void testAnd_equalityIterable() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester - @SuppressWarnings("unchecked") // varargs public void testAnd_serializationIterable() { checkSerialization(Predicates.and(Arrays.asList(TRUE, FALSE))); } - @SuppressWarnings("unchecked") // varargs public void testAnd_arrayDefensivelyCopied() { - Predicate[] array = {Predicates.alwaysFalse()}; + @SuppressWarnings("unchecked") // generic arrays + Predicate[] array = (Predicate[]) new Predicate[] {Predicates.alwaysFalse()}; Predicate predicate = Predicates.and(array); assertFalse(predicate.apply(1)); array[0] = Predicates.alwaysTrue(); @@ -311,7 +311,7 @@ public void testAnd_arrayDefensivelyCopied() { } public void testAnd_listDefensivelyCopied() { - List> list = newArrayList(); + List> list = new ArrayList<>(); Predicate predicate = Predicates.and(list); assertTrue(predicate.apply(1)); list.add(Predicates.alwaysFalse()); @@ -319,7 +319,7 @@ public void testAnd_listDefensivelyCopied() { } public void testAnd_iterableDefensivelyCopied() { - final List> list = newArrayList(); + List> list = new ArrayList<>(); Iterable> iterable = new Iterable>() { @Override @@ -337,12 +337,10 @@ public Iterator> iterator() { * Tests for all the different flavors of Predicates.or(). */ - @SuppressWarnings("unchecked") // varargs public void testOr_applyNoArgs() { assertEvalsToFalse(Predicates.or()); } - @SuppressWarnings("unchecked") // varargs public void testOr_equalityNoArgs() { new EqualsTester() .addEqualityGroup(Predicates.or(), Predicates.or()) @@ -351,19 +349,17 @@ public void testOr_equalityNoArgs() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester - @SuppressWarnings("unchecked") // varargs public void testOr_serializationNoArgs() { checkSerialization(Predicates.or()); } - @SuppressWarnings("unchecked") // varargs public void testOr_applyOneArg() { assertEvalsToTrue(Predicates.or(TRUE)); assertEvalsToFalse(Predicates.or(FALSE)); } - @SuppressWarnings("unchecked") // varargs public void testOr_equalityOneArg() { new EqualsTester() .addEqualityGroup(Predicates.or(NEVER_REACHED), Predicates.or(NEVER_REACHED)) @@ -374,23 +370,22 @@ public void testOr_equalityOneArg() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester - @SuppressWarnings("unchecked") // varargs public void testOr_serializationOneArg() { checkSerialization(Predicates.or(isOdd())); } public void testOr_applyBinary() { - Predicate falseOrFalse = Predicates.or(FALSE, FALSE); - Predicate falseOrTrue = Predicates.or(FALSE, TRUE); - Predicate trueOrAnything = Predicates.or(TRUE, NEVER_REACHED); + Predicate<@Nullable Integer> falseOrFalse = Predicates.or(FALSE, FALSE); + Predicate<@Nullable Integer> falseOrTrue = Predicates.or(FALSE, TRUE); + Predicate<@Nullable Integer> trueOrAnything = Predicates.or(TRUE, NEVER_REACHED); assertEvalsToFalse(falseOrFalse); assertEvalsToTrue(falseOrTrue); assertEvalsToTrue(trueOrAnything); } - @SuppressWarnings("unchecked") // varargs public void testOr_equalityBinary() { new EqualsTester() .addEqualityGroup(Predicates.or(FALSE, NEVER_REACHED), Predicates.or(FALSE, NEVER_REACHED)) @@ -400,12 +395,12 @@ public void testOr_equalityBinary() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testOr_serializationBinary() { checkSerialization(Predicates.or(isOdd(), TRUE)); } - @SuppressWarnings("unchecked") // varargs public void testOr_applyTernary() { assertEvalsLikeOdd(Predicates.or(isOdd(), FALSE, FALSE)); assertEvalsLikeOdd(Predicates.or(FALSE, isOdd(), FALSE)); @@ -413,7 +408,6 @@ public void testOr_applyTernary() { assertEvalsToTrue(Predicates.or(FALSE, TRUE, NEVER_REACHED)); } - @SuppressWarnings("unchecked") // varargs public void testOr_equalityTernary() { new EqualsTester() .addEqualityGroup( @@ -424,28 +418,22 @@ public void testOr_equalityTernary() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester - @SuppressWarnings("unchecked") // varargs public void testOr_serializationTernary() { checkSerialization(Predicates.or(FALSE, isOdd(), TRUE)); } - @SuppressWarnings("unchecked") // varargs public void testOr_applyIterable() { - Predicate vacuouslyFalse = Predicates.or(Collections.>emptyList()); - Predicate troo = Predicates.or(Collections.singletonList(TRUE)); - /* - * newLinkedList() takes varargs. TRUE and FALSE are both instances of - * Predicate, so the call is safe. - */ - Predicate trueAndFalse = Predicates.or(Arrays.asList(TRUE, FALSE)); + Predicate<@Nullable Integer> vacuouslyFalse = Predicates.or(ImmutableList.of()); + Predicate<@Nullable Integer> troo = Predicates.or(ImmutableList.of(TRUE)); + Predicate<@Nullable Integer> trueAndFalse = Predicates.or(ImmutableList.of(TRUE, FALSE)); assertEvalsToFalse(vacuouslyFalse); assertEvalsToTrue(troo); assertEvalsToTrue(trueAndFalse); } - @SuppressWarnings("unchecked") // varargs public void testOr_equalityIterable() { new EqualsTester() .addEqualityGroup( @@ -457,17 +445,17 @@ public void testOr_equalityIterable() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester - @SuppressWarnings("unchecked") // varargs public void testOr_serializationIterable() { Predicate pre = Predicates.or(Arrays.asList(TRUE, FALSE)); Predicate post = SerializableTester.reserializeAndAssert(pre); assertEquals(pre.apply(0), post.apply(0)); } - @SuppressWarnings("unchecked") // varargs public void testOr_arrayDefensivelyCopied() { - Predicate[] array = {Predicates.alwaysFalse()}; + @SuppressWarnings("unchecked") // generic arrays + Predicate[] array = (Predicate[]) new Predicate[] {Predicates.alwaysFalse()}; Predicate predicate = Predicates.or(array); assertFalse(predicate.apply(1)); array[0] = Predicates.alwaysTrue(); @@ -475,7 +463,7 @@ public void testOr_arrayDefensivelyCopied() { } public void testOr_listDefensivelyCopied() { - List> list = newArrayList(); + List> list = new ArrayList<>(); Predicate predicate = Predicates.or(list); assertFalse(predicate.apply(1)); list.add(Predicates.alwaysTrue()); @@ -483,7 +471,7 @@ public void testOr_listDefensivelyCopied() { } public void testOr_iterableDefensivelyCopied() { - final List> list = newArrayList(); + List> list = new ArrayList<>(); Iterable> iterable = new Iterable>() { @Override @@ -502,7 +490,7 @@ public Iterator> iterator() { */ public void testIsEqualTo_apply() { - Predicate isOne = Predicates.equalTo(1); + Predicate<@Nullable Integer> isOne = Predicates.equalTo(1); assertTrue(isOne.apply(1)); assertFalse(isOne.apply(2)); @@ -513,29 +501,33 @@ public void testIsEqualTo_equality() { new EqualsTester() .addEqualityGroup(Predicates.equalTo(1), Predicates.equalTo(1)) .addEqualityGroup(Predicates.equalTo(2)) - .addEqualityGroup(Predicates.equalTo(null)) + .addEqualityGroup(Predicates.<@Nullable Integer>equalTo(null)) .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testIsEqualTo_serialization() { checkSerialization(Predicates.equalTo(1)); } public void testIsEqualToNull_apply() { - Predicate isNull = Predicates.equalTo(null); + Predicate<@Nullable Integer> isNull = Predicates.equalTo(null); assertTrue(isNull.apply(null)); assertFalse(isNull.apply(1)); } public void testIsEqualToNull_equality() { new EqualsTester() - .addEqualityGroup(Predicates.equalTo(null), Predicates.equalTo(null)) + .addEqualityGroup( + Predicates.<@Nullable Integer>equalTo(null), + Predicates.<@Nullable Integer>equalTo(null)) .addEqualityGroup(Predicates.equalTo(1)) .addEqualityGroup(Predicates.equalTo("null")) .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testIsEqualToNull_serialization() { checkSerialization(Predicates.equalTo(null)); @@ -548,7 +540,7 @@ public void testIsEqualToNull_serialization() { */ @GwtIncompatible // Predicates.instanceOf public void testIsInstanceOf_apply() { - Predicate isInteger = Predicates.instanceOf(Integer.class); + Predicate<@Nullable Object> isInteger = Predicates.instanceOf(Integer.class); assertTrue(isInteger.apply(1)); assertFalse(isInteger.apply(2.0f)); @@ -558,7 +550,7 @@ public void testIsInstanceOf_apply() { @GwtIncompatible // Predicates.instanceOf public void testIsInstanceOf_subclass() { - Predicate isNumber = Predicates.instanceOf(Number.class); + Predicate<@Nullable Object> isNumber = Predicates.instanceOf(Number.class); assertTrue(isNumber.apply(1)); assertTrue(isNumber.apply(2.0f)); @@ -568,7 +560,7 @@ public void testIsInstanceOf_subclass() { @GwtIncompatible // Predicates.instanceOf public void testIsInstanceOf_interface() { - Predicate isComparable = Predicates.instanceOf(Comparable.class); + Predicate<@Nullable Object> isComparable = Predicates.instanceOf(Comparable.class); assertTrue(isComparable.apply(1)); assertTrue(isComparable.apply(2.0f)); @@ -586,11 +578,13 @@ public void testIsInstanceOf_equality() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // Predicates.instanceOf, SerializableTester public void testIsInstanceOf_serialization() { checkSerialization(Predicates.instanceOf(Integer.class)); } + @J2ktIncompatible @GwtIncompatible // Predicates.subtypeOf public void testSubtypeOf_apply() { Predicate> isInteger = Predicates.subtypeOf(Integer.class); @@ -598,13 +592,10 @@ public void testSubtypeOf_apply() { assertTrue(isInteger.apply(Integer.class)); assertFalse(isInteger.apply(Float.class)); - try { - isInteger.apply(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> isInteger.apply(null)); } + @J2ktIncompatible @GwtIncompatible // Predicates.subtypeOf public void testSubtypeOf_subclass() { Predicate> isNumber = Predicates.subtypeOf(Number.class); @@ -613,6 +604,7 @@ public void testSubtypeOf_subclass() { assertTrue(isNumber.apply(Float.class)); } + @J2ktIncompatible @GwtIncompatible // Predicates.subtypeOf public void testSubtypeOf_interface() { Predicate> isComparable = Predicates.subtypeOf(Comparable.class); @@ -621,6 +613,7 @@ public void testSubtypeOf_interface() { assertTrue(isComparable.apply(Float.class)); } + @J2ktIncompatible @GwtIncompatible // Predicates.subtypeOf public void testSubtypeOf_equality() { new EqualsTester() @@ -630,6 +623,7 @@ public void testSubtypeOf_equality() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // Predicates.subtypeOf, SerializableTester public void testSubtypeOf_serialization() { Predicate> predicate = Predicates.subtypeOf(Integer.class); @@ -645,7 +639,7 @@ public void testSubtypeOf_serialization() { */ public void testIsNull_apply() { - Predicate isNull = Predicates.isNull(); + Predicate<@Nullable Integer> isNull = Predicates.isNull(); assertTrue(isNull.apply(null)); assertFalse(isNull.apply(1)); } @@ -657,6 +651,7 @@ public void testIsNull_equality() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testIsNull_serialization() { Predicate pre = Predicates.isNull(); @@ -666,7 +661,7 @@ public void testIsNull_serialization() { } public void testNotNull_apply() { - Predicate notNull = Predicates.notNull(); + Predicate<@Nullable Integer> notNull = Predicates.notNull(); assertFalse(notNull.apply(null)); assertTrue(notNull.apply(1)); } @@ -678,6 +673,7 @@ public void testNotNull_equality() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNotNull_serialization() { checkSerialization(Predicates.notNull()); @@ -685,7 +681,7 @@ public void testNotNull_serialization() { public void testIn_apply() { Collection nums = Arrays.asList(1, 5); - Predicate isOneOrFive = Predicates.in(nums); + Predicate<@Nullable Integer> isOneOrFive = Predicates.in(nums); assertTrue(isOneOrFive.apply(1)); assertTrue(isOneOrFive.apply(5)); @@ -709,36 +705,37 @@ public void testIn_equality() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testIn_serialization() { checkSerialization(Predicates.in(Arrays.asList(1, 2, 3, null))); } public void testIn_handlesNullPointerException() { - class CollectionThatThrowsNPE extends ArrayList { - private static final long serialVersionUID = 1L; + class CollectionThatThrowsNullPointerException extends ArrayList { + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1L; @Override - public boolean contains(Object element) { + public boolean contains(@Nullable Object element) { Preconditions.checkNotNull(element); return super.contains(element); } } - Collection nums = new CollectionThatThrowsNPE<>(); - Predicate isFalse = Predicates.in(nums); + Collection nums = new CollectionThatThrowsNullPointerException<>(); + Predicate<@Nullable Integer> isFalse = Predicates.in(nums); assertFalse(isFalse.apply(null)); } public void testIn_handlesClassCastException() { - class CollectionThatThrowsCCE extends ArrayList { - private static final long serialVersionUID = 1L; + class CollectionThatThrowsClassCastException extends ArrayList { + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1L; @Override - public boolean contains(Object element) { + public boolean contains(@Nullable Object element) { throw new ClassCastException(""); } } - Collection nums = new CollectionThatThrowsCCE<>(); + Collection nums = new CollectionThatThrowsClassCastException<>(); nums.add(3); Predicate isThree = Predicates.in(nums); assertFalse(isThree.apply(3)); @@ -757,14 +754,15 @@ public void testIn_compilesWithExplicitSupertype() { // Predicate p4 = Predicates.in(nums); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicStaticMethods(Predicates.class); } - @SuppressWarnings("unchecked") // varargs - @GwtIncompatible // SerializbleTester + @J2ktIncompatible + @GwtIncompatible // SerializableTester public void testCascadingSerialization() throws Exception { // Eclipse says Predicate; javac says Predicate. Predicate nasty = @@ -815,6 +813,7 @@ public void testCompose() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testComposeSerialization() { Function trim = TrimStringFunction.INSTANCE; @@ -843,6 +842,7 @@ public void testContains_apply() { assertFalse(isFoobar.apply("Foobarx")); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testContainsPattern_nulls() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -851,6 +851,7 @@ public void testContainsPattern_nulls() throws Exception { tester.testAllPublicInstanceMethods(isWooString); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testContains_nulls() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -859,6 +860,7 @@ public void testContains_nulls() throws Exception { tester.testAllPublicInstanceMethods(isWooPattern); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testContainsPattern_serialization() { Predicate pre = Predicates.containsPattern("foo"); @@ -877,13 +879,13 @@ public void testContains_equals() { } public void assertEqualHashCode( - Predicate expected, Predicate actual) { + Predicate expected, Predicate actual) { assertEquals(actual + " should hash like " + expected, expected.hashCode(), actual.hashCode()); } public void testHashCodeForBooleanOperations() { - Predicate p1 = Predicates.isNull(); - Predicate p2 = isOdd(); + Predicate<@Nullable Integer> p1 = Predicates.isNull(); + Predicate<@Nullable Integer> p2 = isOdd(); // Make sure that hash codes are not computed per-instance. assertEqualHashCode(Predicates.not(p1), Predicates.not(p1)); @@ -897,41 +899,43 @@ public void testHashCodeForBooleanOperations() { assertTrue(Predicates.and(p1, p2).hashCode() != Predicates.or(p1, p2).hashCode()); } + @J2ktIncompatible @GwtIncompatible // reflection public void testNulls() throws Exception { new ClassSanityTester().forAllPublicStaticMethods(Predicates.class).testNulls(); } + @J2ktIncompatible @GwtIncompatible // reflection @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException: com.google.common.base.Function public void testEqualsAndSerializable() throws Exception { new ClassSanityTester().forAllPublicStaticMethods(Predicates.class).testEqualsAndSerializable(); } - private static void assertEvalsToTrue(Predicate predicate) { + private static void assertEvalsToTrue(Predicate predicate) { assertTrue(predicate.apply(0)); assertTrue(predicate.apply(1)); assertTrue(predicate.apply(null)); } - private static void assertEvalsToFalse(Predicate predicate) { + private static void assertEvalsToFalse(Predicate predicate) { assertFalse(predicate.apply(0)); assertFalse(predicate.apply(1)); assertFalse(predicate.apply(null)); } - private static void assertEvalsLikeOdd(Predicate predicate) { + private static void assertEvalsLikeOdd(Predicate predicate) { assertEvalsLike(isOdd(), predicate); } private static void assertEvalsLike( - Predicate expected, Predicate actual) { + Predicate expected, Predicate actual) { assertEvalsLike(expected, actual, 0); assertEvalsLike(expected, actual, 1); - assertEvalsLike(expected, actual, null); + PredicatesTest.<@Nullable Integer>assertEvalsLike(expected, actual, null); } - private static void assertEvalsLike( + private static void assertEvalsLike( Predicate expected, Predicate actual, T input) { Boolean expectedResult = null; RuntimeException expectedRuntimeException = null; @@ -951,14 +955,16 @@ private static void assertEvalsLike( assertEquals(expectedResult, actualResult); if (expectedRuntimeException != null) { - assertNotNull(actualRuntimeException); + assertThat(actualRuntimeException).isNotNull(); assertEquals(expectedRuntimeException.getClass(), actualRuntimeException.getClass()); } } + @J2ktIncompatible @GwtIncompatible // SerializableTester - private static void checkSerialization(Predicate predicate) { - Predicate reserialized = SerializableTester.reserializeAndAssert(predicate); + private static void checkSerialization(Predicate predicate) { + Predicate reserialized = + SerializableTester.reserializeAndAssert(predicate); assertEvalsLike(predicate, reserialized); } } diff --git a/android/guava-tests/test/com/google/common/base/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/base/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..bf8e7e84cbac --- /dev/null +++ b/android/guava-tests/test/com/google/common/base/ReflectionFreeAssertThrows.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.TestExceptions.SomeCheckedException; +import com.google.common.base.TestExceptions.SomeError; +import com.google.common.base.TestExceptions.SomeOtherCheckedException; +import com.google.common.base.TestExceptions.SomeUncheckedException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(SomeCheckedException.class, e -> e instanceof SomeCheckedException) + .put(SomeError.class, e -> e instanceof SomeError) + .put(SomeOtherCheckedException.class, e -> e instanceof SomeOtherCheckedException) + .put(SomeUncheckedException.class, e -> e instanceof SomeUncheckedException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/base/SplitterTest.java b/android/guava-tests/test/com/google/common/base/SplitterTest.java index 46928aa36a3b..239c25235ce8 100644 --- a/android/guava-tests/test/com/google/common/base/SplitterTest.java +++ b/android/guava-tests/test/com/google/common/base/SplitterTest.java @@ -16,10 +16,13 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Splitter.MapSplitter; import com.google.common.collect.ImmutableMap; import com.google.common.testing.NullPointerTester; @@ -28,19 +31,19 @@ import java.util.Map; import java.util.regex.Pattern; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; -/** @author Julien Silland */ -@GwtCompatible(emulated = true) +/** + * @author Julien Silland + */ +@NullMarked +@GwtCompatible public class SplitterTest extends TestCase { private static final Splitter COMMA_SPLITTER = Splitter.on(','); public void testSplitNullString() { - try { - COMMA_SPLITTER.split(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> COMMA_SPLITTER.split(null)); } public void testCharacterSimpleSplit() { @@ -62,6 +65,12 @@ public void testCharacterSimpleSplitToList() { assertThat(letters).containsExactly("a", "b", "c").inOrder(); } + public void testCharacterSimpleSplitToStream() { + String simple = "a,b,c"; + List letters = COMMA_SPLITTER.splitToStream(simple).collect(toImmutableList()); + assertThat(letters).containsExactly("a", "b", "c").inOrder(); + } + public void testToString() { assertEquals("[]", COMMA_SPLITTER.split("").toString()); assertEquals("[a, b, c]", COMMA_SPLITTER.split("a,b,c").toString()); @@ -152,8 +161,7 @@ public void testCharacterSplitOnOnlyDelimitersOmitEmptyStrings() { } public void testCharacterSplitWithTrim() { - String jacksons = - "arfo(Marlon)aorf, (Michael)orfa, afro(Jackie)orfa, " + "ofar(Jemaine), aff(Tito)"; + String jacksons = "arfo(Marlon)aorf, (Michael)orfa, afro(Jackie)orfa, ofar(Jemaine), aff(Tito)"; Iterable family = COMMA_SPLITTER .trimResults(CharMatcher.anyOf("afro").or(CharMatcher.whitespace())) @@ -249,11 +257,7 @@ public void testStringSplitWithDelimiterSubstringInValue() { } public void testStringSplitWithEmptyString() { - try { - Splitter.on(""); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Splitter.on("")); } public void testStringSplitOnEmptyString() { @@ -276,8 +280,7 @@ public void testStringSplitOnOnlyDelimitersOmitEmptyStrings() { } public void testStringSplitWithTrim() { - String jacksons = - "arfo(Marlon)aorf, (Michael)orfa, afro(Jackie)orfa, " + "ofar(Jemaine), aff(Tito)"; + String jacksons = "arfo(Marlon)aorf, (Michael)orfa, afro(Jackie)orfa, ofar(Jemaine), aff(Tito)"; Iterable family = Splitter.on(",") .trimResults(CharMatcher.anyOf("afro").or(CharMatcher.whitespace())) @@ -352,6 +355,7 @@ public void testPatternSplitWithDoubleDelimiterOmitEmptyStrings() { assertThat(letters).containsExactly("a", "b", "c").inOrder(); } + @J2ktIncompatible // Kotlin Native's regex is based on Apache Harmony, like old Android @GwtIncompatible // java.util.regex.Pattern @AndroidIncompatible // Bug in older versions of Android we test against, since fixed. public void testPatternSplitLookBehind() { @@ -365,6 +369,7 @@ public void testPatternSplitLookBehind() { // splits into chunks ending in : } + @J2ktIncompatible // Kotlin Native's regex is based on Apache Harmony, like old Android @GwtIncompatible // java.util.regex.Pattern @AndroidIncompatible // Bug in older versions of Android we test against, since fixed. public void testPatternSplitWordBoundary() { @@ -381,6 +386,7 @@ public void testPatternSplitWordBoundary_singleCharInput() { } @AndroidIncompatible // Apparently Gingerbread's regex API is buggy. + @J2ktIncompatible // Kotlin Native's regex is based on Apache Harmony, like old Android @GwtIncompatible // java.util.regex.Pattern public void testPatternSplitWordBoundary_singleWordInput() { String string = "foo"; @@ -439,17 +445,12 @@ public void testPatternSplitWithLongTrailingDelimiter() { @GwtIncompatible // java.util.regex.Pattern public void testPatternSplitInvalidPattern() { - try { - Splitter.on(Pattern.compile("a*")); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Splitter.on(Pattern.compile("a*"))); } @GwtIncompatible // java.util.regex.Pattern public void testPatternSplitWithTrim() { - String jacksons = - "arfo(Marlon)aorf, (Michael)orfa, afro(Jackie)orfa, " + "ofar(Jemaine), aff(Tito)"; + String jacksons = "arfo(Marlon)aorf, (Michael)orfa, afro(Jackie)orfa, ofar(Jemaine), aff(Tito)"; Iterable family = Splitter.on(Pattern.compile(",")) .trimResults(CharMatcher.anyOf("afro").or(CharMatcher.whitespace())) @@ -489,6 +490,7 @@ public void testSplitterIterableIsLazy_string() { assertSplitterIterableIsLazy(Splitter.on(",")); } + @J2ktIncompatible @GwtIncompatible // java.util.regex.Pattern @AndroidIncompatible // not clear that j.u.r.Matcher promises to handle mutations during use public void testSplitterIterableIsLazy_pattern() { @@ -556,19 +558,11 @@ public void testFixedLengthSplitIntoChars() { } public void testFixedLengthSplitZeroChunkLen() { - try { - Splitter.fixedLength(0); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Splitter.fixedLength(0)); } public void testFixedLengthSplitNegativeChunkLen() { - try { - Splitter.fixedLength(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Splitter.fixedLength(-1)); } public void testLimitLarge() { @@ -656,13 +650,10 @@ public void testLimitExtraSeparatorsTrim1EmptyOmit() { } public void testInvalidZeroLimit() { - try { - COMMA_SPLITTER.limit(0); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> COMMA_SPLITTER.limit(0)); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -718,7 +709,7 @@ public void testMapSplitter_notTrimmed() { assertThat(m.entrySet()).containsExactlyElementsIn(expected.entrySet()).inOrder(); } - public void testMapSplitter_CharacterSeparator() { + public void testMapSplitter_characterSeparator() { // try different delimiters. Map m = Splitter.on(",").withKeyValueSeparator(':').split("boy:tom,girl:tina,cat:kitty,dog:tommy"); @@ -743,19 +734,13 @@ public void testMapSplitter_multiCharacterSeparator() { } public void testMapSplitter_emptySeparator() { - try { - COMMA_SPLITTER.withKeyValueSeparator(""); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> COMMA_SPLITTER.withKeyValueSeparator("")); } public void testMapSplitter_malformedEntry() { - try { - COMMA_SPLITTER.withKeyValueSeparator("=").split("a=1,b,c=2"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> COMMA_SPLITTER.withKeyValueSeparator("=").split("a=1,b,c=2")); } /** @@ -763,11 +748,9 @@ public void testMapSplitter_malformedEntry() { * be changed? */ public void testMapSplitter_extraValueDelimiter() { - try { - COMMA_SPLITTER.withKeyValueSeparator("=").split("a=1,c=2="); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> COMMA_SPLITTER.withKeyValueSeparator("=").split("a=1,c=2=")); } public void testMapSplitter_orderedResults() { @@ -787,11 +770,9 @@ public void testMapSplitter_orderedResults() { } public void testMapSplitter_duplicateKeys() { - try { - COMMA_SPLITTER.withKeyValueSeparator(":").split("a:1,b:2,a:3"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> COMMA_SPLITTER.withKeyValueSeparator(":").split("a:1,b:2,a:3")); } public void testMapSplitter_varyingTrimLevels() { diff --git a/android/guava-tests/test/com/google/common/base/StandardSystemPropertyTest.java b/android/guava-tests/test/com/google/common/base/StandardSystemPropertyTest.java index 3a88366c0cc0..6c898bf3dc52 100644 --- a/android/guava-tests/test/com/google/common/base/StandardSystemPropertyTest.java +++ b/android/guava-tests/test/com/google/common/base/StandardSystemPropertyTest.java @@ -20,13 +20,17 @@ import static com.google.common.base.StandardSystemProperty.JAVA_EXT_DIRS; import static com.google.common.truth.Truth.assertWithMessage; +import com.google.common.annotations.GwtIncompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link StandardSystemProperty}. * * @author Kurt Alfred Kluever */ +@GwtIncompatible +@NullUnmarked public class StandardSystemPropertyTest extends TestCase { public void testGetKeyMatchesString() { diff --git a/android/guava-tests/test/com/google/common/base/StopwatchTest.java b/android/guava-tests/test/com/google/common/base/StopwatchTest.java index b85ebb7b0e94..6edce7ea535b 100644 --- a/android/guava-tests/test/com/google/common/base/StopwatchTest.java +++ b/android/guava-tests/test/com/google/common/base/StopwatchTest.java @@ -16,13 +16,18 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.FakeTicker; +import java.time.Duration; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Stopwatch}. @@ -30,6 +35,7 @@ * @author Kevin Bourrillion */ @GwtCompatible +@NullUnmarked public class StopwatchTest extends TestCase { private final FakeTicker ticker = new FakeTicker(); @@ -58,11 +64,7 @@ public void testStart() { public void testStart_whileRunning() { stopwatch.start(); - try { - stopwatch.start(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, stopwatch::start); assertTrue(stopwatch.isRunning()); } @@ -73,22 +75,14 @@ public void testStop() { } public void testStop_new() { - try { - stopwatch.stop(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, stopwatch::stop); assertFalse(stopwatch.isRunning()); } public void testStop_alreadyStopped() { stopwatch.start(); stopwatch.stop(); - try { - stopwatch.stop(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, stopwatch::stop); assertFalse(stopwatch.isRunning()); } @@ -166,6 +160,7 @@ public void testElapsed_millis() { assertEquals(1, stopwatch.elapsed(MILLISECONDS)); } + @J2ktIncompatible // TODO(b/259213718): Switch J2kt to String.format("%.4g") once that's supported public void testToString() { stopwatch.start(); assertEquals("0.000 ns", stopwatch.toString()); @@ -200,4 +195,14 @@ public void testToString() { ticker.advance((long) (7.25 * 24 * 60 * 60 * 1000000000L)); assertEquals("7.250 d", stopwatch.toString()); } + + @GwtIncompatible + @J2ktIncompatible + public void testElapsed_duration() { + stopwatch.start(); + ticker.advance(999999); + assertEquals(Duration.ofNanos(999999), stopwatch.elapsed()); + ticker.advance(1); + assertEquals(Duration.ofMillis(1), stopwatch.elapsed()); + } } diff --git a/android/guava-tests/test/com/google/common/base/StringsTest.java b/android/guava-tests/test/com/google/common/base/StringsTest.java index ac3ad20a52dc..639dca2d41cf 100644 --- a/android/guava-tests/test/com/google/common/base/StringsTest.java +++ b/android/guava-tests/test/com/google/common/base/StringsTest.java @@ -16,19 +16,23 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link Strings}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class StringsTest extends TestCase { public void testNullToEmpty() { assertEquals("", Strings.nullToEmpty(null)); @@ -37,8 +41,8 @@ public void testNullToEmpty() { } public void testEmptyToNull() { - assertNull(Strings.emptyToNull(null)); - assertNull(Strings.emptyToNull("")); + assertThat(Strings.emptyToNull(null)).isNull(); + assertThat(Strings.emptyToNull("")).isNull(); assertEquals("a", Strings.emptyToNull("a")); } @@ -70,11 +74,7 @@ public void testPadStart_negativeMinLength() { // TODO: could remove if we got NPT working in GWT somehow public void testPadStart_null() { - try { - Strings.padStart(null, 5, '0'); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Strings.padStart(null, 5, '0')); } public void testPadEnd_noPadding() { @@ -97,15 +97,11 @@ public void testPadEnd_negativeMinLength() { assertSame("x", Strings.padEnd("x", -1, '-')); } - // TODO: could remove if we got NPT working in GWT somehow public void testPadEnd_null() { - try { - Strings.padEnd(null, 5, '0'); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Strings.padEnd(null, 5, '0')); } + @SuppressWarnings("InlineMeInliner") // test of method that doesn't just delegate public void testRepeat() { String input = "20"; assertEquals("", Strings.repeat(input, 0)); @@ -119,28 +115,17 @@ public void testRepeat() { assertEquals(2 * i, Strings.repeat(input, i).length()); } - try { - Strings.repeat("x", -1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - // Massive string - Strings.repeat("12345678", (1 << 30) + 3); - fail(); - } catch (ArrayIndexOutOfBoundsException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Strings.repeat("x", -1)); + assertThrows( + ArrayIndexOutOfBoundsException.class, () -> Strings.repeat("12345678", (1 << 30) + 3)); } - // TODO: could remove if we got NPT working in GWT somehow + @SuppressWarnings("InlineMeInliner") // test of method that doesn't just delegate public void testRepeat_null() { - try { - Strings.repeat(null, 5); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Strings.repeat(null, 5)); } + @SuppressWarnings("UnnecessaryStringBuilder") // We want to test a non-String CharSequence public void testCommonPrefix() { assertEquals("", Strings.commonPrefix("", "")); assertEquals("", Strings.commonPrefix("abc", "")); @@ -150,7 +135,7 @@ public void testCommonPrefix() { assertEquals("", Strings.commonPrefix("xyz", "abcxyz")); assertEquals("a", Strings.commonPrefix("abc", "aaaaa")); assertEquals("aa", Strings.commonPrefix("aa", "aaaaa")); - assertEquals("abc", Strings.commonPrefix(new StringBuffer("abcdef"), "abcxyz")); + assertEquals("abc", Strings.commonPrefix(new StringBuilder("abcdef"), "abcxyz")); // Identical valid surrogate pairs. assertEquals( @@ -170,6 +155,7 @@ public void testCommonPrefix() { assertEquals("\uD8AB", Strings.commonPrefix("\uD8AB", "\uD8AB")); } + @SuppressWarnings("UnnecessaryStringBuilder") // We want to test a non-String CharSequence public void testCommonSuffix() { assertEquals("", Strings.commonSuffix("", "")); assertEquals("", Strings.commonSuffix("abc", "")); @@ -179,7 +165,7 @@ public void testCommonSuffix() { assertEquals("", Strings.commonSuffix("xyz", "xyzabc")); assertEquals("c", Strings.commonSuffix("abc", "ccccc")); assertEquals("aa", Strings.commonSuffix("aa", "aaaaa")); - assertEquals("abc", Strings.commonSuffix(new StringBuffer("xyzabc"), "xxxabc")); + assertEquals("abc", Strings.commonSuffix(new StringBuilder("xyzabc"), "xxxabc")); // Identical valid surrogate pairs. assertEquals( @@ -213,6 +199,7 @@ public void testValidSurrogatePairAt() { assertFalse(Strings.validSurrogatePairAt("\uD8ABx", 0)); } + @SuppressWarnings("LenientFormatStringValidation") // Intentional for testing. public void testLenientFormat() { assertEquals("%s", Strings.lenientFormat("%s")); assertEquals("5", Strings.lenientFormat("%s", 5)); @@ -226,9 +213,15 @@ public void testLenientFormat() { assertEquals("5 + 6 = 11", Strings.lenientFormat("5 + %s = 11", 6)); assertEquals("5 + 6 = 11", Strings.lenientFormat("5 + 6 = %s", 11)); assertEquals("5 + 6 = 11", Strings.lenientFormat("%s + %s = %s", 5, 6, 11)); + assertEquals( + "5 + 6 = 11", Strings.lenientFormat("%s + %s = %s", (Object[]) new Integer[] {5, 6, 11})); assertEquals("null [null, null]", Strings.lenientFormat("%s", null, null, null)); assertEquals("null [5, 6]", Strings.lenientFormat(null, 5, 6)); assertEquals("null", Strings.lenientFormat("%s", (Object) null)); + } + + @J2ktIncompatible // TODO(b/319404022): Allow passing null array as varargs + public void testLenientFormat_nullArrayVarargs() { assertEquals("(Object[])null", Strings.lenientFormat("%s", (Object[]) null)); } @@ -236,7 +229,8 @@ public void testLenientFormat() { public void testLenientFormat_badArgumentToString() { assertThat(Strings.lenientFormat("boiler %s plate", new ThrowsOnToString())) .matches( - "boiler plate"); } @@ -252,6 +246,7 @@ public String toString() { } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/base/SuppliersTest.java b/android/guava-tests/test/com/google/common/base/SuppliersTest.java index a97fe656c91b..214ad41fd791 100644 --- a/android/guava-tests/test/com/google/common/base/SuppliersTest.java +++ b/android/guava-tests/test/com/google/common/base/SuppliersTest.java @@ -16,22 +16,30 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.testing.SerializableTester.reserialize; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Lists; import com.google.common.testing.ClassSanityTester; import com.google.common.testing.EqualsTester; +import java.io.NotSerializableException; import java.io.Serializable; +import java.time.Duration; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests com.google.common.base.Suppliers. @@ -39,7 +47,8 @@ * @author Laurence Gonsalves * @author Harry Heymann */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class SuppliersTest extends TestCase { static class CountingSupplier implements Supplier { @@ -65,11 +74,11 @@ public Integer get() { } static class SerializableCountingSupplier extends CountingSupplier implements Serializable { - private static final long serialVersionUID = 0L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0L; } static class SerializableThrowingSupplier extends ThrowingSupplier implements Serializable { - private static final long serialVersionUID = 0L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0L; } static void checkMemoize(CountingSupplier countingSupplier, Supplier memoizedSupplier) { @@ -98,11 +107,11 @@ private void memoizeTest(CountingSupplier countingSupplier) { } public void testMemoize_redudantly() { - memoize_redudantlyTest(new CountingSupplier()); - memoize_redudantlyTest(new SerializableCountingSupplier()); + memoizeRedudantlyTest(new CountingSupplier()); + memoizeRedudantlyTest(new SerializableCountingSupplier()); } - private void memoize_redudantlyTest(CountingSupplier countingSupplier) { + private void memoizeRedudantlyTest(CountingSupplier countingSupplier) { Supplier memoizedSupplier = Suppliers.memoize(countingSupplier); assertSame(memoizedSupplier, Suppliers.memoize(memoizedSupplier)); } @@ -126,6 +135,7 @@ private void memoizeExceptionThrownTest(ThrowingSupplier throwingSupplier) { } } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testMemoizeNonSerializable() throws Exception { CountingSupplier countingSupplier = new CountingSupplier(); @@ -133,19 +143,16 @@ public void testMemoizeNonSerializable() throws Exception { assertThat(memoizedSupplier.toString()).isEqualTo("Suppliers.memoize(CountingSupplier)"); checkMemoize(countingSupplier, memoizedSupplier); // Calls to the original memoized supplier shouldn't affect its copy. - memoizedSupplier.get(); + Object unused = memoizedSupplier.get(); assertThat(memoizedSupplier.toString()) .isEqualTo("Suppliers.memoize()"); // Should get an exception when we try to serialize. - try { - reserialize(memoizedSupplier); - fail(); - } catch (RuntimeException ex) { - assertThat(ex).hasCauseThat().isInstanceOf(java.io.NotSerializableException.class); - } + RuntimeException ex = assertThrows(RuntimeException.class, () -> reserialize(memoizedSupplier)); + assertThat(ex).hasCauseThat().isInstanceOf(NotSerializableException.class); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testMemoizeSerializable() throws Exception { SerializableCountingSupplier countingSupplier = new SerializableCountingSupplier(); @@ -153,12 +160,12 @@ public void testMemoizeSerializable() throws Exception { assertThat(memoizedSupplier.toString()).isEqualTo("Suppliers.memoize(CountingSupplier)"); checkMemoize(countingSupplier, memoizedSupplier); // Calls to the original memoized supplier shouldn't affect its copy. - memoizedSupplier.get(); + Object unused = memoizedSupplier.get(); assertThat(memoizedSupplier.toString()) .isEqualTo("Suppliers.memoize()"); Supplier copy = reserialize(memoizedSupplier); - memoizedSupplier.get(); + Object unused2 = memoizedSupplier.get(); CountingSupplier countingCopy = (CountingSupplier) ((Suppliers.MemoizingSupplier) copy).delegate; @@ -200,7 +207,7 @@ public ArrayList get() { new Function, List>() { @Override public List apply(List list) { - ArrayList result = Lists.newArrayList(list); + ArrayList result = new ArrayList<>(list); result.add(1); return result; } @@ -213,33 +220,72 @@ public List apply(List list) { assertEquals(Integer.valueOf(1), result.get(1)); } + @J2ktIncompatible + @GwtIncompatible // Thread.sleep + @SuppressWarnings("DoNotCall") + public void testMemoizeWithExpiration_longTimeUnit() throws InterruptedException { + CountingSupplier countingSupplier = new CountingSupplier(); + + Supplier memoizedSupplier = + Suppliers.memoizeWithExpiration(countingSupplier, 75, MILLISECONDS); + + checkExpiration(countingSupplier, memoizedSupplier); + } + + @J2ktIncompatible @GwtIncompatible // Thread.sleep - public void testMemoizeWithExpiration() throws InterruptedException { + public void testMemoizeWithExpiration_duration() throws InterruptedException { CountingSupplier countingSupplier = new CountingSupplier(); Supplier memoizedSupplier = - Suppliers.memoizeWithExpiration(countingSupplier, 75, TimeUnit.MILLISECONDS); + Suppliers.memoizeWithExpiration(countingSupplier, Duration.ofMillis(75)); checkExpiration(countingSupplier, memoizedSupplier); } + @SuppressWarnings("DoNotCall") + public void testMemoizeWithExpiration_longTimeUnitNegative() throws InterruptedException { + assertThrows( + IllegalArgumentException.class, + () -> Suppliers.memoizeWithExpiration(() -> "", 0, MILLISECONDS)); + + assertThrows( + IllegalArgumentException.class, + () -> Suppliers.memoizeWithExpiration(() -> "", -1, MILLISECONDS)); + } + + @J2ktIncompatible // Duration + @GwtIncompatible // Duration + public void testMemoizeWithExpiration_durationNegative() throws InterruptedException { + assertThrows( + IllegalArgumentException.class, + () -> Suppliers.memoizeWithExpiration(() -> "", Duration.ZERO)); + + assertThrows( + IllegalArgumentException.class, + () -> Suppliers.memoizeWithExpiration(() -> "", Duration.ofMillis(-1))); + } + + @J2ktIncompatible @GwtIncompatible // Thread.sleep, SerializationTester + @SuppressWarnings("DoNotCall") public void testMemoizeWithExpirationSerialized() throws InterruptedException { SerializableCountingSupplier countingSupplier = new SerializableCountingSupplier(); Supplier memoizedSupplier = - Suppliers.memoizeWithExpiration(countingSupplier, 75, TimeUnit.MILLISECONDS); + Suppliers.memoizeWithExpiration(countingSupplier, 75, MILLISECONDS); // Calls to the original memoized supplier shouldn't affect its copy. - memoizedSupplier.get(); + Object unused = memoizedSupplier.get(); Supplier copy = reserialize(memoizedSupplier); - memoizedSupplier.get(); + Object unused2 = memoizedSupplier.get(); CountingSupplier countingCopy = (CountingSupplier) ((Suppliers.ExpiringMemoizingSupplier) copy).delegate; checkExpiration(countingCopy, copy); } + @J2ktIncompatible @GwtIncompatible // Thread.sleep private void checkExpiration( CountingSupplier countingSupplier, Supplier memoizedSupplier) @@ -274,25 +320,26 @@ public void testOfInstanceSuppliesSameInstance() { } public void testOfInstanceSuppliesNull() { - Supplier nullSupplier = Suppliers.ofInstance(null); - assertNull(nullSupplier.get()); + Supplier<@Nullable Integer> nullSupplier = Suppliers.ofInstance(null); + assertThat(nullSupplier.get()).isNull(); } + @J2ktIncompatible @GwtIncompatible // Thread - + @SuppressWarnings("DoNotCall") public void testExpiringMemoizedSupplierThreadSafe() throws Throwable { Function, Supplier> memoizer = new Function, Supplier>() { @Override public Supplier apply(Supplier supplier) { - return Suppliers.memoizeWithExpiration(supplier, Long.MAX_VALUE, TimeUnit.NANOSECONDS); + return Suppliers.memoizeWithExpiration(supplier, Long.MAX_VALUE, NANOSECONDS); } }; testSupplierThreadSafe(memoizer); } + @J2ktIncompatible @GwtIncompatible // Thread - public void testMemoizedSupplierThreadSafe() throws Throwable { Function, Supplier> memoizer = new Function, Supplier>() { @@ -304,16 +351,17 @@ public Supplier apply(Supplier supplier) { testSupplierThreadSafe(memoizer); } + @J2ktIncompatible @GwtIncompatible // Thread - public void testSupplierThreadSafe(Function, Supplier> memoizer) + private void testSupplierThreadSafe(Function, Supplier> memoizer) throws Throwable { - final AtomicInteger count = new AtomicInteger(0); - final AtomicReference thrown = new AtomicReference<>(null); - final int numThreads = 3; - final Thread[] threads = new Thread[numThreads]; - final long timeout = TimeUnit.SECONDS.toNanos(60); + AtomicInteger count = new AtomicInteger(0); + AtomicReference thrown = new AtomicReference<>(null); + int numThreads = 3; + Thread[] threads = new Thread[numThreads]; + long timeout = SECONDS.toNanos(60); - final Supplier supplier = + Supplier supplier = new Supplier() { boolean isWaiting(Thread thread) { switch (thread.getState()) { @@ -337,6 +385,7 @@ int waitingThreads() { } @Override + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races public Boolean get() { // Check that this method is called exactly once, by the first // thread to synchronize. @@ -356,7 +405,7 @@ public Boolean get() { } }; - final Supplier memoizedSupplier = memoizer.apply(supplier); + Supplier memoizedSupplier = memoizer.apply(supplier); for (int i = 0; i < numThreads; i++) { threads[i] = @@ -380,10 +429,11 @@ public void run() { assertEquals(1, count.get()); } + @J2ktIncompatible @GwtIncompatible // Thread - + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races public void testSynchronizedSupplierThreadSafe() throws InterruptedException { - final Supplier nonThreadSafe = + Supplier nonThreadSafe = new Supplier() { int counter = 0; @@ -396,8 +446,8 @@ public Integer get() { } }; - final int numThreads = 10; - final int iterations = 1000; + int numThreads = 10; + int iterations = 1000; Thread[] threads = new Thread[numThreads]; for (int i = 0; i < numThreads; i++) { threads[i] = @@ -405,7 +455,7 @@ public Integer get() { @Override public void run() { for (int j = 0; j < iterations; j++) { - Suppliers.synchronizedSupplier(nonThreadSafe).get(); + Object unused = Suppliers.synchronizedSupplier(nonThreadSafe).get(); } } }; @@ -427,7 +477,9 @@ public void testSupplierFunction() { assertEquals(14, (int) supplierFunction.apply(supplier)); } + @J2ktIncompatible @GwtIncompatible // SerializationTester + @SuppressWarnings("DoNotCall") public void testSerialization() { assertEquals(Integer.valueOf(5), reserialize(Suppliers.ofInstance(5)).get()); assertEquals( @@ -436,22 +488,29 @@ public void testSerialization() { assertEquals(Integer.valueOf(5), reserialize(Suppliers.memoize(Suppliers.ofInstance(5))).get()); assertEquals( Integer.valueOf(5), - reserialize(Suppliers.memoizeWithExpiration(Suppliers.ofInstance(5), 30, TimeUnit.SECONDS)) - .get()); + reserialize(Suppliers.memoizeWithExpiration(Suppliers.ofInstance(5), 30, SECONDS)).get()); assertEquals( Integer.valueOf(5), reserialize(Suppliers.synchronizedSupplier(Suppliers.ofInstance(5))).get()); } + @J2ktIncompatible @GwtIncompatible // reflection public void testSuppliersNullChecks() throws Exception { - new ClassSanityTester().forAllPublicStaticMethods(Suppliers.class).testNulls(); + new ClassSanityTester() + .setDefault(Duration.class, Duration.ofSeconds(1)) + .forAllPublicStaticMethods(Suppliers.class) + .testNulls(); } + @J2ktIncompatible @GwtIncompatible // reflection @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException: com.google.common.base.Function public void testSuppliersSerializable() throws Exception { - new ClassSanityTester().forAllPublicStaticMethods(Suppliers.class).testSerializable(); + new ClassSanityTester() + .setDefault(Duration.class, Duration.ofSeconds(1)) + .forAllPublicStaticMethods(Suppliers.class) + .testSerializable(); } public void testOfInstance_equals() { diff --git a/android/guava-tests/test/com/google/common/base/TestExceptions.java b/android/guava-tests/test/com/google/common/base/TestExceptions.java new file mode 100644 index 000000000000..3eadceb43b43 --- /dev/null +++ b/android/guava-tests/test/com/google/common/base/TestExceptions.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullUnmarked; + +/** Exception classes for use in tests. */ +@GwtCompatible +@NullUnmarked +final class TestExceptions { + static class SomeError extends Error {} + + static class SomeCheckedException extends Exception {} + + static class SomeOtherCheckedException extends Exception {} + + static class YetAnotherCheckedException extends Exception {} + + static class SomeUncheckedException extends RuntimeException {} + + static class SomeChainingException extends RuntimeException { + public SomeChainingException(Throwable cause) { + super(cause); + } + } + + private TestExceptions() {} +} diff --git a/android/guava-tests/test/com/google/common/base/ThrowablesTest.java b/android/guava-tests/test/com/google/common/base/ThrowablesTest.java index e4c64aa8df1c..ed7c53552a75 100644 --- a/android/guava-tests/test/com/google/common/base/ThrowablesTest.java +++ b/android/guava-tests/test/com/google/common/base/ThrowablesTest.java @@ -16,615 +16,279 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static com.google.common.base.Throwables.getCausalChain; +import static com.google.common.base.Throwables.getCauseAs; +import static com.google.common.base.Throwables.getRootCause; import static com.google.common.base.Throwables.getStackTraceAsString; import static com.google.common.base.Throwables.lazyStackTrace; import static com.google.common.base.Throwables.lazyStackTraceIsLazy; +import static com.google.common.base.Throwables.propagate; +import static com.google.common.base.Throwables.propagateIfInstanceOf; +import static com.google.common.base.Throwables.propagateIfPossible; import static com.google.common.base.Throwables.throwIfInstanceOf; import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.truth.Truth.assertThat; -import static java.util.Arrays.asList; import static java.util.regex.Pattern.quote; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.TestExceptions.SomeChainingException; +import com.google.common.base.TestExceptions.SomeCheckedException; +import com.google.common.base.TestExceptions.SomeError; +import com.google.common.base.TestExceptions.SomeOtherCheckedException; +import com.google.common.base.TestExceptions.SomeUncheckedException; +import com.google.common.base.TestExceptions.YetAnotherCheckedException; import com.google.common.collect.Iterables; import com.google.common.primitives.Ints; import com.google.common.testing.NullPointerTester; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Throwables}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@SuppressWarnings("deprecation") // tests of numerous deprecated methods +@NullUnmarked public class ThrowablesTest extends TestCase { - public void testThrowIfUnchecked_Unchecked() { - try { - throwIfUnchecked(new SomeUncheckedException()); - fail(); - } catch (SomeUncheckedException expected) { - } + // We're testing that the method is in fact equivalent to throwing the exception directly. + @SuppressWarnings("ThrowIfUncheckedKnownUnchecked") + public void testThrowIfUnchecked_unchecked() { + assertThrows( + SomeUncheckedException.class, () -> throwIfUnchecked(new SomeUncheckedException())); } - public void testThrowIfUnchecked_Error() { - try { - throwIfUnchecked(new SomeError()); - fail(); - } catch (SomeError expected) { - } + // We're testing that the method is in fact equivalent to throwing the exception directly. + @SuppressWarnings("ThrowIfUncheckedKnownUnchecked") + public void testThrowIfUnchecked_error() { + assertThrows(SomeError.class, () -> throwIfUnchecked(new SomeError())); } @SuppressWarnings("ThrowIfUncheckedKnownChecked") - public void testThrowIfUnchecked_Checked() { + public void testThrowIfUnchecked_checked() { throwIfUnchecked(new SomeCheckedException()); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible - public void testPropagateIfPossible_NoneDeclared_NoneThrown() { - Sample sample = - new Sample() { - @Override - public void noneDeclared() { - try { - methodThatDoesntThrowAnything(); - } catch (Throwable t) { - Throwables.propagateIfPossible(t); - throw new SomeChainingException(t); - } - } - }; - - // Expect no exception to be thrown - sample.noneDeclared(); - } - - @GwtIncompatible // propagateIfPossible - public void testPropagateIfPossible_NoneDeclared_UncheckedThrown() { - Sample sample = - new Sample() { - @Override - public void noneDeclared() { - try { - methodThatThrowsUnchecked(); - } catch (Throwable t) { - Throwables.propagateIfPossible(t); - throw new SomeChainingException(t); - } - } - }; - - // Expect the unchecked exception to propagate as-is - try { - sample.noneDeclared(); - fail(); - } catch (SomeUncheckedException expected) { - } + // We're testing that the method is in fact equivalent to throwing the exception directly. + @SuppressWarnings("ThrowIfUncheckedKnownUnchecked") + public void testPropagateIfPossible_noneDeclared_unchecked() { + assertThrows( + SomeUncheckedException.class, () -> propagateIfPossible(new SomeUncheckedException())); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible - public void testPropagateIfPossible_NoneDeclared_UndeclaredThrown() { - Sample sample = - new Sample() { - @Override - public void noneDeclared() { - try { - methodThatThrowsUndeclaredChecked(); - } catch (Throwable t) { - Throwables.propagateIfPossible(t); - throw new SomeChainingException(t); - } - } - }; - - // Expect the undeclared exception to have been chained inside another - try { - sample.noneDeclared(); - fail(); - } catch (SomeChainingException expected) { - } - } - - @GwtIncompatible // propagateIfPossible(Throwable, Class) - public void testPropagateIfPossible_OneDeclared_NoneThrown() throws SomeCheckedException { - Sample sample = - new Sample() { - @Override - public void oneDeclared() throws SomeCheckedException { - try { - methodThatDoesntThrowAnything(); - } catch (Throwable t) { - // yes, this block is never reached, but for purposes of illustration - // we're keeping it the same in each test - Throwables.propagateIfPossible(t, SomeCheckedException.class); - throw new SomeChainingException(t); - } - } - }; - - // Expect no exception to be thrown - sample.oneDeclared(); + @SuppressWarnings("ThrowIfUncheckedKnownChecked") + public void testPropagateIfPossible_noneDeclared_checked() { + propagateIfPossible(new SomeCheckedException()); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class) - public void testPropagateIfPossible_OneDeclared_UncheckedThrown() throws SomeCheckedException { - Sample sample = - new Sample() { - @Override - public void oneDeclared() throws SomeCheckedException { - try { - methodThatThrowsUnchecked(); - } catch (Throwable t) { - Throwables.propagateIfPossible(t, SomeCheckedException.class); - throw new SomeChainingException(t); - } - } - }; - - // Expect the unchecked exception to propagate as-is - try { - sample.oneDeclared(); - fail(); - } catch (SomeUncheckedException expected) { - } + public void testPropagateIfPossible_oneDeclared_unchecked() { + assertThrows( + SomeUncheckedException.class, + () -> propagateIfPossible(new SomeUncheckedException(), SomeCheckedException.class)); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class) - public void testPropagateIfPossible_OneDeclared_CheckedThrown() { - Sample sample = - new Sample() { - @Override - public void oneDeclared() throws SomeCheckedException { - try { - methodThatThrowsChecked(); - } catch (Throwable t) { - Throwables.propagateIfPossible(t, SomeCheckedException.class); - throw new SomeChainingException(t); - } - } - }; - - // Expect the checked exception to propagate as-is - try { - sample.oneDeclared(); - fail(); - } catch (SomeCheckedException expected) { - } + public void testPropagateIfPossible_oneDeclared_checkedSame() { + assertThrows( + SomeCheckedException.class, + () -> propagateIfPossible(new SomeCheckedException(), SomeCheckedException.class)); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class) - public void testPropagateIfPossible_OneDeclared_UndeclaredThrown() throws SomeCheckedException { - Sample sample = - new Sample() { - @Override - public void oneDeclared() throws SomeCheckedException { - try { - methodThatThrowsUndeclaredChecked(); - } catch (Throwable t) { - Throwables.propagateIfPossible(t, SomeCheckedException.class); - throw new SomeChainingException(t); - } - } - }; - - // Expect the undeclared exception to have been chained inside another - try { - sample.oneDeclared(); - fail(); - } catch (SomeChainingException expected) { - } + public void testPropagateIfPossible_oneDeclared_checkedDifferent() throws SomeCheckedException { + propagateIfPossible(new SomeOtherCheckedException(), SomeCheckedException.class); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class, Class) - public void testPropagateIfPossible_TwoDeclared_NoneThrown() - throws SomeCheckedException, SomeOtherCheckedException { - Sample sample = - new Sample() { - @Override - public void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException { - try { - methodThatDoesntThrowAnything(); - } catch (Throwable t) { - Throwables.propagateIfPossible( - t, SomeCheckedException.class, SomeOtherCheckedException.class); - throw new SomeChainingException(t); - } - } - }; - - // Expect no exception to be thrown - sample.twoDeclared(); + public void testPropagateIfPossible_twoDeclared_unchecked() { + assertThrows( + SomeUncheckedException.class, + () -> + propagateIfPossible( + new SomeUncheckedException(), + SomeCheckedException.class, + SomeOtherCheckedException.class)); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class, Class) - public void testPropagateIfPossible_TwoDeclared_UncheckedThrown() - throws SomeCheckedException, SomeOtherCheckedException { - Sample sample = - new Sample() { - @Override - public void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException { - try { - methodThatThrowsUnchecked(); - } catch (Throwable t) { - Throwables.propagateIfPossible( - t, SomeCheckedException.class, SomeOtherCheckedException.class); - throw new SomeChainingException(t); - } - } - }; - - // Expect the unchecked exception to propagate as-is - try { - sample.twoDeclared(); - fail(); - } catch (SomeUncheckedException expected) { - } + public void testPropagateIfPossible_twoDeclared_firstSame() { + assertThrows( + SomeCheckedException.class, + () -> + propagateIfPossible( + new SomeCheckedException(), + SomeCheckedException.class, + SomeOtherCheckedException.class)); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class, Class) - public void testPropagateIfPossible_TwoDeclared_CheckedThrown() throws SomeOtherCheckedException { - Sample sample = - new Sample() { - @Override - public void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException { - try { - methodThatThrowsChecked(); - } catch (Throwable t) { - Throwables.propagateIfPossible( - t, SomeCheckedException.class, SomeOtherCheckedException.class); - throw new SomeChainingException(t); - } - } - }; - - // Expect the checked exception to propagate as-is - try { - sample.twoDeclared(); - fail(); - } catch (SomeCheckedException expected) { - } + public void testPropagateIfPossible_twoDeclared_secondSame() { + assertThrows( + SomeOtherCheckedException.class, + () -> + propagateIfPossible( + new SomeOtherCheckedException(), + SomeCheckedException.class, + SomeOtherCheckedException.class)); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class, Class) - public void testPropagateIfPossible_TwoDeclared_OtherCheckedThrown() throws SomeCheckedException { - Sample sample = - new Sample() { - @Override - public void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException { - try { - methodThatThrowsOtherChecked(); - } catch (Throwable t) { - Throwables.propagateIfPossible( - t, SomeCheckedException.class, SomeOtherCheckedException.class); - throw new SomeChainingException(t); - } - } - }; - - // Expect the checked exception to propagate as-is - try { - sample.twoDeclared(); - fail(); - } catch (SomeOtherCheckedException expected) { - } + public void testPropagateIfPossible_twoDeclared_neitherSame() + throws SomeCheckedException, SomeOtherCheckedException { + propagateIfPossible( + new YetAnotherCheckedException(), + SomeCheckedException.class, + SomeOtherCheckedException.class); } - public void testThrowIfUnchecked_null() throws SomeCheckedException { - try { - throwIfUnchecked(null); - fail(); - } catch (NullPointerException expected) { - } + // I guess it's technically a bug that ThrowIfUncheckedKnownUnchecked fires here. + @SuppressWarnings("ThrowIfUncheckedKnownUnchecked") + public void testThrowIfUnchecked_null() { + assertThrows(NullPointerException.class, () -> throwIfUnchecked(null)); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible - public void testPropageIfPossible_null() throws SomeCheckedException { - Throwables.propagateIfPossible(null); + // I guess it's technically a bug that ThrowIfUncheckedKnownUnchecked fires here. + @SuppressWarnings("ThrowIfUncheckedKnownUnchecked") + public void testPropageIfPossible_null() { + propagateIfPossible(null); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class) - public void testPropageIfPossible_OneDeclared_null() throws SomeCheckedException { - Throwables.propagateIfPossible(null, SomeCheckedException.class); + public void testPropageIfPossible_oneDeclared_null() throws SomeCheckedException { + propagateIfPossible(null, SomeCheckedException.class); } + @J2ktIncompatible @GwtIncompatible // propagateIfPossible(Throwable, Class, Class) - public void testPropageIfPossible_TwoDeclared_null() throws SomeCheckedException { - Throwables.propagateIfPossible(null, SomeCheckedException.class, SomeUncheckedException.class); - } - - @GwtIncompatible // propagate - public void testPropagate_NoneDeclared_NoneThrown() { - Sample sample = - new Sample() { - @Override - public void noneDeclared() { - try { - methodThatDoesntThrowAnything(); - } catch (Throwable t) { - throw Throwables.propagate(t); - } - } - }; - - // Expect no exception to be thrown - sample.noneDeclared(); + public void testPropageIfPossible_twoDeclared_null() + throws SomeCheckedException, SomeOtherCheckedException { + propagateIfPossible(null, SomeCheckedException.class, SomeOtherCheckedException.class); } + @J2ktIncompatible @GwtIncompatible // propagate - public void testPropagate_NoneDeclared_UncheckedThrown() { - Sample sample = - new Sample() { - @Override - public void noneDeclared() { - try { - methodThatThrowsUnchecked(); - } catch (Throwable t) { - throw Throwables.propagate(t); - } - } - }; - - // Expect the unchecked exception to propagate as-is - try { - sample.noneDeclared(); - fail(); - } catch (SomeUncheckedException expected) { - } + public void testPropagate_noneDeclared_unchecked() { + assertThrows(SomeUncheckedException.class, () -> propagate(new SomeUncheckedException())); } + @J2ktIncompatible @GwtIncompatible // propagate - public void testPropagate_NoneDeclared_ErrorThrown() { - Sample sample = - new Sample() { - @Override - public void noneDeclared() { - try { - methodThatThrowsError(); - } catch (Throwable t) { - throw Throwables.propagate(t); - } - } - }; - - // Expect the error to propagate as-is - try { - sample.noneDeclared(); - fail(); - } catch (SomeError expected) { - } + public void testPropagate_noneDeclared_error() { + assertThrows(SomeError.class, () -> propagate(new SomeError())); } + @J2ktIncompatible @GwtIncompatible // propagate - public void testPropagate_NoneDeclared_CheckedThrown() { - Sample sample = - new Sample() { - @Override - public void noneDeclared() { - try { - methodThatThrowsChecked(); - } catch (Throwable t) { - throw Throwables.propagate(t); - } - } - }; - - // Expect the undeclared exception to have been chained inside another - try { - sample.noneDeclared(); - fail(); - } catch (RuntimeException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(SomeCheckedException.class); - } + public void testPropagate_noneDeclared_checked() { + RuntimeException expected = + assertThrows(RuntimeException.class, () -> propagate(new SomeCheckedException())); + assertThat(expected).hasCauseThat().isInstanceOf(SomeCheckedException.class); } @GwtIncompatible // throwIfInstanceOf - public void testThrowIfInstanceOf_Unchecked() throws SomeCheckedException { + public void testThrowIfInstanceOf_unchecked() throws SomeCheckedException { throwIfInstanceOf(new SomeUncheckedException(), SomeCheckedException.class); } @GwtIncompatible // throwIfInstanceOf - public void testThrowIfInstanceOf_CheckedDifferent() throws SomeCheckedException { + public void testThrowIfInstanceOf_checkedDifferent() throws SomeCheckedException { throwIfInstanceOf(new SomeOtherCheckedException(), SomeCheckedException.class); } @GwtIncompatible // throwIfInstanceOf - public void testThrowIfInstanceOf_CheckedSame() { - try { - throwIfInstanceOf(new SomeCheckedException(), SomeCheckedException.class); - fail(); - } catch (SomeCheckedException expected) { - } - } - - @GwtIncompatible // throwIfInstanceOf - public void testThrowIfInstanceOf_CheckedSubclass() { - try { - throwIfInstanceOf(new SomeCheckedException() {}, SomeCheckedException.class); - fail(); - } catch (SomeCheckedException expected) { - } + public void testThrowIfInstanceOf_checkedSame() { + assertThrows( + SomeCheckedException.class, + () -> throwIfInstanceOf(new SomeCheckedException(), SomeCheckedException.class)); } @GwtIncompatible // throwIfInstanceOf - public void testPropagateIfInstanceOf_NoneThrown() throws SomeCheckedException { - Sample sample = - new Sample() { - @Override - public void oneDeclared() throws SomeCheckedException { - try { - methodThatDoesntThrowAnything(); - } catch (Throwable t) { - Throwables.propagateIfInstanceOf(t, SomeCheckedException.class); - throw Throwables.propagate(t); - } - } - }; - - // Expect no exception to be thrown - sample.oneDeclared(); + public void testThrowIfInstanceOf_checkedSubclass() { + assertThrows( + SomeCheckedException.class, + () -> throwIfInstanceOf(new SomeCheckedException() {}, SomeCheckedException.class)); } - @GwtIncompatible // throwIfInstanceOf - public void testPropagateIfInstanceOf_DeclaredThrown() { - Sample sample = - new Sample() { - @Override - public void oneDeclared() throws SomeCheckedException { - try { - methodThatThrowsChecked(); - } catch (Throwable t) { - Throwables.propagateIfInstanceOf(t, SomeCheckedException.class); - throw Throwables.propagate(t); - } - } - }; - - // Expect declared exception to be thrown as-is - try { - sample.oneDeclared(); - fail(); - } catch (SomeCheckedException expected) { - } + @J2ktIncompatible + @GwtIncompatible // propagateIfInstanceOf + public void testPropagateIfInstanceOf_checkedSame() { + assertThrows( + SomeCheckedException.class, + () -> propagateIfInstanceOf(new SomeCheckedException(), SomeCheckedException.class)); } - @GwtIncompatible // throwIfInstanceOf - public void testPropagateIfInstanceOf_UncheckedThrown() throws SomeCheckedException { - Sample sample = - new Sample() { - @Override - public void oneDeclared() throws SomeCheckedException { - try { - methodThatThrowsUnchecked(); - } catch (Throwable t) { - Throwables.propagateIfInstanceOf(t, SomeCheckedException.class); - throw Throwables.propagate(t); - } - } - }; - - // Expect unchecked exception to be thrown as-is - try { - sample.oneDeclared(); - fail(); - } catch (SomeUncheckedException expected) { - } + @J2ktIncompatible + @GwtIncompatible // propagateIfInstanceOf + public void testPropagateIfInstanceOf_unchecked() throws SomeCheckedException { + propagateIfInstanceOf(new SomeUncheckedException(), SomeCheckedException.class); } - @GwtIncompatible // throwIfInstanceOf - public void testPropagateIfInstanceOf_UndeclaredThrown() throws SomeCheckedException { - Sample sample = - new Sample() { - @Override - public void oneDeclared() throws SomeCheckedException { - try { - methodThatThrowsOtherChecked(); - } catch (Throwable t) { - Throwables.propagateIfInstanceOf(t, SomeCheckedException.class); - throw Throwables.propagate(t); - } - } - }; - - // Expect undeclared exception wrapped by RuntimeException to be thrown - try { - sample.oneDeclared(); - fail(); - } catch (RuntimeException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(SomeOtherCheckedException.class); - } + @J2ktIncompatible + @GwtIncompatible // propagateIfInstanceOf + public void testPropagateIfInstanceOf_checkedDifferent() throws SomeCheckedException { + propagateIfInstanceOf(new SomeOtherCheckedException(), SomeCheckedException.class); } @GwtIncompatible // throwIfInstanceOf - public void testThrowIfInstanceOf_null() throws SomeCheckedException { - try { - throwIfInstanceOf(null, SomeCheckedException.class); - fail(); - } catch (NullPointerException expected) { - } + public void testThrowIfInstanceOf_null() { + assertThrows( + NullPointerException.class, () -> throwIfInstanceOf(null, SomeCheckedException.class)); } - @GwtIncompatible // throwIfInstanceOf + @J2ktIncompatible + @GwtIncompatible // propagateIfInstanceOf public void testPropageIfInstanceOf_null() throws SomeCheckedException { - Throwables.propagateIfInstanceOf(null, SomeCheckedException.class); + propagateIfInstanceOf(null, SomeCheckedException.class); } - public void testGetRootCause_NoCause() { + public void testGetRootCause_noCause() { SomeCheckedException exception = new SomeCheckedException(); - assertSame(exception, Throwables.getRootCause(exception)); + assertSame(exception, getRootCause(exception)); } - public void testGetRootCause_SingleWrapped() { + public void testGetRootCause_singleWrapped() { SomeCheckedException cause = new SomeCheckedException(); SomeChainingException exception = new SomeChainingException(cause); - assertSame(cause, Throwables.getRootCause(exception)); + assertSame(cause, getRootCause(exception)); } - public void testGetRootCause_DoubleWrapped() { + public void testGetRootCause_doubleWrapped() { SomeCheckedException cause = new SomeCheckedException(); SomeChainingException exception = new SomeChainingException(new SomeChainingException(cause)); - assertSame(cause, Throwables.getRootCause(exception)); + assertSame(cause, getRootCause(exception)); } - public void testGetRootCause_Loop() { + public void testGetRootCause_loop() { Exception cause = new Exception(); Exception exception = new Exception(cause); cause.initCause(exception); - try { - Throwables.getRootCause(cause); - fail("Should have throw IAE"); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(cause); - } - } - - private static class SomeError extends Error {} - - private static class SomeCheckedException extends Exception {} - - private static class SomeOtherCheckedException extends Exception {} - - private static class SomeUncheckedException extends RuntimeException {} - - private static class SomeUndeclaredCheckedException extends Exception {} - - private static class SomeChainingException extends RuntimeException { - public SomeChainingException(Throwable cause) { - super(cause); - } - } - - static class Sample { - void noneDeclared() {} - - void oneDeclared() throws SomeCheckedException {} - - void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException {} - } - - static void methodThatDoesntThrowAnything() {} - - static void methodThatThrowsError() { - throw new SomeError(); - } - - static void methodThatThrowsUnchecked() { - throw new SomeUncheckedException(); - } - - static void methodThatThrowsChecked() throws SomeCheckedException { - throw new SomeCheckedException(); - } - - static void methodThatThrowsOtherChecked() throws SomeOtherCheckedException { - throw new SomeOtherCheckedException(); - } - - static void methodThatThrowsUndeclaredChecked() throws SomeUndeclaredCheckedException { - throw new SomeUndeclaredCheckedException(); + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> getRootCause(cause)); + assertThat(expected).hasCauseThat().isSameInstanceAs(cause); } + @J2ktIncompatible // Format does not match @GwtIncompatible // getStackTraceAsString(Throwable) public void testGetStackTraceAsString() { class StackTraceException extends Exception { @@ -637,8 +301,9 @@ class StackTraceException extends Exception { String firstLine = quote(e.getClass().getName() + ": " + e.getMessage()); String secondLine = "\\s*at " + ThrowablesTest.class.getName() + "\\..*"; - String moreLines = "(?:.*\n?)*"; - String expected = firstLine + "\n" + secondLine + "\n" + moreLines; + String moreLines = "(?:.*" + System.lineSeparator() + "?)*"; + String expected = + firstLine + System.lineSeparator() + secondLine + System.lineSeparator() + moreLines; assertThat(getStackTraceAsString(e)).matches(expected); } @@ -648,55 +313,43 @@ public void testGetCausalChain() { RuntimeException re = new RuntimeException(iae); IllegalStateException ex = new IllegalStateException(re); - assertEquals(asList(ex, re, iae, sue), Throwables.getCausalChain(ex)); - assertSame(sue, Iterables.getOnlyElement(Throwables.getCausalChain(sue))); + assertThat(getCausalChain(ex)).containsExactly(ex, re, iae, sue).inOrder(); + assertSame(sue, Iterables.getOnlyElement(getCausalChain(sue))); - List causes = Throwables.getCausalChain(ex); - try { - causes.add(new RuntimeException()); - fail("List should be unmodifiable"); - } catch (UnsupportedOperationException expected) { - } + List causes = getCausalChain(ex); + assertThrows(UnsupportedOperationException.class, () -> causes.add(new RuntimeException())); } public void testGetCasualChainNull() { - try { - Throwables.getCausalChain(null); - fail("Should have throw NPE"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> getCausalChain(null)); } public void testGetCasualChainLoop() { Exception cause = new Exception(); Exception exception = new Exception(cause); cause.initCause(exception); - try { - Throwables.getCausalChain(cause); - fail("Should have throw IAE"); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(cause); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> getCausalChain(cause)); + assertThat(expected).hasCauseThat().isSameInstanceAs(cause); } - @GwtIncompatible // Throwables.getCauseAs(Throwable, Class) + @GwtIncompatible // getCauseAs(Throwable, Class) public void testGetCauseAs() { SomeCheckedException cause = new SomeCheckedException(); SomeChainingException thrown = new SomeChainingException(cause); assertThat(thrown).hasCauseThat().isSameInstanceAs(cause); - assertThat(Throwables.getCauseAs(thrown, SomeCheckedException.class)).isSameInstanceAs(cause); - assertThat(Throwables.getCauseAs(thrown, Exception.class)).isSameInstanceAs(cause); - - try { - Throwables.getCauseAs(thrown, IllegalStateException.class); - fail("Should have thrown CCE"); - } catch (ClassCastException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(thrown); - } + assertThat(getCauseAs(thrown, SomeCheckedException.class)).isSameInstanceAs(cause); + assertThat(getCauseAs(thrown, Exception.class)).isSameInstanceAs(cause); + + ClassCastException expected = + assertThrows( + ClassCastException.class, () -> getCauseAs(thrown, IllegalStateException.class)); + assertThat(expected).hasCauseThat().isSameInstanceAs(thrown); } @AndroidIncompatible // No getJavaLangAccess in Android (at least not in the version we use). + @J2ktIncompatible @GwtIncompatible // lazyStackTraceIsLazy() public void testLazyStackTraceWorksInProd() { // TODO(b/64442212): Remove this guard once lazyStackTrace() works in Java 9+. @@ -708,6 +361,7 @@ public void testLazyStackTraceWorksInProd() { assertTrue(lazyStackTraceIsLazy()); } + @J2ktIncompatible @GwtIncompatible // lazyStackTrace(Throwable) public void testLazyStackTrace() { Exception e = new Exception(); @@ -715,11 +369,7 @@ public void testLazyStackTrace() { assertThat(lazyStackTrace(e)).containsExactly((Object[]) originalStackTrace).inOrder(); - try { - lazyStackTrace(e).set(0, null); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> lazyStackTrace(e).set(0, null)); // Now we test a property that holds only for the lazy implementation. @@ -731,24 +381,7 @@ public void testLazyStackTrace() { assertThat(lazyStackTrace(e)).containsExactly((Object[]) originalStackTrace).inOrder(); } - @GwtIncompatible // lazyStackTrace - private void doTestLazyStackTraceFallback() { - assertFalse(lazyStackTraceIsLazy()); - - Exception e = new Exception(); - - assertThat(lazyStackTrace(e)).containsExactly((Object[]) e.getStackTrace()).inOrder(); - - try { - lazyStackTrace(e).set(0, null); - fail(); - } catch (UnsupportedOperationException expected) { - } - - e.setStackTrace(new StackTraceElement[0]); - assertThat(lazyStackTrace(e)).isEmpty(); - } - + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { new NullPointerTester().testAllPublicStaticMethods(Throwables.class); diff --git a/android/guava-tests/test/com/google/common/base/ToStringHelperTest.java b/android/guava-tests/test/com/google/common/base/ToStringHelperTest.java index 3f1ef0f9e829..ddc7025cad78 100644 --- a/android/guava-tests/test/com/google/common/base/ToStringHelperTest.java +++ b/android/guava-tests/test/com/google/common/base/ToStringHelperTest.java @@ -16,12 +16,20 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMap; +import java.nio.CharBuffer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link MoreObjects#toStringHelper(Object)}. @@ -29,6 +37,7 @@ * @author Jason Lee */ @GwtCompatible +@NullUnmarked public class ToStringHelperTest extends TestCase { @GwtIncompatible // Class names are obfuscated in GWT @@ -116,15 +125,15 @@ class LocalInnerNestedClass {} @GwtIncompatible // Class names are obfuscated in GWT public void testToStringHelper_moreThanNineAnonymousClasses() { // The nth anonymous class has a name ending like "Outer.$n" - Object o1 = new Object() {}; - Object o2 = new Object() {}; - Object o3 = new Object() {}; - Object o4 = new Object() {}; - Object o5 = new Object() {}; - Object o6 = new Object() {}; - Object o7 = new Object() {}; - Object o8 = new Object() {}; - Object o9 = new Object() {}; + Object unused1 = new Object() {}; + Object unused2 = new Object() {}; + Object unused3 = new Object() {}; + Object unused4 = new Object() {}; + Object unused5 = new Object() {}; + Object unused6 = new Object() {}; + Object unused7 = new Object() {}; + Object unused8 = new Object() {}; + Object unused9 = new Object() {}; Object o10 = new Object() {}; String toTest = MoreObjects.toStringHelper(o10).toString(); assertEquals("{}", toTest); @@ -132,15 +141,15 @@ public void testToStringHelper_moreThanNineAnonymousClasses() { public void testToStringHelperLenient_moreThanNineAnonymousClasses() { // The nth anonymous class has a name ending like "Outer.$n" - Object o1 = new Object() {}; - Object o2 = new Object() {}; - Object o3 = new Object() {}; - Object o4 = new Object() {}; - Object o5 = new Object() {}; - Object o6 = new Object() {}; - Object o7 = new Object() {}; - Object o8 = new Object() {}; - Object o9 = new Object() {}; + Object unused1 = new Object() {}; + Object unused2 = new Object() {}; + Object unused3 = new Object() {}; + Object unused4 = new Object() {}; + Object unused5 = new Object() {}; + Object unused6 = new Object() {}; + Object unused7 = new Object() {}; + Object unused8 = new Object() {}; + Object unused9 = new Object() {}; Object o10 = new Object() {}; String toTest = MoreObjects.toStringHelper(o10).toString(); assertTrue(toTest, toTest.matches(".*\\{\\}")); @@ -156,7 +165,7 @@ public void testToString_oneField() { @GwtIncompatible // Class names are obfuscated in GWT public void testToString_oneIntegerField() { String toTest = - MoreObjects.toStringHelper(new TestClass()).add("field1", new Integer(42)).toString(); + MoreObjects.toStringHelper(new TestClass()).add("field1", Integer.valueOf(42)).toString(); assertEquals("TestClass{field1=42}", toTest); } @@ -174,7 +183,7 @@ public void testToStringLenient_oneField() { public void testToStringLenient_oneIntegerField() { String toTest = - MoreObjects.toStringHelper(new TestClass()).add("field1", new Integer(42)).toString(); + MoreObjects.toStringHelper(new TestClass()).add("field1", Integer.valueOf(42)).toString(); assertTrue(toTest, toTest.matches(".*\\{field1\\=42\\}")); } @@ -186,7 +195,6 @@ public void testToStringLenient_nullInteger() { @GwtIncompatible // Class names are obfuscated in GWT public void testToString_complexFields() { - Map map = ImmutableMap.builder().put("abc", 1).put("def", 2).put("ghi", 3).build(); String toTest = @@ -195,7 +203,7 @@ public void testToString_complexFields() { .add("field2", Arrays.asList("abc", "def", "ghi")) .add("field3", map) .toString(); - final String expected = + String expected = "TestClass{" + "field1=This is string., field2=[abc, def, ghi], field3={abc=1, def=2, ghi=3}}"; @@ -203,7 +211,6 @@ public void testToString_complexFields() { } public void testToStringLenient_complexFields() { - Map map = ImmutableMap.builder().put("abc", 1).put("def", 2).put("ghi", 3).build(); String toTest = @@ -212,7 +219,7 @@ public void testToStringLenient_complexFields() { .add("field2", Arrays.asList("abc", "def", "ghi")) .add("field3", map) .toString(); - final String expectedRegex = + String expectedRegex = ".*\\{" + "field1\\=This is string\\., " + "field2\\=\\[abc, def, ghi\\], " @@ -223,40 +230,36 @@ public void testToStringLenient_complexFields() { public void testToString_addWithNullName() { MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(new TestClass()); - try { - helper.add(null, "Hello"); - fail("No exception was thrown."); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> helper.add(null, "Hello")); } @GwtIncompatible // Class names are obfuscated in GWT public void testToString_addWithNullValue() { - final String result = MoreObjects.toStringHelper(new TestClass()).add("Hello", null).toString(); + String result = MoreObjects.toStringHelper(new TestClass()).add("Hello", null).toString(); assertEquals("TestClass{Hello=null}", result); } public void testToStringLenient_addWithNullValue() { - final String result = MoreObjects.toStringHelper(new TestClass()).add("Hello", null).toString(); + String result = MoreObjects.toStringHelper(new TestClass()).add("Hello", null).toString(); assertTrue(result, result.matches(".*\\{Hello\\=null\\}")); } @GwtIncompatible // Class names are obfuscated in GWT - public void testToString_ToStringTwice() { + public void testToString_toStringTwice() { MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(new TestClass()) .add("field1", 1) .addValue("value1") .add("field2", "value2"); - final String expected = "TestClass{field1=1, value1, field2=value2}"; + String expected = "TestClass{field1=1, value1, field2=value2}"; assertEquals(expected, helper.toString()); // Call toString again assertEquals(expected, helper.toString()); // Make sure the cached value is reset when we modify the helper at all - final String expected2 = "TestClass{field1=1, value1, field2=value2, 2}"; + String expected2 = "TestClass{field1=1, value1, field2=value2, 2}"; helper.addValue(2); assertEquals(expected2, helper.toString()); } @@ -270,7 +273,7 @@ public void testToString_addValue() { .add("field2", "value2") .addValue(2) .toString(); - final String expected = "TestClass{field1=1, value1, field2=value2, 2}"; + String expected = "TestClass{field1=1, value1, field2=value2, 2}"; assertEquals(expected, toTest); } @@ -283,32 +286,32 @@ public void testToStringLenient_addValue() { .add("field2", "value2") .addValue(2) .toString(); - final String expected = ".*\\{field1\\=1, value1, field2\\=value2, 2\\}"; + String expected = ".*\\{field1\\=1, value1, field2\\=value2, 2\\}"; assertTrue(toTest, toTest.matches(expected)); } @GwtIncompatible // Class names are obfuscated in GWT public void testToString_addValueWithNullValue() { - final String result = + String result = MoreObjects.toStringHelper(new TestClass()) .addValue(null) .addValue("Hello") .addValue(null) .toString(); - final String expected = "TestClass{null, Hello, null}"; + String expected = "TestClass{null, Hello, null}"; assertEquals(expected, result); } public void testToStringLenient_addValueWithNullValue() { - final String result = + String result = MoreObjects.toStringHelper(new TestClass()) .addValue(null) .addValue("Hello") .addValue(null) .toString(); - final String expected = ".*\\{null, Hello, null\\}"; + String expected = ".*\\{null, Hello, null\\}"; assertTrue(result, result.matches(expected)); } @@ -320,6 +323,13 @@ public void testToStringOmitNullValues_oneField() { assertEquals("TestClass{}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_oneField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitEmptyValues().add("field1", "").toString(); + assertEquals("TestClass{}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsFirstNull() { String toTest = @@ -332,6 +342,18 @@ public void testToStringOmitNullValues_manyFieldsFirstNull() { assertEquals("TestClass{field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsFirstEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "") + .add("field2", "Googley") + .add("field3", "World") + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsOmitAfterNull() { String toTest = @@ -344,6 +366,18 @@ public void testToStringOmitNullValues_manyFieldsOmitAfterNull() { assertEquals("TestClass{field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsOmitAfterEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "") + .add("field2", "Googley") + .add("field3", "World") + .omitEmptyValues() + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsLastNull() { String toTest = @@ -356,8 +390,27 @@ public void testToStringOmitNullValues_manyFieldsLastNull() { assertEquals("TestClass{field1=Hello, field2=Googley}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsLastEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", "") + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_oneValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitEmptyValues().addValue("").toString(); + assertEquals("TestClass{}", toTest); + } + + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_oneValue() { String toTest = MoreObjects.toStringHelper(new TestClass()).omitNullValues().addValue(null).toString(); assertEquals("TestClass{}", toTest); @@ -375,6 +428,18 @@ public void testToStringOmitNullValues_manyValuesFirstNull() { assertEquals("TestClass{Googley, World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyValuesFirstEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .addValue("") + .addValue("Googley") + .addValue("World") + .toString(); + assertEquals("TestClass{Googley, World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyValuesLastNull() { String toTest = @@ -387,6 +452,18 @@ public void testToStringOmitNullValues_manyValuesLastNull() { assertEquals("TestClass{Hello, Googley}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyValuesLastEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .addValue("Hello") + .addValue("Googley") + .addValue("") + .toString(); + assertEquals("TestClass{Hello, Googley}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_differentOrder() { String expected = "TestClass{field1=Hello, field2=Googley, field3=World}"; @@ -408,6 +485,27 @@ public void testToStringOmitNullValues_differentOrder() { assertEquals(expected, toTest2); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_differentOrder() { + String expected = "TestClass{field1=Hello, field2=Googley, field3=World}"; + String toTest1 = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", "World") + .toString(); + String toTest2 = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "Hello") + .add("field2", "Googley") + .omitEmptyValues() + .add("field3", "World") + .toString(); + assertEquals(expected, toTest1); + assertEquals(expected, toTest2); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_canBeCalledManyTimes() { String toTest = @@ -423,6 +521,83 @@ public void testToStringOmitNullValues_canBeCalledManyTimes() { assertEquals("TestClass{field1=Hello, field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_canBeCalledManyTimes() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .omitEmptyValues() + .add("field1", "Hello") + .omitEmptyValues() + .add("field2", "Googley") + .omitEmptyValues() + .add("field3", "World") + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley, field3=World}", toTest); + } + + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_allEmptyTypes() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + // CharSequences + .add("field1", "") + .add("field2", new StringBuilder()) + // nio CharBuffer (implements CharSequence) is tested separately below + // Collections and Maps + .add("field11", Arrays.asList("Hello")) + .add("field12", new ArrayList<>()) + .add("field13", new HashMap<>()) + // Optionals + .add("field26", Optional.of("World")) + .add("field27", Optional.absent()) + // Arrays + .add("field31", new Object[] {"!!!"}) + .add("field32", new boolean[0]) + .add("field33", new byte[0]) + .add("field34", new char[0]) + .add("field35", new short[0]) + .add("field36", new int[0]) + .add("field37", new long[0]) + .add("field38", new float[0]) + .add("field39", new double[0]) + .add("field40", new Object[0]) + .toString(); + assertEquals("TestClass{field11=[Hello], field26=Optional.of(World), field31=[!!!]}", toTest); + } + + @J2ktIncompatible // J2kt CharBuffer does not implement CharSequence so not recognized as empty + @GwtIncompatible // CharBuffer not available + public void testToStringOmitEmptyValues_charBuffer() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", CharBuffer.allocate(0)) + .toString(); + assertEquals("TestClass{field1=Hello}", toTest); + } + + public void testToStringHelperWithArrays() { + String[] strings = {"hello", "world"}; + int[] ints = {2, 42}; + Object[] objects = {"obj"}; + @Nullable String[] arrayWithNull = new @Nullable String[] {null}; + Object[] empty = {}; + String toTest = + MoreObjects.toStringHelper("TSH") + .add("strings", strings) + .add("ints", ints) + .add("objects", objects) + .add("arrayWithNull", arrayWithNull) + .add("empty", empty) + .toString(); + assertEquals( + "TSH{strings=[hello, world], ints=[2, 42], objects=[obj], arrayWithNull=[null], empty=[]}", + toTest); + } + /** Test class for testing formatting of inner classes. */ private static class TestClass {} } diff --git a/guava-gwt/src-super/com/google/common/cache/super/com/google/common/cache/LongAdder.java b/android/guava-tests/test/com/google/common/base/UnannotatedJavaClass.java similarity index 62% rename from guava-gwt/src-super/com/google/common/cache/super/com/google/common/cache/LongAdder.java rename to android/guava-tests/test/com/google/common/base/UnannotatedJavaClass.java index 5b89401540ea..0d7cc9cae8e8 100644 --- a/guava-gwt/src-super/com/google/common/cache/super/com/google/common/cache/LongAdder.java +++ b/android/guava-tests/test/com/google/common/base/UnannotatedJavaClass.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Guava Authors + * Copyright (C) 2023 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,26 +14,16 @@ * limitations under the License. */ -package com.google.common.cache; +package com.google.common.base; -/** - * GWT emulated version of LongAdder. - * - * @author Charles Fry - */ -class LongAdder implements LongAddable { - - private long value; +import org.jspecify.annotations.NullUnmarked; - public void increment() { - value++; +/** Class containing an unannotated Java method for use from {@code OptionalExtensionsTest}. */ +@NullUnmarked +final class UnannotatedJavaClass { + static Object getNull() { + return null; } - public void add(long x) { - value += x; - } - - public long sum() { - return value; - } + private UnannotatedJavaClass() {} } diff --git a/android/guava-tests/test/com/google/common/base/Utf8Test.java b/android/guava-tests/test/com/google/common/base/Utf8Test.java index 049e8d2abf5a..a6c301bf65ae 100644 --- a/android/guava-tests/test/com/google/common/base/Utf8Test.java +++ b/android/guava-tests/test/com/google/common/base/Utf8Test.java @@ -23,6 +23,7 @@ import static java.lang.Character.MIN_HIGH_SURROGATE; import static java.lang.Character.MIN_LOW_SURROGATE; import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; @@ -31,6 +32,7 @@ import java.util.HashMap; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Utf8}. @@ -39,7 +41,8 @@ * @author Martin Buchholz * @author Clément Roux */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class Utf8Test extends TestCase { private static final ImmutableList ILL_FORMED_STRINGS; @@ -59,6 +62,8 @@ public class Utf8Test extends TestCase { ILL_FORMED_STRINGS = builder.build(); } + // We can't use Character.isSurrogate(c) because of GWT. + public void testEncodedLength_validStrings() { assertEquals(0, Utf8.encodedLength("")); assertEquals(11, Utf8.encodedLength("Hello world")); @@ -332,8 +337,8 @@ private static void testBytes(int numBytes, long expectedCount, long start, long } boolean isRoundTrippable = Utf8.isWellFormed(bytes); assertEquals(isRoundTrippable, Utf8.isWellFormed(bytes, 0, numBytes)); - String s = new String(bytes, Charsets.UTF_8); - byte[] bytesReencoded = s.getBytes(Charsets.UTF_8); + String s = new String(bytes, UTF_8); + byte[] bytesReencoded = s.getBytes(UTF_8); boolean bytesEqual = Arrays.equals(bytes, bytesReencoded); if (bytesEqual != isRoundTrippable) { diff --git a/android/guava-tests/test/com/google/common/base/VerifyTest.java b/android/guava-tests/test/com/google/common/base/VerifyTest.java index 03d2c2ff4a28..575a7f797154 100644 --- a/android/guava-tests/test/com/google/common/base/VerifyTest.java +++ b/android/guava-tests/test/com/google/common/base/VerifyTest.java @@ -14,27 +14,28 @@ package com.google.common.base; +import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.base.Verify.verify; import static com.google.common.base.Verify.verifyNotNull; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit test for {@link com.google.common.base.Verify}. */ @GwtCompatible +@NullUnmarked public class VerifyTest extends TestCase { public void testVerify_simple_success() { verify(true); } public void testVerify_simple_failure() { - try { - verify(false); - fail(); - } catch (VerifyException expected) { - } + assertThrows(VerifyException.class, () -> verify(false)); } public void testVerify_simpleMessage_success() { @@ -42,12 +43,8 @@ public void testVerify_simpleMessage_success() { } public void testVerify_simpleMessage_failure() { - try { - verify(false, "message"); - fail(); - } catch (VerifyException expected) { - assertThat(expected).hasMessageThat().isEqualTo("message"); - } + VerifyException expected = assertThrows(VerifyException.class, () -> verify(false, "message")); + assertThat(expected).hasMessageThat().isEqualTo("message"); } public void testVerify_complexMessage_success() { @@ -55,12 +52,8 @@ public void testVerify_complexMessage_success() { } public void testVerify_complexMessage_failure() { - try { - verify(false, FORMAT, 5); - fail(); - } catch (VerifyException expected) { - checkMessage(expected); - } + VerifyException expected = assertThrows(VerifyException.class, () -> verify(false, FORMAT, 5)); + checkMessage(expected); } private static final String NON_NULL_STRING = "foo"; @@ -71,11 +64,7 @@ public void testVerifyNotNull_simple_success() { } public void testVerifyNotNull_simple_failure() { - try { - verifyNotNull(null); - fail(); - } catch (VerifyException expected) { - } + assertThrows(VerifyException.class, () -> verifyNotNull(null)); } public void testVerifyNotNull_complexMessage_success() { @@ -84,12 +73,15 @@ public void testVerifyNotNull_complexMessage_success() { } public void testVerifyNotNull_simpleMessage_failure() { - try { - verifyNotNull(null, FORMAT, 5); - fail(); - } catch (VerifyException expected) { - checkMessage(expected); - } + VerifyException expected = + assertThrows(VerifyException.class, () -> verifyNotNull(null, FORMAT, 5)); + checkMessage(expected); + } + + @J2ktIncompatible + @GwtIncompatible // NullPointerTester + public void testNullPointers() { + // Don't bother testing: Verify is like Preconditions. See the discussion on that class. } private static final Object IGNORE_ME = diff --git a/android/guava-tests/test/com/google/common/cache/AbstractCacheTest.java b/android/guava-tests/test/com/google/common/cache/AbstractCacheTest.java index 13ef33db280d..6a1fc772cf57 100644 --- a/android/guava-tests/test/com/google/common/cache/AbstractCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/AbstractCacheTest.java @@ -16,69 +16,72 @@ package com.google.common.cache; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.cache.AbstractCache.SimpleStatsCounter; import com.google.common.cache.AbstractCache.StatsCounter; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link AbstractCache}. * * @author Charles Fry */ +@NullUnmarked public class AbstractCacheTest extends TestCase { public void testGetIfPresent() { - final AtomicReference valueRef = new AtomicReference<>(); + AtomicReference valueRef = new AtomicReference<>(); Cache cache = new AbstractCache() { @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return valueRef.get(); } }; - assertNull(cache.getIfPresent(new Object())); + assertThat(cache.getIfPresent(new Object())).isNull(); Object newValue = new Object(); valueRef.set(newValue); - assertSame(newValue, cache.getIfPresent(new Object())); + assertThat(cache.getIfPresent(new Object())).isSameInstanceAs(newValue); } public void testGetAllPresent_empty() { Cache cache = new AbstractCache() { @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return null; } }; - assertEquals(ImmutableMap.of(), cache.getAllPresent(ImmutableList.of(new Object()))); + assertThat(cache.getAllPresent(ImmutableList.of(new Object()))).isEmpty(); } public void testGetAllPresent_cached() { - final Object cachedKey = new Object(); - final Object cachedValue = new Object(); + Object cachedKey = new Object(); + Object cachedValue = new Object(); Cache cache = new AbstractCache() { @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return cachedKey.equals(key) ? cachedValue : null; } }; - assertEquals( - ImmutableMap.of(cachedKey, cachedValue), - cache.getAllPresent(ImmutableList.of(cachedKey, new Object()))); + assertThat(cache.getAllPresent(ImmutableList.of(cachedKey, new Object()))) + .containsExactly(cachedKey, cachedValue); } public void testInvalidateAll() { - final List invalidated = Lists.newArrayList(); + List invalidated = new ArrayList<>(); Cache cache = new AbstractCache() { @Override @@ -94,23 +97,23 @@ public void invalidate(Object key) { List toInvalidate = ImmutableList.of(1, 2, 3, 4); cache.invalidateAll(toInvalidate); - assertEquals(toInvalidate, invalidated); + assertThat(invalidated).isEqualTo(toInvalidate); } public void testEmptySimpleStats() { StatsCounter counter = new SimpleStatsCounter(); CacheStats stats = counter.snapshot(); - assertEquals(0, stats.requestCount()); - assertEquals(0, stats.hitCount()); - assertEquals(1.0, stats.hitRate()); - assertEquals(0, stats.missCount()); - assertEquals(0.0, stats.missRate()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.loadCount()); - assertEquals(0, stats.totalLoadTime()); - assertEquals(0.0, stats.averageLoadPenalty()); - assertEquals(0, stats.evictionCount()); + assertThat(stats.requestCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + assertThat(stats.hitRate()).isEqualTo(1.0); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.missRate()).isEqualTo(0.0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.loadCount()).isEqualTo(0); + assertThat(stats.totalLoadTime()).isEqualTo(0); + assertThat(stats.averageLoadPenalty()).isEqualTo(0.0); + assertThat(stats.evictionCount()).isEqualTo(0); } public void testSingleSimpleStats() { @@ -132,18 +135,18 @@ public void testSingleSimpleStats() { } CacheStats stats = counter.snapshot(); int requestCount = 11 + 23; - assertEquals(requestCount, stats.requestCount()); - assertEquals(11, stats.hitCount()); - assertEquals(11.0 / requestCount, stats.hitRate()); + assertThat(stats.requestCount()).isEqualTo(requestCount); + assertThat(stats.hitCount()).isEqualTo(11); + assertThat(stats.hitRate()).isEqualTo(11.0 / requestCount); int missCount = 23; - assertEquals(missCount, stats.missCount()); - assertEquals(((double) missCount) / requestCount, stats.missRate()); - assertEquals(13, stats.loadSuccessCount()); - assertEquals(17, stats.loadExceptionCount()); - assertEquals(13 + 17, stats.loadCount()); - assertEquals(214, stats.totalLoadTime()); - assertEquals(214.0 / (13 + 17), stats.averageLoadPenalty()); - assertEquals(27, stats.evictionCount()); + assertThat(stats.missCount()).isEqualTo(missCount); + assertThat(stats.missRate()).isEqualTo(((double) missCount) / requestCount); + assertThat(stats.loadSuccessCount()).isEqualTo(13); + assertThat(stats.loadExceptionCount()).isEqualTo(17); + assertThat(stats.loadCount()).isEqualTo(13 + 17); + assertThat(stats.totalLoadTime()).isEqualTo(214); + assertThat(stats.averageLoadPenalty()).isEqualTo(214.0 / (13 + 17)); + assertThat(stats.evictionCount()).isEqualTo(27); } public void testSimpleStatsOverflow() { @@ -151,7 +154,7 @@ public void testSimpleStatsOverflow() { counter.recordLoadSuccess(Long.MAX_VALUE); counter.recordLoadSuccess(1); CacheStats stats = counter.snapshot(); - assertEquals(Long.MAX_VALUE, stats.totalLoadTime()); + assertThat(stats.totalLoadTime()).isEqualTo(Long.MAX_VALUE); } public void testSimpleStatsIncrementBy() { @@ -196,6 +199,6 @@ public void testSimpleStatsIncrementBy() { } counter1.incrementBy(counter2); - assertEquals(new CacheStats(38, 60, 44, 54, totalLoadTime, 66), counter1.snapshot()); + assertThat(counter1.snapshot()).isEqualTo(new CacheStats(38, 60, 44, 54, totalLoadTime, 66)); } } diff --git a/android/guava-tests/test/com/google/common/cache/AbstractLoadingCacheTest.java b/android/guava-tests/test/com/google/common/cache/AbstractLoadingCacheTest.java index c2ddef7b1522..85b37bf24ed6 100644 --- a/android/guava-tests/test/com/google/common/cache/AbstractLoadingCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/AbstractLoadingCacheTest.java @@ -17,23 +17,27 @@ package com.google.common.cache; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.util.concurrent.ExecutionError; import com.google.common.util.concurrent.UncheckedExecutionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link AbstractLoadingCache}. * * @author Charles Fry */ +@NullUnmarked public class AbstractLoadingCacheTest extends TestCase { public void testGetUnchecked_checked() { - final Exception cause = new Exception(); - final AtomicReference valueRef = new AtomicReference<>(); + Exception cause = new Exception(); + AtomicReference valueRef = new AtomicReference<>(); LoadingCache cache = new AbstractLoadingCache() { @Override @@ -46,26 +50,23 @@ public Object get(Object key) throws ExecutionException { } @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return valueRef.get(); } }; - try { - cache.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isEqualTo(cause); - } + UncheckedExecutionException expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isEqualTo(cause); Object newValue = new Object(); valueRef.set(newValue); - assertSame(newValue, cache.getUnchecked(new Object())); + assertThat(cache.getUnchecked(new Object())).isSameInstanceAs(newValue); } public void testGetUnchecked_unchecked() { - final RuntimeException cause = new RuntimeException(); - final AtomicReference valueRef = new AtomicReference<>(); + RuntimeException cause = new RuntimeException(); + AtomicReference valueRef = new AtomicReference<>(); LoadingCache cache = new AbstractLoadingCache() { @Override @@ -78,26 +79,23 @@ public Object get(Object key) throws ExecutionException { } @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return valueRef.get(); } }; - try { - cache.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isEqualTo(cause); - } + UncheckedExecutionException expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isEqualTo(cause); Object newValue = new Object(); valueRef.set(newValue); - assertSame(newValue, cache.getUnchecked(new Object())); + assertThat(cache.getUnchecked(new Object())).isSameInstanceAs(newValue); } public void testGetUnchecked_error() { - final Error cause = new Error(); - final AtomicReference valueRef = new AtomicReference<>(); + Error cause = new Error(); + AtomicReference valueRef = new AtomicReference<>(); LoadingCache cache = new AbstractLoadingCache() { @Override @@ -110,26 +108,23 @@ public Object get(Object key) throws ExecutionException { } @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return valueRef.get(); } }; - try { - cache.getUnchecked(new Object()); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isEqualTo(cause); - } + ExecutionError expected = + assertThrows(ExecutionError.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isEqualTo(cause); Object newValue = new Object(); valueRef.set(newValue); - assertSame(newValue, cache.getUnchecked(new Object())); + assertThat(cache.getUnchecked(new Object())).isSameInstanceAs(newValue); } public void testGetUnchecked_otherThrowable() { - final Throwable cause = new Throwable(); - final AtomicReference valueRef = new AtomicReference<>(); + Throwable cause = new Throwable(); + AtomicReference valueRef = new AtomicReference<>(); LoadingCache cache = new AbstractLoadingCache() { @Override @@ -142,20 +137,17 @@ public Object get(Object key) throws ExecutionException { } @Override - public Object getIfPresent(Object key) { + public @Nullable Object getIfPresent(Object key) { return valueRef.get(); } }; - try { - cache.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isEqualTo(cause); - } + UncheckedExecutionException expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isEqualTo(cause); Object newValue = new Object(); valueRef.set(newValue); - assertSame(newValue, cache.getUnchecked(new Object())); + assertThat(cache.getUnchecked(new Object())).isSameInstanceAs(newValue); } } diff --git a/android/guava-tests/test/com/google/common/cache/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/cache/AndroidIncompatible.java index 135f524f6306..2c520cb6e610 100644 --- a/android/guava-tests/test/com/google/common/cache/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/cache/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/cache/CacheBuilderFactory.java b/android/guava-tests/test/com/google/common/cache/CacheBuilderFactory.java index 04b6f1ef5d6a..527114371eea 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheBuilderFactory.java +++ b/android/guava-tests/test/com/google/common/cache/CacheBuilderFactory.java @@ -16,17 +16,20 @@ import com.google.common.base.Function; import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.cache.LocalCache.Strength; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Helper class for creating {@link CacheBuilder} instances with all combinations of several sets of @@ -34,6 +37,7 @@ * * @author mike nonemacher */ +@NullUnmarked class CacheBuilderFactory { // Default values contain only 'null', which means don't call the CacheBuilder method (just give // the CacheBuilder default). @@ -46,49 +50,56 @@ class CacheBuilderFactory { private Set keyStrengths = Sets.newHashSet((Strength) null); private Set valueStrengths = Sets.newHashSet((Strength) null); + @CanIgnoreReturnValue CacheBuilderFactory withConcurrencyLevels(Set concurrencyLevels) { - this.concurrencyLevels = Sets.newLinkedHashSet(concurrencyLevels); + this.concurrencyLevels = new LinkedHashSet<>(concurrencyLevels); return this; } + @CanIgnoreReturnValue CacheBuilderFactory withInitialCapacities(Set initialCapacities) { - this.initialCapacities = Sets.newLinkedHashSet(initialCapacities); + this.initialCapacities = new LinkedHashSet<>(initialCapacities); return this; } + @CanIgnoreReturnValue CacheBuilderFactory withMaximumSizes(Set maximumSizes) { - this.maximumSizes = Sets.newLinkedHashSet(maximumSizes); + this.maximumSizes = new LinkedHashSet<>(maximumSizes); return this; } + @CanIgnoreReturnValue CacheBuilderFactory withExpireAfterWrites(Set durations) { - this.expireAfterWrites = Sets.newLinkedHashSet(durations); + this.expireAfterWrites = new LinkedHashSet<>(durations); return this; } + @CanIgnoreReturnValue CacheBuilderFactory withExpireAfterAccesses(Set durations) { - this.expireAfterAccesses = Sets.newLinkedHashSet(durations); + this.expireAfterAccesses = new LinkedHashSet<>(durations); return this; } + @CanIgnoreReturnValue CacheBuilderFactory withRefreshes(Set durations) { - this.refreshes = Sets.newLinkedHashSet(durations); + this.refreshes = new LinkedHashSet<>(durations); return this; } + @CanIgnoreReturnValue CacheBuilderFactory withKeyStrengths(Set keyStrengths) { - this.keyStrengths = Sets.newLinkedHashSet(keyStrengths); + this.keyStrengths = new LinkedHashSet<>(keyStrengths); Preconditions.checkArgument(!this.keyStrengths.contains(Strength.SOFT)); return this; } + @CanIgnoreReturnValue CacheBuilderFactory withValueStrengths(Set valueStrengths) { - this.valueStrengths = Sets.newLinkedHashSet(valueStrengths); + this.valueStrengths = new LinkedHashSet<>(valueStrengths); return this; } Iterable> buildAllPermutations() { - @SuppressWarnings("unchecked") Iterable> combinations = buildCartesianProduct( concurrencyLevels, @@ -120,15 +131,15 @@ public CacheBuilder apply(List combination) { private static final Function> NULLABLE_TO_OPTIONAL = new Function>() { @Override - public Optional apply(@NullableDecl Object obj) { + public Optional apply(@Nullable Object obj) { return Optional.fromNullable(obj); } }; - private static final Function, Object> OPTIONAL_TO_NULLABLE = - new Function, Object>() { + private static final Function, @Nullable Object> OPTIONAL_TO_NULLABLE = + new Function, @Nullable Object>() { @Override - public Object apply(Optional optional) { + public @Nullable Object apply(Optional optional) { return optional.orNull(); } }; @@ -158,14 +169,14 @@ public List apply(List> objs) { } private CacheBuilder createCacheBuilder( - Integer concurrencyLevel, - Integer initialCapacity, - Integer maximumSize, - DurationSpec expireAfterWrite, - DurationSpec expireAfterAccess, - DurationSpec refresh, - Strength keyStrength, - Strength valueStrength) { + @Nullable Integer concurrencyLevel, + @Nullable Integer initialCapacity, + @Nullable Integer maximumSize, + @Nullable DurationSpec expireAfterWrite, + @Nullable DurationSpec expireAfterAccess, + @Nullable DurationSpec refresh, + @Nullable Strength keyStrength, + @Nullable Strength valueStrength) { CacheBuilder builder = CacheBuilder.newBuilder(); if (concurrencyLevel != null) { @@ -210,11 +221,11 @@ public static DurationSpec of(long duration, TimeUnit unit) { @Override public int hashCode() { - return Objects.hashCode(duration, unit); + return Objects.hash(duration, unit); } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof DurationSpec) { DurationSpec that = (DurationSpec) o; return unit.toNanos(duration) == that.unit.toNanos(that.duration); diff --git a/android/guava-tests/test/com/google/common/cache/CacheBuilderGwtTest.java b/android/guava-tests/test/com/google/common/cache/CacheBuilderGwtTest.java index 2ae81443b03e..116d6e66327f 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheBuilderGwtTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheBuilderGwtTest.java @@ -16,6 +16,9 @@ package com.google.common.cache; +import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -29,8 +32,11 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; /** * Test suite for {@link CacheBuilder}. TODO(cpovirk): merge into CacheBuilderTest? @@ -38,20 +44,20 @@ * @author Jon Donovan */ @GwtCompatible -public class CacheBuilderGwtTest extends TestCase { +@NullUnmarked +@RunWith(JUnit4.class) +public class CacheBuilderGwtTest { private FakeTicker fakeTicker; - @Override - protected void setUp() throws Exception { - super.setUp(); - + @Before + public void setUp() { fakeTicker = new FakeTicker(); } - public void testLoader() throws ExecutionException { - - final Cache cache = CacheBuilder.newBuilder().build(); + @Test + public void loader() throws ExecutionException { + Cache cache = CacheBuilder.newBuilder().build(); Callable loader = new Callable() { @@ -65,20 +71,21 @@ public Integer call() throws Exception { cache.put(0, 10); - assertEquals(Integer.valueOf(10), cache.get(0, loader)); - assertEquals(Integer.valueOf(1), cache.get(20, loader)); - assertEquals(Integer.valueOf(2), cache.get(34, loader)); + assertThat(cache.get(0, loader)).isEqualTo(10); + assertThat(cache.get(20, loader)).isEqualTo(1); + assertThat(cache.get(34, loader)).isEqualTo(2); cache.invalidate(0); - assertEquals(Integer.valueOf(3), cache.get(0, loader)); + assertThat(cache.get(0, loader)).isEqualTo(3); cache.put(0, 10); cache.invalidateAll(); - assertEquals(Integer.valueOf(4), cache.get(0, loader)); + assertThat(cache.get(0, loader)).isEqualTo(4); } - public void testSizeConstraint() { - final Cache cache = CacheBuilder.newBuilder().maximumSize(4).build(); + @Test + public void sizeConstraint() { + Cache cache = CacheBuilder.newBuilder().maximumSize(4).build(); cache.put(1, 10); cache.put(2, 20); @@ -86,22 +93,24 @@ public void testSizeConstraint() { cache.put(4, 40); cache.put(5, 50); - assertEquals(null, cache.getIfPresent(10)); + assertThat(cache.getIfPresent(10)).isNull(); // Order required to remove dependence on access order / write order constraint. - assertEquals(Integer.valueOf(20), cache.getIfPresent(2)); - assertEquals(Integer.valueOf(30), cache.getIfPresent(3)); - assertEquals(Integer.valueOf(40), cache.getIfPresent(4)); - assertEquals(Integer.valueOf(50), cache.getIfPresent(5)); + assertThat(cache.getIfPresent(2)).isEqualTo(20); + assertThat(cache.getIfPresent(3)).isEqualTo(30); + assertThat(cache.getIfPresent(4)).isEqualTo(40); + assertThat(cache.getIfPresent(5)).isEqualTo(50); cache.put(1, 10); - assertEquals(Integer.valueOf(10), cache.getIfPresent(1)); - assertEquals(Integer.valueOf(30), cache.getIfPresent(3)); - assertEquals(Integer.valueOf(40), cache.getIfPresent(4)); - assertEquals(Integer.valueOf(50), cache.getIfPresent(5)); - assertEquals(null, cache.getIfPresent(2)); + assertThat(cache.getIfPresent(1)).isEqualTo(10); + assertThat(cache.getIfPresent(3)).isEqualTo(30); + assertThat(cache.getIfPresent(4)).isEqualTo(40); + assertThat(cache.getIfPresent(5)).isEqualTo(50); + assertThat(cache.getIfPresent(2)).isNull(); } - public void testLoadingCache() throws ExecutionException { + @SuppressWarnings({"deprecation", "LoadingCacheApply"}) + @Test + public void loadingCache() throws ExecutionException { CacheLoader loader = new CacheLoader() { int i = 0; @@ -118,67 +127,64 @@ public Integer load(Integer key) throws Exception { Map map = cache.getAll(ImmutableList.of(10, 20, 30, 54, 443, 1)); - assertEquals(Integer.valueOf(20), map.get(10)); - assertEquals(Integer.valueOf(0), map.get(20)); - assertEquals(Integer.valueOf(1), map.get(30)); - assertEquals(Integer.valueOf(2), map.get(54)); - assertEquals(Integer.valueOf(3), map.get(443)); - assertEquals(Integer.valueOf(4), map.get(1)); - assertEquals(Integer.valueOf(5), cache.get(6)); - assertEquals(Integer.valueOf(6), cache.apply(7)); + assertThat(map).containsEntry(10, 20); + assertThat(map).containsEntry(20, 0); + assertThat(map).containsEntry(30, 1); + assertThat(map).containsEntry(54, 2); + assertThat(map).containsEntry(443, 3); + assertThat(map).containsEntry(1, 4); + assertThat(cache.get(6)).isEqualTo(5); + assertThat(cache.apply(7)).isEqualTo(6); } - public void testExpireAfterAccess() { - final Cache cache = - CacheBuilder.newBuilder() - .expireAfterAccess(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + @Test + public void expireAfterAccess() { + Cache cache = + CacheBuilder.newBuilder().expireAfterAccess(1000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(0, 10); cache.put(2, 30); - fakeTicker.advance(999, TimeUnit.MILLISECONDS); - assertEquals(Integer.valueOf(30), cache.getIfPresent(2)); - fakeTicker.advance(1, TimeUnit.MILLISECONDS); - assertEquals(Integer.valueOf(30), cache.getIfPresent(2)); - fakeTicker.advance(1000, TimeUnit.MILLISECONDS); - assertEquals(null, cache.getIfPresent(0)); + fakeTicker.advance(999, MILLISECONDS); + assertThat(cache.getIfPresent(2)).isEqualTo(30); + fakeTicker.advance(1, MILLISECONDS); + assertThat(cache.getIfPresent(2)).isEqualTo(30); + fakeTicker.advance(1000, MILLISECONDS); + assertThat(cache.getIfPresent(0)).isNull(); } - public void testExpireAfterWrite() { - final Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + @Test + public void expireAfterWrite() { + Cache cache = + CacheBuilder.newBuilder().expireAfterWrite(1000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(10, 100); cache.put(20, 200); cache.put(4, 2); - fakeTicker.advance(999, TimeUnit.MILLISECONDS); - assertEquals(Integer.valueOf(100), cache.getIfPresent(10)); - assertEquals(Integer.valueOf(200), cache.getIfPresent(20)); - assertEquals(Integer.valueOf(2), cache.getIfPresent(4)); + fakeTicker.advance(999, MILLISECONDS); + assertThat(cache.getIfPresent(10)).isEqualTo(100); + assertThat(cache.getIfPresent(20)).isEqualTo(200); + assertThat(cache.getIfPresent(4)).isEqualTo(2); - fakeTicker.advance(2, TimeUnit.MILLISECONDS); - assertEquals(null, cache.getIfPresent(10)); - assertEquals(null, cache.getIfPresent(20)); - assertEquals(null, cache.getIfPresent(4)); + fakeTicker.advance(2, MILLISECONDS); + assertThat(cache.getIfPresent(10)).isNull(); + assertThat(cache.getIfPresent(20)).isNull(); + assertThat(cache.getIfPresent(4)).isNull(); cache.put(10, 20); - assertEquals(Integer.valueOf(20), cache.getIfPresent(10)); + assertThat(cache.getIfPresent(10)).isEqualTo(20); - fakeTicker.advance(1000, TimeUnit.MILLISECONDS); - assertEquals(null, cache.getIfPresent(10)); + fakeTicker.advance(1000, MILLISECONDS); + assertThat(cache.getIfPresent(10)).isNull(); } - public void testExpireAfterWriteAndAccess() { - final Cache cache = + @Test + public void expireAfterWriteAndAccess() { + Cache cache = CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .expireAfterAccess(500, TimeUnit.MILLISECONDS) + .expireAfterWrite(1000, MILLISECONDS) + .expireAfterAccess(500, MILLISECONDS) .ticker(fakeTicker) .build(); @@ -186,27 +192,29 @@ public void testExpireAfterWriteAndAccess() { cache.put(20, 200); cache.put(4, 2); - fakeTicker.advance(499, TimeUnit.MILLISECONDS); - assertEquals(Integer.valueOf(100), cache.getIfPresent(10)); - assertEquals(Integer.valueOf(200), cache.getIfPresent(20)); + fakeTicker.advance(499, MILLISECONDS); + assertThat(cache.getIfPresent(10)).isEqualTo(100); + assertThat(cache.getIfPresent(20)).isEqualTo(200); - fakeTicker.advance(2, TimeUnit.MILLISECONDS); - assertEquals(Integer.valueOf(100), cache.getIfPresent(10)); - assertEquals(Integer.valueOf(200), cache.getIfPresent(20)); - assertEquals(null, cache.getIfPresent(4)); + fakeTicker.advance(2, MILLISECONDS); + assertThat(cache.getIfPresent(10)).isEqualTo(100); + assertThat(cache.getIfPresent(20)).isEqualTo(200); + assertThat(cache.getIfPresent(4)).isNull(); - fakeTicker.advance(499, TimeUnit.MILLISECONDS); - assertEquals(null, cache.getIfPresent(10)); - assertEquals(null, cache.getIfPresent(20)); + fakeTicker.advance(499, MILLISECONDS); + assertThat(cache.getIfPresent(10)).isNull(); + assertThat(cache.getIfPresent(20)).isNull(); cache.put(10, 20); - assertEquals(Integer.valueOf(20), cache.getIfPresent(10)); + assertThat(cache.getIfPresent(10)).isEqualTo(20); - fakeTicker.advance(500, TimeUnit.MILLISECONDS); - assertEquals(null, cache.getIfPresent(10)); + fakeTicker.advance(500, MILLISECONDS); + assertThat(cache.getIfPresent(10)).isNull(); } - public void testMapMethods() { + @SuppressWarnings("ContainsEntryAfterGetInteger") // we are testing our implementation of Map.get + @Test + public void mapMethods() { Cache cache = CacheBuilder.newBuilder().build(); ConcurrentMap asMap = cache.asMap(); @@ -217,39 +225,40 @@ public void testMapMethods() { asMap.replace(2, 79); asMap.replace(3, 60); - assertEquals(null, cache.getIfPresent(3)); - assertEquals(null, asMap.get(3)); + assertThat(cache.getIfPresent(3)).isNull(); + assertThat(asMap.get(3)).isNull(); - assertEquals(Integer.valueOf(79), cache.getIfPresent(2)); - assertEquals(Integer.valueOf(79), asMap.get(2)); + assertThat(cache.getIfPresent(2)).isEqualTo(79); + assertThat(asMap.get(2)).isEqualTo(79); asMap.replace(10, 100, 50); asMap.replace(2, 52, 99); - assertEquals(Integer.valueOf(50), cache.getIfPresent(10)); - assertEquals(Integer.valueOf(50), asMap.get(10)); - assertEquals(Integer.valueOf(79), cache.getIfPresent(2)); - assertEquals(Integer.valueOf(79), asMap.get(2)); + assertThat(cache.getIfPresent(10)).isEqualTo(50); + assertThat(asMap.get(10)).isEqualTo(50); + assertThat(cache.getIfPresent(2)).isEqualTo(79); + assertThat(asMap.get(2)).isEqualTo(79); asMap.remove(10, 100); asMap.remove(2, 79); - assertEquals(Integer.valueOf(50), cache.getIfPresent(10)); - assertEquals(Integer.valueOf(50), asMap.get(10)); - assertEquals(null, cache.getIfPresent(2)); - assertEquals(null, asMap.get(2)); + assertThat(cache.getIfPresent(10)).isEqualTo(50); + assertThat(asMap.get(10)).isEqualTo(50); + assertThat(cache.getIfPresent(2)).isNull(); + assertThat(asMap.get(2)).isNull(); asMap.putIfAbsent(2, 20); asMap.putIfAbsent(10, 20); - assertEquals(Integer.valueOf(20), cache.getIfPresent(2)); - assertEquals(Integer.valueOf(20), asMap.get(2)); - assertEquals(Integer.valueOf(50), cache.getIfPresent(10)); - assertEquals(Integer.valueOf(50), asMap.get(10)); + assertThat(cache.getIfPresent(2)).isEqualTo(20); + assertThat(asMap.get(2)).isEqualTo(20); + assertThat(cache.getIfPresent(10)).isEqualTo(50); + assertThat(asMap.get(10)).isEqualTo(50); } - public void testRemovalListener() { - final int[] stats = new int[4]; + @Test + public void removalListener() { + int[] stats = new int[4]; RemovalListener countingListener = new RemovalListener() { @@ -276,7 +285,7 @@ public void onRemoval(RemovalNotification notification) { Cache cache = CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) + .expireAfterWrite(1000, MILLISECONDS) .removalListener(countingListener) .ticker(fakeTicker) .maximumSize(2) @@ -296,7 +305,7 @@ public void onRemoval(RemovalNotification notification) { cache.put(56, 4); // Expire the two present elements. - fakeTicker.advance(1001, TimeUnit.MILLISECONDS); + fakeTicker.advance(1001, MILLISECONDS); cache.getIfPresent(23); cache.getIfPresent(56); @@ -307,30 +316,32 @@ public void onRemoval(RemovalNotification notification) { cache.invalidateAll(); - assertEquals(2, stats[0]); - assertEquals(2, stats[1]); - assertEquals(4, stats[2]); - assertEquals(3, stats[3]); + assertThat(stats[0]).isEqualTo(2); + assertThat(stats[1]).isEqualTo(2); + assertThat(stats[2]).isEqualTo(4); + assertThat(stats[3]).isEqualTo(3); } - public void testPutAll() { + @Test + public void putAll() { Cache cache = CacheBuilder.newBuilder().build(); cache.putAll(ImmutableMap.of(10, 20, 30, 50, 60, 90)); - assertEquals(Integer.valueOf(20), cache.getIfPresent(10)); - assertEquals(Integer.valueOf(50), cache.getIfPresent(30)); - assertEquals(Integer.valueOf(90), cache.getIfPresent(60)); + assertThat(cache.getIfPresent(10)).isEqualTo(20); + assertThat(cache.getIfPresent(30)).isEqualTo(50); + assertThat(cache.getIfPresent(60)).isEqualTo(90); cache.asMap().putAll(ImmutableMap.of(10, 50, 30, 20, 60, 70, 5, 5)); - assertEquals(Integer.valueOf(50), cache.getIfPresent(10)); - assertEquals(Integer.valueOf(20), cache.getIfPresent(30)); - assertEquals(Integer.valueOf(70), cache.getIfPresent(60)); - assertEquals(Integer.valueOf(5), cache.getIfPresent(5)); + assertThat(cache.getIfPresent(10)).isEqualTo(50); + assertThat(cache.getIfPresent(30)).isEqualTo(20); + assertThat(cache.getIfPresent(60)).isEqualTo(70); + assertThat(cache.getIfPresent(5)).isEqualTo(5); } - public void testInvalidate() { + @Test + public void invalidate() { Cache cache = CacheBuilder.newBuilder().build(); cache.put(654, 2675); @@ -339,12 +350,13 @@ public void testInvalidate() { cache.invalidate(654); - assertFalse(cache.asMap().containsKey(654)); - assertTrue(cache.asMap().containsKey(2456)); - assertTrue(cache.asMap().containsKey(2)); + assertThat(cache.asMap().containsKey(654)).isFalse(); + assertThat(cache.asMap().containsKey(2456)).isTrue(); + assertThat(cache.asMap().containsKey(2)).isTrue(); } - public void testInvalidateAll() { + @Test + public void invalidateAll() { Cache cache = CacheBuilder.newBuilder().build(); cache.put(654, 2675); @@ -352,9 +364,9 @@ public void testInvalidateAll() { cache.put(2, 15); cache.invalidateAll(); - assertFalse(cache.asMap().containsKey(654)); - assertFalse(cache.asMap().containsKey(2456)); - assertFalse(cache.asMap().containsKey(2)); + assertThat(cache.asMap().containsKey(654)).isFalse(); + assertThat(cache.asMap().containsKey(2456)).isFalse(); + assertThat(cache.asMap().containsKey(2)).isFalse(); cache.put(654, 2675); cache.put(2456, 56); @@ -363,140 +375,130 @@ public void testInvalidateAll() { cache.invalidateAll(ImmutableSet.of(1, 2)); - assertFalse(cache.asMap().containsKey(1)); - assertFalse(cache.asMap().containsKey(2)); - assertTrue(cache.asMap().containsKey(654)); - assertTrue(cache.asMap().containsKey(2456)); + assertThat(cache.asMap().containsKey(1)).isFalse(); + assertThat(cache.asMap().containsKey(2)).isFalse(); + assertThat(cache.asMap().containsKey(654)).isTrue(); + assertThat(cache.asMap().containsKey(2456)).isTrue(); } - public void testAsMap_containsValue() { + @Test + public void asMap_containsValue() { Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(20000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + CacheBuilder.newBuilder().expireAfterWrite(20000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(654, 2675); - fakeTicker.advance(10000, TimeUnit.MILLISECONDS); + fakeTicker.advance(10000, MILLISECONDS); cache.put(2456, 56); cache.put(2, 15); - fakeTicker.advance(10001, TimeUnit.MILLISECONDS); + fakeTicker.advance(10001, MILLISECONDS); - assertTrue(cache.asMap().containsValue(15)); - assertTrue(cache.asMap().containsValue(56)); - assertFalse(cache.asMap().containsValue(2675)); + assertThat(cache.asMap().containsValue(15)).isTrue(); + assertThat(cache.asMap().containsValue(56)).isTrue(); + assertThat(cache.asMap().containsValue(2675)).isFalse(); } - public void testAsMap_containsKey() { + // we are testing our implementation of Map.containsKey + @SuppressWarnings("ContainsEntryAfterGetInteger") + @Test + public void asMap_containsKey() { Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(20000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + CacheBuilder.newBuilder().expireAfterWrite(20000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(654, 2675); - fakeTicker.advance(10000, TimeUnit.MILLISECONDS); + fakeTicker.advance(10000, MILLISECONDS); cache.put(2456, 56); cache.put(2, 15); - fakeTicker.advance(10001, TimeUnit.MILLISECONDS); + fakeTicker.advance(10001, MILLISECONDS); - assertTrue(cache.asMap().containsKey(2)); - assertTrue(cache.asMap().containsKey(2456)); - assertFalse(cache.asMap().containsKey(654)); + assertThat(cache.asMap().containsKey(2)).isTrue(); + assertThat(cache.asMap().containsKey(2456)).isTrue(); + assertThat(cache.asMap().containsKey(654)).isFalse(); } - public void testAsMapValues_contains() { + // we are testing our implementation of Map.values().contains + @SuppressWarnings("ValuesContainsValue") + @Test + public void asMapValues_contains() { Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + CacheBuilder.newBuilder().expireAfterWrite(1000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(10, 20); - fakeTicker.advance(500, TimeUnit.MILLISECONDS); + fakeTicker.advance(500, MILLISECONDS); cache.put(20, 22); cache.put(5, 10); - fakeTicker.advance(501, TimeUnit.MILLISECONDS); + fakeTicker.advance(501, MILLISECONDS); - assertTrue(cache.asMap().values().contains(22)); - assertTrue(cache.asMap().values().contains(10)); - assertFalse(cache.asMap().values().contains(20)); + assertThat(cache.asMap().values().contains(22)).isTrue(); + assertThat(cache.asMap().values().contains(10)).isTrue(); + assertThat(cache.asMap().values().contains(20)).isFalse(); } - public void testAsMapKeySet() { + @Test + public void asMapKeySet() { Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + CacheBuilder.newBuilder().expireAfterWrite(1000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(10, 20); - fakeTicker.advance(500, TimeUnit.MILLISECONDS); + fakeTicker.advance(500, MILLISECONDS); cache.put(20, 22); cache.put(5, 10); - fakeTicker.advance(501, TimeUnit.MILLISECONDS); + fakeTicker.advance(501, MILLISECONDS); Set foundKeys = new HashSet<>(cache.asMap().keySet()); - assertEquals(ImmutableSet.of(20, 5), foundKeys); + assertThat(foundKeys).containsExactly(20, 5); } - public void testAsMapKeySet_contains() { + @Test + public void asMapKeySet_contains() { Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + CacheBuilder.newBuilder().expireAfterWrite(1000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(10, 20); - fakeTicker.advance(500, TimeUnit.MILLISECONDS); + fakeTicker.advance(500, MILLISECONDS); cache.put(20, 22); cache.put(5, 10); - fakeTicker.advance(501, TimeUnit.MILLISECONDS); + fakeTicker.advance(501, MILLISECONDS); - assertTrue(cache.asMap().keySet().contains(20)); - assertTrue(cache.asMap().keySet().contains(5)); - assertFalse(cache.asMap().keySet().contains(10)); + assertThat(cache.asMap().keySet().contains(20)).isTrue(); + assertThat(cache.asMap().keySet().contains(5)).isTrue(); + assertThat(cache.asMap().keySet().contains(10)).isFalse(); } - public void testAsMapEntrySet() { + @Test + public void asMapEntrySet() { Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + CacheBuilder.newBuilder().expireAfterWrite(1000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(10, 20); - fakeTicker.advance(500, TimeUnit.MILLISECONDS); + fakeTicker.advance(500, MILLISECONDS); cache.put(20, 22); cache.put(5, 10); - fakeTicker.advance(501, TimeUnit.MILLISECONDS); + fakeTicker.advance(501, MILLISECONDS); int sum = 0; for (Entry current : cache.asMap().entrySet()) { sum += current.getKey() + current.getValue(); } - assertEquals(57, sum); + assertThat(sum).isEqualTo(57); } - public void testAsMapValues_iteratorRemove() { + @Test + public void asMapValues_iteratorRemove() { Cache cache = - CacheBuilder.newBuilder() - .expireAfterWrite(1000, TimeUnit.MILLISECONDS) - .ticker(fakeTicker) - .build(); + CacheBuilder.newBuilder().expireAfterWrite(1000, MILLISECONDS).ticker(fakeTicker).build(); cache.put(10, 20); Iterator iterator = cache.asMap().values().iterator(); iterator.next(); iterator.remove(); - assertEquals(0, cache.size()); + assertThat(cache.size()).isEqualTo(0); } } diff --git a/android/guava-tests/test/com/google/common/cache/CacheBuilderSpecTest.java b/android/guava-tests/test/com/google/common/cache/CacheBuilderSpecTest.java index 09f2eb6c2844..7948e82f89f3 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheBuilderSpecTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheBuilderSpecTest.java @@ -18,12 +18,19 @@ import static com.google.common.cache.CacheBuilderSpec.parse; import static com.google.common.cache.TestingWeighers.constantWeigher; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.base.Suppliers; import com.google.common.cache.LocalCache.Strength; import com.google.common.testing.EqualsTester; -import java.util.concurrent.TimeUnit; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests CacheBuilderSpec. TODO(user): tests of a few invalid input conditions, boundary @@ -31,339 +38,272 @@ * * @author Adam Winer */ +@NullUnmarked public class CacheBuilderSpecTest extends TestCase { public void testParse_empty() { CacheBuilderSpec spec = parse(""); - assertNull(spec.initialCapacity); - assertNull(spec.maximumSize); - assertNull(spec.maximumWeight); - assertNull(spec.concurrencyLevel); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumSize).isNull(); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isNull(); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence(CacheBuilder.newBuilder(), CacheBuilder.from(spec)); } public void testParse_initialCapacity() { CacheBuilderSpec spec = parse("initialCapacity=10"); - assertEquals(10, spec.initialCapacity.intValue()); - assertNull(spec.maximumSize); - assertNull(spec.maximumWeight); - assertNull(spec.concurrencyLevel); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isEqualTo(10); + assertThat(spec.maximumSize).isNull(); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isNull(); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence( CacheBuilder.newBuilder().initialCapacity(10), CacheBuilder.from(spec)); } public void testParse_initialCapacityRepeated() { - try { - parse("initialCapacity=10, initialCapacity=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> parse("initialCapacity=10, initialCapacity=20")); } public void testParse_maximumSize() { CacheBuilderSpec spec = parse("maximumSize=9000"); - assertNull(spec.initialCapacity); - assertEquals(9000, spec.maximumSize.longValue()); - assertNull(spec.concurrencyLevel); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumSize).isEqualTo(9000L); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isNull(); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence( CacheBuilder.newBuilder().maximumSize(9000), CacheBuilder.from(spec)); } public void testParse_maximumSizeRepeated() { - try { - parse("maximumSize=10, maximumSize=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("maximumSize=10, maximumSize=20")); } public void testParse_maximumWeight() { CacheBuilderSpec spec = parse("maximumWeight=9000"); - assertNull(spec.initialCapacity); - assertEquals(9000, spec.maximumWeight.longValue()); - assertNull(spec.concurrencyLevel); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumWeight).isEqualTo(9000L); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isNull(); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence( CacheBuilder.newBuilder().maximumWeight(9000), CacheBuilder.from(spec)); } public void testParse_maximumWeightRepeated() { - try { - parse("maximumWeight=10, maximumWeight=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("maximumWeight=10, maximumWeight=20")); } public void testParse_maximumSizeAndMaximumWeight() { - try { - parse("maximumSize=10, maximumWeight=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("maximumSize=10, maximumWeight=20")); } public void testParse_concurrencyLevel() { CacheBuilderSpec spec = parse("concurrencyLevel=32"); - assertNull(spec.initialCapacity); - assertNull(spec.maximumSize); - assertNull(spec.maximumWeight); - assertEquals(32, spec.concurrencyLevel.intValue()); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumSize).isNull(); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isEqualTo(32); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isNull(); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence( CacheBuilder.newBuilder().concurrencyLevel(32), CacheBuilder.from(spec)); } public void testParse_concurrencyLevelRepeated() { - try { - parse("concurrencyLevel=10, concurrencyLevel=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> parse("concurrencyLevel=10, concurrencyLevel=20")); } public void testParse_weakKeys() { CacheBuilderSpec spec = parse("weakKeys"); - assertNull(spec.initialCapacity); - assertNull(spec.maximumSize); - assertNull(spec.maximumWeight); - assertNull(spec.concurrencyLevel); - assertEquals(Strength.WEAK, spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumSize).isNull(); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isEqualTo(Strength.WEAK); + assertThat(spec.valueStrength).isNull(); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence(CacheBuilder.newBuilder().weakKeys(), CacheBuilder.from(spec)); } public void testParse_weakKeysCannotHaveValue() { - try { - parse("weakKeys=true"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("weakKeys=true")); } public void testParse_repeatedKeyStrength() { - try { - parse("weakKeys, weakKeys"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("weakKeys, weakKeys")); } public void testParse_softValues() { CacheBuilderSpec spec = parse("softValues"); - assertNull(spec.initialCapacity); - assertNull(spec.maximumSize); - assertNull(spec.maximumWeight); - assertNull(spec.concurrencyLevel); - assertNull(spec.keyStrength); - assertEquals(Strength.SOFT, spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumSize).isNull(); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isEqualTo(Strength.SOFT); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence(CacheBuilder.newBuilder().softValues(), CacheBuilder.from(spec)); } public void testParse_softValuesCannotHaveValue() { - try { - parse("softValues=true"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("softValues=true")); } public void testParse_weakValues() { CacheBuilderSpec spec = parse("weakValues"); - assertNull(spec.initialCapacity); - assertNull(spec.maximumSize); - assertNull(spec.maximumWeight); - assertNull(spec.concurrencyLevel); - assertNull(spec.keyStrength); - assertEquals(Strength.WEAK, spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumSize).isNull(); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isEqualTo(Strength.WEAK); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence(CacheBuilder.newBuilder().weakValues(), CacheBuilder.from(spec)); } public void testParse_weakValuesCannotHaveValue() { - try { - parse("weakValues=true"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("weakValues=true")); } public void testParse_repeatedValueStrength() { - try { - parse("softValues, softValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } - - try { - parse("softValues, weakValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } - - try { - parse("weakValues, softValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } - - try { - parse("weakValues, weakValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("softValues, softValues")); + + assertThrows(IllegalArgumentException.class, () -> parse("softValues, weakValues")); + + assertThrows(IllegalArgumentException.class, () -> parse("weakValues, softValues")); + + assertThrows(IllegalArgumentException.class, () -> parse("weakValues, weakValues")); } public void testParse_writeExpirationDays() { CacheBuilderSpec spec = parse("expireAfterWrite=10d"); - assertNull(spec.initialCapacity); - assertNull(spec.maximumSize); - assertNull(spec.maximumWeight); - assertNull(spec.concurrencyLevel); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertEquals(TimeUnit.DAYS, spec.writeExpirationTimeUnit); - assertEquals(10L, spec.writeExpirationDuration); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumSize).isNull(); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isNull(); + assertThat(spec.writeExpirationTimeUnit).isEqualTo(DAYS); + assertThat(spec.writeExpirationDuration).isEqualTo(10L); + assertThat(spec.accessExpirationTimeUnit).isNull(); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder().expireAfterWrite(10L, TimeUnit.DAYS), CacheBuilder.from(spec)); + CacheBuilder.newBuilder().expireAfterWrite(10L, DAYS), CacheBuilder.from(spec)); } public void testParse_writeExpirationHours() { CacheBuilderSpec spec = parse("expireAfterWrite=150h"); - assertEquals(TimeUnit.HOURS, spec.writeExpirationTimeUnit); - assertEquals(150L, spec.writeExpirationDuration); + assertThat(spec.writeExpirationTimeUnit).isEqualTo(HOURS); + assertThat(spec.writeExpirationDuration).isEqualTo(150L); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder().expireAfterWrite(150L, TimeUnit.HOURS), CacheBuilder.from(spec)); + CacheBuilder.newBuilder().expireAfterWrite(150L, HOURS), CacheBuilder.from(spec)); } public void testParse_writeExpirationMinutes() { CacheBuilderSpec spec = parse("expireAfterWrite=10m"); - assertEquals(TimeUnit.MINUTES, spec.writeExpirationTimeUnit); - assertEquals(10L, spec.writeExpirationDuration); + assertThat(spec.writeExpirationTimeUnit).isEqualTo(MINUTES); + assertThat(spec.writeExpirationDuration).isEqualTo(10L); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder().expireAfterWrite(10L, TimeUnit.MINUTES), CacheBuilder.from(spec)); + CacheBuilder.newBuilder().expireAfterWrite(10L, MINUTES), CacheBuilder.from(spec)); } public void testParse_writeExpirationSeconds() { CacheBuilderSpec spec = parse("expireAfterWrite=10s"); - assertEquals(TimeUnit.SECONDS, spec.writeExpirationTimeUnit); - assertEquals(10L, spec.writeExpirationDuration); + assertThat(spec.writeExpirationTimeUnit).isEqualTo(SECONDS); + assertThat(spec.writeExpirationDuration).isEqualTo(10L); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder().expireAfterWrite(10L, TimeUnit.SECONDS), CacheBuilder.from(spec)); + CacheBuilder.newBuilder().expireAfterWrite(10L, SECONDS), CacheBuilder.from(spec)); } public void testParse_writeExpirationRepeated() { - try { - parse("expireAfterWrite=10s,expireAfterWrite=10m"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> parse("expireAfterWrite=10s,expireAfterWrite=10m")); } public void testParse_accessExpirationDays() { CacheBuilderSpec spec = parse("expireAfterAccess=10d"); - assertNull(spec.initialCapacity); - assertNull(spec.maximumSize); - assertNull(spec.maximumWeight); - assertNull(spec.concurrencyLevel); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.writeExpirationTimeUnit); - assertEquals(TimeUnit.DAYS, spec.accessExpirationTimeUnit); - assertEquals(10L, spec.accessExpirationDuration); + assertThat(spec.initialCapacity).isNull(); + assertThat(spec.maximumSize).isNull(); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isNull(); + assertThat(spec.valueStrength).isNull(); + assertThat(spec.writeExpirationTimeUnit).isNull(); + assertThat(spec.accessExpirationTimeUnit).isEqualTo(DAYS); + assertThat(spec.accessExpirationDuration).isEqualTo(10L); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder().expireAfterAccess(10L, TimeUnit.DAYS), CacheBuilder.from(spec)); + CacheBuilder.newBuilder().expireAfterAccess(10L, DAYS), CacheBuilder.from(spec)); } public void testParse_accessExpirationHours() { CacheBuilderSpec spec = parse("expireAfterAccess=150h"); - assertEquals(TimeUnit.HOURS, spec.accessExpirationTimeUnit); - assertEquals(150L, spec.accessExpirationDuration); + assertThat(spec.accessExpirationTimeUnit).isEqualTo(HOURS); + assertThat(spec.accessExpirationDuration).isEqualTo(150L); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder().expireAfterAccess(150L, TimeUnit.HOURS), CacheBuilder.from(spec)); + CacheBuilder.newBuilder().expireAfterAccess(150L, HOURS), CacheBuilder.from(spec)); } public void testParse_accessExpirationMinutes() { CacheBuilderSpec spec = parse("expireAfterAccess=10m"); - assertEquals(TimeUnit.MINUTES, spec.accessExpirationTimeUnit); - assertEquals(10L, spec.accessExpirationDuration); + assertThat(spec.accessExpirationTimeUnit).isEqualTo(MINUTES); + assertThat(spec.accessExpirationDuration).isEqualTo(10L); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder().expireAfterAccess(10L, TimeUnit.MINUTES), - CacheBuilder.from(spec)); + CacheBuilder.newBuilder().expireAfterAccess(10L, MINUTES), CacheBuilder.from(spec)); } public void testParse_accessExpirationSeconds() { CacheBuilderSpec spec = parse("expireAfterAccess=10s"); - assertEquals(TimeUnit.SECONDS, spec.accessExpirationTimeUnit); - assertEquals(10L, spec.accessExpirationDuration); + assertThat(spec.accessExpirationTimeUnit).isEqualTo(SECONDS); + assertThat(spec.accessExpirationDuration).isEqualTo(10L); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder().expireAfterAccess(10L, TimeUnit.SECONDS), - CacheBuilder.from(spec)); + CacheBuilder.newBuilder().expireAfterAccess(10L, SECONDS), CacheBuilder.from(spec)); } public void testParse_accessExpirationRepeated() { - try { - parse("expireAfterAccess=10s,expireAfterAccess=10m"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> parse("expireAfterAccess=10s,expireAfterAccess=10m")); } public void testParse_recordStats() { CacheBuilderSpec spec = parse("recordStats"); - assertTrue(spec.recordStats); + assertThat(spec.recordStats).isTrue(); assertCacheBuilderEquivalence(CacheBuilder.newBuilder().recordStats(), CacheBuilder.from(spec)); } public void testParse_recordStatsValueSpecified() { - try { - parse("recordStats=True"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("recordStats=True")); } public void testParse_recordStatsRepeated() { - try { - parse("recordStats,recordStats"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("recordStats,recordStats")); } public void testParse_accessExpirationAndWriteExpiration() { CacheBuilderSpec spec = parse("expireAfterAccess=10s,expireAfterWrite=9m"); - assertEquals(TimeUnit.MINUTES, spec.writeExpirationTimeUnit); - assertEquals(9L, spec.writeExpirationDuration); - assertEquals(TimeUnit.SECONDS, spec.accessExpirationTimeUnit); - assertEquals(10L, spec.accessExpirationDuration); + assertThat(spec.writeExpirationTimeUnit).isEqualTo(MINUTES); + assertThat(spec.writeExpirationDuration).isEqualTo(9L); + assertThat(spec.accessExpirationTimeUnit).isEqualTo(SECONDS); + assertThat(spec.accessExpirationDuration).isEqualTo(10L); assertCacheBuilderEquivalence( - CacheBuilder.newBuilder() - .expireAfterAccess(10L, TimeUnit.SECONDS) - .expireAfterWrite(9L, TimeUnit.MINUTES), + CacheBuilder.newBuilder().expireAfterAccess(10L, SECONDS).expireAfterWrite(9L, MINUTES), CacheBuilder.from(spec)); } @@ -372,16 +312,16 @@ public void testParse_multipleKeys() { parse( "initialCapacity=10,maximumSize=20,concurrencyLevel=30," + "weakKeys,weakValues,expireAfterAccess=10m,expireAfterWrite=1h"); - assertEquals(10, spec.initialCapacity.intValue()); - assertEquals(20, spec.maximumSize.intValue()); - assertNull(spec.maximumWeight); - assertEquals(30, spec.concurrencyLevel.intValue()); - assertEquals(Strength.WEAK, spec.keyStrength); - assertEquals(Strength.WEAK, spec.valueStrength); - assertEquals(TimeUnit.HOURS, spec.writeExpirationTimeUnit); - assertEquals(TimeUnit.MINUTES, spec.accessExpirationTimeUnit); - assertEquals(1L, spec.writeExpirationDuration); - assertEquals(10L, spec.accessExpirationDuration); + assertThat(spec.initialCapacity).isEqualTo(10); + assertThat(spec.maximumSize).isEqualTo(20); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isEqualTo(30); + assertThat(spec.keyStrength).isEqualTo(Strength.WEAK); + assertThat(spec.valueStrength).isEqualTo(Strength.WEAK); + assertThat(spec.writeExpirationTimeUnit).isEqualTo(HOURS); + assertThat(spec.accessExpirationTimeUnit).isEqualTo(MINUTES); + assertThat(spec.writeExpirationDuration).isEqualTo(1L); + assertThat(spec.accessExpirationDuration).isEqualTo(10L); CacheBuilder expected = CacheBuilder.newBuilder() .initialCapacity(10) @@ -389,8 +329,8 @@ public void testParse_multipleKeys() { .concurrencyLevel(30) .weakKeys() .weakValues() - .expireAfterAccess(10L, TimeUnit.MINUTES) - .expireAfterWrite(1L, TimeUnit.HOURS); + .expireAfterAccess(10L, MINUTES) + .expireAfterWrite(1L, HOURS); assertCacheBuilderEquivalence(expected, CacheBuilder.from(spec)); } @@ -399,51 +339,35 @@ public void testParse_whitespaceAllowed() { parse( " initialCapacity=10,\nmaximumSize=20,\t\r" + "weakKeys \t ,softValues \n , \r expireAfterWrite \t = 15s\n\n"); - assertEquals(10, spec.initialCapacity.intValue()); - assertEquals(20, spec.maximumSize.intValue()); - assertNull(spec.maximumWeight); - assertNull(spec.concurrencyLevel); - assertEquals(Strength.WEAK, spec.keyStrength); - assertEquals(Strength.SOFT, spec.valueStrength); - assertEquals(TimeUnit.SECONDS, spec.writeExpirationTimeUnit); - assertEquals(15L, spec.writeExpirationDuration); - assertNull(spec.accessExpirationTimeUnit); + assertThat(spec.initialCapacity).isEqualTo(10); + assertThat(spec.maximumSize).isEqualTo(20); + assertThat(spec.maximumWeight).isNull(); + assertThat(spec.concurrencyLevel).isNull(); + assertThat(spec.keyStrength).isEqualTo(Strength.WEAK); + assertThat(spec.valueStrength).isEqualTo(Strength.SOFT); + assertThat(spec.writeExpirationTimeUnit).isEqualTo(SECONDS); + assertThat(spec.writeExpirationDuration).isEqualTo(15L); + assertThat(spec.accessExpirationTimeUnit).isNull(); CacheBuilder expected = CacheBuilder.newBuilder() .initialCapacity(10) .maximumSize(20) .weakKeys() .softValues() - .expireAfterWrite(15L, TimeUnit.SECONDS); + .expireAfterWrite(15L, SECONDS); assertCacheBuilderEquivalence(expected, CacheBuilder.from(spec)); } public void testParse_unknownKey() { - try { - parse("foo=17"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("foo=17")); } public void testParse_extraCommaIsInvalid() { - try { - parse("weakKeys,"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("weakKeys,")); - try { - parse(",weakKeys"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse(",weakKeys")); - try { - parse("weakKeys,,softValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> parse("weakKeys,,softValues")); } public void testEqualsAndHashCode() { @@ -477,11 +401,9 @@ public void testMaximumWeight_withWeigher() { @SuppressWarnings("ReturnValueIgnored") public void testMaximumWeight_withoutWeigher() { CacheBuilder builder = CacheBuilder.from(parse("maximumWeight=9000")); - try { - builder.build(CacheLoader.from(Suppliers.ofInstance(null))); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows( + IllegalStateException.class, + () -> builder.build(CacheLoader.from(Suppliers.ofInstance(null)))); } @SuppressWarnings("ReturnValueIgnored") @@ -504,9 +426,9 @@ public void testDisableCaching() { Object value = new Object(); LoadingCache cache = builder.build(CacheLoader.from(Suppliers.ofInstance(value))); - assertSame(value, cache.getUnchecked(key)); - assertEquals(0, cache.size()); - assertFalse(cache.asMap().containsKey(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(value); + assertThat(cache.size()).isEqualTo(0); + assertThat(cache.asMap().containsKey(key)).isFalse(); } public void testCacheBuilderFrom_string() { @@ -521,26 +443,45 @@ public void testCacheBuilderFrom_string() { .concurrencyLevel(30) .weakKeys() .weakValues() - .expireAfterAccess(10L, TimeUnit.MINUTES); + .expireAfterAccess(10L, MINUTES); assertCacheBuilderEquivalence(expected, fromString); } - private static void assertCacheBuilderEquivalence(CacheBuilder a, CacheBuilder b) { - assertEquals("concurrencyLevel", a.concurrencyLevel, b.concurrencyLevel); - assertEquals("expireAfterAccessNanos", a.expireAfterAccessNanos, b.expireAfterAccessNanos); - assertEquals("expireAfterWriteNanos", a.expireAfterWriteNanos, b.expireAfterWriteNanos); - assertEquals("initialCapacity", a.initialCapacity, b.initialCapacity); - assertEquals("maximumSize", a.maximumSize, b.maximumSize); - assertEquals("maximumWeight", a.maximumWeight, b.maximumWeight); - assertEquals("refreshNanos", a.refreshNanos, b.refreshNanos); - assertEquals("keyEquivalence", a.keyEquivalence, b.keyEquivalence); - assertEquals("keyStrength", a.keyStrength, b.keyStrength); - assertEquals("removalListener", a.removalListener, b.removalListener); - assertEquals("weigher", a.weigher, b.weigher); - assertEquals("valueEquivalence", a.valueEquivalence, b.valueEquivalence); - assertEquals("valueStrength", a.valueStrength, b.valueStrength); - assertEquals("statsCounterSupplier", a.statsCounterSupplier, b.statsCounterSupplier); - assertEquals("ticker", a.ticker, b.ticker); - assertEquals("recordStats", a.isRecordingStats(), b.isRecordingStats()); + private static void assertCacheBuilderEquivalence( + CacheBuilder expected, CacheBuilder actual) { + assertWithMessage("concurrencyLevel") + .that(actual.concurrencyLevel) + .isEqualTo(expected.concurrencyLevel); + assertWithMessage("expireAfterAccessNanos") + .that(actual.expireAfterAccessNanos) + .isEqualTo(expected.expireAfterAccessNanos); + assertWithMessage("expireAfterWriteNanos") + .that(actual.expireAfterWriteNanos) + .isEqualTo(expected.expireAfterWriteNanos); + assertWithMessage("initialCapacity") + .that(actual.initialCapacity) + .isEqualTo(expected.initialCapacity); + assertWithMessage("maximumSize").that(actual.maximumSize).isEqualTo(expected.maximumSize); + assertWithMessage("maximumWeight").that(actual.maximumWeight).isEqualTo(expected.maximumWeight); + assertWithMessage("refreshNanos").that(actual.refreshNanos).isEqualTo(expected.refreshNanos); + assertWithMessage("keyEquivalence") + .that(actual.keyEquivalence) + .isEqualTo(expected.keyEquivalence); + assertWithMessage("keyStrength").that(actual.keyStrength).isEqualTo(expected.keyStrength); + assertWithMessage("removalListener") + .that(actual.removalListener) + .isEqualTo(expected.removalListener); + assertWithMessage("weigher").that(actual.weigher).isEqualTo(expected.weigher); + assertWithMessage("valueEquivalence") + .that(actual.valueEquivalence) + .isEqualTo(expected.valueEquivalence); + assertWithMessage("valueStrength").that(actual.valueStrength).isEqualTo(expected.valueStrength); + assertWithMessage("statsCounterSupplier") + .that(actual.statsCounterSupplier) + .isEqualTo(expected.statsCounterSupplier); + assertWithMessage("ticker").that(actual.ticker).isEqualTo(expected.ticker); + assertWithMessage("recordStats") + .that(actual.isRecordingStats()) + .isEqualTo(expected.isRecordingStats()); } } diff --git a/android/guava-tests/test/com/google/common/cache/CacheBuilderTest.java b/android/guava-tests/test/com/google/common/cache/CacheBuilderTest.java index 1a9faeeaed2f..cbc2e6b4ed11 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheBuilderTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheBuilderTest.java @@ -16,6 +16,7 @@ package com.google.common.cache; +import static com.google.common.cache.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.cache.TestingCacheLoaders.constantLoader; import static com.google.common.cache.TestingCacheLoaders.identityLoader; import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; @@ -23,6 +24,8 @@ import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener; import static com.google.common.cache.TestingWeighers.constantWeigher; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -32,22 +35,27 @@ import com.google.common.base.Ticker; import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener; import com.google.common.cache.TestingRemovalListeners.QueuingRemovalListener; -import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.testing.NullPointerTester; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.time.Duration; +import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for CacheBuilder. */ -@GwtCompatible(emulated = true) +@GwtCompatible +// We are intentionally testing the TimeUnit overloads, too. +@SuppressWarnings("LongTimeUnit_ExpireAfterWrite_Seconds") +@NullUnmarked public class CacheBuilderTest extends TestCase { public void testNewBuilder() { @@ -56,27 +64,18 @@ public void testNewBuilder() { LoadingCache cache = CacheBuilder.newBuilder().removalListener(countingRemovalListener()).build(loader); - assertEquals(Integer.valueOf(1), cache.getUnchecked("one")); - assertEquals(1, cache.size()); + assertThat(cache.getUnchecked("one")).isEqualTo(1); + assertThat(cache.size()).isEqualTo(1); } public void testInitialCapacity_negative() { CacheBuilder builder = CacheBuilder.newBuilder(); - try { - builder.initialCapacity(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.initialCapacity(-1)); } public void testInitialCapacity_setTwice() { CacheBuilder builder = CacheBuilder.newBuilder().initialCapacity(16); - try { - // even to the same value is not allowed - builder.initialCapacity(16); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.initialCapacity(16)); } @GwtIncompatible // CacheTesting @@ -85,10 +84,10 @@ public void testInitialCapacity_small() { LocalCache map = CacheTesting.toLocalCache(cache); assertThat(map.segments).hasLength(4); - assertEquals(2, map.segments[0].table.length()); - assertEquals(2, map.segments[1].table.length()); - assertEquals(2, map.segments[2].table.length()); - assertEquals(2, map.segments[3].table.length()); + assertThat(map.segments[0].table.length()).isEqualTo(2); + assertThat(map.segments[1].table.length()).isEqualTo(2); + assertThat(map.segments[2].table.length()).isEqualTo(2); + assertThat(map.segments[3].table.length()).isEqualTo(2); } @GwtIncompatible // CacheTesting @@ -98,10 +97,10 @@ public void testInitialCapacity_smallest() { assertThat(map.segments).hasLength(4); // 1 is as low as it goes, not 0. it feels dirty to know this/test this. - assertEquals(1, map.segments[0].table.length()); - assertEquals(1, map.segments[1].table.length()); - assertEquals(1, map.segments[2].table.length()); - assertEquals(1, map.segments[3].table.length()); + assertThat(map.segments[0].table.length()).isEqualTo(1); + assertThat(map.segments[1].table.length()).isEqualTo(1); + assertThat(map.segments[2].table.length()).isEqualTo(1); + assertThat(map.segments[3].table.length()).isEqualTo(1); } public void testInitialCapacity_large() { @@ -112,21 +111,12 @@ public void testInitialCapacity_large() { public void testConcurrencyLevel_zero() { CacheBuilder builder = CacheBuilder.newBuilder(); - try { - builder.concurrencyLevel(0); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.concurrencyLevel(0)); } public void testConcurrencyLevel_setTwice() { CacheBuilder builder = CacheBuilder.newBuilder().concurrencyLevel(16); - try { - // even to the same value is not allowed - builder.concurrencyLevel(16); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.concurrencyLevel(16)); } @GwtIncompatible // CacheTesting @@ -144,31 +134,18 @@ public void testConcurrencyLevel_large() { public void testMaximumSize_negative() { CacheBuilder builder = CacheBuilder.newBuilder(); - try { - builder.maximumSize(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.maximumSize(-1)); } public void testMaximumSize_setTwice() { CacheBuilder builder = CacheBuilder.newBuilder().maximumSize(16); - try { - // even to the same value is not allowed - builder.maximumSize(16); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.maximumSize(16)); } @GwtIncompatible // maximumWeight public void testMaximumSize_andWeight() { CacheBuilder builder = CacheBuilder.newBuilder().maximumSize(16); - try { - builder.maximumWeight(16); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.maximumWeight(16)); } @GwtIncompatible // digs into internals of the non-GWT implementation @@ -182,107 +159,76 @@ public void testMaximumSize_largerThanInt() { @GwtIncompatible // maximumWeight public void testMaximumWeight_negative() { CacheBuilder builder = CacheBuilder.newBuilder(); - try { - builder.maximumWeight(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.maximumWeight(-1)); } @GwtIncompatible // maximumWeight public void testMaximumWeight_setTwice() { CacheBuilder builder = CacheBuilder.newBuilder().maximumWeight(16); - try { - // even to the same value is not allowed - builder.maximumWeight(16); - fail(); - } catch (IllegalStateException expected) { - } - try { - builder.maximumSize(16); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.maximumWeight(16)); + assertThrows(IllegalStateException.class, () -> builder.maximumSize(16)); } @GwtIncompatible // maximumWeight public void testMaximumWeight_withoutWeigher() { CacheBuilder builder = CacheBuilder.newBuilder().maximumWeight(1); - try { - builder.build(identityLoader()); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.build(identityLoader())); } @GwtIncompatible // weigher public void testWeigher_withoutMaximumWeight() { CacheBuilder builder = CacheBuilder.newBuilder().weigher(constantWeigher(42)); - try { - builder.build(identityLoader()); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.build(identityLoader())); } @GwtIncompatible // weigher public void testWeigher_withMaximumSize() { - try { - CacheBuilder.newBuilder().weigher(constantWeigher(42)).maximumSize(1); - fail(); - } catch (IllegalStateException expected) { - } - try { - CacheBuilder.newBuilder().maximumSize(1).weigher(constantWeigher(42)); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows( + IllegalStateException.class, + () -> CacheBuilder.newBuilder().weigher(constantWeigher(42)).maximumSize(1)); + assertThrows( + IllegalStateException.class, + () -> CacheBuilder.newBuilder().maximumSize(1).weigher(constantWeigher(42))); } @GwtIncompatible // weakKeys public void testKeyStrengthSetTwice() { CacheBuilder builder1 = CacheBuilder.newBuilder().weakKeys(); - try { - builder1.weakKeys(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder1.weakKeys()); } @GwtIncompatible // weakValues public void testValueStrengthSetTwice() { CacheBuilder builder1 = CacheBuilder.newBuilder().weakValues(); - try { - builder1.weakValues(); - fail(); - } catch (IllegalStateException expected) { - } - try { - builder1.softValues(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder1.weakValues()); + assertThrows(IllegalStateException.class, () -> builder1.softValues()); CacheBuilder builder2 = CacheBuilder.newBuilder().softValues(); - try { - builder2.softValues(); - fail(); - } catch (IllegalStateException expected) { - } - try { - builder2.weakValues(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder2.softValues()); + assertThrows(IllegalStateException.class, () -> builder2.weakValues()); + } + + @GwtIncompatible // Duration + @IgnoreJRERequirement // No more dangerous than wherever the caller got the Duration from + public void testLargeDurationsAreOk() { + Duration threeHundredYears = Duration.ofDays(365 * 300); + CacheBuilder unused = + CacheBuilder.newBuilder() + .expireAfterWrite(threeHundredYears) + .expireAfterAccess(threeHundredYears) + .refreshAfterWrite(threeHundredYears); } public void testTimeToLive_negative() { CacheBuilder builder = CacheBuilder.newBuilder(); - try { - builder.expireAfterWrite(-1, SECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.expireAfterWrite(-1, SECONDS)); + } + + @GwtIncompatible // Duration + public void testTimeToLive_negative_duration() { + CacheBuilder builder = CacheBuilder.newBuilder(); + assertThrows( + IllegalArgumentException.class, () -> builder.expireAfterWrite(Duration.ofSeconds(-1))); } @SuppressWarnings("ReturnValueIgnored") @@ -294,21 +240,26 @@ public void testTimeToLive_small() { public void testTimeToLive_setTwice() { CacheBuilder builder = CacheBuilder.newBuilder().expireAfterWrite(3600, SECONDS); - try { - // even to the same value is not allowed - builder.expireAfterWrite(3600, SECONDS); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.expireAfterWrite(3600, SECONDS)); + } + + @GwtIncompatible // Duration + public void testTimeToLive_setTwice_duration() { + CacheBuilder builder = + CacheBuilder.newBuilder().expireAfterWrite(Duration.ofHours(1)); + assertThrows(IllegalStateException.class, () -> builder.expireAfterWrite(Duration.ofHours(1))); } public void testTimeToIdle_negative() { CacheBuilder builder = CacheBuilder.newBuilder(); - try { - builder.expireAfterAccess(-1, SECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.expireAfterAccess(-1, SECONDS)); + } + + @GwtIncompatible // Duration + public void testTimeToIdle_negative_duration() { + CacheBuilder builder = CacheBuilder.newBuilder(); + assertThrows( + IllegalArgumentException.class, () -> builder.expireAfterAccess(Duration.ofSeconds(-1))); } @SuppressWarnings("ReturnValueIgnored") @@ -320,69 +271,65 @@ public void testTimeToIdle_small() { public void testTimeToIdle_setTwice() { CacheBuilder builder = CacheBuilder.newBuilder().expireAfterAccess(3600, SECONDS); - try { - // even to the same value is not allowed - builder.expireAfterAccess(3600, SECONDS); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.expireAfterAccess(3600, SECONDS)); + } + + @GwtIncompatible // Duration + public void testTimeToIdle_setTwice_duration() { + CacheBuilder builder = + CacheBuilder.newBuilder().expireAfterAccess(Duration.ofHours(1)); + assertThrows(IllegalStateException.class, () -> builder.expireAfterAccess(Duration.ofHours(1))); } - @SuppressWarnings("ReturnValueIgnored") public void testTimeToIdleAndToLive() { - CacheBuilder.newBuilder() - .expireAfterWrite(1, NANOSECONDS) - .expireAfterAccess(1, NANOSECONDS) - .build(identityLoader()); + LoadingCache unused = + CacheBuilder.newBuilder() + .expireAfterWrite(1, NANOSECONDS) + .expireAfterAccess(1, NANOSECONDS) + .build(identityLoader()); // well, it didn't blow up. } @GwtIncompatible // refreshAfterWrite public void testRefresh_zero() { CacheBuilder builder = CacheBuilder.newBuilder(); - try { - builder.refreshAfterWrite(0, SECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.refreshAfterWrite(0, SECONDS)); + } + + @GwtIncompatible // Duration + public void testRefresh_zero_duration() { + CacheBuilder builder = CacheBuilder.newBuilder(); + assertThrows(IllegalArgumentException.class, () -> builder.refreshAfterWrite(Duration.ZERO)); } @GwtIncompatible // refreshAfterWrite public void testRefresh_setTwice() { CacheBuilder builder = CacheBuilder.newBuilder().refreshAfterWrite(3600, SECONDS); - try { - // even to the same value is not allowed - builder.refreshAfterWrite(3600, SECONDS); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.refreshAfterWrite(3600, SECONDS)); + } + + @GwtIncompatible // Duration + public void testRefresh_setTwice_duration() { + CacheBuilder builder = + CacheBuilder.newBuilder().refreshAfterWrite(Duration.ofHours(1)); + assertThrows(IllegalStateException.class, () -> builder.refreshAfterWrite(Duration.ofHours(1))); } public void testTicker_setTwice() { Ticker testTicker = Ticker.systemTicker(); CacheBuilder builder = CacheBuilder.newBuilder().ticker(testTicker); - try { - // even to the same instance is not allowed - builder.ticker(testTicker); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.ticker(testTicker)); } public void testRemovalListener_setTwice() { RemovalListener testListener = nullRemovalListener(); CacheBuilder builder = CacheBuilder.newBuilder().removalListener(testListener); - try { - // even to the same instance is not allowed - builder = builder.removalListener(testListener); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> builder.removalListener(testListener)); } public void testValuesIsNotASet() { - assertFalse(CacheBuilder.newBuilder().build().asMap().values() instanceof Set); + assertThat(CacheBuilder.newBuilder().build().asMap().values() instanceof Set).isFalse(); } @GwtIncompatible // CacheTesting @@ -390,22 +337,21 @@ public void testNullCache() { CountingRemovalListener listener = countingRemovalListener(); LoadingCache nullCache = CacheBuilder.newBuilder().maximumSize(0).removalListener(listener).build(identityLoader()); - assertEquals(0, nullCache.size()); + assertThat(nullCache.size()).isEqualTo(0); Object key = new Object(); - assertSame(key, nullCache.getUnchecked(key)); - assertEquals(1, listener.getCount()); - assertEquals(0, nullCache.size()); + assertThat(nullCache.getUnchecked(key)).isSameInstanceAs(key); + assertThat(listener.getCount()).isEqualTo(1); + assertThat(nullCache.size()).isEqualTo(0); CacheTesting.checkEmpty(nullCache.asMap()); } @GwtIncompatible // QueuingRemovalListener - public void testRemovalNotification_clear() throws InterruptedException { // If a clear() happens while a computation is pending, we should not get a removal // notification. - final AtomicBoolean shouldWait = new AtomicBoolean(false); - final CountDownLatch computingLatch = new CountDownLatch(1); + AtomicBoolean shouldWait = new AtomicBoolean(false); + CountDownLatch computingLatch = new CountDownLatch(1); CacheLoader computingFunction = new CacheLoader() { @Override @@ -418,7 +364,7 @@ public String load(String key) throws InterruptedException { }; QueuingRemovalListener listener = queuingRemovalListener(); - final LoadingCache cache = + LoadingCache cache = CacheBuilder.newBuilder() .concurrencyLevel(1) .removalListener(listener) @@ -428,8 +374,8 @@ public String load(String key) throws InterruptedException { cache.getUnchecked("a"); shouldWait.set(true); - final CountDownLatch computationStarted = new CountDownLatch(1); - final CountDownLatch computationComplete = new CountDownLatch(1); + CountDownLatch computationStarted = new CountDownLatch(1); + CountDownLatch computationComplete = new CountDownLatch(1); new Thread( new Runnable() { @Override @@ -452,12 +398,12 @@ public void run() { // At this point, the listener should be holding the seed value (a -> a), and the map should // contain the computed value (b -> b), since the clear() happened before the computation // completed. - assertEquals(1, listener.size()); + assertThat(listener).hasSize(1); RemovalNotification notification = listener.remove(); - assertEquals("a", notification.getKey()); - assertEquals("a", notification.getValue()); - assertEquals(1, cache.size()); - assertEquals("b", cache.getUnchecked("b")); + assertThat(notification.getKey()).isEqualTo("a"); + assertThat(notification.getValue()).isEqualTo("a"); + assertThat(cache.size()).isEqualTo(1); + assertThat(cache.getUnchecked("b")).isEqualTo("b"); } // "Basher tests", where we throw a bunch of stuff at a LoadingCache and check basic invariants. @@ -471,6 +417,7 @@ public void run() { */ @GwtIncompatible // QueuingRemovalListener + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. public void testRemovalNotification_clear_basher() throws InterruptedException { // If a clear() happens close to the end of computation, one of two things should happen: // - computation ends first: the removal listener is called, and the cache does not contain the @@ -479,7 +426,7 @@ public void testRemovalNotification_clear_basher() throws InterruptedException { AtomicBoolean computationShouldWait = new AtomicBoolean(); CountDownLatch computationLatch = new CountDownLatch(1); QueuingRemovalListener listener = queuingRemovalListener(); - final LoadingCache cache = + LoadingCache cache = CacheBuilder.newBuilder() .removalListener(listener) .concurrencyLevel(20) @@ -498,11 +445,11 @@ public void testRemovalNotification_clear_basher() throws InterruptedException { } computationShouldWait.set(true); - final AtomicInteger computedCount = new AtomicInteger(); - ExecutorService threadPool = Executors.newFixedThreadPool(nThreads); - final CountDownLatch tasksFinished = new CountDownLatch(nTasks); + AtomicInteger computedCount = new AtomicInteger(); + ExecutorService threadPool = newFixedThreadPool(nThreads); + CountDownLatch tasksFinished = new CountDownLatch(nTasks); for (int i = 0; i < nTasks; i++) { - final String s = "a" + i; + String s = "a" + i; @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError = threadPool.submit( @@ -528,25 +475,27 @@ public void run() { // Check all of the removal notifications we received: they should have had correctly-associated // keys and values. (An earlier bug saw removal notifications for in-progress computations, // which had real keys with null values.) - Map removalNotifications = Maps.newHashMap(); + Map removalNotifications = new HashMap<>(); for (RemovalNotification notification : listener) { removalNotifications.put(notification.getKey(), notification.getValue()); - assertEquals( - "Unexpected key/value pair passed to removalListener", - notification.getKey(), - notification.getValue()); + assertWithMessage("Unexpected key/value pair passed to removalListener") + .that(notification.getValue()) + .isEqualTo(notification.getKey()); } // All of the seed values should have been visible, so we should have gotten removal // notifications for all of them. for (int i = 0; i < nSeededEntries; i++) { - assertEquals("b" + i, removalNotifications.get("b" + i)); + assertThat(removalNotifications.get("b" + i)).isEqualTo("b" + i); } // Each of the values added to the map should either still be there, or have seen a removal // notification. - assertEquals(expectedKeys, Sets.union(cache.asMap().keySet(), removalNotifications.keySet())); - assertTrue(Sets.intersection(cache.asMap().keySet(), removalNotifications.keySet()).isEmpty()); + assertThat(Sets.union(cache.asMap().keySet(), removalNotifications.keySet())) + .isEqualTo(expectedKeys); + assertThat(cache.asMap().keySet()).containsNoneIn(removalNotifications.keySet()); + threadPool.shutdown(); + threadPool.awaitTermination(300, SECONDS); } /** @@ -558,14 +507,15 @@ public void run() { public void testRemovalNotification_get_basher() throws InterruptedException { int nTasks = 1000; int nThreads = 100; - final int getsPerTask = 1000; - final int nUniqueKeys = 10000; - final Random random = new Random(); // Randoms.insecureRandom(); + int getsPerTask = 1000; + int nUniqueKeys = 10000; + Random random = new Random(); // Randoms.insecureRandom(); QueuingRemovalListener removalListener = queuingRemovalListener(); - final AtomicInteger computeCount = new AtomicInteger(); - final AtomicInteger exceptionCount = new AtomicInteger(); - final AtomicInteger computeNullCount = new AtomicInteger(); + AtomicInteger computeCount = new AtomicInteger(); + AtomicInteger exceptionCount = new AtomicInteger(); + AtomicInteger computeNullCount = new AtomicInteger(); + @SuppressWarnings("CacheLoaderNull") // test of handling of erroneous implementation CacheLoader countingIdentityLoader = new CacheLoader() { @Override @@ -587,7 +537,7 @@ public String load(String key) throws InterruptedException { } } }; - final LoadingCache cache = + LoadingCache cache = CacheBuilder.newBuilder() .recordStats() .concurrencyLevel(2) @@ -596,7 +546,7 @@ public String load(String key) throws InterruptedException { .maximumSize(5000) .build(countingIdentityLoader); - ExecutorService threadPool = Executors.newFixedThreadPool(nThreads); + ExecutorService threadPool = newFixedThreadPool(nThreads); for (int i = 0; i < nTasks; i++) { @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError = @@ -622,15 +572,17 @@ public void run() { // Verify that each received removal notification was valid for (RemovalNotification notification : removalListener) { - assertEquals("Invalid removal notification", notification.getKey(), notification.getValue()); + assertWithMessage("Invalid removal notification") + .that(notification.getValue()) + .isEqualTo(notification.getKey()); } CacheStats stats = cache.stats(); - assertEquals(removalListener.size(), stats.evictionCount()); - assertEquals(computeCount.get(), stats.loadSuccessCount()); - assertEquals(exceptionCount.get() + computeNullCount.get(), stats.loadExceptionCount()); + assertThat(stats.evictionCount()).isEqualTo(removalListener.size()); + assertThat(stats.loadSuccessCount()).isEqualTo(computeCount.get()); + assertThat(stats.loadExceptionCount()).isEqualTo(exceptionCount.get() + computeNullCount.get()); // each computed value is still in the cache, or was passed to the removal listener - assertEquals(computeCount.get(), cache.size() + removalListener.size()); + assertThat(cache.size() + removalListener.size()).isEqualTo(computeCount.get()); } @GwtIncompatible // NullPointerTester @@ -645,7 +597,7 @@ public void testSizingDefaults() { LoadingCache cache = CacheBuilder.newBuilder().build(identityLoader()); LocalCache map = CacheTesting.toLocalCache(cache); assertThat(map.segments).hasLength(4); // concurrency level - assertEquals(4, map.segments[0].table.length()); // capacity / conc level + assertThat(map.segments[0].table.length()).isEqualTo(4); // capacity / conc level } @GwtIncompatible // CountDownLatch @@ -658,6 +610,7 @@ static final class DelayingIdentityLoader extends CacheLoader { this.delayLatch = delayLatch; } + @CanIgnoreReturnValue // Sure, why not? @Override public T load(T key) throws InterruptedException { if (shouldWait.get()) { diff --git a/android/guava-tests/test/com/google/common/cache/CacheEvictionTest.java b/android/guava-tests/test/com/google/common/cache/CacheEvictionTest.java index ad5f844fe4e4..b05068d99aa0 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheEvictionTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheEvictionTest.java @@ -20,6 +20,7 @@ import static com.google.common.cache.TestingWeighers.intKeyWeigher; import static com.google.common.cache.TestingWeighers.intValueWeigher; import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.min; import static java.util.Arrays.asList; import com.google.common.cache.CacheTesting.Receiver; @@ -28,6 +29,7 @@ import java.util.List; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests relating to cache eviction: what does and doesn't count toward maximumSize, what happens @@ -35,6 +37,7 @@ * * @author mike nonemacher */ +@NullUnmarked public class CacheEvictionTest extends TestCase { static final int MAX_SIZE = 100; @@ -42,7 +45,7 @@ public void testEviction_setMaxSegmentSize() { IdentityLoader loader = identityLoader(); for (int i = 1; i < 1000; i++) { LoadingCache cache = CacheBuilder.newBuilder().maximumSize(i).build(loader); - assertEquals(i, CacheTesting.getTotalSegmentSize(cache)); + assertThat(CacheTesting.getTotalSegmentSize(cache)).isEqualTo(i); } } @@ -51,7 +54,7 @@ public void testEviction_setMaxSegmentWeight() { for (int i = 1; i < 1000; i++) { LoadingCache cache = CacheBuilder.newBuilder().maximumWeight(i).weigher(constantWeigher(1)).build(loader); - assertEquals(i, CacheTesting.getTotalSegmentSize(cache)); + assertThat(CacheTesting.getTotalSegmentSize(cache)).isEqualTo(i); } } @@ -61,10 +64,10 @@ public void testEviction_maxSizeOneSegment() { CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(MAX_SIZE).build(loader); for (int i = 0; i < 2 * MAX_SIZE; i++) { cache.getUnchecked(i); - assertEquals(Math.min(i + 1, MAX_SIZE), cache.size()); + assertThat(cache.size()).isEqualTo(min(i + 1, MAX_SIZE)); } - assertEquals(MAX_SIZE, cache.size()); + assertThat(cache.size()).isEqualTo(MAX_SIZE); CacheTesting.checkValidState(cache); } @@ -78,10 +81,10 @@ public void testEviction_maxWeightOneSegment() { .build(loader); for (int i = 0; i < 2 * MAX_SIZE; i++) { cache.getUnchecked(i); - assertEquals(Math.min(i + 1, MAX_SIZE), cache.size()); + assertThat(cache.size()).isEqualTo(min(i + 1, MAX_SIZE)); } - assertEquals(MAX_SIZE, cache.size()); + assertThat(cache.size()).isEqualTo(MAX_SIZE); CacheTesting.checkValidState(cache); } @@ -95,13 +98,13 @@ public void testEviction_maxSize() { .build(loader); for (int i = 0; i < 2 * MAX_SIZE; i++) { cache.getUnchecked(i); - assertTrue(cache.size() <= MAX_SIZE); + assertThat(cache.size()).isAtMost(MAX_SIZE); } - assertEquals(MAX_SIZE, CacheTesting.accessQueueSize(cache)); - assertEquals(MAX_SIZE, cache.size()); + assertThat(CacheTesting.accessQueueSize(cache)).isEqualTo(MAX_SIZE); + assertThat(cache.size()).isEqualTo(MAX_SIZE); CacheTesting.processPendingNotifications(cache); - assertEquals(MAX_SIZE, removalListener.getCount()); + assertThat(removalListener.getCount()).isEqualTo(MAX_SIZE); CacheTesting.checkValidState(cache); } @@ -116,13 +119,13 @@ public void testEviction_maxWeight() { .build(loader); for (int i = 0; i < 2 * MAX_SIZE; i++) { cache.getUnchecked(i); - assertTrue(cache.size() <= MAX_SIZE); + assertThat(cache.size()).isAtMost(MAX_SIZE); } - assertEquals(MAX_SIZE, CacheTesting.accessQueueSize(cache)); - assertEquals(MAX_SIZE, cache.size()); + assertThat(CacheTesting.accessQueueSize(cache)).isEqualTo(MAX_SIZE); + assertThat(cache.size()).isEqualTo(MAX_SIZE); CacheTesting.processPendingNotifications(cache); - assertEquals(MAX_SIZE, removalListener.getCount()); + assertThat(removalListener.getCount()).isEqualTo(MAX_SIZE); CacheTesting.checkValidState(cache); } @@ -252,12 +255,12 @@ public void testEviction_overflow() { cache.getUnchecked(objectWithHash(0)); cache.getUnchecked(objectWithHash(0)); CacheTesting.processPendingNotifications(cache); - assertEquals(1, removalListener.getCount()); + assertThat(removalListener.getCount()).isEqualTo(1); } public void testUpdateRecency_onGet() { IdentityLoader loader = identityLoader(); - final LoadingCache cache = + LoadingCache cache = CacheBuilder.newBuilder().maximumSize(MAX_SIZE).build(loader); CacheTesting.checkRecency( cache, @@ -272,7 +275,7 @@ public void accept(ReferenceEntry entry) { public void testUpdateRecency_onInvalidate() { IdentityLoader loader = identityLoader(); - final LoadingCache cache = + LoadingCache cache = CacheBuilder.newBuilder().maximumSize(MAX_SIZE).concurrencyLevel(1).build(loader); CacheTesting.checkRecency( cache, @@ -415,7 +418,7 @@ private static void getAll(LoadingCache cache, List k } } - private Object objectWithHash(final int hash) { + private Object objectWithHash(int hash) { return new Object() { @Override public int hashCode() { diff --git a/android/guava-tests/test/com/google/common/cache/CacheExpirationTest.java b/android/guava-tests/test/com/google/common/cache/CacheExpirationTest.java index c1416bd7d815..75095b28e0b6 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheExpirationTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheExpirationTest.java @@ -17,8 +17,10 @@ import static com.google.common.cache.TestingCacheLoaders.identityLoader; import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; import com.google.common.cache.TestingCacheLoaders.IdentityLoader; import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener; @@ -29,9 +31,9 @@ import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests relating to cache expiration: make sure entries expire at the right times, make sure @@ -40,6 +42,7 @@ * @author mike nonemacher */ @SuppressWarnings("deprecation") // tests of deprecated method +@NullUnmarked public class CacheExpirationTest extends TestCase { private static final long EXPIRING_TIME = 1000; @@ -79,23 +82,29 @@ private void checkExpiration( CountingRemovalListener removalListener) { for (int i = 0; i < 10; i++) { - assertEquals(Integer.valueOf(VALUE_PREFIX + i), cache.getUnchecked(KEY_PREFIX + i)); + assertThat(cache.getUnchecked(KEY_PREFIX + i)).isEqualTo(VALUE_PREFIX + i); } for (int i = 0; i < 10; i++) { loader.reset(); - assertEquals(Integer.valueOf(VALUE_PREFIX + i), cache.getUnchecked(KEY_PREFIX + i)); - assertFalse("Creator should not have been called @#" + i, loader.wasCalled()); + assertThat(cache.getUnchecked(KEY_PREFIX + i)).isEqualTo(VALUE_PREFIX + i); + assertWithMessage("Creator should not have been called @#%s", i) + .that(loader.wasCalled()) + .isFalse(); } CacheTesting.expireEntries((LoadingCache) cache, EXPIRING_TIME, ticker); - assertEquals("Map must be empty by now", 0, cache.size()); - assertEquals("Eviction notifications must be received", 10, removalListener.getCount()); + assertWithMessage("Map must be empty by now").that(cache.size()).isEqualTo(0); + assertWithMessage("Eviction notifications must be received") + .that(removalListener.getCount()) + .isEqualTo(10); CacheTesting.expireEntries((LoadingCache) cache, EXPIRING_TIME, ticker); // ensure that no new notifications are sent - assertEquals("Eviction notifications must be received", 10, removalListener.getCount()); + assertWithMessage("Eviction notifications must be received") + .that(removalListener.getCount()) + .isEqualTo(10); } public void testExpiringGet_expireAfterWrite() { @@ -131,13 +140,15 @@ private void runExpirationTest( CountingRemovalListener removalListener) { for (int i = 0; i < 10; i++) { - assertEquals(Integer.valueOf(VALUE_PREFIX + i), cache.getUnchecked(KEY_PREFIX + i)); + assertThat(cache.getUnchecked(KEY_PREFIX + i)).isEqualTo(VALUE_PREFIX + i); } for (int i = 0; i < 10; i++) { loader.reset(); - assertEquals(Integer.valueOf(VALUE_PREFIX + i), cache.getUnchecked(KEY_PREFIX + i)); - assertFalse("Loader should NOT have been called @#" + i, loader.wasCalled()); + assertThat(cache.getUnchecked(KEY_PREFIX + i)).isEqualTo(VALUE_PREFIX + i); + assertWithMessage("Loader should NOT have been called @#%s", i) + .that(loader.wasCalled()) + .isFalse(); } // wait for entries to expire, but don't call expireEntries @@ -147,38 +158,42 @@ private void runExpirationTest( cache.getUnchecked(KEY_PREFIX + 11); // collections views shouldn't expose expired entries - assertEquals(1, Iterators.size(cache.asMap().entrySet().iterator())); - assertEquals(1, Iterators.size(cache.asMap().keySet().iterator())); - assertEquals(1, Iterators.size(cache.asMap().values().iterator())); + assertThat(Iterators.size(cache.asMap().entrySet().iterator())).isEqualTo(1); + assertThat(Iterators.size(cache.asMap().keySet().iterator())).isEqualTo(1); + assertThat(Iterators.size(cache.asMap().values().iterator())).isEqualTo(1); CacheTesting.expireEntries((LoadingCache) cache, EXPIRING_TIME, ticker); for (int i = 0; i < 11; i++) { - assertFalse(cache.asMap().containsKey(KEY_PREFIX + i)); + assertThat(cache.asMap().containsKey(KEY_PREFIX + i)).isFalse(); } - assertEquals(11, removalListener.getCount()); + assertThat(removalListener.getCount()).isEqualTo(11); for (int i = 0; i < 10; i++) { - assertFalse(cache.asMap().containsKey(KEY_PREFIX + i)); + assertThat(cache.asMap().containsKey(KEY_PREFIX + i)).isFalse(); loader.reset(); - assertEquals(Integer.valueOf(VALUE_PREFIX + i), cache.getUnchecked(KEY_PREFIX + i)); - assertTrue("Creator should have been called @#" + i, loader.wasCalled()); + assertThat(cache.getUnchecked(KEY_PREFIX + i)).isEqualTo(VALUE_PREFIX + i); + assertWithMessage("Loader should have been called @#%s", i).that(loader.wasCalled()).isTrue(); } // expire new values we just created CacheTesting.expireEntries((LoadingCache) cache, EXPIRING_TIME, ticker); - assertEquals("Eviction notifications must be received", 21, removalListener.getCount()); + assertWithMessage("Eviction notifications must be received") + .that(removalListener.getCount()) + .isEqualTo(21); CacheTesting.expireEntries((LoadingCache) cache, EXPIRING_TIME, ticker); // ensure that no new notifications are sent - assertEquals("Eviction notifications must be received", 21, removalListener.getCount()); + assertWithMessage("Eviction notifications must be received") + .that(removalListener.getCount()) + .isEqualTo(21); } public void testRemovalListener_expireAfterWrite() { FakeTicker ticker = new FakeTicker(); - final AtomicInteger evictionCount = new AtomicInteger(); - final AtomicInteger applyCount = new AtomicInteger(); - final AtomicInteger totalSum = new AtomicInteger(); + AtomicInteger evictionCount = new AtomicInteger(); + AtomicInteger applyCount = new AtomicInteger(); + AtomicInteger totalSum = new AtomicInteger(); RemovalListener removalListener = new RemovalListener() { @@ -213,9 +228,9 @@ public AtomicInteger load(Integer key) { ticker.advance(1, MILLISECONDS); } - assertEquals(evictionCount.get() + 1, applyCount.get()); + assertThat(applyCount.get()).isEqualTo(evictionCount.get() + 1); int remaining = cache.getUnchecked(10).get(); - assertEquals(100, totalSum.get() + remaining); + assertThat(totalSum.get() + remaining).isEqualTo(100); } public void testRemovalScheduler_expireAfterWrite() { @@ -339,7 +354,7 @@ public void testExpirationOrder_write() throws ExecutionException { assertThat(keySet).containsExactly(2, 3, 4, 5, 6, 7, 8, 9, 0); // get(K, Callable) doesn't stop 2 from expiring - cache.get(2, Callables.returning(-2)); + Integer unused = cache.get(2, Callables.returning(-2)); CacheTesting.drainRecencyQueues(cache); ticker.advance(1, MILLISECONDS); assertThat(keySet).containsExactly(3, 4, 5, 6, 7, 8, 9, 0); @@ -403,7 +418,7 @@ public void testExpirationOrder_writeAccess() throws ExecutionException { // get(K, Callable) fails to save 8, replace saves 6 cache.asMap().replace(6, -6); - cache.get(8, Callables.returning(-8)); + Integer unused = cache.get(8, Callables.returning(-8)); CacheTesting.drainRecencyQueues(cache); ticker.advance(1, MILLISECONDS); assertThat(keySet).containsExactly(3, 6); @@ -415,12 +430,12 @@ public void testExpiration_invalidateAll() { TestingRemovalListeners.queuingRemovalListener(); Cache cache = CacheBuilder.newBuilder() - .expireAfterAccess(1, TimeUnit.MINUTES) + .expireAfterAccess(1, MINUTES) .removalListener(listener) .ticker(ticker) .build(); cache.put(1, 1); - ticker.advance(10, TimeUnit.MINUTES); + ticker.advance(10, MINUTES); cache.invalidateAll(); assertThat(listener.poll().getCause()).isEqualTo(RemovalCause.EXPIRED); @@ -433,46 +448,50 @@ private void runRemovalScheduler( FakeTicker ticker, String keyPrefix, long ttl) { + loader.setKeyPrefix(keyPrefix); int shift1 = 10 + VALUE_PREFIX; loader.setValuePrefix(shift1); // fill with initial data for (int i = 0; i < 10; i++) { - assertEquals(Integer.valueOf(i + shift1), cache.getUnchecked(keyPrefix + i)); + assertThat(cache.getUnchecked(keyPrefix + i)).isEqualTo(i + shift1); } - assertEquals(10, CacheTesting.expirationQueueSize(cache)); - assertEquals(0, removalListener.getCount()); + assertThat(CacheTesting.expirationQueueSize(cache)).isEqualTo(10); + assertThat(removalListener.getCount()).isEqualTo(0); // wait, so that entries have just 10 ms to live ticker.advance(ttl * 2 / 3, MILLISECONDS); - assertEquals(10, CacheTesting.expirationQueueSize(cache)); - assertEquals(0, removalListener.getCount()); + assertThat(CacheTesting.expirationQueueSize(cache)).isEqualTo(10); + assertThat(removalListener.getCount()).isEqualTo(0); int shift2 = shift1 + 10; loader.setValuePrefix(shift2); // fill with new data - has to live for 20 ms more for (int i = 0; i < 10; i++) { cache.invalidate(keyPrefix + i); - assertEquals( - "key: " + keyPrefix + i, Integer.valueOf(i + shift2), cache.getUnchecked(keyPrefix + i)); + assertWithMessage("key: %s%s", keyPrefix, i) + .that(cache.getUnchecked(keyPrefix + i)) + .isEqualTo(i + shift2); } - assertEquals(10, CacheTesting.expirationQueueSize(cache)); - assertEquals(10, removalListener.getCount()); // these are the invalidated ones + assertThat(CacheTesting.expirationQueueSize(cache)).isEqualTo(10); + assertThat(removalListener.getCount()).isEqualTo(10); // these are the invalidated ones // old timeouts must expire after this wait ticker.advance(ttl * 2 / 3, MILLISECONDS); - assertEquals(10, CacheTesting.expirationQueueSize(cache)); - assertEquals(10, removalListener.getCount()); + assertThat(CacheTesting.expirationQueueSize(cache)).isEqualTo(10); + assertThat(removalListener.getCount()).isEqualTo(10); // check that new values are still there - they still have 10 ms to live for (int i = 0; i < 10; i++) { loader.reset(); - assertEquals(Integer.valueOf(i + shift2), cache.getUnchecked(keyPrefix + i)); - assertFalse("Creator should NOT have been called @#" + i, loader.wasCalled()); + assertThat(cache.getUnchecked(keyPrefix + i)).isEqualTo(i + shift2); + assertWithMessage("Creator should NOT have been called @#%s", i) + .that(loader.wasCalled()) + .isFalse(); } - assertEquals(10, removalListener.getCount()); + assertThat(removalListener.getCount()).isEqualTo(10); } private static void getAll(LoadingCache cache, List keys) { @@ -486,21 +505,21 @@ private static class WatchedCreatorLoader extends CacheLoader { String keyPrefix = KEY_PREFIX; int valuePrefix = VALUE_PREFIX; - public WatchedCreatorLoader() {} + WatchedCreatorLoader() {} - public void reset() { + void reset() { wasCalled = false; } - public boolean wasCalled() { + boolean wasCalled() { return wasCalled; } - public void setKeyPrefix(String keyPrefix) { + void setKeyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; } - public void setValuePrefix(int valuePrefix) { + void setValuePrefix(int valuePrefix) { this.valuePrefix = valuePrefix; } diff --git a/android/guava-tests/test/com/google/common/cache/CacheLoaderTest.java b/android/guava-tests/test/com/google/common/cache/CacheLoaderTest.java index e078dddd7736..8125ff117a6a 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheLoaderTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheLoaderTest.java @@ -16,27 +16,31 @@ package com.google.common.cache; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.Futures.immediateFuture; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Queues; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import java.util.ArrayDeque; import java.util.Deque; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link CacheLoader}. * * @author Charles Fry */ +@NullUnmarked public class CacheLoaderTest extends TestCase { private static class QueuingExecutor implements Executor { - private final Deque tasks = Queues.newArrayDeque(); + private final Deque tasks = new ArrayDeque<>(); @Override public void execute(Runnable task) { @@ -49,9 +53,9 @@ private void runNext() { } public void testAsyncReload() throws Exception { - final AtomicInteger loadCount = new AtomicInteger(); - final AtomicInteger reloadCount = new AtomicInteger(); - final AtomicInteger loadAllCount = new AtomicInteger(); + AtomicInteger loadCount = new AtomicInteger(); + AtomicInteger reloadCount = new AtomicInteger(); + AtomicInteger loadAllCount = new AtomicInteger(); CacheLoader baseLoader = new CacheLoader() { @@ -64,7 +68,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { reloadCount.incrementAndGet(); - return Futures.immediateFuture(new Object()); + return immediateFuture(new Object()); } @Override @@ -74,32 +78,32 @@ public Map loadAll(Iterable keys) { } }; - assertEquals(0, loadCount.get()); - assertEquals(0, reloadCount.get()); - assertEquals(0, loadAllCount.get()); + assertThat(loadCount.get()).isEqualTo(0); + assertThat(reloadCount.get()).isEqualTo(0); + assertThat(loadAllCount.get()).isEqualTo(0); - baseLoader.load(new Object()); + Object unused1 = baseLoader.load(new Object()); @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError = baseLoader.reload(new Object(), new Object()); - baseLoader.loadAll(ImmutableList.of(new Object())); - assertEquals(1, loadCount.get()); - assertEquals(1, reloadCount.get()); - assertEquals(1, loadAllCount.get()); + Map unused2 = baseLoader.loadAll(ImmutableList.of(new Object())); + assertThat(loadCount.get()).isEqualTo(1); + assertThat(reloadCount.get()).isEqualTo(1); + assertThat(loadAllCount.get()).isEqualTo(1); QueuingExecutor executor = new QueuingExecutor(); CacheLoader asyncReloader = CacheLoader.asyncReloading(baseLoader, executor); - asyncReloader.load(new Object()); + Object unused3 = asyncReloader.load(new Object()); @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError1 = asyncReloader.reload(new Object(), new Object()); - asyncReloader.loadAll(ImmutableList.of(new Object())); - assertEquals(2, loadCount.get()); - assertEquals(1, reloadCount.get()); - assertEquals(2, loadAllCount.get()); + Map unused4 = asyncReloader.loadAll(ImmutableList.of(new Object())); + assertThat(loadCount.get()).isEqualTo(2); + assertThat(reloadCount.get()).isEqualTo(1); + assertThat(loadAllCount.get()).isEqualTo(2); executor.runNext(); - assertEquals(2, loadCount.get()); - assertEquals(2, reloadCount.get()); - assertEquals(2, loadAllCount.get()); + assertThat(loadCount.get()).isEqualTo(2); + assertThat(reloadCount.get()).isEqualTo(2); + assertThat(loadAllCount.get()).isEqualTo(2); } } diff --git a/android/guava-tests/test/com/google/common/cache/CacheLoadingTest.java b/android/guava-tests/test/com/google/common/cache/CacheLoadingTest.java index f5ea54603a1e..7dc674ae919c 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheLoadingTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheLoadingTest.java @@ -21,27 +21,29 @@ import static com.google.common.cache.TestingCacheLoaders.identityLoader; import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; import static com.google.common.truth.Truth.assertThat; -import static java.lang.Thread.currentThread; +import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.util.concurrent.Futures.immediateFailedFuture; +import static com.google.common.util.concurrent.Futures.immediateFuture; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.cache.TestingCacheLoaders.CountingLoader; import com.google.common.cache.TestingCacheLoaders.IdentityLoader; import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.testing.FakeTicker; import com.google.common.testing.TestLogHandler; import com.google.common.util.concurrent.Callables; import com.google.common.util.concurrent.ExecutionError; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.UncheckedExecutionException; import java.io.IOException; import java.lang.ref.WeakReference; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -49,17 +51,18 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.logging.LogRecord; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests relating to cache loading: concurrent loading, exceptions during loading, etc. * * @author mike nonemacher */ +@NullUnmarked public class CacheLoadingTest extends TestCase { TestLogHandler logHandler; @@ -74,13 +77,13 @@ public void setUp() throws Exception { public void tearDown() throws Exception { super.tearDown(); // TODO(cpovirk): run tests in other thread instead of messing with main thread interrupt status - currentThread().interrupted(); + Thread.interrupted(); LocalCache.logger.removeHandler(logHandler); } private Throwable popLoggedThrowable() { List logRecords = logHandler.getStoredLogRecords(); - assertEquals(1, logRecords.size()); + assertThat(logRecords).hasSize(1); LogRecord logRecord = logRecords.get(0); logHandler.clear(); return logRecord.getThrown(); @@ -102,64 +105,64 @@ public void testLoad() throws ExecutionException { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(identityLoader()); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); Object key = new Object(); - assertSame(key, cache.get(key)); + assertThat(cache.get(key)).isSameInstanceAs(key); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); key = new Object(); - assertSame(key, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(key); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); key = new Object(); cache.refresh(key); checkNothingLogged(); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(3, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(3); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(key, cache.get(key)); + assertThat(cache.get(key)).isSameInstanceAs(key); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(3, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(3); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); Object value = new Object(); // callable is not called - assertSame(key, cache.get(key, throwing(new Exception()))); + assertThat(cache.get(key, throwing(new Exception()))).isSameInstanceAs(key); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(3, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(3); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(2); key = new Object(); - assertSame(value, cache.get(key, Callables.returning(value))); + assertThat(cache.get(key, Callables.returning(value))).isSameInstanceAs(value); stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(4, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(4); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(2); } public void testReload() throws ExecutionException { - final Object one = new Object(); - final Object two = new Object(); + Object one = new Object(); + Object two = new Object(); CacheLoader loader = new CacheLoader() { @Override @@ -169,44 +172,44 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(two); + return immediateFuture(two); } }; LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkNothingLogged(); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(two, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(two); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); } public void testRefresh() { - final Object one = new Object(); - final Object two = new Object(); + Object one = new Object(); + Object two = new Object(); FakeTicker ticker = new FakeTicker(); CacheLoader loader = new CacheLoader() { @@ -217,7 +220,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(two); + return immediateFuture(two); } }; @@ -229,46 +232,46 @@ public ListenableFuture reload(Object key, Object oldValue) { .build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); ticker.advance(1, MILLISECONDS); - assertSame(two, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(two); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(2); ticker.advance(1, MILLISECONDS); - assertSame(two, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(two); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(3); } public void testRefresh_getIfPresent() { - final Object one = new Object(); - final Object two = new Object(); + Object one = new Object(); + Object two = new Object(); FakeTicker ticker = new FakeTicker(); CacheLoader loader = new CacheLoader() { @@ -279,7 +282,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(two); + return immediateFuture(two); } }; @@ -291,41 +294,41 @@ public ListenableFuture reload(Object key, Object oldValue) { .build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getIfPresent(key)); + assertThat(cache.getIfPresent(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); ticker.advance(1, MILLISECONDS); - assertSame(two, cache.getIfPresent(key)); + assertThat(cache.getIfPresent(key)).isSameInstanceAs(two); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(2); ticker.advance(1, MILLISECONDS); - assertSame(two, cache.getIfPresent(key)); + assertThat(cache.getIfPresent(key)).isSameInstanceAs(two); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(3); } public void testBulkLoad_default() throws ExecutionException { @@ -334,45 +337,45 @@ public void testBulkLoad_default() throws ExecutionException { .recordStats() .build(TestingCacheLoaders.identityLoader()); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertEquals(ImmutableMap.of(), cache.getAll(ImmutableList.of())); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(cache.getAll(ImmutableList.of())).isEmpty(); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertEquals(ImmutableMap.of(1, 1), cache.getAll(asList(1))); + assertThat(cache.getAll(asList(1))).containsExactly(1, 1); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertEquals(ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4), cache.getAll(asList(1, 2, 3, 4))); + assertThat(cache.getAll(asList(1, 2, 3, 4))).containsExactly(1, 1, 2, 2, 3, 3, 4, 4); stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(4, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(4); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); - assertEquals(ImmutableMap.of(2, 2, 3, 3), cache.getAll(asList(2, 3))); + assertThat(cache.getAll(asList(2, 3))).containsExactly(2, 2, 3, 3); stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(4, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(4); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(3); // duplicate keys are ignored, and don't impact stats - assertEquals(ImmutableMap.of(4, 4, 5, 5), cache.getAll(asList(4, 5))); + assertThat(cache.getAll(asList(4, 5))).containsExactly(4, 4, 5, 5); stats = cache.stats(); - assertEquals(5, stats.missCount()); - assertEquals(5, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(4, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(5); + assertThat(stats.loadSuccessCount()).isEqualTo(5); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(4); } public void testBulkLoad_loadAll() throws ExecutionException { @@ -380,45 +383,45 @@ public void testBulkLoad_loadAll() throws ExecutionException { CacheLoader loader = bulkLoader(backingLoader); LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertEquals(ImmutableMap.of(), cache.getAll(ImmutableList.of())); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(cache.getAll(ImmutableList.of())).containsExactly(); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertEquals(ImmutableMap.of(1, 1), cache.getAll(asList(1))); + assertThat(cache.getAll(asList(1))).containsExactly(1, 1); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertEquals(ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4), cache.getAll(asList(1, 2, 3, 4))); + assertThat(cache.getAll(asList(1, 2, 3, 4))).containsExactly(1, 1, 2, 2, 3, 3, 4, 4); stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); - assertEquals(ImmutableMap.of(2, 2, 3, 3), cache.getAll(asList(2, 3))); + assertThat(cache.getAll(asList(2, 3))).containsExactly(2, 2, 3, 3); stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(2, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(2); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(3); // duplicate keys are ignored, and don't impact stats - assertEquals(ImmutableMap.of(4, 4, 5, 5), cache.getAll(asList(4, 5))); + assertThat(cache.getAll(asList(4, 5))).containsExactly(4, 4, 5, 5); stats = cache.stats(); - assertEquals(5, stats.missCount()); - assertEquals(3, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(4, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(5); + assertThat(stats.loadSuccessCount()).isEqualTo(3); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(4); } public void testBulkLoad_extra() throws ExecutionException { @@ -431,7 +434,7 @@ public Object load(Object key) throws Exception { @Override public Map loadAll(Iterable keys) throws Exception { - Map result = Maps.newHashMap(); + Map result = new HashMap<>(); for (Object key : keys) { Object value = new Object(); result.put(key, value); @@ -449,16 +452,16 @@ public Map loadAll(Iterable keys) throws Exception { for (Entry entry : result.entrySet()) { Object key = entry.getKey(); Object value = entry.getValue(); - assertSame(value, result.get(key)); - assertNull(result.get(value)); - assertSame(value, cache.asMap().get(key)); - assertSame(key, cache.asMap().get(value)); + assertThat(result.get(key)).isSameInstanceAs(value); + assertThat(result.get(value)).isNull(); + assertThat(cache.asMap().get(key)).isSameInstanceAs(value); + assertThat(cache.asMap().get(value)).isSameInstanceAs(key); } } public void testBulkLoad_clobber() throws ExecutionException { - final Object extraKey = new Object(); - final Object extraValue = new Object(); + Object extraKey = new Object(); + Object extraValue = new Object(); CacheLoader loader = new CacheLoader() { @Override @@ -468,7 +471,7 @@ public Object load(Object key) throws Exception { @Override public Map loadAll(Iterable keys) throws Exception { - Map result = Maps.newHashMap(); + Map result = new HashMap<>(); for (Object key : keys) { Object value = new Object(); result.put(key, value); @@ -479,7 +482,7 @@ public Map loadAll(Iterable keys) throws Exception { }; LoadingCache cache = CacheBuilder.newBuilder().build(loader); cache.asMap().put(extraKey, extraKey); - assertSame(extraKey, cache.asMap().get(extraKey)); + assertThat(cache.asMap().get(extraKey)).isSameInstanceAs(extraKey); Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()}; Map result = cache.getAll(asList(lookupKeys)); @@ -487,16 +490,16 @@ public Map loadAll(Iterable keys) throws Exception { for (Entry entry : result.entrySet()) { Object key = entry.getKey(); Object value = entry.getValue(); - assertSame(value, result.get(key)); - assertSame(value, cache.asMap().get(key)); + assertThat(result.get(key)).isSameInstanceAs(value); + assertThat(cache.asMap().get(key)).isSameInstanceAs(value); } - assertNull(result.get(extraKey)); - assertSame(extraValue, cache.asMap().get(extraKey)); + assertThat(result.get(extraKey)).isNull(); + assertThat(cache.asMap().get(extraKey)).isSameInstanceAs(extraValue); } public void testBulkLoad_clobberNullValue() throws ExecutionException { - final Object extraKey = new Object(); - final Object extraValue = new Object(); + Object extraKey = new Object(); + Object extraValue = new Object(); CacheLoader loader = new CacheLoader() { @Override @@ -506,7 +509,7 @@ public Object load(Object key) throws Exception { @Override public Map loadAll(Iterable keys) throws Exception { - Map result = Maps.newHashMap(); + Map result = new HashMap<>(); for (Object key : keys) { Object value = new Object(); result.put(key, value); @@ -518,25 +521,21 @@ public Map loadAll(Iterable keys) throws Exception { }; LoadingCache cache = CacheBuilder.newBuilder().build(loader); cache.asMap().put(extraKey, extraKey); - assertSame(extraKey, cache.asMap().get(extraKey)); + assertThat(cache.asMap().get(extraKey)).isSameInstanceAs(extraKey); Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()}; - try { - cache.getAll(asList(lookupKeys)); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(lookupKeys))); for (Object key : lookupKeys) { - assertTrue(cache.asMap().containsKey(key)); + assertThat(cache.asMap().containsKey(key)).isTrue(); } - assertSame(extraValue, cache.asMap().get(extraKey)); - assertFalse(cache.asMap().containsKey(extraValue)); + assertThat(cache.asMap().get(extraKey)).isSameInstanceAs(extraValue); + assertThat(cache.asMap().containsKey(extraValue)).isFalse(); } public void testBulkLoad_clobberNullKey() throws ExecutionException { - final Object extraKey = new Object(); - final Object extraValue = new Object(); + Object extraKey = new Object(); + Object extraValue = new Object(); CacheLoader loader = new CacheLoader() { @Override @@ -546,7 +545,7 @@ public Object load(Object key) throws Exception { @Override public Map loadAll(Iterable keys) throws Exception { - Map result = Maps.newHashMap(); + Map result = new HashMap<>(); for (Object key : keys) { Object value = new Object(); result.put(key, value); @@ -558,25 +557,21 @@ public Map loadAll(Iterable keys) throws Exception { }; LoadingCache cache = CacheBuilder.newBuilder().build(loader); cache.asMap().put(extraKey, extraKey); - assertSame(extraKey, cache.asMap().get(extraKey)); + assertThat(cache.asMap().get(extraKey)).isSameInstanceAs(extraKey); Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()}; - try { - cache.getAll(asList(lookupKeys)); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(lookupKeys))); for (Object key : lookupKeys) { - assertTrue(cache.asMap().containsKey(key)); + assertThat(cache.asMap().containsKey(key)).isTrue(); } - assertSame(extraValue, cache.asMap().get(extraKey)); - assertFalse(cache.asMap().containsValue(extraKey)); + assertThat(cache.asMap().get(extraKey)).isSameInstanceAs(extraValue); + assertThat(cache.asMap().containsValue(extraKey)).isFalse(); } public void testBulkLoad_partial() throws ExecutionException { - final Object extraKey = new Object(); - final Object extraValue = new Object(); + Object extraKey = new Object(); + Object extraValue = new Object(); CacheLoader loader = new CacheLoader() { @Override @@ -586,7 +581,7 @@ public Object load(Object key) throws Exception { @Override public Map loadAll(Iterable keys) throws Exception { - Map result = Maps.newHashMap(); + Map result = new HashMap<>(); // ignore request keys result.put(extraKey, extraValue); return result; @@ -595,78 +590,59 @@ public Map loadAll(Iterable keys) throws Exception { LoadingCache cache = CacheBuilder.newBuilder().build(loader); Object[] lookupKeys = new Object[] {new Object(), new Object(), new Object()}; - try { - cache.getAll(asList(lookupKeys)); - fail(); - } catch (InvalidCacheLoadException expected) { - } - assertSame(extraValue, cache.asMap().get(extraKey)); + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(lookupKeys))); + assertThat(cache.asMap().get(extraKey)).isSameInstanceAs(extraValue); } public void testLoadNull() throws ExecutionException { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(constantLoader(null)); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.get(new Object()); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + assertThrows(InvalidCacheLoadException.class, () -> cache.get(new Object())); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - try { - cache.getUnchecked(new Object()); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThrows(InvalidCacheLoadException.class, () -> cache.getUnchecked(new Object())); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(new Object()); checkLoggedInvalidLoad(); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(3, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(3); + assertThat(stats.hitCount()).isEqualTo(0); - try { - cache.get(new Object(), Callables.returning(null)); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThrows( + InvalidCacheLoadException.class, () -> cache.get(new Object(), Callables.returning(null))); stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(4, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(4); + assertThat(stats.hitCount()).isEqualTo(0); - try { - cache.getAll(asList(new Object())); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(new Object()))); stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(5, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(5); + assertThat(stats.hitCount()).isEqualTo(0); } public void testReloadNull() throws ExecutionException { - final Object one = new Object(); + Object one = new Object(); CacheLoader loader = new CacheLoader() { @Override @@ -683,36 +659,36 @@ public ListenableFuture reload(Object key, Object oldValue) { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkLoggedInvalidLoad(); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(1); } public void testReloadNullFuture() throws ExecutionException { - final Object one = new Object(); + Object one = new Object(); CacheLoader loader = new CacheLoader() { @Override @@ -722,43 +698,43 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(null); + return immediateFuture(null); } }; LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkLoggedInvalidLoad(); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(1); } public void testRefreshNull() { - final Object one = new Object(); + Object one = new Object(); FakeTicker ticker = new FakeTicker(); CacheLoader loader = new CacheLoader() { @@ -769,7 +745,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFuture(null); + return immediateFuture(null); } }; @@ -781,63 +757,59 @@ public ListenableFuture reload(Object key, Object oldValue) { .build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); // refreshed stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(2); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(3); } public void testBulkLoadNull() throws ExecutionException { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(bulkLoader(constantLoader(null))); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(new Object()))); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); } public void testBulkLoadNullMap() throws ExecutionException { @@ -858,21 +830,17 @@ public Map loadAll(Iterable keys) { }); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (InvalidCacheLoadException expected) { - } + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + assertThrows(InvalidCacheLoadException.class, () -> cache.getAll(asList(new Object()))); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); } public void testLoadError() throws ExecutionException { @@ -880,79 +848,67 @@ public void testLoadError() throws ExecutionException { CacheLoader loader = errorLoader(e); LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.get(new Object()); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + ExecutionError expected = assertThrows(ExecutionError.class, () -> cache.get(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - try { - cache.getUnchecked(new Object()); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + expected = assertThrows(ExecutionError.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(new Object()); checkLoggedCause(e); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(3, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - final Error callableError = new Error(); - try { - cache.get( - new Object(), - new Callable() { - @Override - public Object call() { - throw callableError; - } - }); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(callableError); - } - stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(4, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(5, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(3); + assertThat(stats.hitCount()).isEqualTo(0); + + Error callableError = new Error(); + expected = + assertThrows( + ExecutionError.class, + () -> + cache.get( + new Object(), + new Callable() { + @Override + public Object call() { + throw callableError; + } + })); + assertThat(expected).hasCauseThat().isSameInstanceAs(callableError); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(4); + assertThat(stats.hitCount()).isEqualTo(0); + + expected = assertThrows(ExecutionError.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(5); + assertThat(stats.hitCount()).isEqualTo(0); } public void testReloadError() throws ExecutionException { - final Object one = new Object(); - final Error e = new Error(); + Object one = new Object(); + Error e = new Error(); CacheLoader loader = new CacheLoader() { @Override @@ -969,37 +925,37 @@ public ListenableFuture reload(Object key, Object oldValue) { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkLoggedCause(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(1); } public void testReloadFutureError() throws ExecutionException { - final Object one = new Object(); - final Error e = new Error(); + Object one = new Object(); + Error e = new Error(); CacheLoader loader = new CacheLoader() { @Override @@ -1009,44 +965,44 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkLoggedCause(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(1); } public void testRefreshError() { - final Object one = new Object(); - final Error e = new Error(); + Object one = new Object(); + Error e = new Error(); FakeTicker ticker = new FakeTicker(); CacheLoader loader = new CacheLoader() { @@ -1057,7 +1013,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1069,42 +1025,42 @@ public ListenableFuture reload(Object key, Object oldValue) { .build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); // refreshed stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(2); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(3); } public void testBulkLoadError() throws ExecutionException { @@ -1113,22 +1069,19 @@ public void testBulkLoadError() throws ExecutionException { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(bulkLoader(loader)); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + ExecutionError expected = + assertThrows(ExecutionError.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); } public void testLoadCheckedException() { @@ -1136,67 +1089,54 @@ public void testLoadCheckedException() { CacheLoader loader = exceptionLoader(e); LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.get(new Object()); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + Exception expected = assertThrows(ExecutionException.class, () -> cache.get(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - try { - cache.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(new Object()); checkLoggedCause(e); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(3, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(3); + assertThat(stats.hitCount()).isEqualTo(0); Exception callableException = new Exception(); - try { - cache.get(new Object(), throwing(callableException)); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); - } - stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(4, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(5, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + expected = + assertThrows( + ExecutionException.class, () -> cache.get(new Object(), throwing(callableException))); + assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(4); + assertThat(stats.hitCount()).isEqualTo(0); + + expected = assertThrows(ExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(5); + assertThat(stats.hitCount()).isEqualTo(0); } public void testLoadInterruptedException() { @@ -1204,80 +1144,67 @@ public void testLoadInterruptedException() { CacheLoader loader = exceptionLoader(e); LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); // Sanity check: - assertFalse(currentThread().interrupted()); + assertThat(Thread.interrupted()).isFalse(); - try { - cache.get(new Object()); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - assertTrue(currentThread().interrupted()); - stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - assertTrue(currentThread().interrupted()); + Exception expected = assertThrows(ExecutionException.class, () -> cache.get(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + assertThat(Thread.interrupted()).isTrue(); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); + + expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + assertThat(Thread.interrupted()).isTrue(); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(new Object()); - assertTrue(currentThread().interrupted()); + assertThat(Thread.interrupted()).isTrue(); checkLoggedCause(e); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(3, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(3); + assertThat(stats.hitCount()).isEqualTo(0); Exception callableException = new InterruptedException(); - try { - cache.get(new Object(), throwing(callableException)); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); - } - assertTrue(currentThread().interrupted()); - stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(4, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - assertTrue(currentThread().interrupted()); - stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(5, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + expected = + assertThrows( + ExecutionException.class, () -> cache.get(new Object(), throwing(callableException))); + assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); + assertThat(Thread.interrupted()).isTrue(); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(4); + assertThat(stats.hitCount()).isEqualTo(0); + + expected = assertThrows(ExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + assertThat(Thread.interrupted()).isTrue(); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(5); + assertThat(stats.hitCount()).isEqualTo(0); } public void testReloadCheckedException() { - final Object one = new Object(); - final Exception e = new Exception(); + Object one = new Object(); + Exception e = new Exception(); CacheLoader loader = new CacheLoader() { @Override @@ -1294,37 +1221,37 @@ public ListenableFuture reload(Object key, Object oldValue) throws Excep LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkLoggedCause(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(1); } public void testReloadFutureCheckedException() { - final Object one = new Object(); - final Exception e = new Exception(); + Object one = new Object(); + Exception e = new Exception(); CacheLoader loader = new CacheLoader() { @Override @@ -1334,44 +1261,44 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkLoggedCause(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(1); } public void testRefreshCheckedException() { - final Object one = new Object(); - final Exception e = new Exception(); + Object one = new Object(); + Exception e = new Exception(); FakeTicker ticker = new FakeTicker(); CacheLoader loader = new CacheLoader() { @@ -1382,7 +1309,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1394,42 +1321,42 @@ public ListenableFuture reload(Object key, Object oldValue) { .build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); // refreshed stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(2); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(3); } public void testBulkLoadCheckedException() { @@ -1438,22 +1365,19 @@ public void testBulkLoadCheckedException() { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(bulkLoader(loader)); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + ExecutionException expected = + assertThrows(ExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); } public void testBulkLoadInterruptedException() { @@ -1462,23 +1386,20 @@ public void testBulkLoadInterruptedException() { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(bulkLoader(loader)); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - assertTrue(currentThread().interrupted()); - stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + ExecutionException expected = + assertThrows(ExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + assertThat(Thread.interrupted()).isTrue(); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); } public void testLoadUncheckedException() throws ExecutionException { @@ -1486,72 +1407,62 @@ public void testLoadUncheckedException() throws ExecutionException { CacheLoader loader = exceptionLoader(e); LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.get(new Object()); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + UncheckedExecutionException expected = + assertThrows(UncheckedExecutionException.class, () -> cache.get(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - try { - cache.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } + expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked(new Object())); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(new Object()); checkLoggedCause(e); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(3, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(3); + assertThat(stats.hitCount()).isEqualTo(0); Exception callableException = new RuntimeException(); - try { - cache.get(new Object(), throwing(callableException)); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); - } - stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(4, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - stats = cache.stats(); - assertEquals(4, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(5, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + expected = + assertThrows( + UncheckedExecutionException.class, + () -> cache.get(new Object(), throwing(callableException))); + assertThat(expected).hasCauseThat().isSameInstanceAs(callableException); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(4); + assertThat(stats.hitCount()).isEqualTo(0); + + expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(4); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(5); + assertThat(stats.hitCount()).isEqualTo(0); } public void testReloadUncheckedException() throws ExecutionException { - final Object one = new Object(); - final Exception e = new RuntimeException(); + Object one = new Object(); + Exception e = new RuntimeException(); CacheLoader loader = new CacheLoader() { @Override @@ -1568,37 +1479,37 @@ public ListenableFuture reload(Object key, Object oldValue) throws Excep LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkLoggedCause(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(1); } public void testReloadFutureUncheckedException() throws ExecutionException { - final Object one = new Object(); - final Exception e = new RuntimeException(); + Object one = new Object(); + Exception e = new RuntimeException(); CacheLoader loader = new CacheLoader() { @Override @@ -1608,44 +1519,44 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.refresh(key); checkLoggedCause(e); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(1); } public void testRefreshUncheckedException() { - final Object one = new Object(); - final Exception e = new RuntimeException(); + Object one = new Object(); + Exception e = new RuntimeException(); FakeTicker ticker = new FakeTicker(); CacheLoader loader = new CacheLoader() { @@ -1656,7 +1567,7 @@ public Object load(Object key) { @Override public ListenableFuture reload(Object key, Object oldValue) { - return Futures.immediateFailedFuture(e); + return immediateFailedFuture(e); } }; @@ -1668,42 +1579,42 @@ public ListenableFuture reload(Object key, Object oldValue) { .build(loader); Object key = new Object(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); // refreshed stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(2); ticker.advance(1, MILLISECONDS); - assertSame(one, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(1, stats.loadSuccessCount()); - assertEquals(2, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(1); + assertThat(stats.loadExceptionCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(3); } public void testBulkLoadUncheckedException() throws ExecutionException { @@ -1712,27 +1623,24 @@ public void testBulkLoadUncheckedException() throws ExecutionException { LoadingCache cache = CacheBuilder.newBuilder().recordStats().build(bulkLoader(loader)); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - - try { - cache.getAll(asList(new Object())); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(e); - } - stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(1, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + + UncheckedExecutionException expected = + assertThrows(UncheckedExecutionException.class, () -> cache.getAll(asList(new Object()))); + assertThat(expected).hasCauseThat().isSameInstanceAs(e); + stats = cache.stats(); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); } public void testReloadAfterFailure() throws ExecutionException { - final AtomicInteger count = new AtomicInteger(); - final Exception e = new IllegalStateException("exception to trigger failure on first load()"); + AtomicInteger count = new AtomicInteger(); + Exception e = new IllegalStateException("exception to trigger failure on first load()"); CacheLoader failOnceFunction = new CacheLoader() { @@ -1748,25 +1656,23 @@ public String load(Integer key) throws Exception { LoadingCache cache = CacheBuilder.newBuilder().removalListener(removalListener).build(failOnceFunction); - try { - cache.getUnchecked(1); - fail(); - } catch (UncheckedExecutionException ue) { - assertThat(ue).hasCauseThat().isSameInstanceAs(e); - } + UncheckedExecutionException ue = + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked(1)); + assertThat(ue).hasCauseThat().isSameInstanceAs(e); - assertEquals("1", cache.getUnchecked(1)); - assertEquals(0, removalListener.getCount()); + assertThat(cache.getUnchecked(1)).isEqualTo("1"); + assertThat(removalListener.getCount()).isEqualTo(0); count.set(0); cache.refresh(2); checkLoggedCause(e); - assertEquals("2", cache.getUnchecked(2)); - assertEquals(0, removalListener.getCount()); + assertThat(cache.getUnchecked(2)).isEqualTo("2"); + assertThat(removalListener.getCount()).isEqualTo(0); } + @AndroidIncompatible // Depends on GC behavior public void testReloadAfterValueReclamation() throws InterruptedException, ExecutionException { CountingLoader countingLoader = new CountingLoader(); LoadingCache cache = @@ -1787,7 +1693,7 @@ public void testReloadAfterValueReclamation() throws InterruptedException, Execu Thread.sleep(i); System.gc(); } - assertEquals(expectedComputations, countingLoader.getCount()); + assertThat(countingLoader.getCount()).isEqualTo(expectedComputations); for (int i = 0; i < iterations; i++) { // The entry should get garbage collected and recomputed. @@ -1802,7 +1708,7 @@ public void testReloadAfterValueReclamation() throws InterruptedException, Execu Thread.sleep(i); System.gc(); } - assertEquals(expectedComputations, countingLoader.getCount()); + assertThat(countingLoader.getCount()).isEqualTo(expectedComputations); } public void testReloadAfterSimulatedValueReclamation() throws ExecutionException { @@ -1811,20 +1717,20 @@ public void testReloadAfterSimulatedValueReclamation() throws ExecutionException CacheBuilder.newBuilder().concurrencyLevel(1).weakValues().build(countingLoader); Object key = new Object(); - assertNotNull(cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isNotNull(); CacheTesting.simulateValueReclamation(cache, key); // this blocks if computation can't deal with partially-collected values - assertNotNull(cache.getUnchecked(key)); - assertEquals(1, cache.size()); - assertEquals(2, countingLoader.getCount()); + assertThat(cache.getUnchecked(key)).isNotNull(); + assertThat(cache.size()).isEqualTo(1); + assertThat(countingLoader.getCount()).isEqualTo(2); CacheTesting.simulateValueReclamation(cache, key); cache.refresh(key); checkNothingLogged(); - assertEquals(1, cache.size()); - assertEquals(3, countingLoader.getCount()); + assertThat(cache.size()).isEqualTo(1); + assertThat(countingLoader.getCount()).isEqualTo(3); } public void testReloadAfterSimulatedKeyReclamation() throws ExecutionException { @@ -1833,115 +1739,89 @@ public void testReloadAfterSimulatedKeyReclamation() throws ExecutionException { CacheBuilder.newBuilder().concurrencyLevel(1).weakKeys().build(countingLoader); Object key = new Object(); - assertNotNull(cache.getUnchecked(key)); - assertEquals(1, cache.size()); + assertThat(cache.getUnchecked(key)).isNotNull(); + assertThat(cache.size()).isEqualTo(1); CacheTesting.simulateKeyReclamation(cache, key); // this blocks if computation can't deal with partially-collected values - assertNotNull(cache.getUnchecked(key)); - assertEquals(2, countingLoader.getCount()); + assertThat(cache.getUnchecked(key)).isNotNull(); + assertThat(countingLoader.getCount()).isEqualTo(2); CacheTesting.simulateKeyReclamation(cache, key); cache.refresh(key); checkNothingLogged(); - assertEquals(3, countingLoader.getCount()); + assertThat(countingLoader.getCount()).isEqualTo(3); } /** * Make sure LoadingCache correctly wraps ExecutionExceptions and UncheckedExecutionExceptions. */ public void testLoadingExceptionWithCause() { - final Exception cause = new Exception(); - final UncheckedExecutionException uee = new UncheckedExecutionException(cause); - final ExecutionException ee = new ExecutionException(cause); + Exception cause = new Exception(); + UncheckedExecutionException uee = new UncheckedExecutionException(cause); + ExecutionException ee = new ExecutionException(cause); LoadingCache cacheUnchecked = CacheBuilder.newBuilder().build(exceptionLoader(uee)); LoadingCache cacheChecked = CacheBuilder.newBuilder().build(exceptionLoader(ee)); - try { - cacheUnchecked.get(new Object()); - fail(); - } catch (ExecutionException e) { - fail(); - } catch (UncheckedExecutionException caughtEe) { - assertThat(caughtEe).hasCauseThat().isSameInstanceAs(uee); - } + UncheckedExecutionException caughtUee = + assertThrows(UncheckedExecutionException.class, () -> cacheUnchecked.get(new Object())); + assertThat(caughtUee).hasCauseThat().isSameInstanceAs(uee); - try { - cacheUnchecked.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException caughtUee) { - assertThat(caughtUee).hasCauseThat().isSameInstanceAs(uee); - } + caughtUee = + assertThrows( + UncheckedExecutionException.class, () -> cacheUnchecked.getUnchecked(new Object())); + assertThat(caughtUee).hasCauseThat().isSameInstanceAs(uee); cacheUnchecked.refresh(new Object()); checkLoggedCause(uee); - try { - cacheUnchecked.getAll(asList(new Object())); - fail(); - } catch (ExecutionException e) { - fail(); - } catch (UncheckedExecutionException caughtEe) { - assertThat(caughtEe).hasCauseThat().isSameInstanceAs(uee); - } + caughtUee = + assertThrows( + UncheckedExecutionException.class, () -> cacheUnchecked.getAll(asList(new Object()))); + assertThat(caughtUee).hasCauseThat().isSameInstanceAs(uee); - try { - cacheChecked.get(new Object()); - fail(); - } catch (ExecutionException caughtEe) { - assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee); - } + ExecutionException caughtEe = + assertThrows(ExecutionException.class, () -> cacheChecked.get(new Object())); + assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee); - try { - cacheChecked.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException caughtUee) { - assertThat(caughtUee).hasCauseThat().isSameInstanceAs(ee); - } + caughtUee = + assertThrows( + UncheckedExecutionException.class, () -> cacheChecked.getUnchecked(new Object())); + assertThat(caughtUee).hasCauseThat().isSameInstanceAs(ee); cacheChecked.refresh(new Object()); checkLoggedCause(ee); - try { - cacheChecked.getAll(asList(new Object())); - fail(); - } catch (ExecutionException caughtEe) { - assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee); - } + caughtEe = + assertThrows(ExecutionException.class, () -> cacheChecked.getAll(asList(new Object()))); + assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee); } public void testBulkLoadingExceptionWithCause() { - final Exception cause = new Exception(); - final UncheckedExecutionException uee = new UncheckedExecutionException(cause); - final ExecutionException ee = new ExecutionException(cause); + Exception cause = new Exception(); + UncheckedExecutionException uee = new UncheckedExecutionException(cause); + ExecutionException ee = new ExecutionException(cause); LoadingCache cacheUnchecked = CacheBuilder.newBuilder().build(bulkLoader(exceptionLoader(uee))); LoadingCache cacheChecked = CacheBuilder.newBuilder().build(bulkLoader(exceptionLoader(ee))); - try { - cacheUnchecked.getAll(asList(new Object())); - fail(); - } catch (ExecutionException e) { - fail(); - } catch (UncheckedExecutionException caughtEe) { - assertThat(caughtEe).hasCauseThat().isSameInstanceAs(uee); - } + UncheckedExecutionException caughtUee = + assertThrows( + UncheckedExecutionException.class, () -> cacheUnchecked.getAll(asList(new Object()))); + assertThat(caughtUee).hasCauseThat().isSameInstanceAs(uee); - try { - cacheChecked.getAll(asList(new Object())); - fail(); - } catch (ExecutionException caughtEe) { - assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee); - } + ExecutionException caughtEe = + assertThrows(ExecutionException.class, () -> cacheChecked.getAll(asList(new Object()))); + assertThat(caughtEe).hasCauseThat().isSameInstanceAs(ee); } - + @AndroidIncompatible // Bug? expected:<1> but was:<2> public void testConcurrentLoading() throws InterruptedException { testConcurrentLoading(CacheBuilder.newBuilder()); } @@ -1954,9 +1834,9 @@ private static void testConcurrentLoading(CacheBuilder builder) testConcurrentLoadingCheckedException(builder); } - + @AndroidIncompatible // Bug? expected:<1> but was:<2> public void testConcurrentExpirationLoading() throws InterruptedException { - testConcurrentLoading(CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS)); + testConcurrentLoading(CacheBuilder.newBuilder().expireAfterWrite(10, SECONDS)); } /** @@ -1967,9 +1847,9 @@ private static void testConcurrentLoadingDefault(CacheBuilder bu throws InterruptedException { int count = 10; - final AtomicInteger callCount = new AtomicInteger(); - final CountDownLatch startSignal = new CountDownLatch(count + 1); - final Object result = new Object(); + AtomicInteger callCount = new AtomicInteger(); + CountDownLatch startSignal = new CountDownLatch(count + 1); + Object result = new Object(); LoadingCache cache = builder.build( @@ -1984,9 +1864,11 @@ public Object load(String key) throws InterruptedException { List resultArray = doConcurrentGet(cache, "bar", count, startSignal); - assertEquals(1, callCount.get()); + assertThat(callCount.get()).isEqualTo(1); for (int i = 0; i < count; i++) { - assertSame("result(" + i + ") didn't match expected", result, resultArray.get(i)); + assertWithMessage("result(%s) didn't match expected", i) + .that(resultArray.get(i)) + .isSameInstanceAs(result); } } @@ -1999,13 +1881,14 @@ private static void testConcurrentLoadingNull(CacheBuilder build throws InterruptedException { int count = 10; - final AtomicInteger callCount = new AtomicInteger(); - final CountDownLatch startSignal = new CountDownLatch(count + 1); + AtomicInteger callCount = new AtomicInteger(); + CountDownLatch startSignal = new CountDownLatch(count + 1); LoadingCache cache = builder.build( new CacheLoader() { @Override + @SuppressWarnings("CacheLoaderNull") // test of broken user implementation public String load(String key) throws InterruptedException { callCount.incrementAndGet(); startSignal.await(); @@ -2015,18 +1898,14 @@ public String load(String key) throws InterruptedException { List result = doConcurrentGet(cache, "bar", count, startSignal); - assertEquals(1, callCount.get()); + assertThat(callCount.get()).isEqualTo(1); for (int i = 0; i < count; i++) { assertThat(result.get(i)).isInstanceOf(InvalidCacheLoadException.class); } // subsequent calls should call the loader again, not get the old exception - try { - cache.getUnchecked("bar"); - fail(); - } catch (InvalidCacheLoadException expected) { - } - assertEquals(2, callCount.get()); + assertThrows(InvalidCacheLoadException.class, () -> cache.getUnchecked("bar")); + assertThat(callCount.get()).isEqualTo(2); } /** @@ -2038,9 +1917,9 @@ private static void testConcurrentLoadingUncheckedException(CacheBuilder cache = builder.build( @@ -2055,21 +1934,17 @@ public String load(String key) throws InterruptedException { List result = doConcurrentGet(cache, "bar", count, startSignal); - assertEquals(1, callCount.get()); + assertThat(callCount.get()).isEqualTo(1); for (int i = 0; i < count; i++) { // doConcurrentGet alternates between calling getUnchecked and calling get, but an unchecked // exception thrown by the loader is always wrapped as an UncheckedExecutionException. assertThat(result.get(i)).isInstanceOf(UncheckedExecutionException.class); - assertThat(((UncheckedExecutionException) result.get(i))).hasCauseThat().isSameInstanceAs(e); + assertThat((UncheckedExecutionException) result.get(i)).hasCauseThat().isSameInstanceAs(e); } // subsequent calls should call the loader again, not get the old exception - try { - cache.getUnchecked("bar"); - fail(); - } catch (UncheckedExecutionException expected) { - } - assertEquals(2, callCount.get()); + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked("bar")); + assertThat(callCount.get()).isEqualTo(2); } /** @@ -2081,9 +1956,9 @@ private static void testConcurrentLoadingCheckedException(CacheBuilder cache = builder.build( @@ -2098,7 +1973,7 @@ public String load(String key) throws IOException, InterruptedException { List result = doConcurrentGet(cache, "bar", count, startSignal); - assertEquals(1, callCount.get()); + assertThat(callCount.get()).isEqualTo(1); for (int i = 0; i < count; i++) { // doConcurrentGet alternates between calling getUnchecked and calling get. If we call get(), // we should get an ExecutionException; if we call getUnchecked(), we should get an @@ -2114,12 +1989,8 @@ public String load(String key) throws IOException, InterruptedException { } // subsequent calls should call the loader again, not get the old exception - try { - cache.getUnchecked("bar"); - fail(); - } catch (UncheckedExecutionException expected) { - } - assertEquals(2, callCount.get()); + assertThrows(UncheckedExecutionException.class, () -> cache.getUnchecked("bar")); + assertThat(callCount.get()).isEqualTo(2); } /** @@ -2132,17 +2003,15 @@ public String load(String key) throws IOException, InterruptedException { * {@code getUnchecked}, and threads with an odd index will call {@code get}. If the cache throws * exceptions, this difference may be visible in the returned List. */ + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. private static List doConcurrentGet( - final LoadingCache cache, - final K key, - int nThreads, - final CountDownLatch gettersStartedSignal) + LoadingCache cache, K key, int nThreads, CountDownLatch gettersStartedSignal) throws InterruptedException { - final AtomicReferenceArray result = new AtomicReferenceArray<>(nThreads); - final CountDownLatch gettersComplete = new CountDownLatch(nThreads); + AtomicReferenceArray result = new AtomicReferenceArray<>(nThreads); + CountDownLatch gettersComplete = new CountDownLatch(nThreads); for (int i = 0; i < nThreads; i++) { - final int index = i; + int index = i; Thread thread = new Thread( new Runnable() { @@ -2184,14 +2053,13 @@ public void run() { return resultList; } - public void testAsMapDuringLoading() throws InterruptedException, ExecutionException { - final CountDownLatch getStartedSignal = new CountDownLatch(2); - final CountDownLatch letGetFinishSignal = new CountDownLatch(1); - final CountDownLatch getFinishedSignal = new CountDownLatch(2); - final String getKey = "get"; - final String refreshKey = "refresh"; - final String suffix = "Suffix"; + CountDownLatch getStartedSignal = new CountDownLatch(2); + CountDownLatch letGetFinishSignal = new CountDownLatch(1); + CountDownLatch getFinishedSignal = new CountDownLatch(2); + String getKey = "get"; + String refreshKey = "refresh"; + String suffix = "Suffix"; CacheLoader computeFunction = new CacheLoader() { @@ -2203,12 +2071,12 @@ public String load(String key) throws InterruptedException { } }; - final LoadingCache cache = CacheBuilder.newBuilder().build(computeFunction); + LoadingCache cache = CacheBuilder.newBuilder().build(computeFunction); ConcurrentMap map = cache.asMap(); map.put(refreshKey, refreshKey); - assertEquals(1, map.size()); - assertFalse(map.containsKey(getKey)); - assertSame(refreshKey, map.get(refreshKey)); + assertThat(map).hasSize(1); + assertThat(map.containsKey(getKey)).isFalse(); + assertThat(map.get(refreshKey)).isSameInstanceAs(refreshKey); new Thread() { @Override @@ -2228,9 +2096,9 @@ public void run() { getStartedSignal.await(); // computation is in progress; asMap shouldn't have changed - assertEquals(1, map.size()); - assertFalse(map.containsKey(getKey)); - assertSame(refreshKey, map.get(refreshKey)); + assertThat(map).hasSize(1); + assertThat(map.containsKey(getKey)).isFalse(); + assertThat(map.get(refreshKey)).isSameInstanceAs(refreshKey); // let computation complete letGetFinishSignal.countDown(); @@ -2238,20 +2106,19 @@ public void run() { checkNothingLogged(); // asMap view should have been updated - assertEquals(2, cache.size()); - assertEquals(getKey + suffix, map.get(getKey)); - assertEquals(refreshKey + suffix, map.get(refreshKey)); + assertThat(cache.size()).isEqualTo(2); + assertThat(map.get(getKey)).isEqualTo(getKey + suffix); + assertThat(map.get(refreshKey)).isEqualTo(refreshKey + suffix); } - public void testInvalidateDuringLoading() throws InterruptedException, ExecutionException { // computation starts; invalidate() is called on the key being computed, computation finishes - final CountDownLatch computationStarted = new CountDownLatch(2); - final CountDownLatch letGetFinishSignal = new CountDownLatch(1); - final CountDownLatch getFinishedSignal = new CountDownLatch(2); - final String getKey = "get"; - final String refreshKey = "refresh"; - final String suffix = "Suffix"; + CountDownLatch computationStarted = new CountDownLatch(2); + CountDownLatch letGetFinishSignal = new CountDownLatch(1); + CountDownLatch getFinishedSignal = new CountDownLatch(2); + String getKey = "get"; + String refreshKey = "refresh"; + String suffix = "Suffix"; CacheLoader computeFunction = new CacheLoader() { @@ -2263,7 +2130,7 @@ public String load(String key) throws InterruptedException { } }; - final LoadingCache cache = CacheBuilder.newBuilder().build(computeFunction); + LoadingCache cache = CacheBuilder.newBuilder().build(computeFunction); ConcurrentMap map = cache.asMap(); map.put(refreshKey, refreshKey); @@ -2285,8 +2152,8 @@ public void run() { computationStarted.await(); cache.invalidate(getKey); cache.invalidate(refreshKey); - assertFalse(map.containsKey(getKey)); - assertFalse(map.containsKey(refreshKey)); + assertThat(map.containsKey(getKey)).isFalse(); + assertThat(map.containsKey(refreshKey)).isFalse(); // let computation complete letGetFinishSignal.countDown(); @@ -2294,22 +2161,21 @@ public void run() { checkNothingLogged(); // results should be visible - assertEquals(2, cache.size()); - assertEquals(getKey + suffix, map.get(getKey)); - assertEquals(refreshKey + suffix, map.get(refreshKey)); - assertEquals(2, cache.size()); + assertThat(cache.size()).isEqualTo(2); + assertThat(map.get(getKey)).isEqualTo(getKey + suffix); + assertThat(map.get(refreshKey)).isEqualTo(refreshKey + suffix); + assertThat(cache.size()).isEqualTo(2); } - public void testInvalidateAndReloadDuringLoading() throws InterruptedException, ExecutionException { // computation starts; clear() is called, computation finishes - final CountDownLatch computationStarted = new CountDownLatch(2); - final CountDownLatch letGetFinishSignal = new CountDownLatch(1); - final CountDownLatch getFinishedSignal = new CountDownLatch(4); - final String getKey = "get"; - final String refreshKey = "refresh"; - final String suffix = "Suffix"; + CountDownLatch computationStarted = new CountDownLatch(2); + CountDownLatch letGetFinishSignal = new CountDownLatch(1); + CountDownLatch getFinishedSignal = new CountDownLatch(4); + String getKey = "get"; + String refreshKey = "refresh"; + String suffix = "Suffix"; CacheLoader computeFunction = new CacheLoader() { @@ -2321,7 +2187,7 @@ public String load(String key) throws InterruptedException { } }; - final LoadingCache cache = CacheBuilder.newBuilder().build(computeFunction); + LoadingCache cache = CacheBuilder.newBuilder().build(computeFunction); ConcurrentMap map = cache.asMap(); map.put(refreshKey, refreshKey); @@ -2343,8 +2209,8 @@ public void run() { computationStarted.await(); cache.invalidate(getKey); cache.invalidate(refreshKey); - assertFalse(map.containsKey(getKey)); - assertFalse(map.containsKey(refreshKey)); + assertThat(map.containsKey(getKey)).isFalse(); + assertThat(map.containsKey(refreshKey)).isFalse(); // start new computations new Thread() { @@ -2368,25 +2234,25 @@ public void run() { checkNothingLogged(); // results should be visible - assertEquals(2, cache.size()); - assertEquals(getKey + suffix, map.get(getKey)); - assertEquals(refreshKey + suffix, map.get(refreshKey)); + assertThat(cache.size()).isEqualTo(2); + assertThat(map.get(getKey)).isEqualTo(getKey + suffix); + assertThat(map.get(refreshKey)).isEqualTo(refreshKey + suffix); } - + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races public void testExpandDuringLoading() throws InterruptedException { - final int count = 3; - final AtomicInteger callCount = new AtomicInteger(); + int count = 3; + AtomicInteger callCount = new AtomicInteger(); // tells the computing thread when to start computing - final CountDownLatch computeSignal = new CountDownLatch(1); + CountDownLatch computeSignal = new CountDownLatch(1); // tells the main thread when computation is pending - final CountDownLatch secondSignal = new CountDownLatch(1); + CountDownLatch secondSignal = new CountDownLatch(1); // tells the main thread when the second get has started - final CountDownLatch thirdSignal = new CountDownLatch(1); + CountDownLatch thirdSignal = new CountDownLatch(1); // tells the main thread when the third get has started - final CountDownLatch fourthSignal = new CountDownLatch(1); + CountDownLatch fourthSignal = new CountDownLatch(1); // tells the test when all gets have returned - final CountDownLatch doneSignal = new CountDownLatch(count); + CountDownLatch doneSignal = new CountDownLatch(count); CacheLoader computeFunction = new CacheLoader() { @@ -2399,12 +2265,12 @@ public String load(String key) throws InterruptedException { } }; - final LoadingCache cache = + LoadingCache cache = CacheBuilder.newBuilder().weakKeys().build(computeFunction); - final AtomicReferenceArray result = new AtomicReferenceArray<>(count); + AtomicReferenceArray result = new AtomicReferenceArray<>(count); - final String key = "bar"; + String key = "bar"; // start computing thread new Thread() { @@ -2455,30 +2321,30 @@ public void run() { computeSignal.countDown(); doneSignal.await(); - assertTrue(callCount.get() == 1); - assertEquals("barfoo", result.get(0)); - assertEquals("barfoo", result.get(1)); - assertEquals("barfoo", result.get(2)); - assertEquals("barfoo", cache.getUnchecked(key)); + assertThat(callCount.get()).isEqualTo(1); + assertThat(result.get(0)).isEqualTo("barfoo"); + assertThat(result.get(1)).isEqualTo("barfoo"); + assertThat(result.get(2)).isEqualTo("barfoo"); + assertThat(cache.getUnchecked(key)).isEqualTo("barfoo"); } // Test ignored because it is extremely flaky in CI builds - + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races public void ignoreTestExpandDuringRefresh() throws InterruptedException, ExecutionException { - final AtomicInteger callCount = new AtomicInteger(); + AtomicInteger callCount = new AtomicInteger(); // tells the computing thread when to start computing - final CountDownLatch computeSignal = new CountDownLatch(1); + CountDownLatch computeSignal = new CountDownLatch(1); // tells the main thread when computation is pending - final CountDownLatch secondSignal = new CountDownLatch(1); + CountDownLatch secondSignal = new CountDownLatch(1); // tells the main thread when the second get has started - final CountDownLatch thirdSignal = new CountDownLatch(1); + CountDownLatch thirdSignal = new CountDownLatch(1); // tells the main thread when the third get has started - final CountDownLatch fourthSignal = new CountDownLatch(1); + CountDownLatch fourthSignal = new CountDownLatch(1); // tells the test when all gets have returned - final CountDownLatch doneSignal = new CountDownLatch(3); - final String suffix = "Suffix"; + CountDownLatch doneSignal = new CountDownLatch(3); + String suffix = "Suffix"; CacheLoader computeFunction = new CacheLoader() { @@ -2491,10 +2357,10 @@ public String load(String key) throws InterruptedException { } }; - final AtomicReferenceArray result = new AtomicReferenceArray<>(2); + AtomicReferenceArray result = new AtomicReferenceArray<>(2); - final LoadingCache cache = CacheBuilder.newBuilder().build(computeFunction); - final String key = "bar"; + LoadingCache cache = CacheBuilder.newBuilder().build(computeFunction); + String key = "bar"; cache.asMap().put(key, key); // start computing thread @@ -2547,13 +2413,13 @@ public void run() { computeSignal.countDown(); doneSignal.await(); - assertTrue(callCount.get() == 1); - assertEquals(key, result.get(0)); - assertEquals(key, result.get(1)); - assertEquals(key + suffix, cache.getUnchecked(key)); + assertThat(callCount.get()).isEqualTo(1); + assertThat(result.get(0)).isEqualTo(key); + assertThat(result.get(1)).isEqualTo(key); + assertThat(cache.getUnchecked(key)).isEqualTo(key + suffix); } - static Callable throwing(final Exception exception) { + static Callable throwing(Exception exception) { return new Callable() { @Override public T call() throws Exception { diff --git a/android/guava-tests/test/com/google/common/cache/CacheManualTest.java b/android/guava-tests/test/com/google/common/cache/CacheManualTest.java index abe0b15eb6cc..e3b358a738b0 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheManualTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheManualTest.java @@ -14,138 +14,144 @@ package com.google.common.cache; +import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author Charles Fry */ +/** + * @author Charles Fry + */ +@NullUnmarked public class CacheManualTest extends TestCase { public void testGetIfPresent() { Cache cache = CacheBuilder.newBuilder().recordStats().build(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); Object one = new Object(); Object two = new Object(); - assertNull(cache.getIfPresent(one)); + assertThat(cache.getIfPresent(one)).isNull(); stats = cache.stats(); - assertEquals(1, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - assertNull(cache.asMap().get(one)); - assertFalse(cache.asMap().containsKey(one)); - assertFalse(cache.asMap().containsValue(two)); - - assertNull(cache.getIfPresent(two)); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + assertThat(cache.asMap().get(one)).isNull(); + assertThat(cache.asMap().containsKey(one)).isFalse(); + assertThat(cache.asMap().containsValue(two)).isFalse(); + + assertThat(cache.getIfPresent(two)).isNull(); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); - assertNull(cache.asMap().get(two)); - assertFalse(cache.asMap().containsKey(two)); - assertFalse(cache.asMap().containsValue(one)); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + assertThat(cache.asMap().get(two)).isNull(); + assertThat(cache.asMap().containsKey(two)).isFalse(); + assertThat(cache.asMap().containsValue(one)).isFalse(); cache.put(one, two); - assertSame(two, cache.getIfPresent(one)); + assertThat(cache.getIfPresent(one)).isSameInstanceAs(two); stats = cache.stats(); - assertEquals(2, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); - assertSame(two, cache.asMap().get(one)); - assertTrue(cache.asMap().containsKey(one)); - assertTrue(cache.asMap().containsValue(two)); - - assertNull(cache.getIfPresent(two)); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); + assertThat(cache.asMap().get(one)).isSameInstanceAs(two); + assertThat(cache.asMap().containsKey(one)).isTrue(); + assertThat(cache.asMap().containsValue(two)).isTrue(); + + assertThat(cache.getIfPresent(two)).isNull(); stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); - assertNull(cache.asMap().get(two)); - assertFalse(cache.asMap().containsKey(two)); - assertFalse(cache.asMap().containsValue(one)); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); + assertThat(cache.asMap().get(two)).isNull(); + assertThat(cache.asMap().containsKey(two)).isFalse(); + assertThat(cache.asMap().containsValue(one)).isFalse(); cache.put(two, one); - assertSame(two, cache.getIfPresent(one)); + assertThat(cache.getIfPresent(one)).isSameInstanceAs(two); stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(2, stats.hitCount()); - assertSame(two, cache.asMap().get(one)); - assertTrue(cache.asMap().containsKey(one)); - assertTrue(cache.asMap().containsValue(two)); - - assertSame(one, cache.getIfPresent(two)); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(2); + assertThat(cache.asMap().get(one)).isSameInstanceAs(two); + assertThat(cache.asMap().containsKey(one)).isTrue(); + assertThat(cache.asMap().containsValue(two)).isTrue(); + + assertThat(cache.getIfPresent(two)).isSameInstanceAs(one); stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); - assertSame(one, cache.asMap().get(two)); - assertTrue(cache.asMap().containsKey(two)); - assertTrue(cache.asMap().containsValue(one)); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(3); + assertThat(cache.asMap().get(two)).isSameInstanceAs(one); + assertThat(cache.asMap().containsKey(two)).isTrue(); + assertThat(cache.asMap().containsValue(one)).isTrue(); } public void testGetAllPresent() { Cache cache = CacheBuilder.newBuilder().recordStats().build(); CacheStats stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertEquals(ImmutableMap.of(), cache.getAllPresent(ImmutableList.of())); + assertThat(cache.getAllPresent(ImmutableList.of())).isEmpty(); stats = cache.stats(); - assertEquals(0, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); - assertEquals(ImmutableMap.of(), cache.getAllPresent(asList(1, 2, 3))); + assertThat(cache.getAllPresent(asList(1, 2, 3))).isEqualTo(ImmutableMap.of()); stats = cache.stats(); - assertEquals(3, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); cache.put(2, 22); - assertEquals(ImmutableMap.of(2, 22), cache.getAllPresent(asList(1, 2, 3))); + assertThat(cache.getAllPresent(asList(1, 2, 3))).containsExactly(2, 22); stats = cache.stats(); - assertEquals(5, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(1, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(5); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(1); cache.put(3, 33); - assertEquals(ImmutableMap.of(2, 22, 3, 33), cache.getAllPresent(asList(1, 2, 3))); + assertThat(cache.getAllPresent(asList(1, 2, 3))).containsExactly(2, 22, 3, 33); stats = cache.stats(); - assertEquals(6, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(3, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(6); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(3); cache.put(1, 11); - assertEquals(ImmutableMap.of(1, 11, 2, 22, 3, 33), cache.getAllPresent(asList(1, 2, 3))); + assertThat(cache.getAllPresent(asList(1, 2, 3))) + .isEqualTo(ImmutableMap.of(1, 11, 2, 22, 3, 33)); stats = cache.stats(); - assertEquals(6, stats.missCount()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(6, stats.hitCount()); + assertThat(stats.missCount()).isEqualTo(6); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(6); } } diff --git a/android/guava-tests/test/com/google/common/cache/CacheReferencesTest.java b/android/guava-tests/test/com/google/common/cache/CacheReferencesTest.java index 1fe26361a106..11ce62e0a0b1 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheReferencesTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheReferencesTest.java @@ -20,11 +20,11 @@ import com.google.common.base.Function; import com.google.common.cache.LocalCache.Strength; -import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import java.lang.ref.WeakReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests of basic {@link LoadingCache} operations with all possible combinations of key & value @@ -32,6 +32,7 @@ * * @author mike nonemacher */ +@NullUnmarked public class CacheReferencesTest extends TestCase { private static final CacheLoader KEY_TO_STRING_LOADER = @@ -65,10 +66,10 @@ public void testContainsKeyAndValue() { // maintain strong refs so these won't be collected, regardless of cache's key/value strength Key key = new Key(1); String value = key.toString(); - assertSame(value, cache.getUnchecked(key)); - assertTrue(cache.asMap().containsKey(key)); - assertTrue(cache.asMap().containsValue(value)); - assertEquals(1, cache.size()); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(value); + assertThat(cache.asMap().containsKey(key)).isTrue(); + assertThat(cache.asMap().containsValue(value)).isTrue(); + assertThat(cache.size()).isEqualTo(1); } } @@ -76,13 +77,13 @@ public void testClear() { for (LoadingCache cache : caches()) { Key key = new Key(1); String value = key.toString(); - assertSame(value, cache.getUnchecked(key)); - assertFalse(cache.asMap().isEmpty()); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(value); + assertThat(cache.asMap().isEmpty()).isFalse(); cache.invalidateAll(); - assertEquals(0, cache.size()); - assertTrue(cache.asMap().isEmpty()); - assertFalse(cache.asMap().containsKey(key)); - assertFalse(cache.asMap().containsValue(value)); + assertThat(cache.size()).isEqualTo(0); + assertThat(cache.asMap().isEmpty()).isTrue(); + assertThat(cache.asMap().containsKey(key)).isFalse(); + assertThat(cache.asMap().containsValue(value)).isFalse(); } } @@ -92,13 +93,12 @@ public void testKeySetEntrySetValues() { String value1 = key1.toString(); Key key2 = new Key(2); String value2 = key2.toString(); - assertSame(value1, cache.getUnchecked(key1)); - assertSame(value2, cache.getUnchecked(key2)); - assertEquals(ImmutableSet.of(key1, key2), cache.asMap().keySet()); + assertThat(cache.getUnchecked(key1)).isSameInstanceAs(value1); + assertThat(cache.getUnchecked(key2)).isSameInstanceAs(value2); + assertThat(cache.asMap().keySet()).isEqualTo(ImmutableSet.of(key1, key2)); assertThat(cache.asMap().values()).containsExactly(value1, value2); - assertEquals( - ImmutableSet.of(immutableEntry(key1, value1), immutableEntry(key2, value2)), - cache.asMap().entrySet()); + assertThat(cache.asMap().entrySet()) + .containsExactly(immutableEntry(key1, value1), immutableEntry(key2, value2)); } } @@ -108,54 +108,19 @@ public void testInvalidate() { String value1 = key1.toString(); Key key2 = new Key(2); String value2 = key2.toString(); - assertSame(value1, cache.getUnchecked(key1)); - assertSame(value2, cache.getUnchecked(key2)); + assertThat(cache.getUnchecked(key1)).isSameInstanceAs(value1); + assertThat(cache.getUnchecked(key2)).isSameInstanceAs(value2); cache.invalidate(key1); - assertFalse(cache.asMap().containsKey(key1)); - assertTrue(cache.asMap().containsKey(key2)); - assertEquals(1, cache.size()); - assertEquals(ImmutableSet.of(key2), cache.asMap().keySet()); + assertThat(cache.asMap().containsKey(key1)).isFalse(); + assertThat(cache.asMap().containsKey(key2)).isTrue(); + assertThat(cache.size()).isEqualTo(1); + assertThat(cache.asMap().keySet()).isEqualTo(ImmutableSet.of(key2)); assertThat(cache.asMap().values()).contains(value2); - assertEquals(ImmutableSet.of(immutableEntry(key2, value2)), cache.asMap().entrySet()); + assertThat(cache.asMap().entrySet()).containsExactly(immutableEntry(key2, value2)); } } - // fails in Maven with 64-bit JDK: http://code.google.com/p/guava-libraries/issues/detail?id=1568 - - private void assertCleanup( - LoadingCache cache, - CountingRemovalListener removalListener) { - - // initialSize will most likely be 2, but it's possible for the GC to have already run, so we'll - // observe a size of 1 - long initialSize = cache.size(); - assertTrue(initialSize == 1 || initialSize == 2); - - // wait up to 5s - byte[] filler = new byte[1024]; - for (int i = 0; i < 500; i++) { - System.gc(); - - CacheTesting.drainReferenceQueues(cache); - if (cache.size() == 1) { - break; - } - try { - Thread.sleep(10); - } catch (InterruptedException e) { - /* ignore */ - } - try { - // Fill up heap so soft references get cleared. - filler = new byte[Math.max(filler.length, filler.length * 2)]; - } catch (OutOfMemoryError e) { - } - } - - CacheTesting.processPendingNotifications(cache); - assertEquals(1, cache.size()); - assertEquals(1, removalListener.getCount()); - } + // fails in Maven with 64-bit JDK: https://github.com/google/guava/issues/1568 // A simple type whose .toString() will return the same value each time, but without maintaining // a strong reference to that value. diff --git a/android/guava-tests/test/com/google/common/cache/CacheRefreshTest.java b/android/guava-tests/test/com/google/common/cache/CacheRefreshTest.java index 823ad360f8b9..83c3ea3b5777 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheRefreshTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheRefreshTest.java @@ -15,17 +15,20 @@ package com.google.common.cache; import static com.google.common.cache.TestingCacheLoaders.incrementingLoader; +import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.MILLISECONDS; import com.google.common.cache.TestingCacheLoaders.IncrementingLoader; import com.google.common.testing.FakeTicker; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests relating to automatic cache refreshing. * * @author Charles Fry */ +@NullUnmarked public class CacheRefreshTest extends TestCase { public void testAutoRefresh() { FakeTicker ticker = new FakeTicker(); @@ -40,61 +43,61 @@ public void testAutoRefresh() { int expectedLoads = 0; int expectedReloads = 0; for (int i = 0; i < 3; i++) { - assertEquals(Integer.valueOf(i), cache.getUnchecked(i)); + assertThat(cache.getUnchecked(i)).isEqualTo(i); expectedLoads++; - assertEquals(expectedLoads, loader.getLoadCount()); - assertEquals(expectedReloads, loader.getReloadCount()); + assertThat(loader.getLoadCount()).isEqualTo(expectedLoads); + assertThat(loader.getReloadCount()).isEqualTo(expectedReloads); ticker.advance(1, MILLISECONDS); } - assertEquals(Integer.valueOf(0), cache.getUnchecked(0)); - assertEquals(Integer.valueOf(1), cache.getUnchecked(1)); - assertEquals(Integer.valueOf(2), cache.getUnchecked(2)); - assertEquals(expectedLoads, loader.getLoadCount()); - assertEquals(expectedReloads, loader.getReloadCount()); + assertThat(cache.getUnchecked(0)).isEqualTo(0); + assertThat(cache.getUnchecked(1)).isEqualTo(1); + assertThat(cache.getUnchecked(2)).isEqualTo(2); + assertThat(loader.getLoadCount()).isEqualTo(expectedLoads); + assertThat(loader.getReloadCount()).isEqualTo(expectedReloads); // refresh 0 ticker.advance(1, MILLISECONDS); - assertEquals(Integer.valueOf(1), cache.getUnchecked(0)); + assertThat(cache.getUnchecked(0)).isEqualTo(1); expectedReloads++; - assertEquals(Integer.valueOf(1), cache.getUnchecked(1)); - assertEquals(Integer.valueOf(2), cache.getUnchecked(2)); - assertEquals(expectedLoads, loader.getLoadCount()); - assertEquals(expectedReloads, loader.getReloadCount()); + assertThat(cache.getUnchecked(1)).isEqualTo(1); + assertThat(cache.getUnchecked(2)).isEqualTo(2); + assertThat(loader.getLoadCount()).isEqualTo(expectedLoads); + assertThat(loader.getReloadCount()).isEqualTo(expectedReloads); // write to 1 to delay its refresh cache.asMap().put(1, -1); ticker.advance(1, MILLISECONDS); - assertEquals(Integer.valueOf(1), cache.getUnchecked(0)); - assertEquals(Integer.valueOf(-1), cache.getUnchecked(1)); - assertEquals(Integer.valueOf(2), cache.getUnchecked(2)); - assertEquals(expectedLoads, loader.getLoadCount()); - assertEquals(expectedReloads, loader.getReloadCount()); + assertThat(cache.getUnchecked(0)).isEqualTo(1); + assertThat(cache.getUnchecked(1)).isEqualTo(-1); + assertThat(cache.getUnchecked(2)).isEqualTo(2); + assertThat(loader.getLoadCount()).isEqualTo(expectedLoads); + assertThat(loader.getReloadCount()).isEqualTo(expectedReloads); // refresh 2 ticker.advance(1, MILLISECONDS); - assertEquals(Integer.valueOf(1), cache.getUnchecked(0)); - assertEquals(Integer.valueOf(-1), cache.getUnchecked(1)); - assertEquals(Integer.valueOf(3), cache.getUnchecked(2)); + assertThat(cache.getUnchecked(0)).isEqualTo(1); + assertThat(cache.getUnchecked(1)).isEqualTo(-1); + assertThat(cache.getUnchecked(2)).isEqualTo(3); expectedReloads++; - assertEquals(expectedLoads, loader.getLoadCount()); - assertEquals(expectedReloads, loader.getReloadCount()); + assertThat(loader.getLoadCount()).isEqualTo(expectedLoads); + assertThat(loader.getReloadCount()).isEqualTo(expectedReloads); ticker.advance(1, MILLISECONDS); - assertEquals(Integer.valueOf(1), cache.getUnchecked(0)); - assertEquals(Integer.valueOf(-1), cache.getUnchecked(1)); - assertEquals(Integer.valueOf(3), cache.getUnchecked(2)); - assertEquals(expectedLoads, loader.getLoadCount()); - assertEquals(expectedReloads, loader.getReloadCount()); + assertThat(cache.getUnchecked(0)).isEqualTo(1); + assertThat(cache.getUnchecked(1)).isEqualTo(-1); + assertThat(cache.getUnchecked(2)).isEqualTo(3); + assertThat(loader.getLoadCount()).isEqualTo(expectedLoads); + assertThat(loader.getReloadCount()).isEqualTo(expectedReloads); // refresh 0 and 1 ticker.advance(1, MILLISECONDS); - assertEquals(Integer.valueOf(2), cache.getUnchecked(0)); + assertThat(cache.getUnchecked(0)).isEqualTo(2); expectedReloads++; - assertEquals(Integer.valueOf(0), cache.getUnchecked(1)); + assertThat(cache.getUnchecked(1)).isEqualTo(0); expectedReloads++; - assertEquals(Integer.valueOf(3), cache.getUnchecked(2)); - assertEquals(expectedLoads, loader.getLoadCount()); - assertEquals(expectedReloads, loader.getReloadCount()); + assertThat(cache.getUnchecked(2)).isEqualTo(3); + assertThat(loader.getLoadCount()).isEqualTo(expectedLoads); + assertThat(loader.getReloadCount()).isEqualTo(expectedReloads); } } diff --git a/android/guava-tests/test/com/google/common/cache/CacheStatsTest.java b/android/guava-tests/test/com/google/common/cache/CacheStatsTest.java index 3e715f1fefdb..72cbfee29807 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheStatsTest.java +++ b/android/guava-tests/test/com/google/common/cache/CacheStatsTest.java @@ -16,45 +16,49 @@ package com.google.common.cache; +import static com.google.common.truth.Truth.assertThat; + import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link CacheStats}. * * @author Charles Fry */ +@NullUnmarked public class CacheStatsTest extends TestCase { public void testEmpty() { CacheStats stats = new CacheStats(0, 0, 0, 0, 0, 0); - assertEquals(0, stats.requestCount()); - assertEquals(0, stats.hitCount()); - assertEquals(1.0, stats.hitRate()); - assertEquals(0, stats.missCount()); - assertEquals(0.0, stats.missRate()); - assertEquals(0, stats.loadSuccessCount()); - assertEquals(0, stats.loadExceptionCount()); - assertEquals(0.0, stats.loadExceptionRate()); - assertEquals(0, stats.loadCount()); - assertEquals(0, stats.totalLoadTime()); - assertEquals(0.0, stats.averageLoadPenalty()); - assertEquals(0, stats.evictionCount()); + assertThat(stats.requestCount()).isEqualTo(0); + assertThat(stats.hitCount()).isEqualTo(0); + assertThat(stats.hitRate()).isEqualTo(1.0); + assertThat(stats.missCount()).isEqualTo(0); + assertThat(stats.missRate()).isEqualTo(0.0); + assertThat(stats.loadSuccessCount()).isEqualTo(0); + assertThat(stats.loadExceptionCount()).isEqualTo(0); + assertThat(stats.loadExceptionRate()).isEqualTo(0.0); + assertThat(stats.loadCount()).isEqualTo(0); + assertThat(stats.totalLoadTime()).isEqualTo(0); + assertThat(stats.averageLoadPenalty()).isEqualTo(0.0); + assertThat(stats.evictionCount()).isEqualTo(0); } public void testSingle() { CacheStats stats = new CacheStats(11, 13, 17, 19, 23, 27); - assertEquals(24, stats.requestCount()); - assertEquals(11, stats.hitCount()); - assertEquals(11.0 / 24, stats.hitRate()); - assertEquals(13, stats.missCount()); - assertEquals(13.0 / 24, stats.missRate()); - assertEquals(17, stats.loadSuccessCount()); - assertEquals(19, stats.loadExceptionCount()); - assertEquals(19.0 / 36, stats.loadExceptionRate()); - assertEquals(17 + 19, stats.loadCount()); - assertEquals(23, stats.totalLoadTime()); - assertEquals(23.0 / (17 + 19), stats.averageLoadPenalty()); - assertEquals(27, stats.evictionCount()); + assertThat(stats.requestCount()).isEqualTo(24); + assertThat(stats.hitCount()).isEqualTo(11); + assertThat(stats.hitRate()).isEqualTo(11.0 / 24); + assertThat(stats.missCount()).isEqualTo(13); + assertThat(stats.missRate()).isEqualTo(13.0 / 24); + assertThat(stats.loadSuccessCount()).isEqualTo(17); + assertThat(stats.loadExceptionCount()).isEqualTo(19); + assertThat(stats.loadExceptionRate()).isEqualTo(19.0 / 36); + assertThat(stats.loadCount()).isEqualTo(17 + 19); + assertThat(stats.totalLoadTime()).isEqualTo(23); + assertThat(stats.averageLoadPenalty()).isEqualTo(23.0 / (17 + 19)); + assertThat(stats.evictionCount()).isEqualTo(27); } public void testMinus() { @@ -62,20 +66,20 @@ public void testMinus() { CacheStats two = new CacheStats(53, 47, 43, 41, 37, 31); CacheStats diff = two.minus(one); - assertEquals(76, diff.requestCount()); - assertEquals(42, diff.hitCount()); - assertEquals(42.0 / 76, diff.hitRate()); - assertEquals(34, diff.missCount()); - assertEquals(34.0 / 76, diff.missRate()); - assertEquals(26, diff.loadSuccessCount()); - assertEquals(22, diff.loadExceptionCount()); - assertEquals(22.0 / 48, diff.loadExceptionRate()); - assertEquals(26 + 22, diff.loadCount()); - assertEquals(14, diff.totalLoadTime()); - assertEquals(14.0 / (26 + 22), diff.averageLoadPenalty()); - assertEquals(4, diff.evictionCount()); + assertThat(diff.requestCount()).isEqualTo(76); + assertThat(diff.hitCount()).isEqualTo(42); + assertThat(diff.hitRate()).isEqualTo(42.0 / 76); + assertThat(diff.missCount()).isEqualTo(34); + assertThat(diff.missRate()).isEqualTo(34.0 / 76); + assertThat(diff.loadSuccessCount()).isEqualTo(26); + assertThat(diff.loadExceptionCount()).isEqualTo(22); + assertThat(diff.loadExceptionRate()).isEqualTo(22.0 / 48); + assertThat(diff.loadCount()).isEqualTo(26 + 22); + assertThat(diff.totalLoadTime()).isEqualTo(14); + assertThat(diff.averageLoadPenalty()).isEqualTo(14.0 / (26 + 22)); + assertThat(diff.evictionCount()).isEqualTo(4); - assertEquals(new CacheStats(0, 0, 0, 0, 0, 0), one.minus(two)); + assertThat(one.minus(two)).isEqualTo(new CacheStats(0, 0, 0, 0, 0, 0)); } public void testPlus() { @@ -83,20 +87,20 @@ public void testPlus() { CacheStats two = new CacheStats(53, 47, 41, 39, 37, 35); CacheStats sum = two.plus(one); - assertEquals(124, sum.requestCount()); - assertEquals(64, sum.hitCount()); - assertEquals(64.0 / 124, sum.hitRate()); - assertEquals(60, sum.missCount()); - assertEquals(60.0 / 124, sum.missRate()); - assertEquals(56, sum.loadSuccessCount()); - assertEquals(52, sum.loadExceptionCount()); - assertEquals(52.0 / 108, sum.loadExceptionRate()); - assertEquals(56 + 52, sum.loadCount()); - assertEquals(48, sum.totalLoadTime()); - assertEquals(48.0 / (56 + 52), sum.averageLoadPenalty()); - assertEquals(44, sum.evictionCount()); + assertThat(sum.requestCount()).isEqualTo(124); + assertThat(sum.hitCount()).isEqualTo(64); + assertThat(sum.hitRate()).isEqualTo(64.0 / 124); + assertThat(sum.missCount()).isEqualTo(60); + assertThat(sum.missRate()).isEqualTo(60.0 / 124); + assertThat(sum.loadSuccessCount()).isEqualTo(56); + assertThat(sum.loadExceptionCount()).isEqualTo(52); + assertThat(sum.loadExceptionRate()).isEqualTo(52.0 / 108); + assertThat(sum.loadCount()).isEqualTo(56 + 52); + assertThat(sum.totalLoadTime()).isEqualTo(48); + assertThat(sum.averageLoadPenalty()).isEqualTo(48.0 / (56 + 52)); + assertThat(sum.evictionCount()).isEqualTo(44); - assertEquals(sum, one.plus(two)); + assertThat(one.plus(two)).isEqualTo(sum); } public void testPlusLarge() { @@ -111,19 +115,19 @@ public void testPlusLarge() { CacheStats smallCacheStats = new CacheStats(1, 1, 1, 1, 1, 1); CacheStats sum = smallCacheStats.plus(maxCacheStats); - assertEquals(Long.MAX_VALUE, sum.requestCount()); - assertEquals(Long.MAX_VALUE, sum.hitCount()); - assertEquals(1.0, sum.hitRate()); - assertEquals(Long.MAX_VALUE, sum.missCount()); - assertEquals(1.0, sum.missRate()); - assertEquals(Long.MAX_VALUE, sum.loadSuccessCount()); - assertEquals(Long.MAX_VALUE, sum.loadExceptionCount()); - assertEquals(1.0, sum.loadExceptionRate()); - assertEquals(Long.MAX_VALUE, sum.loadCount()); - assertEquals(Long.MAX_VALUE, sum.totalLoadTime()); - assertEquals(1.0, sum.averageLoadPenalty()); - assertEquals(Long.MAX_VALUE, sum.evictionCount()); + assertThat(sum.requestCount()).isEqualTo(Long.MAX_VALUE); + assertThat(sum.hitCount()).isEqualTo(Long.MAX_VALUE); + assertThat(sum.hitRate()).isEqualTo(1.0); + assertThat(sum.missCount()).isEqualTo(Long.MAX_VALUE); + assertThat(sum.missRate()).isEqualTo(1.0); + assertThat(sum.loadSuccessCount()).isEqualTo(Long.MAX_VALUE); + assertThat(sum.loadExceptionCount()).isEqualTo(Long.MAX_VALUE); + assertThat(sum.loadExceptionRate()).isEqualTo(1.0); + assertThat(sum.loadCount()).isEqualTo(Long.MAX_VALUE); + assertThat(sum.totalLoadTime()).isEqualTo(Long.MAX_VALUE); + assertThat(sum.averageLoadPenalty()).isEqualTo(1.0); + assertThat(sum.evictionCount()).isEqualTo(Long.MAX_VALUE); - assertEquals(sum, maxCacheStats.plus(smallCacheStats)); + assertThat(maxCacheStats.plus(smallCacheStats)).isEqualTo(sum); } } diff --git a/android/guava-tests/test/com/google/common/cache/CacheTesting.java b/android/guava-tests/test/com/google/common/cache/CacheTesting.java index 255a89480ff2..6c33cde9d009 100644 --- a/android/guava-tests/test/com/google/common/cache/CacheTesting.java +++ b/android/guava-tests/test/com/google/common/cache/CacheTesting.java @@ -16,13 +16,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNotSame; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertSame; -import static junit.framework.Assert.assertTrue; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.lang.Math.max; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import com.google.common.base.Preconditions; import com.google.common.cache.LocalCache.LocalLoadingCache; @@ -31,20 +27,20 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.testing.EqualsTester; import com.google.common.testing.FakeTicker; import java.lang.ref.Reference; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReferenceArray; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * A collection of utilities for {@link Cache} testing. @@ -52,7 +48,8 @@ * @author mike nonemacher */ @SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress. -class CacheTesting { +@NullUnmarked +final class CacheTesting { /** * Poke into the Cache internals to simulate garbage collection of the value associated with the @@ -79,7 +76,6 @@ static void simulateValueReclamation(Cache cache, K key) { * that the given entry is a weak or soft reference, and throws an IllegalStateException if that * assumption does not hold. */ - @SuppressWarnings("unchecked") // the instanceof check and the cast generate this warning static void simulateKeyReclamation(Cache cache, K key) { ReferenceEntry entry = getReferenceEntry(cache, key); @@ -98,7 +94,7 @@ static ReferenceEntry getReferenceEntry(Cache cache, K key) { } /** - * Forces the segment containing the given {@code key} to expand (see {@link Segment#expand()}. + * Forces the segment containing the given {@code key} to expand (see {@link Segment#expand()}). */ static void forceExpandSegment(Cache cache, K key) { checkNotNull(cache); @@ -126,7 +122,7 @@ static LocalCache toLocalCache(Cache cache) { * without throwing an exception. */ static boolean hasLocalCache(Cache cache) { - return (checkNotNull(cache) instanceof LocalLoadingCache); + return checkNotNull(cache) instanceof LocalLoadingCache; } static void drainRecencyQueues(Cache cache) { @@ -168,9 +164,9 @@ static void drainReferenceQueue(LocalCache.Segment segment) { } } - static int getTotalSegmentSize(Cache cache) { + static long getTotalSegmentSize(Cache cache) { LocalCache map = toLocalCache(cache); - int totalSize = 0; + long totalSize = 0; for (Segment segment : map.segments) { totalSize += segment.maxSegmentWeight; } @@ -192,16 +188,16 @@ static void checkValidState(Cache cache) { static void checkValidState(LocalCache cchm) { for (Segment segment : cchm.segments) { segment.cleanUp(); - assertFalse(segment.isLocked()); + assertThat(segment.isLocked()).isFalse(); Map table = segmentTable(segment); // cleanup and then check count after we have a strong reference to all entries segment.cleanUp(); // under high memory pressure keys/values may be nulled out but not yet enqueued assertThat(table.size()).isAtMost(segment.count); for (Entry entry : table.entrySet()) { - assertNotNull(entry.getKey()); - assertNotNull(entry.getValue()); - assertSame(entry.getValue(), cchm.get(entry.getKey())); + assertThat(entry.getKey()).isNotNull(); + assertThat(entry.getValue()).isNotNull(); + assertThat(cchm.get(entry.getKey())).isSameInstanceAs(entry.getValue()); } } checkEviction(cchm); @@ -226,21 +222,21 @@ static void checkExpiration(LocalCache cchm) { ReferenceEntry prev = null; for (ReferenceEntry current : segment.writeQueue) { - assertTrue(entries.add(current)); + assertThat(entries.add(current)).isTrue(); if (prev != null) { - assertSame(prev, current.getPreviousInWriteQueue()); - assertSame(prev.getNextInWriteQueue(), current); + assertThat(current.getPreviousInWriteQueue()).isSameInstanceAs(prev); + assertThat(current).isSameInstanceAs(prev.getNextInWriteQueue()); assertThat(prev.getWriteTime()).isAtMost(current.getWriteTime()); } Object key = current.getKey(); if (key != null) { - assertSame(current, segment.getEntry(key, current.getHash())); + assertThat(segment.getEntry(key, current.getHash())).isSameInstanceAs(current); } prev = current; } - assertEquals(segment.count, entries.size()); + assertThat(entries).hasSize(segment.count); } else { - assertTrue(segment.writeQueue.isEmpty()); + assertThat(segment.writeQueue.isEmpty()).isTrue(); } if (cchm.usesAccessQueue()) { @@ -248,24 +244,25 @@ static void checkExpiration(LocalCache cchm) { ReferenceEntry prev = null; for (ReferenceEntry current : segment.accessQueue) { - assertTrue(entries.add(current)); + assertThat(entries.add(current)).isTrue(); if (prev != null) { - assertSame(prev, current.getPreviousInAccessQueue()); - assertSame(prev.getNextInAccessQueue(), current); + assertThat(current.getPreviousInAccessQueue()).isSameInstanceAs(prev); + assertThat(current).isSameInstanceAs(prev.getNextInAccessQueue()); // read accesses may be slightly misordered - assertTrue( - prev.getAccessTime() <= current.getAccessTime() - || prev.getAccessTime() - current.getAccessTime() < 1000); + assertThat( + prev.getAccessTime() <= current.getAccessTime() + || prev.getAccessTime() - current.getAccessTime() < 1000) + .isTrue(); } Object key = current.getKey(); if (key != null) { - assertSame(current, segment.getEntry(key, current.getHash())); + assertThat(segment.getEntry(key, current.getHash())).isSameInstanceAs(current); } prev = current; } - assertEquals(segment.count, entries.size()); + assertThat(entries).hasSize(segment.count); } else { - assertTrue(segment.accessQueue.isEmpty()); + assertThat(segment.accessQueue).isEmpty(); } } } @@ -285,25 +282,25 @@ static void checkEviction(LocalCache map) { if (map.evictsBySize()) { for (Segment segment : map.segments) { drainRecencyQueue(segment); - assertEquals(0, segment.recencyQueue.size()); - assertEquals(0, segment.readCount.get()); + assertThat(segment.recencyQueue).isEmpty(); + assertThat(segment.readCount.get()).isEqualTo(0); ReferenceEntry prev = null; for (ReferenceEntry current : segment.accessQueue) { if (prev != null) { - assertSame(prev, current.getPreviousInAccessQueue()); - assertSame(prev.getNextInAccessQueue(), current); + assertThat(current.getPreviousInAccessQueue()).isSameInstanceAs(prev); + assertThat(current).isSameInstanceAs(prev.getNextInAccessQueue()); } Object key = current.getKey(); if (key != null) { - assertSame(current, segment.getEntry(key, current.getHash())); + assertThat(segment.getEntry(key, current.getHash())).isSameInstanceAs(current); } prev = current; } } } else { for (Segment segment : map.segments) { - assertEquals(0, segment.recencyQueue.size()); + assertThat(segment.recencyQueue).isEmpty(); } } } @@ -315,13 +312,13 @@ static int segmentSize(Segment segment) { static Map segmentTable(Segment segment) { AtomicReferenceArray> table = segment.table; - Map map = Maps.newLinkedHashMap(); + Map map = new LinkedHashMap<>(); for (int i = 0; i < table.length(); i++) { for (ReferenceEntry entry = table.get(i); entry != null; entry = entry.getNext()) { K key = entry.getKey(); V value = entry.getValueReference().get(); if (key != null && value != null) { - assertNull(map.put(key, value)); + assertThat(map.put(key, value)).isNull(); } } } @@ -356,7 +353,7 @@ static int accessQueueSize(Segment segment) { } static int expirationQueueSize(Cache cache) { - return Math.max(accessQueueSize(cache), writeQueueSize(cache)); + return max(accessQueueSize(cache), writeQueueSize(cache)); } static void processPendingNotifications(Cache cache) { @@ -367,7 +364,7 @@ static void processPendingNotifications(Cache cache) { } interface Receiver { - void accept(@NullableDecl T object); + void accept(@Nullable T object); } /** @@ -388,8 +385,8 @@ static void checkRecency( LocalCache cchm = toLocalCache(cache); Segment segment = cchm.segments[0]; drainRecencyQueue(segment); - assertEquals(maxSize, accessQueueSize(cache)); - assertEquals(maxSize, cache.size()); + assertThat(accessQueueSize(cache)).isEqualTo(maxSize); + assertThat(cache.size()).isEqualTo(maxSize); ReferenceEntry originalHead = segment.accessQueue.peek(); @SuppressWarnings("unchecked") @@ -397,8 +394,8 @@ static void checkRecency( operation.accept(entry); drainRecencyQueue(segment); - assertNotSame(originalHead, segment.accessQueue.peek()); - assertEquals(cache.size(), accessQueueSize(cache)); + assertThat(segment.accessQueue.peek()).isNotSameInstanceAs(originalHead); + assertThat(accessQueueSize(cache)).isEqualTo(cache.size()); } } @@ -421,14 +418,18 @@ static void expireEntries(LocalCache cchm, long expiringTime, FakeTicker t drainRecencyQueue(segment); } - ticker.advance(2 * expiringTime, TimeUnit.MILLISECONDS); + ticker.advance(2 * expiringTime, MILLISECONDS); long now = ticker.read(); for (Segment segment : cchm.segments) { expireEntries(segment, now); - assertEquals("Expiration queue must be empty by now", 0, writeQueueSize(segment)); - assertEquals("Expiration queue must be empty by now", 0, accessQueueSize(segment)); - assertEquals("Segments must be empty by now", 0, segmentSize(segment)); + assertWithMessage("Expiration queue must be empty by now") + .that(writeQueueSize(segment)) + .isEqualTo(0); + assertWithMessage("Expiration queue must be empty by now") + .that(accessQueueSize(segment)) + .isEqualTo(0); + assertWithMessage("Segments must be empty by now").that(segmentSize(segment)).isEqualTo(0); } cchm.processPendingNotifications(); } @@ -444,11 +445,11 @@ static void expireEntries(Segment segment, long now) { } static void checkEmpty(Cache cache) { - assertEquals(0, cache.size()); - assertFalse(cache.asMap().containsKey(null)); - assertFalse(cache.asMap().containsKey(6)); - assertFalse(cache.asMap().containsValue(null)); - assertFalse(cache.asMap().containsValue(6)); + assertThat(cache.size()).isEqualTo(0); + assertThat(cache.asMap().containsKey(null)).isFalse(); + assertThat(cache.asMap().containsKey(6)).isFalse(); + assertThat(cache.asMap().containsValue(null)).isFalse(); + assertThat(cache.asMap().containsValue(6)).isFalse(); checkEmpty(cache.asMap()); } @@ -456,29 +457,29 @@ static void checkEmpty(ConcurrentMap map) { checkEmpty(map.keySet()); checkEmpty(map.values()); checkEmpty(map.entrySet()); - assertEquals(ImmutableMap.of(), map); - assertEquals(ImmutableMap.of().hashCode(), map.hashCode()); - assertEquals(ImmutableMap.of().toString(), map.toString()); + assertThat(map).isEqualTo(ImmutableMap.of()); + assertThat(map.hashCode()).isEqualTo(ImmutableMap.of().hashCode()); + assertThat(map.toString()).isEqualTo(ImmutableMap.of().toString()); if (map instanceof LocalCache) { LocalCache cchm = (LocalCache) map; checkValidState(cchm); - assertTrue(cchm.isEmpty()); - assertEquals(0, cchm.size()); + assertThat(cchm.isEmpty()).isTrue(); + assertThat(cchm).isEmpty(); for (LocalCache.Segment segment : cchm.segments) { - assertEquals(0, segment.count); - assertEquals(0, segmentSize(segment)); - assertTrue(segment.writeQueue.isEmpty()); - assertTrue(segment.accessQueue.isEmpty()); + assertThat(segment.count).isEqualTo(0); + assertThat(segmentSize(segment)).isEqualTo(0); + assertThat(segment.writeQueue.isEmpty()).isTrue(); + assertThat(segment.accessQueue.isEmpty()).isTrue(); } } } static void checkEmpty(Collection collection) { - assertTrue(collection.isEmpty()); - assertEquals(0, collection.size()); - assertFalse(collection.iterator().hasNext()); + assertThat(collection.isEmpty()).isTrue(); + assertThat(collection).isEmpty(); + assertThat(collection.iterator().hasNext()).isFalse(); assertThat(collection.toArray()).isEmpty(); assertThat(collection.toArray(new Object[0])).isEmpty(); if (collection instanceof Set) { @@ -493,4 +494,6 @@ static void checkEmpty(Collection collection) { .testEquals(); } } + + private CacheTesting() {} } diff --git a/android/guava-tests/test/com/google/common/cache/EmptyCachesTest.java b/android/guava-tests/test/com/google/common/cache/EmptyCachesTest.java index a5e3a59a01ff..94a912347d9d 100644 --- a/android/guava-tests/test/com/google/common/cache/EmptyCachesTest.java +++ b/android/guava-tests/test/com/google/common/cache/EmptyCachesTest.java @@ -16,9 +16,11 @@ import static com.google.common.cache.CacheTesting.checkEmpty; import static com.google.common.cache.TestingCacheLoaders.identityLoader; +import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.base.Function; import com.google.common.cache.CacheBuilderFactory.DurationSpec; @@ -32,6 +34,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * {@link LoadingCache} tests that deal with empty caches. @@ -39,6 +42,7 @@ * @author mike nonemacher */ +@NullUnmarked public class EmptyCachesTest extends TestCase { public void testEmpty() { @@ -72,7 +76,7 @@ public void testInvalidateAll_empty() { public void testEquals_null() { for (LoadingCache cache : caches()) { - assertFalse(cache.equals(null)); + assertThat(cache.equals(null)).isFalse(); } } @@ -89,22 +93,14 @@ public void testEqualsAndHashCode_different() { public void testGet_null() throws ExecutionException { for (LoadingCache cache : caches()) { - try { - cache.get(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> cache.get(null)); checkEmpty(cache); } } public void testGetUnchecked_null() { for (LoadingCache cache : caches()) { - try { - cache.getUnchecked(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> cache.getUnchecked(null)); checkEmpty(cache); } } @@ -114,28 +110,17 @@ public void testGetUnchecked_null() { public void testKeySet_nullToArray() { for (LoadingCache cache : caches()) { Set keys = cache.asMap().keySet(); - try { - keys.toArray((Object[]) null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> keys.toArray((Object[]) null)); checkEmpty(cache); } } public void testKeySet_addNotSupported() { for (LoadingCache cache : caches()) { - try { - cache.asMap().keySet().add(1); - fail(); - } catch (UnsupportedOperationException expected) { - } - - try { - cache.asMap().keySet().addAll(asList(1, 2)); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> cache.asMap().keySet().add(1)); + + assertThrows( + UnsupportedOperationException.class, () -> cache.asMap().keySet().addAll(asList(1, 2))); } } @@ -154,11 +139,11 @@ public void testKeySet_clear() { public void testKeySet_empty_remove() { for (LoadingCache cache : caches()) { Set keys = cache.asMap().keySet(); - assertFalse(keys.remove(null)); - assertFalse(keys.remove(6)); - assertFalse(keys.remove(-6)); - assertFalse(keys.removeAll(asList(null, 0, 15, 1500))); - assertFalse(keys.retainAll(asList(null, 0, 15, 1500))); + assertThat(keys.remove(null)).isFalse(); + assertThat(keys.remove(6)).isFalse(); + assertThat(keys.remove(-6)).isFalse(); + assertThat(keys.removeAll(asList(null, 0, 15, 1500))).isFalse(); + assertThat(keys.retainAll(asList(null, 0, 15, 1500))).isFalse(); checkEmpty(keys); checkEmpty(cache); } @@ -174,11 +159,11 @@ public void testKeySet_remove() { // values of these removes, but the cache should be empty after the removes, regardless. keys.remove(1); keys.remove(2); - assertFalse(keys.remove(null)); - assertFalse(keys.remove(6)); - assertFalse(keys.remove(-6)); - assertFalse(keys.removeAll(asList(null, 0, 15, 1500))); - assertFalse(keys.retainAll(asList(null, 0, 15, 1500))); + assertThat(keys.remove(null)).isFalse(); + assertThat(keys.remove(6)).isFalse(); + assertThat(keys.remove(-6)).isFalse(); + assertThat(keys.removeAll(asList(null, 0, 15, 1500))).isFalse(); + assertThat(keys.retainAll(asList(null, 0, 15, 1500))).isFalse(); checkEmpty(keys); checkEmpty(cache); } @@ -189,28 +174,17 @@ public void testKeySet_remove() { public void testValues_nullToArray() { for (LoadingCache cache : caches()) { Collection values = cache.asMap().values(); - try { - values.toArray((Object[]) null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> values.toArray((Object[]) null)); checkEmpty(cache); } } public void testValues_addNotSupported() { for (LoadingCache cache : caches()) { - try { - cache.asMap().values().add(1); - fail(); - } catch (UnsupportedOperationException expected) { - } - - try { - cache.asMap().values().addAll(asList(1, 2)); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> cache.asMap().values().add(1)); + + assertThrows( + UnsupportedOperationException.class, () -> cache.asMap().values().addAll(asList(1, 2))); } } @@ -229,11 +203,11 @@ public void testValues_clear() { public void testValues_empty_remove() { for (LoadingCache cache : caches()) { Collection values = cache.asMap().values(); - assertFalse(values.remove(null)); - assertFalse(values.remove(6)); - assertFalse(values.remove(-6)); - assertFalse(values.removeAll(asList(null, 0, 15, 1500))); - assertFalse(values.retainAll(asList(null, 0, 15, 1500))); + assertThat(values.remove(null)).isFalse(); + assertThat(values.remove(6)).isFalse(); + assertThat(values.remove(-6)).isFalse(); + assertThat(values.removeAll(asList(null, 0, 15, 1500))).isFalse(); + assertThat(values.retainAll(asList(null, 0, 15, 1500))).isFalse(); checkEmpty(values); checkEmpty(cache); } @@ -249,11 +223,11 @@ public void testValues_remove() { // values of these removes, but the cache should be empty after the removes, regardless. values.remove(1); values.remove(2); - assertFalse(values.remove(null)); - assertFalse(values.remove(6)); - assertFalse(values.remove(-6)); - assertFalse(values.removeAll(asList(null, 0, 15, 1500))); - assertFalse(values.retainAll(asList(null, 0, 15, 1500))); + assertThat(values.remove(null)).isFalse(); + assertThat(values.remove(6)).isFalse(); + assertThat(values.remove(-6)).isFalse(); + assertThat(values.removeAll(asList(null, 0, 15, 1500))).isFalse(); + assertThat(values.retainAll(asList(null, 0, 15, 1500))).isFalse(); checkEmpty(values); checkEmpty(cache); } @@ -264,28 +238,20 @@ public void testValues_remove() { public void testEntrySet_nullToArray() { for (LoadingCache cache : caches()) { Set> entries = cache.asMap().entrySet(); - try { - entries.toArray((Entry[]) null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> entries.toArray((Entry[]) null)); checkEmpty(cache); } } public void testEntrySet_addNotSupported() { for (LoadingCache cache : caches()) { - try { - cache.asMap().entrySet().add(entryOf(1, 1)); - fail(); - } catch (UnsupportedOperationException expected) { - } - - try { - cache.asMap().values().addAll(asList(entryOf(1, 1), entryOf(2, 2))); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> cache.asMap().entrySet().add(entryOf(1, 1))); + + assertThrows( + UnsupportedOperationException.class, + () -> cache.asMap().values().addAll(asList(entryOf(1, 1), entryOf(2, 2)))); } } @@ -304,11 +270,11 @@ public void testEntrySet_clear() { public void testEntrySet_empty_remove() { for (LoadingCache cache : caches()) { Set> entrySet = cache.asMap().entrySet(); - assertFalse(entrySet.remove(null)); - assertFalse(entrySet.remove(entryOf(6, 6))); - assertFalse(entrySet.remove(entryOf(-6, -6))); - assertFalse(entrySet.removeAll(asList(null, entryOf(0, 0), entryOf(15, 15)))); - assertFalse(entrySet.retainAll(asList(null, entryOf(0, 0), entryOf(15, 15)))); + assertThat(entrySet.remove(null)).isFalse(); + assertThat(entrySet.remove(entryOf(6, 6))).isFalse(); + assertThat(entrySet.remove(entryOf(-6, -6))).isFalse(); + assertThat(entrySet.removeAll(asList(null, entryOf(0, 0), entryOf(15, 15)))).isFalse(); + assertThat(entrySet.retainAll(asList(null, entryOf(0, 0), entryOf(15, 15)))).isFalse(); checkEmpty(entrySet); checkEmpty(cache); } @@ -324,11 +290,11 @@ public void testEntrySet_remove() { // values of these removes, but the cache should be empty after the removes, regardless. entrySet.remove(entryOf(1, 1)); entrySet.remove(entryOf(2, 2)); - assertFalse(entrySet.remove(null)); - assertFalse(entrySet.remove(entryOf(1, 1))); - assertFalse(entrySet.remove(entryOf(6, 6))); - assertFalse(entrySet.removeAll(asList(null, entryOf(1, 1), entryOf(15, 15)))); - assertFalse(entrySet.retainAll(asList(null, entryOf(1, 1), entryOf(15, 15)))); + assertThat(entrySet.remove(null)).isFalse(); + assertThat(entrySet.remove(entryOf(1, 1))).isFalse(); + assertThat(entrySet.remove(entryOf(6, 6))).isFalse(); + assertThat(entrySet.removeAll(asList(null, entryOf(1, 1), entryOf(15, 15)))).isFalse(); + assertThat(entrySet.retainAll(asList(null, entryOf(1, 1), entryOf(15, 15)))).isFalse(); checkEmpty(entrySet); checkEmpty(cache); } diff --git a/android/guava-tests/test/com/google/common/cache/ForwardingCacheTest.java b/android/guava-tests/test/com/google/common/cache/ForwardingCacheTest.java index 412ff7802f1a..9eb3fe7e33c7 100644 --- a/android/guava-tests/test/com/google/common/cache/ForwardingCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/ForwardingCacheTest.java @@ -16,20 +16,24 @@ package com.google.common.cache; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Keep; import java.util.concurrent.ExecutionException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link ForwardingCache}. * * @author Charles Fry */ +@NullUnmarked public class ForwardingCacheTest extends TestCase { private Cache forward; private Cache mock; @@ -54,15 +58,13 @@ protected Cache delegate() { } public void testGetIfPresent() throws ExecutionException { - when(mock.getIfPresent("key")).thenReturn(Boolean.TRUE); - assertSame(Boolean.TRUE, forward.getIfPresent("key")); + when(mock.getIfPresent("key")).thenReturn(true); + assertThat(forward.getIfPresent("key")).isSameInstanceAs(true); } public void testGetAllPresent() throws ExecutionException { - when(mock.getAllPresent(ImmutableList.of("key"))) - .thenReturn(ImmutableMap.of("key", Boolean.TRUE)); - assertEquals( - ImmutableMap.of("key", Boolean.TRUE), forward.getAllPresent(ImmutableList.of("key"))); + when(mock.getAllPresent(ImmutableList.of("key"))).thenReturn(ImmutableMap.of("key", true)); + assertThat(forward.getAllPresent(ImmutableList.of("key"))).containsExactly("key", true); } public void testInvalidate() { @@ -82,17 +84,17 @@ public void testInvalidateAll() { public void testSize() { when(mock.size()).thenReturn(0L); - assertEquals(0, forward.size()); + assertThat(forward.size()).isEqualTo(0); } public void testStats() { when(mock.stats()).thenReturn(null); - assertNull(forward.stats()); + assertThat(forward.stats()).isNull(); } public void testAsMap() { when(mock.asMap()).thenReturn(null); - assertNull(forward.asMap()); + assertThat(forward.asMap()).isNull(); } public void testCleanUp() { @@ -101,10 +103,11 @@ public void testCleanUp() { } /** Make sure that all methods are forwarded. */ + @Keep private static class OnlyGet extends ForwardingCache { @Override protected Cache delegate() { - return null; + throw new AssertionError(); } } } diff --git a/android/guava-tests/test/com/google/common/cache/ForwardingLoadingCacheTest.java b/android/guava-tests/test/com/google/common/cache/ForwardingLoadingCacheTest.java index d78db2d375cc..89029ab0ef67 100644 --- a/android/guava-tests/test/com/google/common/cache/ForwardingLoadingCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/ForwardingLoadingCacheTest.java @@ -16,20 +16,24 @@ package com.google.common.cache; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Keep; import java.util.concurrent.ExecutionException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link ForwardingLoadingCache}. * * @author Charles Fry */ +@NullUnmarked public class ForwardingLoadingCacheTest extends TestCase { private LoadingCache forward; private LoadingCache mock; @@ -54,23 +58,23 @@ protected LoadingCache delegate() { } public void testGet() throws ExecutionException { - when(mock.get("key")).thenReturn(Boolean.TRUE); - assertSame(Boolean.TRUE, forward.get("key")); + when(mock.get("key")).thenReturn(true); + assertThat(forward.get("key")).isSameInstanceAs(true); } public void testGetUnchecked() { - when(mock.getUnchecked("key")).thenReturn(Boolean.TRUE); - assertSame(Boolean.TRUE, forward.getUnchecked("key")); + when(mock.getUnchecked("key")).thenReturn(true); + assertThat(forward.getUnchecked("key")).isSameInstanceAs(true); } public void testGetAll() throws ExecutionException { - when(mock.getAll(ImmutableList.of("key"))).thenReturn(ImmutableMap.of("key", Boolean.TRUE)); - assertEquals(ImmutableMap.of("key", Boolean.TRUE), forward.getAll(ImmutableList.of("key"))); + when(mock.getAll(ImmutableList.of("key"))).thenReturn(ImmutableMap.of("key", true)); + assertThat(forward.getAll(ImmutableList.of("key"))).containsExactly("key", true); } public void testApply() { - when(mock.apply("key")).thenReturn(Boolean.TRUE); - assertSame(Boolean.TRUE, forward.apply("key")); + when(mock.apply("key")).thenReturn(true); + assertThat(forward.apply("key")).isSameInstanceAs(true); } public void testInvalidate() { @@ -95,12 +99,12 @@ public void testSize() { public void testStats() { when(mock.stats()).thenReturn(null); - assertNull(forward.stats()); + assertThat(forward.stats()).isNull(); } public void testAsMap() { when(mock.asMap()).thenReturn(null); - assertNull(forward.asMap()); + assertThat(forward.asMap()).isNull(); } public void testCleanUp() { @@ -109,10 +113,11 @@ public void testCleanUp() { } /** Make sure that all methods are forwarded. */ + @Keep private static class OnlyGet extends ForwardingLoadingCache { @Override protected LoadingCache delegate() { - return null; + throw new AssertionError(); } } } diff --git a/android/guava-tests/test/com/google/common/cache/LocalCacheTest.java b/android/guava-tests/test/com/google/common/cache/LocalCacheTest.java index 3c4529cafc0f..fa1f2999148c 100644 --- a/android/guava-tests/test/com/google/common/cache/LocalCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/LocalCacheTest.java @@ -25,9 +25,14 @@ import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener; import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener; import static com.google.common.cache.TestingWeighers.constantWeigher; -import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; +import static java.lang.Math.max; +import static java.lang.Thread.State.WAITING; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -47,8 +52,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; +import com.google.common.collect.Iterables; import com.google.common.collect.testing.ConcurrentMapTestSuiteBuilder; import com.google.common.collect.testing.TestStringMapGenerator; import com.google.common.collect.testing.features.CollectionFeature; @@ -58,11 +62,17 @@ import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import com.google.common.testing.TestLogHandler; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.SettableFuture; +import com.google.common.util.concurrent.UncheckedExecutionException; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -76,10 +86,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; -/** @author Charles Fry */ +/** + * @author Charles Fry + */ @SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress. +@NullUnmarked public class LocalCacheTest extends TestCase { + @AndroidIncompatible private static class TestStringCacheGenerator extends TestStringMapGenerator { private final CacheBuilder builder; @@ -97,7 +113,7 @@ protected Map create(Entry[] entries) { } } - + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(LocalCacheTest.class); @@ -232,25 +248,38 @@ public void tearDown() throws Exception { private Throwable popLoggedThrowable() { List logRecords = logHandler.getStoredLogRecords(); - assertSame(1, logRecords.size()); + assertThat(logRecords).hasSize(1); LogRecord logRecord = logRecords.get(0); logHandler.clear(); return logRecord.getThrown(); } private void checkNothingLogged() { - assertTrue(logHandler.getStoredLogRecords().isEmpty()); + assertThat(logHandler.getStoredLogRecords().isEmpty()).isTrue(); } private void checkLogged(Throwable t) { - assertSame(t, popLoggedThrowable()); + assertThat(popLoggedThrowable()).isSameInstanceAs(t); } + /* + * TODO(cpovirk): Can we replace makeLocalCache with a call to builder.build()? Some tests may + * need access to LocalCache APIs, but maybe we can at least make makeLocalCache use + * builder.build() and then cast? + */ + private static LocalCache makeLocalCache( CacheBuilder builder) { return new LocalCache<>(builder, null); } + private static LocalCache makeLocalCache( + CacheBuilder builder, CacheLoader loader) { + return new LocalCache<>(builder, loader); + } + + // TODO(cpovirk): Inline createCacheBuilder()? + private static CacheBuilder createCacheBuilder() { return CacheBuilder.newBuilder(); } @@ -260,33 +289,33 @@ private static CacheBuilder createCacheBuilder() { public void testDefaults() { LocalCache map = makeLocalCache(createCacheBuilder()); - assertSame(Strength.STRONG, map.keyStrength); - assertSame(Strength.STRONG, map.valueStrength); - assertSame(map.keyStrength.defaultEquivalence(), map.keyEquivalence); - assertSame(map.valueStrength.defaultEquivalence(), map.valueEquivalence); + assertThat(map.keyStrength).isEqualTo(Strength.STRONG); + assertThat(map.valueStrength).isEqualTo(Strength.STRONG); + assertThat(map.keyEquivalence).isSameInstanceAs(map.keyStrength.defaultEquivalence()); + assertThat(map.valueEquivalence).isSameInstanceAs(map.valueStrength.defaultEquivalence()); - assertEquals(0, map.expireAfterAccessNanos); - assertEquals(0, map.expireAfterWriteNanos); - assertEquals(0, map.refreshNanos); - assertEquals(CacheBuilder.UNSET_INT, map.maxWeight); + assertThat(map.expireAfterAccessNanos).isEqualTo(0); + assertThat(map.expireAfterWriteNanos).isEqualTo(0); + assertThat(map.refreshNanos).isEqualTo(0); + assertThat(map.maxWeight).isEqualTo(CacheBuilder.UNSET_INT); - assertSame(EntryFactory.STRONG, map.entryFactory); - assertSame(CacheBuilder.NullListener.INSTANCE, map.removalListener); - assertSame(DISCARDING_QUEUE, map.removalNotificationQueue); - assertSame(NULL_TICKER, map.ticker); + assertThat(map.entryFactory).isSameInstanceAs(EntryFactory.STRONG); + assertThat(map.removalListener).isSameInstanceAs(CacheBuilder.NullListener.INSTANCE); + assertThat(map.removalNotificationQueue).isSameInstanceAs(DISCARDING_QUEUE); + assertThat(map.ticker).isSameInstanceAs(NULL_TICKER); - assertEquals(4, map.concurrencyLevel); + assertThat(map.concurrencyLevel).isEqualTo(4); // concurrency level assertThat(map.segments).hasLength(4); // initial capacity / concurrency level - assertEquals(16 / map.segments.length, map.segments[0].table.length()); + assertThat(map.segments[0].table.length()).isEqualTo(16 / map.segments.length); - assertFalse(map.evictsBySize()); - assertFalse(map.expires()); - assertFalse(map.expiresAfterWrite()); - assertFalse(map.expiresAfterAccess()); - assertFalse(map.refreshes()); + assertThat(map.evictsBySize()).isFalse(); + assertThat(map.expires()).isFalse(); + assertThat(map.expiresAfterWrite()).isFalse(); + assertThat(map.expiresAfterAccess()).isFalse(); + assertThat(map.refreshes()).isFalse(); } public void testSetKeyEquivalence() { @@ -305,8 +334,8 @@ protected int doHash(Object t) { LocalCache map = makeLocalCache(createCacheBuilder().keyEquivalence(testEquivalence)); - assertSame(testEquivalence, map.keyEquivalence); - assertSame(map.valueStrength.defaultEquivalence(), map.valueEquivalence); + assertThat(map.keyEquivalence).isSameInstanceAs(testEquivalence); + assertThat(map.valueEquivalence).isSameInstanceAs(map.valueStrength.defaultEquivalence()); } public void testSetValueEquivalence() { @@ -325,12 +354,12 @@ protected int doHash(Object t) { LocalCache map = makeLocalCache(createCacheBuilder().valueEquivalence(testEquivalence)); - assertSame(testEquivalence, map.valueEquivalence); - assertSame(map.keyStrength.defaultEquivalence(), map.keyEquivalence); + assertThat(map.valueEquivalence).isSameInstanceAs(testEquivalence); + assertThat(map.keyEquivalence).isSameInstanceAs(map.keyStrength.defaultEquivalence()); } public void testSetConcurrencyLevel() { - // round up to nearest power of two + // round up to the nearest power of two checkConcurrencyLevel(1, 1); checkConcurrencyLevel(2, 2); @@ -349,7 +378,7 @@ private static void checkConcurrencyLevel(int concurrencyLevel, int segmentCount } public void testSetInitialCapacity() { - // share capacity over each segment, then round up to nearest power of two + // share capacity over each segment, then round up to the nearest power of two checkInitialCapacity(1, 0, 1); checkInitialCapacity(1, 1, 1); @@ -390,7 +419,7 @@ private static void checkInitialCapacity( .concurrencyLevel(concurrencyLevel) .initialCapacity(initialCapacity)); for (int i = 0; i < map.segments.length; i++) { - assertEquals(segmentSize, map.segments[i].table.length()); + assertThat(map.segments[i].table.length()).isEqualTo(segmentSize); } } @@ -427,13 +456,15 @@ private static void checkMaximumSize(int concurrencyLevel, int initialCapacity, .initialCapacity(initialCapacity) .maximumSize(maxSize)); long totalCapacity = 0; - assertTrue( - "segments=" + map.segments.length + ", maxSize=" + maxSize, - map.segments.length <= Math.max(1, maxSize / 10)); + assertWithMessage("segments=%s, maxSize=%s", map.segments.length, maxSize) + .that((long) map.segments.length) + .isAtMost(max(1, maxSize / 10)); for (int i = 0; i < map.segments.length; i++) { totalCapacity += map.segments[i].maxSegmentWeight; } - assertTrue("totalCapacity=" + totalCapacity + ", maxSize=" + maxSize, totalCapacity == maxSize); + assertWithMessage("totalCapacity=%s, maxSize=%s", totalCapacity, maxSize) + .that(totalCapacity) + .isEqualTo(maxSize); map = makeLocalCache( @@ -442,14 +473,16 @@ private static void checkMaximumSize(int concurrencyLevel, int initialCapacity, .initialCapacity(initialCapacity) .maximumWeight(maxSize) .weigher(constantWeigher(1))); - assertTrue( - "segments=" + map.segments.length + ", maxSize=" + maxSize, - map.segments.length <= Math.max(1, maxSize / 10)); + assertWithMessage("segments=%s, maxSize=%s", map.segments.length, maxSize) + .that((long) map.segments.length) + .isAtMost(max(1, maxSize / 10)); totalCapacity = 0; for (int i = 0; i < map.segments.length; i++) { totalCapacity += map.segments[i].maxSegmentWeight; } - assertTrue("totalCapacity=" + totalCapacity + ", maxSize=" + maxSize, totalCapacity == maxSize); + assertWithMessage("totalCapacity=%s, maxSize=%s", totalCapacity, maxSize) + .that(totalCapacity) + .isEqualTo(maxSize); } public void testSetWeigher() { @@ -462,64 +495,116 @@ public int weigh(Object key, Object value) { }; LocalCache map = makeLocalCache(createCacheBuilder().maximumWeight(1).weigher(testWeigher)); - assertSame(testWeigher, map.weigher); + assertThat(map.weigher).isSameInstanceAs(testWeigher); } public void testSetWeakKeys() { LocalCache map = makeLocalCache(createCacheBuilder().weakKeys()); checkStrength(map, Strength.WEAK, Strength.STRONG); - assertSame(EntryFactory.WEAK, map.entryFactory); + assertThat(map.entryFactory).isSameInstanceAs(EntryFactory.WEAK); } public void testSetWeakValues() { LocalCache map = makeLocalCache(createCacheBuilder().weakValues()); checkStrength(map, Strength.STRONG, Strength.WEAK); - assertSame(EntryFactory.STRONG, map.entryFactory); + assertThat(map.entryFactory).isSameInstanceAs(EntryFactory.STRONG); } public void testSetSoftValues() { LocalCache map = makeLocalCache(createCacheBuilder().softValues()); checkStrength(map, Strength.STRONG, Strength.SOFT); - assertSame(EntryFactory.STRONG, map.entryFactory); + assertThat(map.entryFactory).isSameInstanceAs(EntryFactory.STRONG); } private static void checkStrength( LocalCache map, Strength keyStrength, Strength valueStrength) { - assertSame(keyStrength, map.keyStrength); - assertSame(valueStrength, map.valueStrength); - assertSame(keyStrength.defaultEquivalence(), map.keyEquivalence); - assertSame(valueStrength.defaultEquivalence(), map.valueEquivalence); + assertThat(map.keyStrength).isSameInstanceAs(keyStrength); + assertThat(map.valueStrength).isSameInstanceAs(valueStrength); + assertThat(map.keyEquivalence).isSameInstanceAs(keyStrength.defaultEquivalence()); + assertThat(map.valueEquivalence).isSameInstanceAs(valueStrength.defaultEquivalence()); } public void testSetExpireAfterWrite() { long duration = 42; - TimeUnit unit = TimeUnit.SECONDS; + TimeUnit unit = SECONDS; LocalCache map = makeLocalCache(createCacheBuilder().expireAfterWrite(duration, unit)); - assertEquals(unit.toNanos(duration), map.expireAfterWriteNanos); + assertThat(map.expireAfterWriteNanos).isEqualTo(unit.toNanos(duration)); } public void testSetExpireAfterAccess() { long duration = 42; - TimeUnit unit = TimeUnit.SECONDS; + TimeUnit unit = SECONDS; LocalCache map = makeLocalCache(createCacheBuilder().expireAfterAccess(duration, unit)); - assertEquals(unit.toNanos(duration), map.expireAfterAccessNanos); + assertThat(map.expireAfterAccessNanos).isEqualTo(unit.toNanos(duration)); } public void testSetRefresh() { long duration = 42; - TimeUnit unit = TimeUnit.SECONDS; + TimeUnit unit = SECONDS; LocalCache map = makeLocalCache(createCacheBuilder().refreshAfterWrite(duration, unit)); - assertEquals(unit.toNanos(duration), map.refreshNanos); + assertThat(map.refreshNanos).isEqualTo(unit.toNanos(duration)); + } + + public void testLongAsyncRefresh() throws Exception { + FakeTicker ticker = new FakeTicker(); + CountDownLatch reloadStarted = new CountDownLatch(1); + SettableFuture threadAboutToBlockForRefresh = SettableFuture.create(); + + ListeningExecutorService refreshExecutor = listeningDecorator(newSingleThreadExecutor()); + try { + CacheBuilder builder = + createCacheBuilder() + .expireAfterWrite(100, MILLISECONDS) + .refreshAfterWrite(5, MILLISECONDS) + .ticker(ticker); + + CacheLoader loader = + new CacheLoader() { + @Override + public String load(String key) { + return key + "Load"; + } + + @Override + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. + public ListenableFuture reload(String key, String oldValue) { + return refreshExecutor.submit( + () -> { + reloadStarted.countDown(); + + Thread blockingForRefresh = threadAboutToBlockForRefresh.get(); + while (blockingForRefresh.isAlive() + && blockingForRefresh.getState() != WAITING) { + Thread.yield(); + } + + return key + "Reload"; + }); + } + }; + LocalCache cache = makeLocalCache(builder, loader); + + assertThat(cache.getOrLoad("test")).isEqualTo("testLoad"); + + ticker.advance(10, MILLISECONDS); // so that the next call will trigger refresh + assertThat(cache.getOrLoad("test")).isEqualTo("testLoad"); + reloadStarted.await(); + ticker.advance(500, MILLISECONDS); // so that the entry expires during the reload + threadAboutToBlockForRefresh.set(Thread.currentThread()); + assertThat(cache.getOrLoad("test")).isEqualTo("testReload"); + } finally { + refreshExecutor.shutdown(); + } } public void testSetRemovalListener() { RemovalListener testListener = TestingRemovalListeners.nullRemovalListener(); LocalCache map = makeLocalCache(createCacheBuilder().removalListener(testListener)); - assertSame(testListener, map.removalListener); + assertThat(map.removalListener).isSameInstanceAs(testListener); } public void testSetTicker() { @@ -531,19 +616,26 @@ public long read() { } }; LocalCache map = makeLocalCache(createCacheBuilder().ticker(testTicker)); - assertSame(testTicker, map.ticker); + assertThat(map.ticker).isSameInstanceAs(testTicker); } public void testEntryFactory() { - assertSame(EntryFactory.STRONG, EntryFactory.getFactory(Strength.STRONG, false, false)); - assertSame(EntryFactory.STRONG_ACCESS, EntryFactory.getFactory(Strength.STRONG, true, false)); - assertSame(EntryFactory.STRONG_WRITE, EntryFactory.getFactory(Strength.STRONG, false, true)); - assertSame( - EntryFactory.STRONG_ACCESS_WRITE, EntryFactory.getFactory(Strength.STRONG, true, true)); - assertSame(EntryFactory.WEAK, EntryFactory.getFactory(Strength.WEAK, false, false)); - assertSame(EntryFactory.WEAK_ACCESS, EntryFactory.getFactory(Strength.WEAK, true, false)); - assertSame(EntryFactory.WEAK_WRITE, EntryFactory.getFactory(Strength.WEAK, false, true)); - assertSame(EntryFactory.WEAK_ACCESS_WRITE, EntryFactory.getFactory(Strength.WEAK, true, true)); + assertThat(EntryFactory.getFactory(Strength.STRONG, false, false)) + .isSameInstanceAs(EntryFactory.STRONG); + assertThat(EntryFactory.getFactory(Strength.STRONG, true, false)) + .isSameInstanceAs(EntryFactory.STRONG_ACCESS); + assertThat(EntryFactory.getFactory(Strength.STRONG, false, true)) + .isSameInstanceAs(EntryFactory.STRONG_WRITE); + assertThat(EntryFactory.getFactory(Strength.STRONG, true, true)) + .isSameInstanceAs(EntryFactory.STRONG_ACCESS_WRITE); + assertThat(EntryFactory.getFactory(Strength.WEAK, false, false)) + .isSameInstanceAs(EntryFactory.WEAK); + assertThat(EntryFactory.getFactory(Strength.WEAK, true, false)) + .isSameInstanceAs(EntryFactory.WEAK_ACCESS); + assertThat(EntryFactory.getFactory(Strength.WEAK, false, true)) + .isSameInstanceAs(EntryFactory.WEAK_WRITE); + assertThat(EntryFactory.getFactory(Strength.WEAK, true, true)) + .isSameInstanceAs(EntryFactory.WEAK_ACCESS_WRITE); } // computation tests @@ -551,13 +643,13 @@ public void testEntryFactory() { public void testCompute() throws ExecutionException { CountingLoader loader = new CountingLoader(); LocalCache map = makeLocalCache(createCacheBuilder()); - assertEquals(0, loader.getCount()); + assertThat(loader.getCount()).isEqualTo(0); Object key = new Object(); Object value = map.get(key, loader); - assertEquals(1, loader.getCount()); - assertEquals(value, map.get(key, loader)); - assertEquals(1, loader.getCount()); + assertThat(loader.getCount()).isEqualTo(1); + assertThat(map.get(key, loader)).isEqualTo(value); + assertThat(loader.getCount()).isEqualTo(1); } public void testRecordReadOnCompute() throws ExecutionException { @@ -565,8 +657,8 @@ public void testRecordReadOnCompute() throws ExecutionException { for (CacheBuilder builder : allEvictingMakers()) { LocalCache map = makeLocalCache(builder.concurrencyLevel(1)); Segment segment = map.segments[0]; - List> writeOrder = Lists.newLinkedList(); - List> readOrder = Lists.newLinkedList(); + List> writeOrder = new LinkedList<>(); + List> readOrder = new LinkedList<>(); for (int i = 0; i < SMALL_MAX_SIZE; i++) { Object key = new Object(); int hash = map.hash(key); @@ -579,11 +671,11 @@ public void testRecordReadOnCompute() throws ExecutionException { checkEvictionQueues(map, segment, readOrder, writeOrder); checkExpirationTimes(map); - assertTrue(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isTrue(); // access some of the elements Random random = new Random(); - List> reads = Lists.newArrayList(); + List> reads = new ArrayList<>(); Iterator> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry entry = i.next(); @@ -591,7 +683,7 @@ public void testRecordReadOnCompute() throws ExecutionException { map.get(entry.getKey(), loader); reads.add(entry); i.remove(); - assertTrue(segment.recencyQueue.size() <= DRAIN_THRESHOLD); + assertThat(segment.recencyQueue.size()).isAtMost(DRAIN_THRESHOLD); } } int undrainedIndex = reads.size() - segment.recencyQueue.size(); @@ -606,14 +698,14 @@ public void testRecordReadOnCompute() throws ExecutionException { public void testComputeExistingEntry() throws ExecutionException { CountingLoader loader = new CountingLoader(); LocalCache map = makeLocalCache(createCacheBuilder()); - assertEquals(0, loader.getCount()); + assertThat(loader.getCount()).isEqualTo(0); Object key = new Object(); Object value = new Object(); map.put(key, value); - assertEquals(value, map.get(key, loader)); - assertEquals(0, loader.getCount()); + assertThat(map.get(key, loader)).isEqualTo(value); + assertThat(loader.getCount()).isEqualTo(0); } public void testComputePartiallyCollectedKey() throws ExecutionException { @@ -622,7 +714,7 @@ public void testComputePartiallyCollectedKey() throws ExecutionException { LocalCache map = makeLocalCache(builder); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertEquals(0, loader.getCount()); + assertThat(loader.getCount()).isEqualTo(0); Object key = new Object(); int hash = map.hash(key); @@ -635,14 +727,14 @@ public void testComputePartiallyCollectedKey() throws ExecutionException { table.set(index, entry); segment.count++; - assertSame(value, map.get(key, loader)); - assertEquals(0, loader.getCount()); - assertEquals(1, segment.count); + assertThat(map.get(key, loader)).isSameInstanceAs(value); + assertThat(loader.getCount()).isEqualTo(0); + assertThat(segment.count).isEqualTo(1); entry.clearKey(); - assertNotSame(value, map.get(key, loader)); - assertEquals(1, loader.getCount()); - assertEquals(2, segment.count); + assertThat(map.get(key, loader)).isNotSameInstanceAs(value); + assertThat(loader.getCount()).isEqualTo(1); + assertThat(segment.count).isEqualTo(2); } public void testComputePartiallyCollectedValue() throws ExecutionException { @@ -651,7 +743,7 @@ public void testComputePartiallyCollectedValue() throws ExecutionException { LocalCache map = makeLocalCache(builder); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertEquals(0, loader.getCount()); + assertThat(loader.getCount()).isEqualTo(0); Object key = new Object(); int hash = map.hash(key); @@ -664,31 +756,30 @@ public void testComputePartiallyCollectedValue() throws ExecutionException { table.set(index, entry); segment.count++; - assertSame(value, map.get(key, loader)); - assertEquals(0, loader.getCount()); - assertEquals(1, segment.count); + assertThat(map.get(key, loader)).isSameInstanceAs(value); + assertThat(loader.getCount()).isEqualTo(0); + assertThat(segment.count).isEqualTo(1); valueRef.clear(); - assertNotSame(value, map.get(key, loader)); - assertEquals(1, loader.getCount()); - assertEquals(1, segment.count); + assertThat(map.get(key, loader)).isNotSameInstanceAs(value); + assertThat(loader.getCount()).isEqualTo(1); + assertThat(segment.count).isEqualTo(1); } @AndroidIncompatible // Perhaps emulator clock does not update between the two get() calls? public void testComputeExpiredEntry() throws ExecutionException { - CacheBuilder builder = - createCacheBuilder().expireAfterWrite(1, TimeUnit.NANOSECONDS); + CacheBuilder builder = createCacheBuilder().expireAfterWrite(1, NANOSECONDS); CountingLoader loader = new CountingLoader(); LocalCache map = makeLocalCache(builder); - assertEquals(0, loader.getCount()); + assertThat(loader.getCount()).isEqualTo(0); Object key = new Object(); Object one = map.get(key, loader); - assertEquals(1, loader.getCount()); + assertThat(loader.getCount()).isEqualTo(1); Object two = map.get(key, loader); - assertNotSame(one, two); - assertEquals(2, loader.getCount()); + assertThat(two).isNotSameInstanceAs(one); + assertThat(loader.getCount()).isEqualTo(2); } public void testValues() { @@ -696,19 +787,18 @@ public void testValues() { map.put("foo", "bar"); map.put("baz", "bar"); map.put("quux", "quux"); - assertFalse(map.values() instanceof Set); - assertTrue(map.values().removeAll(ImmutableSet.of("bar"))); - assertEquals(1, map.size()); + assertThat(map.values() instanceof Set).isFalse(); + assertThat(map.values().removeAll(ImmutableSet.of("bar"))).isTrue(); + assertThat(map).hasSize(1); } - public void testCopyEntry_computing() { - final CountDownLatch startSignal = new CountDownLatch(1); - final CountDownLatch computingSignal = new CountDownLatch(1); - final CountDownLatch doneSignal = new CountDownLatch(2); - final Object computedObject = new Object(); + CountDownLatch startSignal = new CountDownLatch(1); + CountDownLatch computingSignal = new CountDownLatch(1); + CountDownLatch doneSignal = new CountDownLatch(2); + Object computedObject = new Object(); - final CacheLoader loader = + CacheLoader loader = new CacheLoader() { @Override public Object load(Object key) throws Exception { @@ -721,12 +811,12 @@ public Object load(Object key) throws Exception { QueuingRemovalListener listener = queuingRemovalListener(); CacheBuilder builder = createCacheBuilder().concurrencyLevel(1).removalListener(listener); - final LocalCache map = makeLocalCache(builder); + LocalCache map = makeLocalCache(builder); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); - final Object one = new Object(); + Object one = new Object(); int hash = map.hash(one); int index = hash & (table.length() - 1); @@ -767,7 +857,7 @@ public void run() { @SuppressWarnings("unchecked") LoadingValueReference valueReference = (LoadingValueReference) newEntry.getValueReference(); - assertFalse(valueReference.futureValue.isDone()); + assertThat(valueReference.futureValue.isDone()).isFalse(); startSignal.countDown(); try { @@ -777,14 +867,14 @@ public void run() { } map.cleanUp(); // force notifications - assertTrue(listener.isEmpty()); - assertTrue(map.containsKey(one)); - assertEquals(1, map.size()); - assertSame(computedObject, map.get(one)); + assertThat(listener.isEmpty()).isTrue(); + assertThat(map.containsKey(one)).isTrue(); + assertThat(map).hasSize(1); + assertThat(map.get(one)).isSameInstanceAs(computedObject); } public void testRemovalListenerCheckedException() { - final RuntimeException e = new RuntimeException(); + RuntimeException e = new RuntimeException(); RemovalListener listener = new RemovalListener() { @Override @@ -794,7 +884,7 @@ public void onRemoval(RemovalNotification notification) { }; CacheBuilder builder = createCacheBuilder().removalListener(listener); - final LocalCache cache = makeLocalCache(builder); + LocalCache cache = makeLocalCache(builder); Object key = new Object(); cache.put(key, new Object()); checkNothingLogged(); @@ -803,14 +893,13 @@ public void onRemoval(RemovalNotification notification) { checkLogged(e); } - public void testRemovalListener_replaced_computing() { - final CountDownLatch startSignal = new CountDownLatch(1); - final CountDownLatch computingSignal = new CountDownLatch(1); - final CountDownLatch doneSignal = new CountDownLatch(1); - final Object computedObject = new Object(); + CountDownLatch startSignal = new CountDownLatch(1); + CountDownLatch computingSignal = new CountDownLatch(1); + CountDownLatch doneSignal = new CountDownLatch(1); + Object computedObject = new Object(); - final CacheLoader loader = + CacheLoader loader = new CacheLoader() { @Override public Object load(Object key) throws Exception { @@ -822,11 +911,11 @@ public Object load(Object key) throws Exception { QueuingRemovalListener listener = queuingRemovalListener(); CacheBuilder builder = createCacheBuilder().removalListener(listener); - final LocalCache map = makeLocalCache(builder); - assertTrue(listener.isEmpty()); + LocalCache map = makeLocalCache(builder); + assertThat(listener.isEmpty()).isTrue(); - final Object one = new Object(); - final Object two = new Object(); + Object one = new Object(); + Object two = new Object(); new Thread() { @Override @@ -847,7 +936,7 @@ public void run() { } map.put(one, two); - assertSame(two, map.get(one)); + assertThat(map.get(one)).isSameInstanceAs(two); startSignal.countDown(); try { @@ -858,7 +947,7 @@ public void run() { map.cleanUp(); // force notifications assertNotified(listener, one, computedObject, RemovalCause.REPLACED); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); } public void testSegmentRefresh_duplicate() throws ExecutionException { @@ -876,7 +965,7 @@ public void testSegmentRefresh_duplicate() throws ExecutionException { valueRef.setLoading(true); entry.setValueReference(valueRef); table.set(index, entry); - assertNull(segment.refresh(key, hash, identityLoader(), false)); + assertThat(segment.refresh(key, hash, identityLoader(), false)).isNull(); } // Removal listener tests @@ -884,7 +973,7 @@ public void testSegmentRefresh_duplicate() throws ExecutionException { public void testRemovalListener_explicit() { QueuingRemovalListener listener = queuingRemovalListener(); LocalCache map = makeLocalCache(createCacheBuilder().removalListener(listener)); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); Object one = new Object(); Object two = new Object(); @@ -919,13 +1008,13 @@ public void testRemovalListener_explicit() { i.remove(); assertNotified(listener, five, six, RemovalCause.EXPLICIT); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); } public void testRemovalListener_replaced() { QueuingRemovalListener listener = queuingRemovalListener(); LocalCache map = makeLocalCache(createCacheBuilder().removalListener(listener)); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); Object one = new Object(); Object two = new Object(); @@ -955,7 +1044,7 @@ public void testRemovalListener_collected() { makeLocalCache( createCacheBuilder().concurrencyLevel(1).softValues().removalListener(listener)); Segment segment = map.segments[0]; - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); Object one = new Object(); Object two = new Object(); @@ -963,14 +1052,14 @@ public void testRemovalListener_collected() { map.put(one, two); map.put(two, three); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); int hash = map.hash(one); ReferenceEntry entry = segment.getEntry(one, hash); map.reclaimValue(entry.getValueReference()); assertNotified(listener, one, two, RemovalCause.COLLECTED); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); } public void testRemovalListener_expired() { @@ -980,10 +1069,10 @@ public void testRemovalListener_expired() { makeLocalCache( createCacheBuilder() .concurrencyLevel(1) - .expireAfterWrite(3, TimeUnit.NANOSECONDS) + .expireAfterWrite(3, NANOSECONDS) .ticker(ticker) .removalListener(listener)); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); Object one = new Object(); Object two = new Object(); @@ -996,12 +1085,12 @@ public void testRemovalListener_expired() { map.put(two, three); ticker.advance(1); map.put(three, four); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); ticker.advance(1); map.put(four, five); assertNotified(listener, one, two, RemovalCause.EXPIRED); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); } public void testRemovalListener_size() { @@ -1009,7 +1098,7 @@ public void testRemovalListener_size() { LocalCache map = makeLocalCache( createCacheBuilder().concurrencyLevel(1).maximumSize(2).removalListener(listener)); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); Object one = new Object(); Object two = new Object(); @@ -1018,19 +1107,19 @@ public void testRemovalListener_size() { map.put(one, two); map.put(two, three); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); map.put(three, four); assertNotified(listener, one, two, RemovalCause.SIZE); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); } static void assertNotified( QueuingRemovalListener listener, K key, V value, RemovalCause cause) { RemovalNotification notification = listener.remove(); - assertSame(key, notification.getKey()); - assertSame(value, notification.getValue()); - assertSame(cause, notification.getCause()); + assertThat(notification.getKey()).isSameInstanceAs(key); + assertThat(notification.getValue()).isSameInstanceAs(value); + assertThat(notification.getCause()).isSameInstanceAs(cause); } // Segment core tests @@ -1044,26 +1133,26 @@ public void testNewEntry() { int hashOne = map.hash(keyOne); ReferenceEntry entryOne = map.newEntry(keyOne, hashOne, null); ValueReference valueRefOne = map.newValueReference(entryOne, valueOne, 1); - assertSame(valueOne, valueRefOne.get()); + assertThat(valueRefOne.get()).isSameInstanceAs(valueOne); entryOne.setValueReference(valueRefOne); - assertSame(keyOne, entryOne.getKey()); - assertEquals(hashOne, entryOne.getHash()); - assertNull(entryOne.getNext()); - assertSame(valueRefOne, entryOne.getValueReference()); + assertThat(entryOne.getKey()).isSameInstanceAs(keyOne); + assertThat(entryOne.getHash()).isEqualTo(hashOne); + assertThat(entryOne.getNext()).isNull(); + assertThat(entryOne.getValueReference()).isSameInstanceAs(valueRefOne); Object keyTwo = new Object(); Object valueTwo = new Object(); int hashTwo = map.hash(keyTwo); ReferenceEntry entryTwo = map.newEntry(keyTwo, hashTwo, entryOne); ValueReference valueRefTwo = map.newValueReference(entryTwo, valueTwo, 1); - assertSame(valueTwo, valueRefTwo.get()); + assertThat(valueRefTwo.get()).isSameInstanceAs(valueTwo); entryTwo.setValueReference(valueRefTwo); - assertSame(keyTwo, entryTwo.getKey()); - assertEquals(hashTwo, entryTwo.getHash()); - assertSame(entryOne, entryTwo.getNext()); - assertSame(valueRefTwo, entryTwo.getValueReference()); + assertThat(entryTwo.getKey()).isSameInstanceAs(keyTwo); + assertThat(entryTwo.getHash()).isEqualTo(hashTwo); + assertThat(entryTwo.getNext()).isSameInstanceAs(entryOne); + assertThat(entryTwo.getValueReference()).isSameInstanceAs(valueRefTwo); } } @@ -1091,17 +1180,17 @@ public void testCopyEntry() { assertConnected(map, entryOne, entryTwo); ReferenceEntry copyOne = map.copyEntry(entryOne, null); - assertSame(keyOne, entryOne.getKey()); - assertEquals(hashOne, entryOne.getHash()); - assertNull(entryOne.getNext()); - assertSame(valueOne, copyOne.getValueReference().get()); + assertThat(entryOne.getKey()).isSameInstanceAs(keyOne); + assertThat(entryOne.getHash()).isEqualTo(hashOne); + assertThat(entryOne.getNext()).isNull(); + assertThat(copyOne.getValueReference().get()).isSameInstanceAs(valueOne); assertConnected(map, copyOne, entryTwo); ReferenceEntry copyTwo = map.copyEntry(entryTwo, copyOne); - assertSame(keyTwo, copyTwo.getKey()); - assertEquals(hashTwo, copyTwo.getHash()); - assertSame(copyOne, copyTwo.getNext()); - assertSame(valueTwo, copyTwo.getValueReference().get()); + assertThat(copyTwo.getKey()).isSameInstanceAs(keyTwo); + assertThat(copyTwo.getHash()).isEqualTo(hashTwo); + assertThat(copyTwo.getNext()).isSameInstanceAs(copyOne); + assertThat(copyTwo.getValueReference().get()).isSameInstanceAs(valueTwo); assertConnected(map, copyOne, copyTwo); } } @@ -1109,10 +1198,10 @@ public void testCopyEntry() { private static void assertConnected( LocalCache map, ReferenceEntry one, ReferenceEntry two) { if (map.usesWriteQueue()) { - assertSame(two, one.getNextInWriteQueue()); + assertThat(one.getNextInWriteQueue()).isSameInstanceAs(two); } if (map.usesAccessQueue()) { - assertSame(two, one.getNextInAccessQueue()); + assertThat(one.getNextInAccessQueue()).isSameInstanceAs(two); } } @@ -1123,7 +1212,7 @@ public void testSegmentGetAndContains() { createCacheBuilder() .concurrencyLevel(1) .ticker(ticker) - .expireAfterAccess(1, TimeUnit.NANOSECONDS)); + .expireAfterAccess(1, NANOSECONDS)); Segment segment = map.segments[0]; // TODO(fry): check recency ordering @@ -1137,21 +1226,21 @@ public void testSegmentGetAndContains() { ValueReference valueRef = map.newValueReference(entry, value, 1); entry.setValueReference(valueRef); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); // count == 0 table.set(index, entry); - assertNull(segment.get(key, hash)); - assertFalse(segment.containsKey(key, hash)); - assertFalse(segment.containsValue(value)); + assertThat(segment.get(key, hash)).isNull(); + assertThat(segment.containsKey(key, hash)).isFalse(); + assertThat(segment.containsValue(value)).isFalse(); // count == 1 segment.count++; - assertSame(value, segment.get(key, hash)); - assertTrue(segment.containsKey(key, hash)); - assertTrue(segment.containsValue(value)); + assertThat(segment.get(key, hash)).isSameInstanceAs(value); + assertThat(segment.containsKey(key, hash)).isTrue(); + assertThat(segment.containsValue(value)).isTrue(); // don't see absent values now that count > 0 - assertNull(segment.get(new Object(), hash)); + assertThat(segment.get(new Object(), hash)).isNull(); // null key DummyEntry nullEntry = DummyEntry.create(null, hash, entry); @@ -1160,10 +1249,10 @@ public void testSegmentGetAndContains() { nullEntry.setValueReference(nullValueRef); table.set(index, nullEntry); // skip the null key - assertSame(value, segment.get(key, hash)); - assertTrue(segment.containsKey(key, hash)); - assertTrue(segment.containsValue(value)); - assertFalse(segment.containsValue(nullValue)); + assertThat(segment.get(key, hash)).isSameInstanceAs(value); + assertThat(segment.containsKey(key, hash)).isTrue(); + assertThat(segment.containsValue(value)).isTrue(); + assertThat(segment.containsValue(nullValue)).isFalse(); // hash collision DummyEntry dummy = DummyEntry.create(new Object(), hash, entry); @@ -1171,10 +1260,10 @@ public void testSegmentGetAndContains() { ValueReference dummyValueRef = map.newValueReference(dummy, dummyValue, 1); dummy.setValueReference(dummyValueRef); table.set(index, dummy); - assertSame(value, segment.get(key, hash)); - assertTrue(segment.containsKey(key, hash)); - assertTrue(segment.containsValue(value)); - assertTrue(segment.containsValue(dummyValue)); + assertThat(segment.get(key, hash)).isSameInstanceAs(value); + assertThat(segment.containsKey(key, hash)).isTrue(); + assertThat(segment.containsValue(value)).isTrue(); + assertThat(segment.containsValue(dummyValue)).isTrue(); // key collision dummy = DummyEntry.create(key, hash, entry); @@ -1183,17 +1272,17 @@ public void testSegmentGetAndContains() { dummy.setValueReference(dummyValueRef); table.set(index, dummy); // returns the most recent entry - assertSame(dummyValue, segment.get(key, hash)); - assertTrue(segment.containsKey(key, hash)); - assertTrue(segment.containsValue(value)); - assertTrue(segment.containsValue(dummyValue)); + assertThat(segment.get(key, hash)).isSameInstanceAs(dummyValue); + assertThat(segment.containsKey(key, hash)).isTrue(); + assertThat(segment.containsValue(value)).isTrue(); + assertThat(segment.containsValue(dummyValue)).isTrue(); // expired dummy.setAccessTime(ticker.read() - 2); - assertNull(segment.get(key, hash)); - assertFalse(segment.containsKey(key, hash)); - assertTrue(segment.containsValue(value)); - assertFalse(segment.containsValue(dummyValue)); + assertThat(segment.get(key, hash)).isNull(); + assertThat(segment.containsKey(key, hash)).isFalse(); + assertThat(segment.containsValue(value)).isTrue(); + assertThat(segment.containsValue(dummyValue)).isFalse(); } public void testSegmentReplaceValue() { @@ -1214,30 +1303,30 @@ public void testSegmentReplaceValue() { entry.setValueReference(oldValueRef); // no entry - assertFalse(segment.replace(key, hash, oldValue, newValue)); - assertEquals(0, segment.count); + assertThat(segment.replace(key, hash, oldValue, newValue)).isFalse(); + assertThat(segment.count).isEqualTo(0); // same value table.set(index, entry); segment.count++; - assertEquals(1, segment.count); - assertSame(oldValue, segment.get(key, hash)); - assertTrue(segment.replace(key, hash, oldValue, newValue)); - assertEquals(1, segment.count); - assertSame(newValue, segment.get(key, hash)); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); + assertThat(segment.replace(key, hash, oldValue, newValue)).isTrue(); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(newValue); // different value - assertFalse(segment.replace(key, hash, oldValue, newValue)); - assertEquals(1, segment.count); - assertSame(newValue, segment.get(key, hash)); + assertThat(segment.replace(key, hash, oldValue, newValue)).isFalse(); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(newValue); // cleared entry.setValueReference(oldValueRef); - assertSame(oldValue, segment.get(key, hash)); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); oldValueRef.clear(); - assertFalse(segment.replace(key, hash, oldValue, newValue)); - assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.replace(key, hash, oldValue, newValue)).isFalse(); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.get(key, hash)).isNull(); } public void testSegmentReplace() { @@ -1258,25 +1347,25 @@ public void testSegmentReplace() { entry.setValueReference(oldValueRef); // no entry - assertNull(segment.replace(key, hash, newValue)); - assertEquals(0, segment.count); + assertThat(segment.replace(key, hash, newValue)).isNull(); + assertThat(segment.count).isEqualTo(0); // same key table.set(index, entry); segment.count++; - assertEquals(1, segment.count); - assertSame(oldValue, segment.get(key, hash)); - assertSame(oldValue, segment.replace(key, hash, newValue)); - assertEquals(1, segment.count); - assertSame(newValue, segment.get(key, hash)); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); + assertThat(segment.replace(key, hash, newValue)).isSameInstanceAs(oldValue); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(newValue); // cleared entry.setValueReference(oldValueRef); - assertSame(oldValue, segment.get(key, hash)); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); oldValueRef.clear(); - assertNull(segment.replace(key, hash, newValue)); - assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.replace(key, hash, newValue)).isNull(); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.get(key, hash)).isNull(); } public void testSegmentPut() { @@ -1291,24 +1380,24 @@ public void testSegmentPut() { Object newValue = new Object(); // no entry - assertEquals(0, segment.count); - assertNull(segment.put(key, hash, oldValue, false)); - assertEquals(1, segment.count); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.put(key, hash, oldValue, false)).isNull(); + assertThat(segment.count).isEqualTo(1); // same key - assertSame(oldValue, segment.put(key, hash, newValue, false)); - assertEquals(1, segment.count); - assertSame(newValue, segment.get(key, hash)); + assertThat(segment.put(key, hash, newValue, false)).isSameInstanceAs(oldValue); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(newValue); // cleared ReferenceEntry entry = segment.getEntry(key, hash); DummyValueReference oldValueRef = DummyValueReference.create(oldValue); entry.setValueReference(oldValueRef); - assertSame(oldValue, segment.get(key, hash)); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); oldValueRef.clear(); - assertNull(segment.put(key, hash, newValue, false)); - assertEquals(1, segment.count); - assertSame(newValue, segment.get(key, hash)); + assertThat(segment.put(key, hash, newValue, false)).isNull(); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(newValue); } public void testSegmentPutIfAbsent() { @@ -1323,39 +1412,39 @@ public void testSegmentPutIfAbsent() { Object newValue = new Object(); // no entry - assertEquals(0, segment.count); - assertNull(segment.put(key, hash, oldValue, true)); - assertEquals(1, segment.count); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.put(key, hash, oldValue, true)).isNull(); + assertThat(segment.count).isEqualTo(1); // same key - assertSame(oldValue, segment.put(key, hash, newValue, true)); - assertEquals(1, segment.count); - assertSame(oldValue, segment.get(key, hash)); + assertThat(segment.put(key, hash, newValue, true)).isSameInstanceAs(oldValue); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); // cleared ReferenceEntry entry = segment.getEntry(key, hash); DummyValueReference oldValueRef = DummyValueReference.create(oldValue); entry.setValueReference(oldValueRef); - assertSame(oldValue, segment.get(key, hash)); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); oldValueRef.clear(); - assertNull(segment.put(key, hash, newValue, true)); - assertEquals(1, segment.count); - assertSame(newValue, segment.get(key, hash)); + assertThat(segment.put(key, hash, newValue, true)).isNull(); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(newValue); } public void testSegmentPut_expand() { LocalCache map = makeLocalCache(createCacheBuilder().concurrencyLevel(1).initialCapacity(1)); Segment segment = map.segments[0]; - assertEquals(1, segment.table.length()); + assertThat(segment.table.length()).isEqualTo(1); int count = 1024; for (int i = 0; i < count; i++) { Object key = new Object(); Object value = new Object(); int hash = map.hash(key); - assertNull(segment.put(key, hash, value, false)); - assertTrue(segment.table.length() > i); + assertThat(segment.put(key, hash, value, false)).isNull(); + assertThat(segment.table.length()).isGreaterThan(i); } } @@ -1366,7 +1455,7 @@ public void testSegmentPut_evict() { // manually add elements to avoid eviction int originalCount = 1024; - LinkedHashMap originalMap = Maps.newLinkedHashMap(); + LinkedHashMap originalMap = new LinkedHashMap<>(); for (int i = 0; i < originalCount; i++) { Object key = new Object(); Object value = new Object(); @@ -1377,7 +1466,7 @@ public void testSegmentPut_evict() { it.next(); it.remove(); } - assertEquals(originalMap, map); + assertThat(map).isEqualTo(originalMap); } } @@ -1398,34 +1487,34 @@ public void testSegmentStoreComputedValue() { // absent Object value = new Object(); - assertTrue(listener.isEmpty()); - assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); - assertTrue(segment.storeLoadedValue(key, hash, valueRef, value)); - assertSame(value, segment.get(key, hash)); - assertEquals(1, segment.count); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.get(key, hash)).isNull(); + assertThat(segment.storeLoadedValue(key, hash, valueRef, value)).isTrue(); + assertThat(segment.get(key, hash)).isSameInstanceAs(value); + assertThat(segment.count).isEqualTo(1); + assertThat(listener.isEmpty()).isTrue(); // clobbered Object value2 = new Object(); - assertFalse(segment.storeLoadedValue(key, hash, valueRef, value2)); - assertEquals(1, segment.count); - assertSame(value, segment.get(key, hash)); + assertThat(segment.storeLoadedValue(key, hash, valueRef, value2)).isFalse(); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(value); RemovalNotification notification = listener.remove(); - assertEquals(immutableEntry(key, value2), notification); - assertEquals(RemovalCause.REPLACED, notification.getCause()); - assertTrue(listener.isEmpty()); + assertThat(notification).isEqualTo(immutableEntry(key, value2)); + assertThat(notification.getCause()).isEqualTo(RemovalCause.REPLACED); + assertThat(listener.isEmpty()).isTrue(); // inactive Object value3 = new Object(); map.clear(); listener.clear(); - assertEquals(0, segment.count); + assertThat(segment.count).isEqualTo(0); table.set(index, entry); - assertTrue(segment.storeLoadedValue(key, hash, valueRef, value3)); - assertSame(value3, segment.get(key, hash)); - assertEquals(1, segment.count); - assertTrue(listener.isEmpty()); + assertThat(segment.storeLoadedValue(key, hash, valueRef, value3)).isTrue(); + assertThat(segment.get(key, hash)).isSameInstanceAs(value3); + assertThat(segment.count).isEqualTo(1); + assertThat(listener.isEmpty()).isTrue(); // replaced Object value4 = new Object(); @@ -1433,29 +1522,29 @@ public void testSegmentStoreComputedValue() { valueRef = new LoadingValueReference<>(value3Ref); entry.setValueReference(valueRef); table.set(index, entry); - assertSame(value3, segment.get(key, hash)); - assertEquals(1, segment.count); - assertTrue(segment.storeLoadedValue(key, hash, valueRef, value4)); - assertSame(value4, segment.get(key, hash)); - assertEquals(1, segment.count); + assertThat(segment.get(key, hash)).isSameInstanceAs(value3); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.storeLoadedValue(key, hash, valueRef, value4)).isTrue(); + assertThat(segment.get(key, hash)).isSameInstanceAs(value4); + assertThat(segment.count).isEqualTo(1); notification = listener.remove(); - assertEquals(immutableEntry(key, value3), notification); - assertEquals(RemovalCause.REPLACED, notification.getCause()); - assertTrue(listener.isEmpty()); + assertThat(notification).isEqualTo(immutableEntry(key, value3)); + assertThat(notification.getCause()).isEqualTo(RemovalCause.REPLACED); + assertThat(listener.isEmpty()).isTrue(); // collected entry.setValueReference(valueRef); table.set(index, entry); - assertSame(value3, segment.get(key, hash)); - assertEquals(1, segment.count); + assertThat(segment.get(key, hash)).isSameInstanceAs(value3); + assertThat(segment.count).isEqualTo(1); value3Ref.clear(); - assertTrue(segment.storeLoadedValue(key, hash, valueRef, value4)); - assertSame(value4, segment.get(key, hash)); - assertEquals(1, segment.count); + assertThat(segment.storeLoadedValue(key, hash, valueRef, value4)).isTrue(); + assertThat(segment.get(key, hash)).isSameInstanceAs(value4); + assertThat(segment.count).isEqualTo(1); notification = listener.remove(); - assertEquals(immutableEntry(key, null), notification); - assertEquals(RemovalCause.COLLECTED, notification.getCause()); - assertTrue(listener.isEmpty()); + assertThat(notification).isEqualTo(immutableEntry(key, null)); + assertThat(notification.getCause()).isEqualTo(RemovalCause.COLLECTED); + assertThat(listener.isEmpty()).isTrue(); } public void testSegmentRemove() { @@ -1473,28 +1562,28 @@ public void testSegmentRemove() { entry.setValueReference(oldValueRef); // no entry - assertEquals(0, segment.count); - assertNull(segment.remove(key, hash)); - assertEquals(0, segment.count); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.remove(key, hash)).isNull(); + assertThat(segment.count).isEqualTo(0); // same key table.set(index, entry); segment.count++; - assertEquals(1, segment.count); - assertSame(oldValue, segment.get(key, hash)); - assertSame(oldValue, segment.remove(key, hash)); - assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); + assertThat(segment.remove(key, hash)).isSameInstanceAs(oldValue); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.get(key, hash)).isNull(); // cleared table.set(index, entry); segment.count++; - assertEquals(1, segment.count); - assertSame(oldValue, segment.get(key, hash)); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); oldValueRef.clear(); - assertNull(segment.remove(key, hash)); - assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.remove(key, hash)).isNull(); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.get(key, hash)).isNull(); } public void testSegmentRemoveValue() { @@ -1513,41 +1602,41 @@ public void testSegmentRemoveValue() { entry.setValueReference(oldValueRef); // no entry - assertEquals(0, segment.count); - assertNull(segment.remove(key, hash)); - assertEquals(0, segment.count); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.remove(key, hash)).isNull(); + assertThat(segment.count).isEqualTo(0); // same value table.set(index, entry); segment.count++; - assertEquals(1, segment.count); - assertSame(oldValue, segment.get(key, hash)); - assertTrue(segment.remove(key, hash, oldValue)); - assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); + assertThat(segment.remove(key, hash, oldValue)).isTrue(); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.get(key, hash)).isNull(); // different value table.set(index, entry); segment.count++; - assertEquals(1, segment.count); - assertSame(oldValue, segment.get(key, hash)); - assertFalse(segment.remove(key, hash, newValue)); - assertEquals(1, segment.count); - assertSame(oldValue, segment.get(key, hash)); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); + assertThat(segment.remove(key, hash, newValue)).isFalse(); + assertThat(segment.count).isEqualTo(1); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); // cleared - assertSame(oldValue, segment.get(key, hash)); + assertThat(segment.get(key, hash)).isSameInstanceAs(oldValue); oldValueRef.clear(); - assertFalse(segment.remove(key, hash, oldValue)); - assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.remove(key, hash, oldValue)).isFalse(); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.get(key, hash)).isNull(); } public void testExpand() { LocalCache map = makeLocalCache(createCacheBuilder().concurrencyLevel(1).initialCapacity(1)); Segment segment = map.segments[0]; - assertEquals(1, segment.table.length()); + assertThat(segment.table.length()).isEqualTo(1); // manually add elements to avoid expansion int originalCount = 1024; @@ -1564,17 +1653,17 @@ public void testExpand() { segment.table.set(0, entry); segment.count = originalCount; ImmutableMap originalMap = ImmutableMap.copyOf(map); - assertEquals(originalCount, originalMap.size()); - assertEquals(originalMap, map); + assertThat(originalMap).hasSize(originalCount); + assertThat(map).isEqualTo(originalMap); for (int i = 1; i <= originalCount * 2; i *= 2) { if (i > 1) { segment.expand(); } - assertEquals(i, segment.table.length()); - assertEquals(originalCount, countLiveEntries(map, 0)); - assertEquals(originalCount, segment.count); - assertEquals(originalMap, map); + assertThat(segment.table.length()).isEqualTo(i); + assertThat(countLiveEntries(map, 0)).isEqualTo(originalCount); + assertThat(segment.count).isEqualTo(originalCount); + assertThat(map).isEqualTo(originalMap); } } @@ -1583,11 +1672,11 @@ public void testGetCausesExpansion() throws ExecutionException { LocalCache map = makeLocalCache(createCacheBuilder().concurrencyLevel(1).initialCapacity(1)); Segment segment = map.segments[0]; - assertEquals(1, segment.table.length()); + assertThat(segment.table.length()).isEqualTo(1); for (int i = 0; i < count; i++) { Object key = new Object(); - final Object value = new Object(); + Object value = new Object(); segment.get( key, key.hashCode(), @@ -1598,10 +1687,10 @@ public Object load(Object key) { } }); } - assertEquals(count, segment.count); - assertTrue(count <= segment.threshold); - assertTrue(count <= (segment.table.length() * 3 / 4)); - assertTrue(count > (segment.table.length() * 3 / 8)); + assertThat(segment.count).isEqualTo(count); + assertThat(count).isAtMost(segment.threshold); + assertThat(count).isAtMost((segment.table.length() * 3 / 4)); + assertThat(count).isGreaterThan(segment.table.length() * 3 / 8); } } @@ -1609,8 +1698,8 @@ public void testGetOrDefault() { LocalCache map = makeLocalCache(createCacheBuilder().concurrencyLevel(1).initialCapacity(1)); map.put(1, 1); - assertEquals(1, map.getOrDefault(1, 2)); - assertEquals(2, map.getOrDefault(2, 2)); + assertThat(map.getOrDefault(1, 2)).isEqualTo(1); + assertThat(map.getOrDefault(2, 2)).isEqualTo(2); } public void testPutCausesExpansion() { @@ -1618,17 +1707,17 @@ public void testPutCausesExpansion() { LocalCache map = makeLocalCache(createCacheBuilder().concurrencyLevel(1).initialCapacity(1)); Segment segment = map.segments[0]; - assertEquals(1, segment.table.length()); + assertThat(segment.table.length()).isEqualTo(1); for (int i = 0; i < count; i++) { Object key = new Object(); Object value = new Object(); segment.put(key, key.hashCode(), value, true); } - assertEquals(count, segment.count); - assertTrue(count <= segment.threshold); - assertTrue(count <= (segment.table.length() * 3 / 4)); - assertTrue(count > (segment.table.length() * 3 / 8)); + assertThat(segment.count).isEqualTo(count); + assertThat(count).isAtMost(segment.threshold); + assertThat(count).isAtMost((segment.table.length() * 3 / 4)); + assertThat(count).isGreaterThan(segment.table.length() * 3 / 8); } } @@ -1644,7 +1733,7 @@ public void testReclaimKey() { .removalListener(listener)); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertEquals(1, table.length()); + assertThat(table.length()).isEqualTo(1); // create 3 objects and chain them together Object keyOne = new Object(); @@ -1662,28 +1751,28 @@ public void testReclaimKey() { createDummyEntry(keyThree, hashThree, valueThree, entryTwo); // absent - assertEquals(0, listener.getCount()); - assertFalse(segment.reclaimKey(entryOne, hashOne)); - assertEquals(0, listener.getCount()); + assertThat(listener.getCount()).isEqualTo(0); + assertThat(segment.reclaimKey(entryOne, hashOne)).isFalse(); + assertThat(listener.getCount()).isEqualTo(0); table.set(0, entryOne); - assertFalse(segment.reclaimKey(entryTwo, hashTwo)); - assertEquals(0, listener.getCount()); + assertThat(segment.reclaimKey(entryTwo, hashTwo)).isFalse(); + assertThat(listener.getCount()).isEqualTo(0); table.set(0, entryTwo); - assertFalse(segment.reclaimKey(entryThree, hashThree)); - assertEquals(0, listener.getCount()); + assertThat(segment.reclaimKey(entryThree, hashThree)).isFalse(); + assertThat(listener.getCount()).isEqualTo(0); // present table.set(0, entryOne); segment.count = 1; - assertTrue(segment.reclaimKey(entryOne, hashOne)); - assertEquals(1, listener.getCount()); - assertSame(keyOne, listener.getLastEvictedKey()); - assertSame(valueOne, listener.getLastEvictedValue()); - assertTrue(map.removalNotificationQueue.isEmpty()); - assertFalse(segment.accessQueue.contains(entryOne)); - assertFalse(segment.writeQueue.contains(entryOne)); - assertEquals(0, segment.count); - assertNull(table.get(0)); + assertThat(segment.reclaimKey(entryOne, hashOne)).isTrue(); + assertThat(listener.getCount()).isEqualTo(1); + assertThat(listener.getLastEvictedKey()).isSameInstanceAs(keyOne); + assertThat(listener.getLastEvictedValue()).isSameInstanceAs(valueOne); + assertThat(map.removalNotificationQueue.isEmpty()).isTrue(); + assertThat(segment.accessQueue.contains(entryOne)).isFalse(); + assertThat(segment.writeQueue.contains(entryOne)).isFalse(); + assertThat(segment.count).isEqualTo(0); + assertThat(table.get(0)).isNull(); } public void testRemoveEntryFromChain() { @@ -1706,35 +1795,35 @@ public void testRemoveEntryFromChain() { createDummyEntry(keyThree, hashThree, valueThree, entryTwo); // alone - assertNull(segment.removeEntryFromChain(entryOne, entryOne)); + assertThat(segment.removeEntryFromChain(entryOne, entryOne)).isNull(); // head - assertSame(entryOne, segment.removeEntryFromChain(entryTwo, entryTwo)); + assertThat(segment.removeEntryFromChain(entryTwo, entryTwo)).isSameInstanceAs(entryOne); // middle ReferenceEntry newFirst = segment.removeEntryFromChain(entryThree, entryTwo); - assertSame(keyThree, newFirst.getKey()); - assertSame(valueThree, newFirst.getValueReference().get()); - assertEquals(hashThree, newFirst.getHash()); - assertSame(entryOne, newFirst.getNext()); + assertThat(newFirst.getKey()).isSameInstanceAs(keyThree); + assertThat(newFirst.getValueReference().get()).isSameInstanceAs(valueThree); + assertThat(newFirst.getHash()).isEqualTo(hashThree); + assertThat(newFirst.getNext()).isSameInstanceAs(entryOne); // tail (remaining entries are copied in reverse order) newFirst = segment.removeEntryFromChain(entryThree, entryOne); - assertSame(keyTwo, newFirst.getKey()); - assertSame(valueTwo, newFirst.getValueReference().get()); - assertEquals(hashTwo, newFirst.getHash()); + assertThat(newFirst.getKey()).isSameInstanceAs(keyTwo); + assertThat(newFirst.getValueReference().get()).isSameInstanceAs(valueTwo); + assertThat(newFirst.getHash()).isEqualTo(hashTwo); newFirst = newFirst.getNext(); - assertSame(keyThree, newFirst.getKey()); - assertSame(valueThree, newFirst.getValueReference().get()); - assertEquals(hashThree, newFirst.getHash()); - assertNull(newFirst.getNext()); + assertThat(newFirst.getKey()).isSameInstanceAs(keyThree); + assertThat(newFirst.getValueReference().get()).isSameInstanceAs(valueThree); + assertThat(newFirst.getHash()).isEqualTo(hashThree); + assertThat(newFirst.getNext()).isNull(); } public void testExpand_cleanup() { LocalCache map = makeLocalCache(createCacheBuilder().concurrencyLevel(1).initialCapacity(1)); Segment segment = map.segments[0]; - assertEquals(1, segment.table.length()); + assertThat(segment.table.length()).isEqualTo(1); // manually add elements to avoid expansion // 1/3 null keys, 1/3 null values @@ -1755,22 +1844,22 @@ public void testExpand_cleanup() { segment.table.set(0, entry); segment.count = originalCount; int liveCount = originalCount / 3; - assertEquals(1, segment.table.length()); - assertEquals(liveCount, countLiveEntries(map, 0)); + assertThat(segment.table.length()).isEqualTo(1); + assertThat(countLiveEntries(map, 0)).isEqualTo(liveCount); ImmutableMap originalMap = ImmutableMap.copyOf(map); - assertEquals(liveCount, originalMap.size()); + assertThat(originalMap).hasSize(liveCount); // can't compare map contents until cleanup occurs for (int i = 1; i <= originalCount * 2; i *= 2) { if (i > 1) { segment.expand(); } - assertEquals(i, segment.table.length()); - assertEquals(liveCount, countLiveEntries(map, 0)); + assertThat(segment.table.length()).isEqualTo(i); + assertThat(countLiveEntries(map, 0)).isEqualTo(liveCount); // expansion cleanup is sloppy, with a goal of avoiding unnecessary copies - assertTrue(segment.count >= liveCount); - assertTrue(segment.count <= originalCount); - assertEquals(originalMap, ImmutableMap.copyOf(map)); + assertThat(segment.count).isAtLeast(liveCount); + assertThat(segment.count).isAtMost(originalCount); + assertThat(ImmutableMap.copyOf(map)).isEqualTo(originalMap); } } @@ -1799,7 +1888,7 @@ public void testClear() { .expireAfterWrite(99999, SECONDS)); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertEquals(1, table.length()); + assertThat(table.length()).isEqualTo(1); Object key = new Object(); Object value = new Object(); @@ -1811,17 +1900,17 @@ public void testClear() { segment.count = 1; segment.totalWeight = 1; - assertSame(entry, table.get(0)); - assertSame(entry, segment.accessQueue.peek()); - assertSame(entry, segment.writeQueue.peek()); + assertThat(table.get(0)).isSameInstanceAs(entry); + assertThat(segment.accessQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.writeQueue.peek()).isSameInstanceAs(entry); segment.clear(); - assertNull(table.get(0)); - assertTrue(segment.accessQueue.isEmpty()); - assertTrue(segment.writeQueue.isEmpty()); - assertEquals(0, segment.readCount.get()); - assertEquals(0, segment.count); - assertEquals(0, segment.totalWeight); + assertThat(table.get(0)).isNull(); + assertThat(segment.accessQueue.isEmpty()).isTrue(); + assertThat(segment.writeQueue.isEmpty()).isTrue(); + assertThat(segment.readCount.get()).isEqualTo(0); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.totalWeight).isEqualTo(0); } public void testClear_notification() { @@ -1836,7 +1925,7 @@ public void testClear_notification() { .removalListener(listener)); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertEquals(1, table.length()); + assertThat(table.length()).isEqualTo(1); Object key = new Object(); Object value = new Object(); @@ -1848,17 +1937,17 @@ public void testClear_notification() { segment.count = 1; segment.totalWeight = 1; - assertSame(entry, table.get(0)); - assertSame(entry, segment.accessQueue.peek()); - assertSame(entry, segment.writeQueue.peek()); + assertThat(table.get(0)).isSameInstanceAs(entry); + assertThat(segment.accessQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.writeQueue.peek()).isSameInstanceAs(entry); segment.clear(); - assertNull(table.get(0)); - assertTrue(segment.accessQueue.isEmpty()); - assertTrue(segment.writeQueue.isEmpty()); - assertEquals(0, segment.readCount.get()); - assertEquals(0, segment.count); - assertEquals(0, segment.totalWeight); + assertThat(table.get(0)).isNull(); + assertThat(segment.accessQueue.isEmpty()).isTrue(); + assertThat(segment.writeQueue.isEmpty()).isTrue(); + assertThat(segment.readCount.get()).isEqualTo(0); + assertThat(segment.count).isEqualTo(0); + assertThat(segment.totalWeight).isEqualTo(0); assertNotified(listener, key, value, RemovalCause.EXPLICIT); } @@ -1873,7 +1962,7 @@ public void testRemoveEntry() { .removalListener(countingRemovalListener())); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertEquals(1, table.length()); + assertThat(table.length()).isEqualTo(1); Object key = new Object(); Object value = new Object(); @@ -1881,19 +1970,19 @@ public void testRemoveEntry() { DummyEntry entry = createDummyEntry(key, hash, value, null); // remove absent - assertFalse(segment.removeEntry(entry, hash, RemovalCause.COLLECTED)); + assertThat(segment.removeEntry(entry, hash, RemovalCause.COLLECTED)).isFalse(); // remove live segment.recordWrite(entry, 1, map.ticker.read()); table.set(0, entry); segment.count = 1; - assertTrue(segment.removeEntry(entry, hash, RemovalCause.COLLECTED)); - assertNotificationEnqueued(map, key, value, hash); - assertTrue(map.removalNotificationQueue.isEmpty()); - assertFalse(segment.accessQueue.contains(entry)); - assertFalse(segment.writeQueue.contains(entry)); - assertEquals(0, segment.count); - assertNull(table.get(0)); + assertThat(segment.removeEntry(entry, hash, RemovalCause.COLLECTED)).isTrue(); + assertNotificationEnqueued(map, key, value); + assertThat(map.removalNotificationQueue.isEmpty()).isTrue(); + assertThat(segment.accessQueue.contains(entry)).isFalse(); + assertThat(segment.writeQueue.contains(entry)).isFalse(); + assertThat(segment.count).isEqualTo(0); + assertThat(table.get(0)).isNull(); } public void testReclaimValue() { @@ -1908,7 +1997,7 @@ public void testReclaimValue() { .removalListener(listener)); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertEquals(1, table.length()); + assertThat(table.length()).isEqualTo(1); Object key = new Object(); Object value = new Object(); @@ -1918,32 +2007,32 @@ public void testReclaimValue() { entry.setValueReference(valueRef); // reclaim absent - assertFalse(segment.reclaimValue(key, hash, valueRef)); + assertThat(segment.reclaimValue(key, hash, valueRef)).isFalse(); // reclaim live segment.recordWrite(entry, 1, map.ticker.read()); table.set(0, entry); segment.count = 1; - assertTrue(segment.reclaimValue(key, hash, valueRef)); - assertEquals(1, listener.getCount()); - assertSame(key, listener.getLastEvictedKey()); - assertSame(value, listener.getLastEvictedValue()); - assertTrue(map.removalNotificationQueue.isEmpty()); - assertFalse(segment.accessQueue.contains(entry)); - assertFalse(segment.writeQueue.contains(entry)); - assertEquals(0, segment.count); - assertNull(table.get(0)); + assertThat(segment.reclaimValue(key, hash, valueRef)).isTrue(); + assertThat(listener.getCount()).isEqualTo(1); + assertThat(listener.getLastEvictedKey()).isSameInstanceAs(key); + assertThat(listener.getLastEvictedValue()).isSameInstanceAs(value); + assertThat(map.removalNotificationQueue.isEmpty()).isTrue(); + assertThat(segment.accessQueue.contains(entry)).isFalse(); + assertThat(segment.writeQueue.contains(entry)).isFalse(); + assertThat(segment.count).isEqualTo(0); + assertThat(table.get(0)).isNull(); // reclaim wrong value reference table.set(0, entry); DummyValueReference otherValueRef = DummyValueReference.create(value); entry.setValueReference(otherValueRef); - assertFalse(segment.reclaimValue(key, hash, valueRef)); - assertEquals(1, listener.getCount()); - assertTrue(segment.reclaimValue(key, hash, otherValueRef)); - assertEquals(2, listener.getCount()); - assertSame(key, listener.getLastEvictedKey()); - assertSame(value, listener.getLastEvictedValue()); + assertThat(segment.reclaimValue(key, hash, valueRef)).isFalse(); + assertThat(listener.getCount()).isEqualTo(1); + assertThat(segment.reclaimValue(key, hash, otherValueRef)).isTrue(); + assertThat(listener.getCount()).isEqualTo(2); + assertThat(listener.getLastEvictedKey()).isSameInstanceAs(key); + assertThat(listener.getLastEvictedValue()).isSameInstanceAs(value); } public void testRemoveComputingValue() { @@ -1957,7 +2046,7 @@ public void testRemoveComputingValue() { .removalListener(countingRemovalListener())); Segment segment = map.segments[0]; AtomicReferenceArray> table = segment.table; - assertEquals(1, table.length()); + assertThat(table.length()).isEqualTo(1); Object key = new Object(); int hash = map.hash(key); @@ -1966,16 +2055,16 @@ public void testRemoveComputingValue() { entry.setValueReference(valueRef); // absent - assertFalse(segment.removeLoadingValue(key, hash, valueRef)); + assertThat(segment.removeLoadingValue(key, hash, valueRef)).isFalse(); // live table.set(0, entry); // don't increment count; this is used during computation - assertTrue(segment.removeLoadingValue(key, hash, valueRef)); + assertThat(segment.removeLoadingValue(key, hash, valueRef)).isTrue(); // no notification sent with removeLoadingValue - assertTrue(map.removalNotificationQueue.isEmpty()); - assertEquals(0, segment.count); - assertNull(table.get(0)); + assertThat(map.removalNotificationQueue.isEmpty()).isTrue(); + assertThat(segment.count).isEqualTo(0); + assertThat(table.get(0)).isNull(); // active Object value = new Object(); @@ -1984,24 +2073,23 @@ public void testRemoveComputingValue() { entry.setValueReference(valueRef); table.set(0, entry); segment.count = 1; - assertTrue(segment.removeLoadingValue(key, hash, valueRef)); - assertSame(entry, table.get(0)); - assertSame(value, segment.get(key, hash)); + assertThat(segment.removeLoadingValue(key, hash, valueRef)).isTrue(); + assertThat(table.get(0)).isSameInstanceAs(entry); + assertThat(segment.get(key, hash)).isSameInstanceAs(value); // wrong value reference table.set(0, entry); DummyValueReference otherValueRef = DummyValueReference.create(value); entry.setValueReference(otherValueRef); - assertFalse(segment.removeLoadingValue(key, hash, valueRef)); + assertThat(segment.removeLoadingValue(key, hash, valueRef)).isFalse(); entry.setValueReference(valueRef); - assertTrue(segment.removeLoadingValue(key, hash, valueRef)); + assertThat(segment.removeLoadingValue(key, hash, valueRef)).isTrue(); } - private static void assertNotificationEnqueued( - LocalCache map, K key, V value, int hash) { + private static void assertNotificationEnqueued(LocalCache map, K key, V value) { RemovalNotification notification = map.removalNotificationQueue.poll(); - assertSame(key, notification.getKey()); - assertSame(value, notification.getValue()); + assertThat(notification.getKey()).isSameInstanceAs(key); + assertThat(notification.getValue()).isSameInstanceAs(value); } // Segment eviction tests @@ -2018,15 +2106,15 @@ public void testDrainRecencyQueueOnWrite() { Object valueTwo = new Object(); map.put(keyOne, valueOne); - assertTrue(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isTrue(); for (int i = 0; i < DRAIN_THRESHOLD / 2; i++) { map.get(keyOne); } - assertFalse(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isFalse(); map.put(keyTwo, valueTwo); - assertTrue(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isTrue(); } } } @@ -2043,16 +2131,16 @@ public void testDrainRecencyQueueOnRead() { // repeated get of the same key map.put(keyOne, valueOne); - assertTrue(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isTrue(); for (int i = 0; i < DRAIN_THRESHOLD / 2; i++) { map.get(keyOne); } - assertFalse(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isFalse(); for (int i = 0; i < DRAIN_THRESHOLD * 2; i++) { map.get(keyOne); - assertTrue(segment.recencyQueue.size() <= DRAIN_THRESHOLD); + assertThat(segment.recencyQueue.size()).isAtMost(DRAIN_THRESHOLD); } // get over many different keys @@ -2060,16 +2148,16 @@ public void testDrainRecencyQueueOnRead() { for (int i = 0; i < DRAIN_THRESHOLD * 2; i++) { map.put(new Object(), new Object()); } - assertTrue(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isTrue(); for (int i = 0; i < DRAIN_THRESHOLD / 2; i++) { map.get(keyOne); } - assertFalse(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isFalse(); for (Object key : map.keySet()) { map.get(key); - assertTrue(segment.recencyQueue.size() <= DRAIN_THRESHOLD); + assertThat(segment.recencyQueue.size()).isAtMost(DRAIN_THRESHOLD); } } } @@ -2079,8 +2167,8 @@ public void testRecordRead() { for (CacheBuilder builder : allEvictingMakers()) { LocalCache map = makeLocalCache(builder.concurrencyLevel(1)); Segment segment = map.segments[0]; - List> writeOrder = Lists.newLinkedList(); - List> readOrder = Lists.newLinkedList(); + List> writeOrder = new LinkedList<>(); + List> readOrder = new LinkedList<>(); for (int i = 0; i < DRAIN_THRESHOLD * 2; i++) { Object key = new Object(); int hash = map.hash(key); @@ -2098,7 +2186,7 @@ public void testRecordRead() { // access some of the elements Random random = new Random(); - List> reads = Lists.newArrayList(); + List> reads = new ArrayList<>(); Iterator> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry entry = i.next(); @@ -2120,8 +2208,8 @@ public void testRecordReadOnGet() { for (CacheBuilder builder : allEvictingMakers()) { LocalCache map = makeLocalCache(builder.concurrencyLevel(1)); Segment segment = map.segments[0]; - List> writeOrder = Lists.newLinkedList(); - List> readOrder = Lists.newLinkedList(); + List> writeOrder = new LinkedList<>(); + List> readOrder = new LinkedList<>(); for (int i = 0; i < DRAIN_THRESHOLD * 2; i++) { Object key = new Object(); int hash = map.hash(key); @@ -2135,11 +2223,11 @@ public void testRecordReadOnGet() { checkEvictionQueues(map, segment, readOrder, writeOrder); checkExpirationTimes(map); - assertTrue(segment.recencyQueue.isEmpty()); + assertThat(segment.recencyQueue.isEmpty()).isTrue(); // access some of the elements Random random = new Random(); - List> reads = Lists.newArrayList(); + List> reads = new ArrayList<>(); Iterator> i = readOrder.iterator(); while (i.hasNext()) { ReferenceEntry entry = i.next(); @@ -2147,7 +2235,7 @@ public void testRecordReadOnGet() { map.get(entry.getKey()); reads.add(entry); i.remove(); - assertTrue(segment.recencyQueue.size() <= DRAIN_THRESHOLD); + assertThat(segment.recencyQueue.size()).isAtMost(DRAIN_THRESHOLD); } } int undrainedIndex = reads.size() - segment.recencyQueue.size(); @@ -2163,7 +2251,7 @@ public void testRecordWrite() { for (CacheBuilder builder : allEvictingMakers()) { LocalCache map = makeLocalCache(builder.concurrencyLevel(1)); Segment segment = map.segments[0]; - List> writeOrder = Lists.newLinkedList(); + List> writeOrder = new LinkedList<>(); for (int i = 0; i < DRAIN_THRESHOLD * 2; i++) { Object key = new Object(); int hash = map.hash(key); @@ -2180,7 +2268,7 @@ public void testRecordWrite() { // access some of the elements Random random = new Random(); - List> writes = Lists.newArrayList(); + List> writes = new ArrayList<>(); Iterator> i = writeOrder.iterator(); while (i.hasNext()) { ReferenceEntry entry = i.next(); @@ -2221,12 +2309,13 @@ static void checkEvictionQueues( private static void assertSameEntries( List> expectedEntries, List> actualEntries) { int size = expectedEntries.size(); - assertEquals(size, actualEntries.size()); + assertThat(actualEntries).hasSize(size); for (int i = 0; i < size; i++) { ReferenceEntry expectedEntry = expectedEntries.get(i); ReferenceEntry actualEntry = actualEntries.get(i); - assertSame(expectedEntry.getKey(), actualEntry.getKey()); - assertSame(expectedEntry.getValueReference().get(), actualEntry.getValueReference().get()); + assertThat(actualEntry.getKey()).isSameInstanceAs(expectedEntry.getKey()); + assertThat(actualEntry.getValueReference().get()) + .isSameInstanceAs(expectedEntry.getValueReference().get()); } } @@ -2240,10 +2329,10 @@ static void checkExpirationTimes(LocalCache map) { long lastWriteTime = 0; for (ReferenceEntry e : segment.recencyQueue) { long accessTime = e.getAccessTime(); - assertTrue(accessTime >= lastAccessTime); + assertThat(accessTime).isAtLeast(lastAccessTime); lastAccessTime = accessTime; long writeTime = e.getWriteTime(); - assertTrue(writeTime >= lastWriteTime); + assertThat(writeTime).isAtLeast(lastWriteTime); lastWriteTime = writeTime; } @@ -2251,12 +2340,12 @@ static void checkExpirationTimes(LocalCache map) { lastWriteTime = 0; for (ReferenceEntry e : segment.accessQueue) { long accessTime = e.getAccessTime(); - assertTrue(accessTime >= lastAccessTime); + assertThat(accessTime).isAtLeast(lastAccessTime); lastAccessTime = accessTime; } for (ReferenceEntry e : segment.writeQueue) { long writeTime = e.getWriteTime(); - assertTrue(writeTime >= lastWriteTime); + assertThat(writeTime).isAtLeast(lastWriteTime); lastWriteTime = writeTime; } } @@ -2269,38 +2358,38 @@ public void testExpireAfterWrite() { createCacheBuilder() .concurrencyLevel(1) .ticker(ticker) - .expireAfterWrite(2, TimeUnit.NANOSECONDS)); + .expireAfterWrite(2, NANOSECONDS)); Segment segment = map.segments[0]; Object key = new Object(); Object value = new Object(); map.put(key, value); ReferenceEntry entry = map.getEntry(key); - assertTrue(map.isLive(entry, ticker.read())); + assertThat(map.isLive(entry, ticker.read())).isTrue(); segment.writeQueue.add(entry); - assertSame(value, map.get(key)); - assertSame(entry, segment.writeQueue.peek()); - assertEquals(1, segment.writeQueue.size()); + assertThat(map.get(key)).isSameInstanceAs(value); + assertThat(segment.writeQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.writeQueue).hasSize(1); segment.recordRead(entry, ticker.read()); segment.expireEntries(ticker.read()); - assertSame(value, map.get(key)); - assertSame(entry, segment.writeQueue.peek()); - assertEquals(1, segment.writeQueue.size()); + assertThat(map.get(key)).isSameInstanceAs(value); + assertThat(segment.writeQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.writeQueue).hasSize(1); ticker.advance(1); segment.recordRead(entry, ticker.read()); segment.expireEntries(ticker.read()); - assertSame(value, map.get(key)); - assertSame(entry, segment.writeQueue.peek()); - assertEquals(1, segment.writeQueue.size()); + assertThat(map.get(key)).isSameInstanceAs(value); + assertThat(segment.writeQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.writeQueue).hasSize(1); ticker.advance(1); - assertNull(map.get(key)); + assertThat(map.get(key)).isNull(); segment.expireEntries(ticker.read()); - assertNull(map.get(key)); - assertTrue(segment.writeQueue.isEmpty()); + assertThat(map.get(key)).isNull(); + assertThat(segment.writeQueue.isEmpty()).isTrue(); } public void testExpireAfterAccess() { @@ -2310,53 +2399,53 @@ public void testExpireAfterAccess() { createCacheBuilder() .concurrencyLevel(1) .ticker(ticker) - .expireAfterAccess(2, TimeUnit.NANOSECONDS)); + .expireAfterAccess(2, NANOSECONDS)); Segment segment = map.segments[0]; Object key = new Object(); Object value = new Object(); map.put(key, value); ReferenceEntry entry = map.getEntry(key); - assertTrue(map.isLive(entry, ticker.read())); + assertThat(map.isLive(entry, ticker.read())).isTrue(); segment.accessQueue.add(entry); - assertSame(value, map.get(key)); - assertSame(entry, segment.accessQueue.peek()); - assertEquals(1, segment.accessQueue.size()); + assertThat(map.get(key)).isSameInstanceAs(value); + assertThat(segment.accessQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.accessQueue).hasSize(1); segment.recordRead(entry, ticker.read()); segment.expireEntries(ticker.read()); - assertTrue(map.containsKey(key)); - assertSame(entry, segment.accessQueue.peek()); - assertEquals(1, segment.accessQueue.size()); + assertThat(map.containsKey(key)).isTrue(); + assertThat(segment.accessQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.accessQueue).hasSize(1); ticker.advance(1); segment.recordRead(entry, ticker.read()); segment.expireEntries(ticker.read()); - assertTrue(map.containsKey(key)); - assertSame(entry, segment.accessQueue.peek()); - assertEquals(1, segment.accessQueue.size()); + assertThat(map.containsKey(key)).isTrue(); + assertThat(segment.accessQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.accessQueue).hasSize(1); ticker.advance(1); segment.recordRead(entry, ticker.read()); segment.expireEntries(ticker.read()); - assertTrue(map.containsKey(key)); - assertSame(entry, segment.accessQueue.peek()); - assertEquals(1, segment.accessQueue.size()); + assertThat(map.containsKey(key)).isTrue(); + assertThat(segment.accessQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.accessQueue).hasSize(1); ticker.advance(1); segment.expireEntries(ticker.read()); - assertTrue(map.containsKey(key)); - assertSame(entry, segment.accessQueue.peek()); - assertEquals(1, segment.accessQueue.size()); + assertThat(map.containsKey(key)).isTrue(); + assertThat(segment.accessQueue.peek()).isSameInstanceAs(entry); + assertThat(segment.accessQueue).hasSize(1); ticker.advance(1); - assertFalse(map.containsKey(key)); - assertNull(map.get(key)); + assertThat(map.containsKey(key)).isFalse(); + assertThat(map.get(key)).isNull(); segment.expireEntries(ticker.read()); - assertFalse(map.containsKey(key)); - assertNull(map.get(key)); - assertTrue(segment.accessQueue.isEmpty()); + assertThat(map.containsKey(key)).isFalse(); + assertThat(map.get(key)).isNull(); + assertThat(segment.accessQueue.isEmpty()).isTrue(); } public void testEvictEntries() { @@ -2368,7 +2457,7 @@ public void testEvictEntries() { // manually add elements to avoid eviction int originalCount = 1024; ReferenceEntry entry = null; - LinkedHashMap originalMap = Maps.newLinkedHashMap(); + LinkedHashMap originalMap = new LinkedHashMap<>(); for (int i = 0; i < originalCount; i++) { Object key = new Object(); Object value = new Object(); @@ -2385,8 +2474,8 @@ public void testEvictEntries() { } segment.count = originalCount; segment.totalWeight = originalCount; - assertEquals(originalCount, map.size()); - assertEquals(originalMap, map); + assertThat(map).hasSize(originalCount); + assertThat(map).isEqualTo(originalMap); Iterator it = originalMap.keySet().iterator(); for (int i = 0; i < originalCount - maxSize; i++) { @@ -2394,8 +2483,8 @@ public void testEvictEntries() { it.remove(); } segment.evictEntries(entry); - assertEquals(maxSize, map.size()); - assertEquals(originalMap, map); + assertThat(map).hasSize(maxSize); + assertThat(map).isEqualTo(originalMap); } // reference queues @@ -2420,11 +2509,11 @@ public void testDrainKeyReferenceQueueOnWrite() { reference.enqueue(); map.put(keyTwo, valueTwo); - assertFalse(map.containsKey(keyOne)); - assertFalse(map.containsValue(valueOne)); - assertNull(map.get(keyOne)); - assertEquals(1, map.size()); - assertNull(segment.keyReferenceQueue.poll()); + assertThat(map.containsKey(keyOne)).isFalse(); + assertThat(map.containsValue(valueOne)).isFalse(); + assertThat(map.get(keyOne)).isNull(); + assertThat(map).hasSize(1); + assertThat(segment.keyReferenceQueue.poll()).isNull(); } } } @@ -2450,11 +2539,11 @@ public void testDrainValueReferenceQueueOnWrite() { reference.enqueue(); map.put(keyTwo, valueTwo); - assertFalse(map.containsKey(keyOne)); - assertFalse(map.containsValue(valueOne)); - assertNull(map.get(keyOne)); - assertEquals(1, map.size()); - assertNull(segment.valueReferenceQueue.poll()); + assertThat(map.containsKey(keyOne)).isFalse(); + assertThat(map.containsValue(valueOne)).isFalse(); + assertThat(map.get(keyOne)).isNull(); + assertThat(map).hasSize(1); + assertThat(segment.valueReferenceQueue.poll()).isNull(); } } } @@ -2480,11 +2569,11 @@ public void testDrainKeyReferenceQueueOnRead() { for (int i = 0; i < SMALL_MAX_SIZE; i++) { map.get(keyTwo); } - assertFalse(map.containsKey(keyOne)); - assertFalse(map.containsValue(valueOne)); - assertNull(map.get(keyOne)); - assertEquals(0, map.size()); - assertNull(segment.keyReferenceQueue.poll()); + assertThat(map.containsKey(keyOne)).isFalse(); + assertThat(map.containsValue(valueOne)).isFalse(); + assertThat(map.get(keyOne)).isNull(); + assertThat(map).isEmpty(); + assertThat(segment.keyReferenceQueue.poll()).isNull(); } } } @@ -2511,11 +2600,11 @@ public void testDrainValueReferenceQueueOnRead() { for (int i = 0; i < SMALL_MAX_SIZE; i++) { map.get(keyTwo); } - assertFalse(map.containsKey(keyOne)); - assertFalse(map.containsValue(valueOne)); - assertNull(map.get(keyOne)); - assertEquals(0, map.size()); - assertNull(segment.valueReferenceQueue.poll()); + assertThat(map.containsKey(keyOne)).isFalse(); + assertThat(map.containsValue(valueOne)).isFalse(); + assertThat(map.get(keyOne)).isNull(); + assertThat(map).isEmpty(); + assertThat(segment.valueReferenceQueue.poll()).isNull(); } } } @@ -2524,7 +2613,7 @@ public void testNullParameters() throws Exception { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicInstanceMethods(makeLocalCache(createCacheBuilder())); CacheLoader loader = identityLoader(); - tester.testAllPublicInstanceMethods(makeLocalCache(createCacheBuilder())); + tester.testAllPublicInstanceMethods(makeLocalCache(createCacheBuilder(), loader)); } public void testSerializationProxyLoading() { @@ -2548,42 +2637,45 @@ public void testSerializationProxyLoading() { .build(loader); // add a non-serializable entry one.getUnchecked(new Object()); - assertEquals(1, one.size()); - assertFalse(one.asMap().isEmpty()); + assertThat(one.size()).isEqualTo(1); + assertThat(one.asMap().isEmpty()).isFalse(); LocalLoadingCache two = SerializableTester.reserialize(one); - assertEquals(0, two.size()); - assertTrue(two.asMap().isEmpty()); + assertThat(two.size()).isEqualTo(0); + assertThat(two.asMap().isEmpty()).isTrue(); LocalCache localCacheOne = one.localCache; LocalCache localCacheTwo = two.localCache; - assertEquals(localCacheOne.keyStrength, localCacheTwo.keyStrength); - assertEquals(localCacheOne.keyStrength, localCacheTwo.keyStrength); - assertEquals(localCacheOne.valueEquivalence, localCacheTwo.valueEquivalence); - assertEquals(localCacheOne.valueEquivalence, localCacheTwo.valueEquivalence); - assertEquals(localCacheOne.maxWeight, localCacheTwo.maxWeight); - assertEquals(localCacheOne.weigher, localCacheTwo.weigher); - assertEquals(localCacheOne.expireAfterAccessNanos, localCacheTwo.expireAfterAccessNanos); - assertEquals(localCacheOne.expireAfterWriteNanos, localCacheTwo.expireAfterWriteNanos); - assertEquals(localCacheOne.refreshNanos, localCacheTwo.refreshNanos); - assertEquals(localCacheOne.removalListener, localCacheTwo.removalListener); - assertEquals(localCacheOne.ticker, localCacheTwo.ticker); + assertThat(localCacheTwo.keyStrength).isEqualTo(localCacheOne.keyStrength); + assertThat(localCacheTwo.keyStrength).isEqualTo(localCacheOne.keyStrength); + assertThat(localCacheTwo.valueEquivalence).isEqualTo(localCacheOne.valueEquivalence); + assertThat(localCacheTwo.valueEquivalence).isEqualTo(localCacheOne.valueEquivalence); + assertThat(localCacheTwo.maxWeight).isEqualTo(localCacheOne.maxWeight); + assertThat(localCacheTwo.weigher).isEqualTo(localCacheOne.weigher); + assertThat(localCacheTwo.expireAfterAccessNanos) + .isEqualTo(localCacheOne.expireAfterAccessNanos); + assertThat(localCacheTwo.expireAfterWriteNanos).isEqualTo(localCacheOne.expireAfterWriteNanos); + assertThat(localCacheTwo.refreshNanos).isEqualTo(localCacheOne.refreshNanos); + assertThat(localCacheTwo.removalListener).isEqualTo(localCacheOne.removalListener); + assertThat(localCacheTwo.ticker).isEqualTo(localCacheOne.ticker); // serialize the reconstituted version to be sure we haven't lost the ability to reserialize LocalLoadingCache three = SerializableTester.reserialize(two); LocalCache localCacheThree = three.localCache; - assertEquals(localCacheTwo.defaultLoader, localCacheThree.defaultLoader); - assertEquals(localCacheTwo.keyStrength, localCacheThree.keyStrength); - assertEquals(localCacheTwo.keyStrength, localCacheThree.keyStrength); - assertEquals(localCacheTwo.valueEquivalence, localCacheThree.valueEquivalence); - assertEquals(localCacheTwo.valueEquivalence, localCacheThree.valueEquivalence); - assertEquals(localCacheTwo.maxWeight, localCacheThree.maxWeight); - assertEquals(localCacheTwo.weigher, localCacheThree.weigher); - assertEquals(localCacheTwo.expireAfterAccessNanos, localCacheThree.expireAfterAccessNanos); - assertEquals(localCacheTwo.expireAfterWriteNanos, localCacheThree.expireAfterWriteNanos); - assertEquals(localCacheTwo.removalListener, localCacheThree.removalListener); - assertEquals(localCacheTwo.ticker, localCacheThree.ticker); + assertThat(localCacheThree.defaultLoader).isEqualTo(localCacheTwo.defaultLoader); + assertThat(localCacheThree.keyStrength).isEqualTo(localCacheTwo.keyStrength); + assertThat(localCacheThree.keyStrength).isEqualTo(localCacheTwo.keyStrength); + assertThat(localCacheThree.valueEquivalence).isEqualTo(localCacheTwo.valueEquivalence); + assertThat(localCacheThree.valueEquivalence).isEqualTo(localCacheTwo.valueEquivalence); + assertThat(localCacheThree.maxWeight).isEqualTo(localCacheTwo.maxWeight); + assertThat(localCacheThree.weigher).isEqualTo(localCacheTwo.weigher); + assertThat(localCacheThree.expireAfterAccessNanos) + .isEqualTo(localCacheTwo.expireAfterAccessNanos); + assertThat(localCacheThree.expireAfterWriteNanos) + .isEqualTo(localCacheTwo.expireAfterWriteNanos); + assertThat(localCacheThree.removalListener).isEqualTo(localCacheTwo.removalListener); + assertThat(localCacheThree.ticker).isEqualTo(localCacheTwo.ticker); } public void testSerializationProxyManual() { @@ -2605,51 +2697,132 @@ public void testSerializationProxyManual() { .build(); // add a non-serializable entry one.put(new Object(), new Object()); - assertEquals(1, one.size()); - assertFalse(one.asMap().isEmpty()); + assertThat(one.size()).isEqualTo(1); + assertThat(one.asMap().isEmpty()).isFalse(); LocalManualCache two = SerializableTester.reserialize(one); - assertEquals(0, two.size()); - assertTrue(two.asMap().isEmpty()); + assertThat(two.size()).isEqualTo(0); + assertThat(two.asMap().isEmpty()).isTrue(); LocalCache localCacheOne = one.localCache; LocalCache localCacheTwo = two.localCache; - assertEquals(localCacheOne.keyStrength, localCacheTwo.keyStrength); - assertEquals(localCacheOne.keyStrength, localCacheTwo.keyStrength); - assertEquals(localCacheOne.valueEquivalence, localCacheTwo.valueEquivalence); - assertEquals(localCacheOne.valueEquivalence, localCacheTwo.valueEquivalence); - assertEquals(localCacheOne.maxWeight, localCacheTwo.maxWeight); - assertEquals(localCacheOne.weigher, localCacheTwo.weigher); - assertEquals(localCacheOne.expireAfterAccessNanos, localCacheTwo.expireAfterAccessNanos); - assertEquals(localCacheOne.expireAfterWriteNanos, localCacheTwo.expireAfterWriteNanos); - assertEquals(localCacheOne.removalListener, localCacheTwo.removalListener); - assertEquals(localCacheOne.ticker, localCacheTwo.ticker); + assertThat(localCacheTwo.keyStrength).isEqualTo(localCacheOne.keyStrength); + assertThat(localCacheTwo.keyStrength).isEqualTo(localCacheOne.keyStrength); + assertThat(localCacheTwo.valueEquivalence).isEqualTo(localCacheOne.valueEquivalence); + assertThat(localCacheTwo.valueEquivalence).isEqualTo(localCacheOne.valueEquivalence); + assertThat(localCacheTwo.maxWeight).isEqualTo(localCacheOne.maxWeight); + assertThat(localCacheTwo.weigher).isEqualTo(localCacheOne.weigher); + assertThat(localCacheTwo.expireAfterAccessNanos) + .isEqualTo(localCacheOne.expireAfterAccessNanos); + assertThat(localCacheTwo.expireAfterWriteNanos).isEqualTo(localCacheOne.expireAfterWriteNanos); + assertThat(localCacheTwo.removalListener).isEqualTo(localCacheOne.removalListener); + assertThat(localCacheTwo.ticker).isEqualTo(localCacheOne.ticker); // serialize the reconstituted version to be sure we haven't lost the ability to reserialize LocalManualCache three = SerializableTester.reserialize(two); LocalCache localCacheThree = three.localCache; - assertEquals(localCacheTwo.keyStrength, localCacheThree.keyStrength); - assertEquals(localCacheTwo.keyStrength, localCacheThree.keyStrength); - assertEquals(localCacheTwo.valueEquivalence, localCacheThree.valueEquivalence); - assertEquals(localCacheTwo.valueEquivalence, localCacheThree.valueEquivalence); - assertEquals(localCacheTwo.maxWeight, localCacheThree.maxWeight); - assertEquals(localCacheTwo.weigher, localCacheThree.weigher); - assertEquals(localCacheTwo.expireAfterAccessNanos, localCacheThree.expireAfterAccessNanos); - assertEquals(localCacheTwo.expireAfterWriteNanos, localCacheThree.expireAfterWriteNanos); - assertEquals(localCacheTwo.removalListener, localCacheThree.removalListener); - assertEquals(localCacheTwo.ticker, localCacheThree.ticker); + assertThat(localCacheThree.keyStrength).isEqualTo(localCacheTwo.keyStrength); + assertThat(localCacheThree.keyStrength).isEqualTo(localCacheTwo.keyStrength); + assertThat(localCacheThree.valueEquivalence).isEqualTo(localCacheTwo.valueEquivalence); + assertThat(localCacheThree.valueEquivalence).isEqualTo(localCacheTwo.valueEquivalence); + assertThat(localCacheThree.maxWeight).isEqualTo(localCacheTwo.maxWeight); + assertThat(localCacheThree.weigher).isEqualTo(localCacheTwo.weigher); + assertThat(localCacheThree.expireAfterAccessNanos) + .isEqualTo(localCacheTwo.expireAfterAccessNanos); + assertThat(localCacheThree.expireAfterWriteNanos) + .isEqualTo(localCacheTwo.expireAfterWriteNanos); + assertThat(localCacheThree.removalListener).isEqualTo(localCacheTwo.removalListener); + assertThat(localCacheThree.ticker).isEqualTo(localCacheTwo.ticker); + } + + public void testLoadDifferentKeyInLoader() throws ExecutionException, InterruptedException { + LocalCache cache = makeLocalCache(createCacheBuilder()); + String key1 = "key1"; + String key2 = "key2"; + + assertThat( + cache.get( + key1, + new CacheLoader() { + @Override + public String load(String key) throws Exception { + return cache.get(key2, identityLoader()); // loads a different key, should work + } + })) + .isEqualTo(key2); + } + + public void testRecursiveLoad() throws InterruptedException { + LocalCache cache = makeLocalCache(createCacheBuilder()); + String key = "key"; + CacheLoader loader = + new CacheLoader() { + @Override + public String load(String key) throws Exception { + return cache.get(key, identityLoader()); // recursive load, this should fail + } + }; + testLoadThrows(key, cache, loader); + } + + public void testRecursiveLoadWithProxy() throws InterruptedException { + String key = "key"; + String otherKey = "otherKey"; + LocalCache cache = makeLocalCache(createCacheBuilder()); + CacheLoader loader = + new CacheLoader() { + @Override + public String load(String key) throws Exception { + return cache.get( + key, + identityLoader()); // recursive load (same as the initial one), this should fail + } + }; + CacheLoader proxyLoader = + new CacheLoader() { + @Override + public String load(String key) throws Exception { + return cache.get(otherKey, loader); // loads another key, is ok + } + }; + testLoadThrows(key, cache, proxyLoader); } // utility methods + private void testLoadThrows( + String key, LocalCache cache, CacheLoader loader) + throws InterruptedException { + CountDownLatch doneSignal = new CountDownLatch(1); + Thread thread = + new Thread( + () -> { + try { + cache.get(key, loader); + } catch (UncheckedExecutionException | ExecutionException e) { + doneSignal.countDown(); + } + }); + thread.start(); + + boolean done = doneSignal.await(1, SECONDS); + if (!done) { + StringBuilder builder = new StringBuilder(); + for (StackTraceElement trace : thread.getStackTrace()) { + builder.append("\tat ").append(trace).append('\n'); + } + fail(builder.toString()); + } + } + /** * Returns an iterable containing all combinations of maximumSize, expireAfterAccess/Write, * weakKeys and weak/softValues. */ - @SuppressWarnings("unchecked") // varargs private static Iterable> allEntryTypeMakers() { - List> result = newArrayList(allKeyValueStrengthMakers()); + List> result = new ArrayList<>(); + Iterables.addAll(result, allKeyValueStrengthMakers()); for (CacheBuilder builder : allKeyValueStrengthMakers()) { result.add(builder.maximumSize(SMALL_MAX_SIZE)); } @@ -2669,22 +2842,16 @@ private static Iterable> allEntryTypeMakers() { } /** Returns an iterable containing all combinations of maximumSize and expireAfterAccess/Write. */ - @SuppressWarnings("unchecked") // varargs static Iterable> allEvictingMakers() { return ImmutableList.of( createCacheBuilder().maximumSize(SMALL_MAX_SIZE), createCacheBuilder().expireAfterAccess(99999, SECONDS), createCacheBuilder().expireAfterWrite(99999, SECONDS), - createCacheBuilder() - .maximumSize(SMALL_MAX_SIZE) - .expireAfterAccess(SMALL_MAX_SIZE, TimeUnit.SECONDS), - createCacheBuilder() - .maximumSize(SMALL_MAX_SIZE) - .expireAfterWrite(SMALL_MAX_SIZE, TimeUnit.SECONDS)); + createCacheBuilder().maximumSize(SMALL_MAX_SIZE).expireAfterAccess(SMALL_MAX_SIZE, SECONDS), + createCacheBuilder().maximumSize(SMALL_MAX_SIZE).expireAfterWrite(SMALL_MAX_SIZE, SECONDS)); } /** Returns an iterable containing all combinations weakKeys and weak/softValues. */ - @SuppressWarnings("unchecked") // varargs private static Iterable> allKeyValueStrengthMakers() { return ImmutableList.of( createCacheBuilder(), @@ -2698,7 +2865,7 @@ private static Iterable> allKeyValueStrengthMakers( // entries and values private static DummyEntry createDummyEntry( - K key, int hash, V value, ReferenceEntry next) { + K key, int hash, V value, @Nullable ReferenceEntry next) { DummyEntry entry = DummyEntry.create(key, hash, next); DummyValueReference valueRef = DummyValueReference.create(value); entry.setValueReference(valueRef); @@ -2706,7 +2873,7 @@ private static DummyEntry createDummyEntry( } static class DummyEntry implements ReferenceEntry { - private K key; + private @Nullable K key; private final int hash; private final ReferenceEntry next; @@ -2716,7 +2883,8 @@ public DummyEntry(K key, int hash, ReferenceEntry next) { this.next = next; } - public static DummyEntry create(K key, int hash, ReferenceEntry next) { + public static DummyEntry create( + K key, int hash, @Nullable ReferenceEntry next) { return new DummyEntry<>(key, hash, next); } @@ -2825,7 +2993,7 @@ public void setPreviousInWriteQueue(ReferenceEntry previous) { } static class DummyValueReference implements ValueReference { - private V value; + private @Nullable V value; boolean loading = false; public DummyValueReference() { @@ -2855,7 +3023,7 @@ public int getWeight() { } @Override - public ReferenceEntry getEntry() { + public @Nullable ReferenceEntry getEntry() { return null; } @@ -2905,8 +3073,8 @@ public int hashCode() { } @Override - public boolean equals(Object o) { - return (o instanceof SerializableCacheLoader); + public boolean equals(@Nullable Object o) { + return o instanceof SerializableCacheLoader; } } @@ -2921,8 +3089,8 @@ public int hashCode() { } @Override - public boolean equals(Object o) { - return (o instanceof SerializableRemovalListener); + public boolean equals(@Nullable Object o) { + return o instanceof SerializableRemovalListener; } } @@ -2938,8 +3106,8 @@ public int hashCode() { } @Override - public boolean equals(Object o) { - return (o instanceof SerializableTicker); + public boolean equals(@Nullable Object o) { + return o instanceof SerializableTicker; } } @@ -2955,8 +3123,8 @@ public int hashCode() { } @Override - public boolean equals(Object o) { - return (o instanceof SerializableWeigher); + public boolean equals(@Nullable Object o) { + return o instanceof SerializableWeigher; } } } diff --git a/android/guava-tests/test/com/google/common/cache/LocalLoadingCacheTest.java b/android/guava-tests/test/com/google/common/cache/LocalLoadingCacheTest.java index 8ba9dbc97e9a..83d929534819 100644 --- a/android/guava-tests/test/com/google/common/cache/LocalLoadingCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/LocalLoadingCacheTest.java @@ -20,6 +20,7 @@ import static com.google.common.cache.LocalCacheTest.SMALL_MAX_SIZE; import static com.google.common.cache.TestingCacheLoaders.identityLoader; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.cache.LocalCache.LocalLoadingCache; import com.google.common.cache.LocalCache.Segment; @@ -31,11 +32,14 @@ import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author Charles Fry */ +/** + * @author Charles Fry + */ +@NullUnmarked public class LocalLoadingCacheTest extends TestCase { private static LocalLoadingCache makeCache( @@ -58,7 +62,7 @@ public Object load(Object from) { } }; LocalLoadingCache cache = makeCache(createCacheBuilder(), loader); - assertSame(loader, cache.localCache.defaultLoader); + assertThat(cache.localCache.defaultLoader).isSameInstanceAs(loader); } // null parameters test @@ -74,90 +78,89 @@ public void testNullParameters() throws Exception { public void testStats() { CacheBuilder builder = createCacheBuilder().concurrencyLevel(1).maximumSize(2); LocalLoadingCache cache = makeCache(builder, identityLoader()); - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); Object one = new Object(); cache.getUnchecked(one); CacheStats stats = cache.stats(); - assertEquals(1, stats.requestCount()); - assertEquals(0, stats.hitCount()); - assertEquals(0.0, stats.hitRate()); - assertEquals(1, stats.missCount()); - assertEquals(1.0, stats.missRate()); - assertEquals(1, stats.loadCount()); + assertThat(stats.requestCount()).isEqualTo(1); + assertThat(stats.hitCount()).isEqualTo(0); + assertThat(stats.hitRate()).isEqualTo(0.0); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.missRate()).isEqualTo(1.0); + assertThat(stats.loadCount()).isEqualTo(1); long totalLoadTime = stats.totalLoadTime(); - assertTrue(totalLoadTime >= 0); - assertTrue(stats.averageLoadPenalty() >= 0.0); - assertEquals(0, stats.evictionCount()); + assertThat(totalLoadTime).isAtLeast(0); + assertThat(stats.averageLoadPenalty()).isAtLeast(0.0); + assertThat(stats.evictionCount()).isEqualTo(0); cache.getUnchecked(one); stats = cache.stats(); - assertEquals(2, stats.requestCount()); - assertEquals(1, stats.hitCount()); - assertEquals(1.0 / 2, stats.hitRate()); - assertEquals(1, stats.missCount()); - assertEquals(1.0 / 2, stats.missRate()); - assertEquals(1, stats.loadCount()); - assertEquals(0, stats.evictionCount()); + assertThat(stats.requestCount()).isEqualTo(2); + assertThat(stats.hitCount()).isEqualTo(1); + assertThat(stats.hitRate()).isEqualTo(1.0 / 2); + assertThat(stats.missCount()).isEqualTo(1); + assertThat(stats.missRate()).isEqualTo(1.0 / 2); + assertThat(stats.loadCount()).isEqualTo(1); + assertThat(stats.evictionCount()).isEqualTo(0); Object two = new Object(); cache.getUnchecked(two); stats = cache.stats(); - assertEquals(3, stats.requestCount()); - assertEquals(1, stats.hitCount()); - assertEquals(1.0 / 3, stats.hitRate()); - assertEquals(2, stats.missCount()); - assertEquals(2.0 / 3, stats.missRate()); - assertEquals(2, stats.loadCount()); - assertTrue(stats.totalLoadTime() >= totalLoadTime); + assertThat(stats.requestCount()).isEqualTo(3); + assertThat(stats.hitCount()).isEqualTo(1); + assertThat(stats.hitRate()).isEqualTo(1.0 / 3); + assertThat(stats.missCount()).isEqualTo(2); + assertThat(stats.missRate()).isEqualTo(2.0 / 3); + assertThat(stats.loadCount()).isEqualTo(2); + assertThat(stats.totalLoadTime()).isAtLeast(totalLoadTime); totalLoadTime = stats.totalLoadTime(); - assertTrue(stats.averageLoadPenalty() >= 0.0); - assertEquals(0, stats.evictionCount()); + assertThat(stats.averageLoadPenalty()).isAtLeast(0.0); + assertThat(stats.evictionCount()).isEqualTo(0); Object three = new Object(); cache.getUnchecked(three); stats = cache.stats(); - assertEquals(4, stats.requestCount()); - assertEquals(1, stats.hitCount()); - assertEquals(1.0 / 4, stats.hitRate()); - assertEquals(3, stats.missCount()); - assertEquals(3.0 / 4, stats.missRate()); - assertEquals(3, stats.loadCount()); - assertTrue(stats.totalLoadTime() >= totalLoadTime); - totalLoadTime = stats.totalLoadTime(); - assertTrue(stats.averageLoadPenalty() >= 0.0); - assertEquals(1, stats.evictionCount()); + assertThat(stats.requestCount()).isEqualTo(4); + assertThat(stats.hitCount()).isEqualTo(1); + assertThat(stats.hitRate()).isEqualTo(1.0 / 4); + assertThat(stats.missCount()).isEqualTo(3); + assertThat(stats.missRate()).isEqualTo(3.0 / 4); + assertThat(stats.loadCount()).isEqualTo(3); + assertThat(stats.totalLoadTime()).isAtLeast(totalLoadTime); + assertThat(stats.averageLoadPenalty()).isAtLeast(0.0); + assertThat(stats.evictionCount()).isEqualTo(1); } public void testStatsNoops() { CacheBuilder builder = createCacheBuilder().concurrencyLevel(1); LocalLoadingCache cache = makeCache(builder, identityLoader()); ConcurrentMap map = cache.localCache; // modifiable map view - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); Object one = new Object(); - assertNull(map.put(one, one)); - assertSame(one, map.get(one)); - assertTrue(map.containsKey(one)); - assertTrue(map.containsValue(one)); + assertThat(map.put(one, one)).isNull(); + assertThat(map.get(one)).isSameInstanceAs(one); + assertThat(map.containsKey(one)).isTrue(); + assertThat(map.containsValue(one)).isTrue(); Object two = new Object(); - assertSame(one, map.replace(one, two)); - assertTrue(map.containsKey(one)); - assertFalse(map.containsValue(one)); + assertThat(map.replace(one, two)).isSameInstanceAs(one); + assertThat(map.containsKey(one)).isTrue(); + assertThat(map.containsValue(one)).isFalse(); Object three = new Object(); - assertTrue(map.replace(one, two, three)); - assertTrue(map.remove(one, three)); - assertFalse(map.containsKey(one)); - assertFalse(map.containsValue(one)); - assertNull(map.putIfAbsent(two, three)); - assertSame(three, map.remove(two)); - assertNull(map.put(three, one)); - assertNull(map.put(one, two)); + assertThat(map.replace(one, two, three)).isTrue(); + assertThat(map.remove(one, three)).isTrue(); + assertThat(map.containsKey(one)).isFalse(); + assertThat(map.containsValue(one)).isFalse(); + assertThat(map.putIfAbsent(two, three)).isNull(); + assertThat(map.remove(two)).isSameInstanceAs(three); + assertThat(map.put(three, one)).isNull(); + assertThat(map.put(one, two)).isNull(); assertThat(map).containsEntry(three, one); assertThat(map).containsEntry(one, two); - // TODO(user): Confirm with fry@ that this is a reasonable substitute. + // TODO(cgruber): Confirm with fry@ that this is a reasonable substitute. // Set> entries = map.entrySet(); // assertThat(entries).containsExactly( // Maps.immutableEntry(three, one), Maps.immutableEntry(one, two)); @@ -168,56 +171,56 @@ public void testStatsNoops() { map.clear(); - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); } public void testNoStats() { CacheBuilder builder = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(2); LocalLoadingCache cache = makeCache(builder, identityLoader()); - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); Object one = new Object(); cache.getUnchecked(one); - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); cache.getUnchecked(one); - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); Object two = new Object(); cache.getUnchecked(two); - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); Object three = new Object(); cache.getUnchecked(three); - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); } public void testRecordStats() { CacheBuilder builder = createCacheBuilder().recordStats().concurrencyLevel(1).maximumSize(2); LocalLoadingCache cache = makeCache(builder, identityLoader()); - assertEquals(0, cache.stats().hitCount()); - assertEquals(0, cache.stats().missCount()); + assertThat(cache.stats().hitCount()).isEqualTo(0); + assertThat(cache.stats().missCount()).isEqualTo(0); Object one = new Object(); cache.getUnchecked(one); - assertEquals(0, cache.stats().hitCount()); - assertEquals(1, cache.stats().missCount()); + assertThat(cache.stats().hitCount()).isEqualTo(0); + assertThat(cache.stats().missCount()).isEqualTo(1); cache.getUnchecked(one); - assertEquals(1, cache.stats().hitCount()); - assertEquals(1, cache.stats().missCount()); + assertThat(cache.stats().hitCount()).isEqualTo(1); + assertThat(cache.stats().missCount()).isEqualTo(1); Object two = new Object(); cache.getUnchecked(two); - assertEquals(1, cache.stats().hitCount()); - assertEquals(2, cache.stats().missCount()); + assertThat(cache.stats().hitCount()).isEqualTo(1); + assertThat(cache.stats().missCount()).isEqualTo(2); Object three = new Object(); cache.getUnchecked(three); - assertEquals(1, cache.stats().hitCount()); - assertEquals(3, cache.stats().missCount()); + assertThat(cache.stats().hitCount()).isEqualTo(1); + assertThat(cache.stats().missCount()).isEqualTo(3); } // asMap tests @@ -225,55 +228,55 @@ public void testRecordStats() { public void testAsMap() { CacheBuilder builder = createCacheBuilder(); LocalLoadingCache cache = makeCache(builder, identityLoader()); - assertEquals(EMPTY_STATS, cache.stats()); + assertThat(cache.stats()).isEqualTo(EMPTY_STATS); Object one = new Object(); Object two = new Object(); Object three = new Object(); ConcurrentMap map = cache.asMap(); - assertNull(map.put(one, two)); - assertSame(two, map.get(one)); + assertThat(map.put(one, two)).isNull(); + assertThat(map.get(one)).isSameInstanceAs(two); map.putAll(ImmutableMap.of(two, three)); - assertSame(three, map.get(two)); - assertSame(two, map.putIfAbsent(one, three)); - assertSame(two, map.get(one)); - assertNull(map.putIfAbsent(three, one)); - assertSame(one, map.get(three)); - assertSame(two, map.replace(one, three)); - assertSame(three, map.get(one)); - assertFalse(map.replace(one, two, three)); - assertSame(three, map.get(one)); - assertTrue(map.replace(one, three, two)); - assertSame(two, map.get(one)); - assertEquals(3, map.size()); + assertThat(map.get(two)).isSameInstanceAs(three); + assertThat(map.putIfAbsent(one, three)).isSameInstanceAs(two); + assertThat(map.get(one)).isSameInstanceAs(two); + assertThat(map.putIfAbsent(three, one)).isNull(); + assertThat(map.get(three)).isSameInstanceAs(one); + assertThat(map.replace(one, three)).isSameInstanceAs(two); + assertThat(map.get(one)).isSameInstanceAs(three); + assertThat(map.replace(one, two, three)).isFalse(); + assertThat(map.get(one)).isSameInstanceAs(three); + assertThat(map.replace(one, three, two)).isTrue(); + assertThat(map.get(one)).isSameInstanceAs(two); + assertThat(map).hasSize(3); map.clear(); - assertTrue(map.isEmpty()); - assertEquals(0, map.size()); + assertThat(map.isEmpty()).isTrue(); + assertThat(map).isEmpty(); cache.getUnchecked(one); - assertEquals(1, map.size()); - assertSame(one, map.get(one)); - assertTrue(map.containsKey(one)); - assertTrue(map.containsValue(one)); - assertSame(one, map.remove(one)); - assertEquals(0, map.size()); + assertThat(map).hasSize(1); + assertThat(map.get(one)).isSameInstanceAs(one); + assertThat(map.containsKey(one)).isTrue(); + assertThat(map.containsValue(one)).isTrue(); + assertThat(map.remove(one)).isSameInstanceAs(one); + assertThat(map).isEmpty(); cache.getUnchecked(one); - assertEquals(1, map.size()); - assertFalse(map.remove(one, two)); - assertTrue(map.remove(one, one)); - assertEquals(0, map.size()); + assertThat(map).hasSize(1); + assertThat(map.remove(one, two)).isFalse(); + assertThat(map.remove(one, one)).isTrue(); + assertThat(map).isEmpty(); cache.getUnchecked(one); Map newMap = ImmutableMap.of(one, one); - assertEquals(newMap, map); - assertEquals(newMap.entrySet(), map.entrySet()); - assertEquals(newMap.keySet(), map.keySet()); + assertThat(map).isEqualTo(newMap); + assertThat(map.entrySet()).isEqualTo(newMap.entrySet()); + assertThat(map.keySet()).isEqualTo(newMap.keySet()); Set expectedValues = ImmutableSet.of(one); Set actualValues = ImmutableSet.copyOf(map.values()); - assertEquals(expectedValues, actualValues); + assertThat(actualValues).isEqualTo(expectedValues); } /** Lookups on the map view shouldn't impact the recency queue. */ @@ -285,17 +288,16 @@ public void testAsMapRecency() { ConcurrentMap map = cache.asMap(); Object one = new Object(); - assertSame(one, cache.getUnchecked(one)); - assertTrue(segment.recencyQueue.isEmpty()); - assertSame(one, map.get(one)); - assertSame(one, segment.recencyQueue.peek().getKey()); - assertSame(one, cache.getUnchecked(one)); - assertFalse(segment.recencyQueue.isEmpty()); + assertThat(cache.getUnchecked(one)).isSameInstanceAs(one); + assertThat(segment.recencyQueue.isEmpty()).isTrue(); + assertThat(map.get(one)).isSameInstanceAs(one); + assertThat(segment.recencyQueue.peek().getKey()).isSameInstanceAs(one); + assertThat(cache.getUnchecked(one)).isSameInstanceAs(one); + assertThat(segment.recencyQueue.isEmpty()).isFalse(); } - public void testRecursiveComputation() throws InterruptedException { - final AtomicReference> cacheRef = new AtomicReference<>(); + AtomicReference> cacheRef = new AtomicReference<>(); CacheLoader recursiveLoader = new CacheLoader() { @Override @@ -311,7 +313,7 @@ public String load(Integer key) { LoadingCache recursiveCache = CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader); cacheRef.set(recursiveCache); - assertEquals("3, 2, 1, 0", recursiveCache.getUnchecked(3)); + assertThat(recursiveCache.getUnchecked(3)).isEqualTo("3, 2, 1, 0"); recursiveLoader = new CacheLoader() { @@ -324,8 +326,8 @@ public String load(Integer key) { recursiveCache = CacheBuilder.newBuilder().weakKeys().weakValues().build(recursiveLoader); cacheRef.set(recursiveCache); - // tells the test when the compution has completed - final CountDownLatch doneSignal = new CountDownLatch(1); + // tells the test when the computation has completed + CountDownLatch doneSignal = new CountDownLatch(1); Thread thread = new Thread() { @@ -345,7 +347,7 @@ public void uncaughtException(Thread t, Throwable e) {} }); thread.start(); - boolean done = doneSignal.await(1, TimeUnit.SECONDS); + boolean done = doneSignal.await(1, SECONDS); if (!done) { StringBuilder builder = new StringBuilder(); for (StackTraceElement trace : thread.getStackTrace()) { diff --git a/android/guava-tests/test/com/google/common/cache/LongAdderTest.java b/android/guava-tests/test/com/google/common/cache/LongAdderTest.java index 876f90748880..8a5bed0bb235 100644 --- a/android/guava-tests/test/com/google/common/cache/LongAdderTest.java +++ b/android/guava-tests/test/com/google/common/cache/LongAdderTest.java @@ -17,8 +17,10 @@ import static com.google.common.truth.Truth.assertThat; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link LongAdder}. */ +@NullUnmarked public class LongAdderTest extends TestCase { /** diff --git a/android/guava-tests/test/com/google/common/cache/NullCacheTest.java b/android/guava-tests/test/com/google/common/cache/NullCacheTest.java index 89dc3fb146df..5def1a0ac218 100644 --- a/android/guava-tests/test/com/google/common/cache/NullCacheTest.java +++ b/android/guava-tests/test/com/google/common/cache/NullCacheTest.java @@ -20,17 +20,20 @@ import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener; import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.cache.TestingRemovalListeners.QueuingRemovalListener; import com.google.common.util.concurrent.UncheckedExecutionException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * {@link LoadingCache} tests for caches with a maximum size of zero. * * @author mike nonemacher */ +@NullUnmarked public class NullCacheTest extends TestCase { QueuingRemovalListener listener; @@ -48,12 +51,12 @@ public void testGet() { .build(constantLoader(computed)); Object key = new Object(); - assertSame(computed, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(computed); RemovalNotification notification = listener.remove(); - assertSame(key, notification.getKey()); - assertSame(computed, notification.getValue()); - assertSame(RemovalCause.SIZE, notification.getCause()); - assertTrue(listener.isEmpty()); + assertThat(notification.getKey()).isSameInstanceAs(key); + assertThat(notification.getValue()).isSameInstanceAs(computed); + assertThat(notification.getCause()).isSameInstanceAs(RemovalCause.SIZE); + assertThat(listener.isEmpty()).isTrue(); checkEmpty(cache); } @@ -66,12 +69,12 @@ public void testGet_expireAfterWrite() { .build(constantLoader(computed)); Object key = new Object(); - assertSame(computed, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(computed); RemovalNotification notification = listener.remove(); - assertSame(key, notification.getKey()); - assertSame(computed, notification.getValue()); - assertSame(RemovalCause.SIZE, notification.getCause()); - assertTrue(listener.isEmpty()); + assertThat(notification.getKey()).isSameInstanceAs(key); + assertThat(notification.getValue()).isSameInstanceAs(computed); + assertThat(notification.getCause()).isSameInstanceAs(RemovalCause.SIZE); + assertThat(listener.isEmpty()).isTrue(); checkEmpty(cache); } @@ -84,12 +87,12 @@ public void testGet_expireAfterAccess() { .build(constantLoader(computed)); Object key = new Object(); - assertSame(computed, cache.getUnchecked(key)); + assertThat(cache.getUnchecked(key)).isSameInstanceAs(computed); RemovalNotification notification = listener.remove(); - assertSame(key, notification.getKey()); - assertSame(computed, notification.getValue()); - assertSame(RemovalCause.SIZE, notification.getCause()); - assertTrue(listener.isEmpty()); + assertThat(notification.getKey()).isSameInstanceAs(key); + assertThat(notification.getValue()).isSameInstanceAs(computed); + assertThat(notification.getCause()).isSameInstanceAs(RemovalCause.SIZE); + assertThat(listener.isEmpty()).isTrue(); checkEmpty(cache); } @@ -100,32 +103,24 @@ public void testGet_computeNull() { .removalListener(listener) .build(constantLoader(null)); - try { - cache.getUnchecked(new Object()); - fail(); - } catch (InvalidCacheLoadException e) { - /* expected */ - } + assertThrows(InvalidCacheLoadException.class, () -> cache.getUnchecked(new Object())); - assertTrue(listener.isEmpty()); + assertThat(listener.isEmpty()).isTrue(); checkEmpty(cache); } public void testGet_runtimeException() { - final RuntimeException e = new RuntimeException(); + RuntimeException e = new RuntimeException(); LoadingCache map = CacheBuilder.newBuilder() .maximumSize(0) .removalListener(listener) .build(exceptionLoader(e)); - try { - map.getUnchecked(new Object()); - fail(); - } catch (UncheckedExecutionException uee) { - assertThat(uee).hasCauseThat().isSameInstanceAs(e); - } - assertTrue(listener.isEmpty()); + UncheckedExecutionException uee = + assertThrows(UncheckedExecutionException.class, () -> map.getUnchecked(new Object())); + assertThat(uee).hasCauseThat().isSameInstanceAs(e); + assertThat(listener.isEmpty()).isTrue(); checkEmpty(map); } } diff --git a/android/guava-tests/test/com/google/common/cache/PackageSanityTests.java b/android/guava-tests/test/com/google/common/cache/PackageSanityTests.java index 64cdc13375f2..e4909906e5d8 100644 --- a/android/guava-tests/test/com/google/common/cache/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/cache/PackageSanityTests.java @@ -17,6 +17,7 @@ package com.google.common.cache; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** * Basic sanity tests for the entire package. @@ -24,6 +25,7 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() { setDefault( diff --git a/android/guava-tests/test/com/google/common/cache/PopulatedCachesTest.java b/android/guava-tests/test/com/google/common/cache/PopulatedCachesTest.java index b02b8ac61a48..0c529574f1d1 100644 --- a/android/guava-tests/test/com/google/common/cache/PopulatedCachesTest.java +++ b/android/guava-tests/test/com/google/common/cache/PopulatedCachesTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.base.Function; import com.google.common.cache.CacheBuilderFactory.DurationSpec; @@ -28,15 +29,17 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.testing.EqualsTester; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * {@link LoadingCache} tests that deal with caches that actually contain some key-value mappings. @@ -44,6 +47,7 @@ * @author mike nonemacher */ +@NullUnmarked public class PopulatedCachesTest extends TestCase { // we use integers as keys; make sure the range covers some values that ARE cached by // Integer.valueOf(int), and some that are not cached. (127 is the highest cached value.) @@ -54,8 +58,8 @@ public class PopulatedCachesTest extends TestCase { public void testSize_populated() { for (LoadingCache cache : caches()) { // don't let the entries get GCed - List> warmed = warmUp(cache); - assertEquals(WARMUP_SIZE, cache.size()); + List> unused = warmUp(cache); + assertThat(cache.size()).isEqualTo(WARMUP_SIZE); assertMapSize(cache.asMap(), WARMUP_SIZE); checkValidState(cache); } @@ -67,12 +71,12 @@ public void testContainsKey_found() { List> warmed = warmUp(cache); for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Entry entry = warmed.get(i - WARMUP_MIN); - assertTrue(cache.asMap().containsKey(entry.getKey())); - assertTrue(cache.asMap().containsValue(entry.getValue())); + assertThat(cache.asMap().containsKey(entry.getKey())).isTrue(); + assertThat(cache.asMap().containsValue(entry.getValue())).isTrue(); // this getUnchecked() call shouldn't be a cache miss; verified below - assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); + assertThat(cache.getUnchecked(entry.getKey())).isEqualTo(entry.getValue()); } - assertEquals(WARMUP_SIZE, cache.stats().missCount()); + assertThat(cache.stats().missCount()).isEqualTo(WARMUP_SIZE); checkValidState(cache); } } @@ -84,18 +88,18 @@ public void testPut_populated() { for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Entry entry = warmed.get(i - WARMUP_MIN); Object newValue = new Object(); - assertSame(entry.getValue(), cache.asMap().put(entry.getKey(), newValue)); + assertThat(cache.asMap().put(entry.getKey(), newValue)).isSameInstanceAs(entry.getValue()); // don't let the new entry get GCed warmed.add(entryOf(entry.getKey(), newValue)); Object newKey = new Object(); - assertNull(cache.asMap().put(newKey, entry.getValue())); + assertThat(cache.asMap().put(newKey, entry.getValue())).isNull(); // this getUnchecked() call shouldn't be a cache miss; verified below - assertEquals(newValue, cache.getUnchecked(entry.getKey())); - assertEquals(entry.getValue(), cache.getUnchecked(newKey)); + assertThat(cache.getUnchecked(entry.getKey())).isEqualTo(newValue); + assertThat(cache.getUnchecked(newKey)).isEqualTo(entry.getValue()); // don't let the new entry get GCed warmed.add(entryOf(newKey, entry.getValue())); } - assertEquals(WARMUP_SIZE, cache.stats().missCount()); + assertThat(cache.stats().missCount()).isEqualTo(WARMUP_SIZE); checkValidState(cache); } } @@ -107,16 +111,17 @@ public void testPutIfAbsent_populated() { for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Entry entry = warmed.get(i - WARMUP_MIN); Object newValue = new Object(); - assertSame(entry.getValue(), cache.asMap().putIfAbsent(entry.getKey(), newValue)); + assertThat(cache.asMap().putIfAbsent(entry.getKey(), newValue)) + .isSameInstanceAs(entry.getValue()); Object newKey = new Object(); - assertNull(cache.asMap().putIfAbsent(newKey, entry.getValue())); + assertThat(cache.asMap().putIfAbsent(newKey, entry.getValue())).isNull(); // this getUnchecked() call shouldn't be a cache miss; verified below - assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); - assertEquals(entry.getValue(), cache.getUnchecked(newKey)); + assertThat(cache.getUnchecked(entry.getKey())).isEqualTo(entry.getValue()); + assertThat(cache.getUnchecked(newKey)).isEqualTo(entry.getValue()); // don't let the new entry get GCed warmed.add(entryOf(newKey, entry.getValue())); } - assertEquals(WARMUP_SIZE, cache.stats().missCount()); + assertThat(cache.stats().missCount()).isEqualTo(WARMUP_SIZE); checkValidState(cache); } } @@ -124,13 +129,13 @@ public void testPutIfAbsent_populated() { public void testPutAll_populated() { for (LoadingCache cache : caches()) { // don't let the entries get GCed - List> warmed = warmUp(cache); + List> unused = warmUp(cache); Object newKey = new Object(); Object newValue = new Object(); cache.asMap().putAll(ImmutableMap.of(newKey, newValue)); // this getUnchecked() call shouldn't be a cache miss; verified below - assertEquals(newValue, cache.getUnchecked(newKey)); - assertEquals(WARMUP_SIZE, cache.stats().missCount()); + assertThat(cache.getUnchecked(newKey)).isEqualTo(newValue); + assertThat(cache.stats().missCount()).isEqualTo(WARMUP_SIZE); checkValidState(cache); } } @@ -142,16 +147,17 @@ public void testReplace_populated() { for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Entry entry = warmed.get(i - WARMUP_MIN); Object newValue = new Object(); - assertSame(entry.getValue(), cache.asMap().replace(entry.getKey(), newValue)); - assertTrue(cache.asMap().replace(entry.getKey(), newValue, entry.getValue())); + assertThat(cache.asMap().replace(entry.getKey(), newValue)) + .isSameInstanceAs(entry.getValue()); + assertThat(cache.asMap().replace(entry.getKey(), newValue, entry.getValue())).isTrue(); Object newKey = new Object(); - assertNull(cache.asMap().replace(newKey, entry.getValue())); - assertFalse(cache.asMap().replace(newKey, entry.getValue(), newValue)); + assertThat(cache.asMap().replace(newKey, entry.getValue())).isNull(); + assertThat(cache.asMap().replace(newKey, entry.getValue(), newValue)).isFalse(); // this getUnchecked() call shouldn't be a cache miss; verified below - assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); - assertFalse(cache.asMap().containsKey(newKey)); + assertThat(cache.getUnchecked(entry.getKey())).isEqualTo(entry.getValue()); + assertThat(cache.asMap().containsKey(newKey)).isFalse(); } - assertEquals(WARMUP_SIZE, cache.stats().missCount()); + assertThat(cache.stats().missCount()).isEqualTo(WARMUP_SIZE); checkValidState(cache); } } @@ -163,9 +169,9 @@ public void testRemove_byKey() { for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Entry entry = warmed.get(i - WARMUP_MIN); Object key = entry.getKey(); - assertEquals(entry.getValue(), cache.asMap().remove(key)); - assertNull(cache.asMap().remove(key)); - assertFalse(cache.asMap().containsKey(key)); + assertThat(cache.asMap().remove(key)).isEqualTo(entry.getValue()); + assertThat(cache.asMap().remove(key)).isNull(); + assertThat(cache.asMap().containsKey(key)).isFalse(); } checkEmpty(cache); } @@ -178,10 +184,10 @@ public void testRemove_byKeyAndValue() { for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Object key = warmed.get(i - WARMUP_MIN).getKey(); Object value = warmed.get(i - WARMUP_MIN).getValue(); - assertFalse(cache.asMap().remove(key, -1)); - assertTrue(cache.asMap().remove(key, value)); - assertFalse(cache.asMap().remove(key, -1)); - assertFalse(cache.asMap().containsKey(key)); + assertThat(cache.asMap().remove(key, -1)).isFalse(); + assertThat(cache.asMap().remove(key, value)).isTrue(); + assertThat(cache.asMap().remove(key, -1)).isFalse(); + assertThat(cache.asMap().containsKey(key)).isFalse(); } checkEmpty(cache); } @@ -193,7 +199,7 @@ public void testKeySet_populated() { Set keys = cache.asMap().keySet(); List> warmed = warmUp(cache); - Set expected = Maps.newHashMap(cache.asMap()).keySet(); + Set expected = new HashMap<>(cache.asMap()).keySet(); assertThat(keys).containsExactlyElementsIn(expected); assertThat(keys.toArray()).asList().containsExactlyElementsIn(expected); assertThat(keys.toArray(new Object[0])).asList().containsExactlyElementsIn(expected); @@ -202,13 +208,13 @@ public void testKeySet_populated() { .addEqualityGroup(cache.asMap().keySet(), keys) .addEqualityGroup(ImmutableSet.of()) .testEquals(); - assertEquals(WARMUP_SIZE, keys.size()); + assertThat(keys).hasSize(WARMUP_SIZE); for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Object key = warmed.get(i - WARMUP_MIN).getKey(); - assertTrue(keys.contains(key)); - assertTrue(keys.remove(key)); - assertFalse(keys.remove(key)); - assertFalse(keys.contains(key)); + assertThat(keys.contains(key)).isTrue(); + assertThat(keys.remove(key)).isTrue(); + assertThat(keys.remove(key)).isFalse(); + assertThat(keys.contains(key)).isFalse(); } checkEmpty(keys); checkEmpty(cache); @@ -220,18 +226,18 @@ public void testValues_populated() { Collection values = cache.asMap().values(); List> warmed = warmUp(cache); - Collection expected = Maps.newHashMap(cache.asMap()).values(); + Collection expected = new HashMap<>(cache.asMap()).values(); assertThat(values).containsExactlyElementsIn(expected); assertThat(values.toArray()).asList().containsExactlyElementsIn(expected); assertThat(values.toArray(new Object[0])).asList().containsExactlyElementsIn(expected); - assertEquals(WARMUP_SIZE, values.size()); + assertThat(values).hasSize(WARMUP_SIZE); for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Object value = warmed.get(i - WARMUP_MIN).getValue(); - assertTrue(values.contains(value)); - assertTrue(values.remove(value)); - assertFalse(values.remove(value)); - assertFalse(values.contains(value)); + assertThat(values.contains(value)).isTrue(); + assertThat(values.remove(value)).isTrue(); + assertThat(values.remove(value)).isFalse(); + assertThat(values.contains(value)).isFalse(); } checkEmpty(values); checkEmpty(cache); @@ -244,7 +250,7 @@ public void testEntrySet_populated() { Set> entries = cache.asMap().entrySet(); List> warmed = warmUp(cache, WARMUP_MIN, WARMUP_MAX); - Set expected = Maps.newHashMap(cache.asMap()).entrySet(); + Set expected = new HashMap<>(cache.asMap()).entrySet(); assertThat(entries).containsExactlyElementsIn(expected); assertThat(entries.toArray()).asList().containsExactlyElementsIn(expected); assertThat(entries.toArray(new Object[0])).asList().containsExactlyElementsIn(expected); @@ -253,13 +259,13 @@ public void testEntrySet_populated() { .addEqualityGroup(cache.asMap().entrySet(), entries) .addEqualityGroup(ImmutableSet.of()) .testEquals(); - assertEquals(WARMUP_SIZE, entries.size()); + assertThat(entries).hasSize(WARMUP_SIZE); for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { Entry newEntry = warmed.get(i - WARMUP_MIN); - assertTrue(entries.contains(newEntry)); - assertTrue(entries.remove(newEntry)); - assertFalse(entries.remove(newEntry)); - assertFalse(entries.contains(newEntry)); + assertThat(entries.contains(newEntry)).isTrue(); + assertThat(entries.remove(newEntry)).isTrue(); + assertThat(entries.remove(newEntry)).isFalse(); + assertThat(entries.contains(newEntry)).isFalse(); } checkEmpty(entries); checkEmpty(cache); @@ -272,18 +278,14 @@ public void testWriteThroughEntry() { Entry entry = Iterables.getOnlyElement(cache.asMap().entrySet()); cache.invalidate(1); - assertEquals(0, cache.size()); + assertThat(cache.size()).isEqualTo(0); entry.setValue(3); - assertEquals(1, cache.size()); - assertEquals(3, cache.getIfPresent(1)); + assertThat(cache.size()).isEqualTo(1); + assertThat(cache.getIfPresent(1)).isEqualTo(3); checkValidState(cache); - try { - entry.setValue(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> entry.setValue(null)); checkValidState(cache); } } @@ -342,7 +344,7 @@ private List> warmUp(LoadingCache cache) { private List> warmUp( LoadingCache cache, int minimum, int maximum) { - List> entries = Lists.newArrayList(); + List> entries = new ArrayList<>(); for (int i = minimum; i < maximum; i++) { Object key = i; Object value = cache.getUnchecked(key); @@ -356,25 +358,26 @@ private Entry entryOf(Object key, Object value) { } private void assertMapSize(Map map, int size) { - assertEquals(size, map.size()); + assertThat(map).hasSize(size); if (size > 0) { - assertFalse(map.isEmpty()); + assertThat(map.isEmpty()).isFalse(); } else { - assertTrue(map.isEmpty()); + assertThat(map.isEmpty()).isTrue(); } assertCollectionSize(map.keySet(), size); assertCollectionSize(map.entrySet(), size); assertCollectionSize(map.values(), size); } + @SuppressWarnings("IterablesSizeOfCollection") // we are testing our iterator implementation private void assertCollectionSize(Collection collection, int size) { - assertEquals(size, collection.size()); + assertThat(collection.size()).isEqualTo(size); if (size > 0) { - assertFalse(collection.isEmpty()); + assertThat(collection.isEmpty()).isFalse(); } else { - assertTrue(collection.isEmpty()); + assertThat(collection.isEmpty()).isTrue(); } - assertEquals(size, Iterables.size(collection)); - assertEquals(size, Iterators.size(collection.iterator())); + assertThat(Iterables.size(collection)).isEqualTo(size); + assertThat(Iterators.size(collection.iterator())).isEqualTo(size); } } diff --git a/android/guava-tests/test/com/google/common/cache/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/cache/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..d8c7548ec58e --- /dev/null +++ b/android/guava-tests/test/com/google/common/cache/ReflectionFreeAssertThrows.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionError.class, e -> e instanceof ExecutionError) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UncheckedExecutionException.class, e -> e instanceof UncheckedExecutionException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/cache/RemovalNotificationTest.java b/android/guava-tests/test/com/google/common/cache/RemovalNotificationTest.java index 9cd587db7c82..12d6e0e0cfcd 100644 --- a/android/guava-tests/test/com/google/common/cache/RemovalNotificationTest.java +++ b/android/guava-tests/test/com/google/common/cache/RemovalNotificationTest.java @@ -18,12 +18,14 @@ import com.google.common.testing.EqualsTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests of {@link RemovalNotification}. * * @author Ben Yu */ +@NullUnmarked public class RemovalNotificationTest extends TestCase { public void testEquals() { diff --git a/android/guava-tests/test/com/google/common/cache/TestingCacheLoaders.java b/android/guava-tests/test/com/google/common/cache/TestingCacheLoaders.java index 8507e68f2eb9..4b2a96a4fccc 100644 --- a/android/guava-tests/test/com/google/common/cache/TestingCacheLoaders.java +++ b/android/guava-tests/test/com/google/common/cache/TestingCacheLoaders.java @@ -15,29 +15,32 @@ package com.google.common.cache; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Futures.immediateFuture; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.Maps; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Utility {@link CacheLoader} implementations intended for use in testing. * * @author mike nonemacher */ -@GwtCompatible(emulated = true) -class TestingCacheLoaders { +@GwtCompatible +@NullUnmarked +final class TestingCacheLoaders { /** * Returns a {@link CacheLoader} that implements a naive {@link CacheLoader#loadAll}, delegating * {@link CacheLoader#load} calls to {@code loader}. */ - static CacheLoader bulkLoader(final CacheLoader loader) { + static CacheLoader bulkLoader(CacheLoader loader) { checkNotNull(loader); return new CacheLoader() { @Override @@ -47,7 +50,7 @@ public V load(K key) throws Exception { @Override public Map loadAll(Iterable keys) throws Exception { - Map result = Maps.newHashMap(); // allow nulls + Map result = new HashMap<>(); // allow nulls for (K key : keys) { result.put(key, load(key)); } @@ -57,7 +60,7 @@ public Map loadAll(Iterable keys) throws Exception { } /** Returns a {@link CacheLoader} that returns the given {@code constant} for every request. */ - static ConstantLoader constantLoader(@NullableDecl V constant) { + static ConstantLoader constantLoader(@Nullable V constant) { return new ConstantLoader<>(constant); } @@ -67,7 +70,7 @@ static IncrementingLoader incrementingLoader() { } /** Returns a {@link CacheLoader} that throws the given error for every request. */ - static CacheLoader errorLoader(final Error e) { + static CacheLoader errorLoader(Error e) { checkNotNull(e); return new CacheLoader() { @Override @@ -78,7 +81,7 @@ public V load(K key) { } /** Returns a {@link CacheLoader} that throws the given exception for every request. */ - static CacheLoader exceptionLoader(final Exception e) { + static CacheLoader exceptionLoader(Exception e) { checkNotNull(e); return new CacheLoader() { @Override @@ -134,6 +137,7 @@ static class IncrementingLoader extends CacheLoader { private final AtomicInteger countLoad = new AtomicInteger(); private final AtomicInteger countReload = new AtomicInteger(); + @CanIgnoreReturnValue // Sure, why not? @Override public Integer load(Integer key) { countLoad.incrementAndGet(); @@ -144,7 +148,7 @@ public Integer load(Integer key) { @Override public ListenableFuture reload(Integer key, Integer oldValue) { countReload.incrementAndGet(); - return Futures.immediateFuture(oldValue + 1); + return immediateFuture(oldValue + 1); } public int getLoadCount() { @@ -162,4 +166,6 @@ public T load(T key) { return key; } } + + private TestingCacheLoaders() {} } diff --git a/android/guava-tests/test/com/google/common/cache/TestingRemovalListeners.java b/android/guava-tests/test/com/google/common/cache/TestingRemovalListeners.java index 698467fceb07..1251fa269ba1 100644 --- a/android/guava-tests/test/com/google/common/cache/TestingRemovalListeners.java +++ b/android/guava-tests/test/com/google/common/cache/TestingRemovalListeners.java @@ -18,14 +18,16 @@ import com.google.common.annotations.GwtIncompatible; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; +import org.jspecify.annotations.NullUnmarked; /** * Utility {@link RemovalListener} implementations intended for use in testing. * * @author mike nonemacher */ -@GwtCompatible(emulated = true) -class TestingRemovalListeners { +@GwtCompatible +@NullUnmarked +final class TestingRemovalListeners { /** Returns a new no-op {@code RemovalListener}. */ static NullRemovalListener nullRemovalListener() { @@ -90,4 +92,6 @@ static class NullRemovalListener implements RemovalListener { @Override public void onRemoval(RemovalNotification notification) {} } + + private TestingRemovalListeners() {} } diff --git a/android/guava-tests/test/com/google/common/cache/TestingWeighers.java b/android/guava-tests/test/com/google/common/cache/TestingWeighers.java index b09fdf03e78a..743d5ce3346c 100644 --- a/android/guava-tests/test/com/google/common/cache/TestingWeighers.java +++ b/android/guava-tests/test/com/google/common/cache/TestingWeighers.java @@ -14,12 +14,15 @@ package com.google.common.cache; +import org.jspecify.annotations.NullUnmarked; + /** * Utility {@link Weigher} implementations intended for use in testing. * * @author Charles Fry */ -public class TestingWeighers { +@NullUnmarked +public final class TestingWeighers { /** Returns a {@link Weigher} that returns the given {@code constant} for every request. */ static Weigher constantWeigher(int constant) { @@ -62,4 +65,6 @@ public int weigh(Object key, Integer value) { return value; } } + + private TestingWeighers() {} } diff --git a/android/guava-tests/test/com/google/common/collect/AbstractBiMapTest.java b/android/guava-tests/test/com/google/common/collect/AbstractBiMapTest.java index e430f0e523b9..b0e4055ab9cb 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractBiMapTest.java @@ -18,12 +18,14 @@ import java.util.Iterator; import java.util.Map.Entry; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code AbstractBiMap}. * * @author Mike Bostock */ +@NullUnmarked public class AbstractBiMapTest extends TestCase { // The next two tests verify that map entries are not accessed after they're diff --git a/android/guava-tests/test/com/google/common/collect/AbstractFilteredMapTest.java b/android/guava-tests/test/com/google/common/collect/AbstractFilteredMapTest.java new file mode 100644 index 000000000000..ab39d750352d --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/AbstractFilteredMapTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import java.util.Map; +import java.util.Map.Entry; +import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@GwtCompatible +@NullMarked +abstract class AbstractFilteredMapTest extends TestCase { + private static final Predicate<@Nullable String> NOT_LENGTH_3 = + input -> input == null || input.length() != 3; + private static final Predicate<@Nullable Integer> EVEN = input -> input == null || input % 2 == 0; + static final Predicate> CORRECT_LENGTH = + input -> input.getKey().length() == input.getValue(); + + abstract Map createUnfiltered(); + + public void testFilteredKeysIllegalPut() { + Map unfiltered = createUnfiltered(); + Map filtered = Maps.filterKeys(unfiltered, NOT_LENGTH_3); + filtered.put("a", 1); + filtered.put("b", 2); + assertEquals(ImmutableMap.of("a", 1, "b", 2), filtered); + + assertThrows(IllegalArgumentException.class, () -> filtered.put("yyy", 3)); + } + + public void testFilteredKeysIllegalPutAll() { + Map unfiltered = createUnfiltered(); + Map filtered = Maps.filterKeys(unfiltered, NOT_LENGTH_3); + filtered.put("a", 1); + filtered.put("b", 2); + assertEquals(ImmutableMap.of("a", 1, "b", 2), filtered); + + assertThrows( + IllegalArgumentException.class, + () -> filtered.putAll(ImmutableMap.of("c", 3, "zzz", 4, "b", 5))); + + assertEquals(ImmutableMap.of("a", 1, "b", 2), filtered); + } + + public void testFilteredKeysFilteredReflectsBackingChanges() { + Map unfiltered = createUnfiltered(); + Map filtered = Maps.filterKeys(unfiltered, NOT_LENGTH_3); + unfiltered.put("two", 2); + unfiltered.put("three", 3); + unfiltered.put("four", 4); + assertEquals(ImmutableMap.of("two", 2, "three", 3, "four", 4), unfiltered); + assertEquals(ImmutableMap.of("three", 3, "four", 4), filtered); + + unfiltered.remove("three"); + assertEquals(ImmutableMap.of("two", 2, "four", 4), unfiltered); + assertEquals(ImmutableMap.of("four", 4), filtered); + + unfiltered.clear(); + assertEquals(ImmutableMap.of(), unfiltered); + assertEquals(ImmutableMap.of(), filtered); + } + + public void testFilteredValuesIllegalPut() { + Map unfiltered = createUnfiltered(); + Map filtered = Maps.filterValues(unfiltered, EVEN); + filtered.put("a", 2); + unfiltered.put("b", 4); + unfiltered.put("c", 5); + assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); + + assertThrows(IllegalArgumentException.class, () -> filtered.put("yyy", 3)); + assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); + } + + public void testFilteredValuesIllegalPutAll() { + Map unfiltered = createUnfiltered(); + Map filtered = Maps.filterValues(unfiltered, EVEN); + filtered.put("a", 2); + unfiltered.put("b", 4); + unfiltered.put("c", 5); + assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); + + assertThrows( + IllegalArgumentException.class, + () -> filtered.putAll(ImmutableMap.of("c", 4, "zzz", 5, "b", 6))); + assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); + } + + public void testFilteredValuesIllegalSetValue() { + Map unfiltered = createUnfiltered(); + Map filtered = Maps.filterValues(unfiltered, EVEN); + filtered.put("a", 2); + filtered.put("b", 4); + assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); + + Entry entry = filtered.entrySet().iterator().next(); + assertThrows(IllegalArgumentException.class, () -> entry.setValue(5)); + + assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); + } + + public void testFilteredValuesClear() { + Map unfiltered = createUnfiltered(); + unfiltered.put("one", 1); + unfiltered.put("two", 2); + unfiltered.put("three", 3); + unfiltered.put("four", 4); + Map filtered = Maps.filterValues(unfiltered, EVEN); + assertEquals(ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4), unfiltered); + assertEquals(ImmutableMap.of("two", 2, "four", 4), filtered); + + filtered.clear(); + assertEquals(ImmutableMap.of("one", 1, "three", 3), unfiltered); + assertTrue(filtered.isEmpty()); + } + + public void testFilteredEntriesIllegalPut() { + Map unfiltered = createUnfiltered(); + unfiltered.put("cat", 3); + unfiltered.put("dog", 2); + unfiltered.put("horse", 5); + Map filtered = Maps.filterEntries(unfiltered, CORRECT_LENGTH); + assertEquals(ImmutableMap.of("cat", 3, "horse", 5), filtered); + + filtered.put("chicken", 7); + assertEquals(ImmutableMap.of("cat", 3, "horse", 5, "chicken", 7), filtered); + + assertThrows(IllegalArgumentException.class, () -> filtered.put("cow", 7)); + assertEquals(ImmutableMap.of("cat", 3, "horse", 5, "chicken", 7), filtered); + } + + public void testFilteredEntriesIllegalPutAll() { + Map unfiltered = createUnfiltered(); + unfiltered.put("cat", 3); + unfiltered.put("dog", 2); + unfiltered.put("horse", 5); + Map filtered = Maps.filterEntries(unfiltered, CORRECT_LENGTH); + assertEquals(ImmutableMap.of("cat", 3, "horse", 5), filtered); + + filtered.put("chicken", 7); + assertEquals(ImmutableMap.of("cat", 3, "horse", 5, "chicken", 7), filtered); + + assertThrows( + IllegalArgumentException.class, + () -> filtered.putAll(ImmutableMap.of("sheep", 5, "cow", 7))); + assertEquals(ImmutableMap.of("cat", 3, "horse", 5, "chicken", 7), filtered); + } + + public void testFilteredEntriesObjectPredicate() { + Map unfiltered = createUnfiltered(); + unfiltered.put("cat", 3); + unfiltered.put("dog", 2); + unfiltered.put("horse", 5); + Predicate predicate = Predicates.alwaysFalse(); + Map filtered = Maps.filterEntries(unfiltered, predicate); + assertTrue(filtered.isEmpty()); + } + + public void testFilteredEntriesWildCardEntryPredicate() { + Map unfiltered = createUnfiltered(); + unfiltered.put("cat", 3); + unfiltered.put("dog", 2); + unfiltered.put("horse", 5); + Predicate> predicate = e -> e.getKey().equals("cat") || e.getValue().equals(2); + Map filtered = Maps.filterEntries(unfiltered, predicate); + assertEquals(ImmutableMap.of("cat", 3, "dog", 2), filtered); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/AbstractImmutableBiMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/AbstractImmutableBiMapMapInterfaceTest.java new file mode 100644 index 000000000000..c2818c9e96a9 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/AbstractImmutableBiMapMapInterfaceTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Joiner; +import com.google.common.collect.testing.MapInterfaceTest; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +abstract class AbstractImmutableBiMapMapInterfaceTest extends MapInterfaceTest { + AbstractImmutableBiMapMapInterfaceTest() { + super(false, false, false, false, false); + } + + @Override + protected Map makeEmptyMap() { + throw new UnsupportedOperationException(); + } + + private static final Joiner JOINER = Joiner.on(", "); + + @Override + protected final void assertMoreInvariants(Map map) { + BiMap bimap = (BiMap) map; + + for (Entry entry : map.entrySet()) { + assertEquals(entry.getKey() + "=" + entry.getValue(), entry.toString()); + assertEquals(entry.getKey(), bimap.inverse().get(entry.getValue())); + } + + assertEquals("{" + JOINER.join(map.entrySet()) + "}", map.toString()); + assertEquals("[" + JOINER.join(map.entrySet()) + "]", map.entrySet().toString()); + assertEquals("[" + JOINER.join(map.keySet()) + "]", map.keySet().toString()); + assertEquals("[" + JOINER.join(map.values()) + "]", map.values().toString()); + + assertEquals(new HashSet<>(map.entrySet()), map.entrySet()); + assertEquals(new HashSet<>(map.keySet()), map.keySet()); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/AbstractImmutableMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/AbstractImmutableMapMapInterfaceTest.java new file mode 100644 index 000000000000..8ca84ef78e0f --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/AbstractImmutableMapMapInterfaceTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Joiner; +import com.google.common.collect.testing.MapInterfaceTest; +import com.google.common.collect.testing.MinimalSet; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +abstract class AbstractImmutableMapMapInterfaceTest extends MapInterfaceTest { + AbstractImmutableMapMapInterfaceTest() { + super(false, false, false, false, false); + } + + @Override + protected Map makeEmptyMap() { + throw new UnsupportedOperationException(); + } + + private static final Joiner JOINER = Joiner.on(", "); + + @Override + protected final void assertMoreInvariants(Map map) { + // TODO: can these be moved to MapInterfaceTest? + for (Entry entry : map.entrySet()) { + assertEquals(entry.getKey() + "=" + entry.getValue(), entry.toString()); + } + + assertEquals("{" + JOINER.join(map.entrySet()) + "}", map.toString()); + assertEquals("[" + JOINER.join(map.entrySet()) + "]", map.entrySet().toString()); + assertEquals("[" + JOINER.join(map.keySet()) + "]", map.keySet().toString()); + assertEquals("[" + JOINER.join(map.values()) + "]", map.values().toString()); + + assertEquals(MinimalSet.from(map.entrySet()), map.entrySet()); + assertEquals(new HashSet<>(map.keySet()), map.keySet()); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/AbstractImmutableSetTest.java b/android/guava-tests/test/com/google/common/collect/AbstractImmutableSetTest.java index d7a8d16cadf1..ec9801561daf 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractImmutableSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractImmutableSetTest.java @@ -16,13 +16,20 @@ package com.google.common.collect; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Iterators.singletonIterator; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static java.util.Arrays.asList; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.base.Strings; import com.google.common.collect.testing.IteratorTester; import com.google.common.collect.testing.MinimalCollection; import com.google.common.collect.testing.MinimalIterable; @@ -33,6 +40,8 @@ import java.util.List; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Base class for {@link ImmutableSet} and {@link ImmutableSortedSet} tests. @@ -40,7 +49,8 @@ * @author Kevin Bourrillion * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public abstract class AbstractImmutableSetTest extends TestCase { protected abstract > Set of(); @@ -55,7 +65,6 @@ public abstract class AbstractImmutableSetTest extends TestCase { protected abstract > Set of(E e1, E e2, E e3, E e4, E e5); - @SuppressWarnings("unchecked") protected abstract > Set of( E e1, E e2, E e3, E e4, E e5, E e6, E... rest); @@ -73,97 +82,88 @@ protected abstract > Set copyOf( public void testCreation_noArgs() { Set set = of(); assertEquals(Collections.emptySet(), set); - assertSame(of(), set); + assertSame(this.of(), set); } public void testCreation_oneElement() { Set set = of("a"); - assertEquals(Collections.singleton("a"), set); + assertEquals(singleton("a"), set); } public void testCreation_twoElements() { Set set = of("a", "b"); - assertEquals(Sets.newHashSet("a", "b"), set); + assertEquals(newHashSet("a", "b"), set); } public void testCreation_threeElements() { Set set = of("a", "b", "c"); - assertEquals(Sets.newHashSet("a", "b", "c"), set); + assertEquals(newHashSet("a", "b", "c"), set); } public void testCreation_fourElements() { Set set = of("a", "b", "c", "d"); - assertEquals(Sets.newHashSet("a", "b", "c", "d"), set); + assertEquals(newHashSet("a", "b", "c", "d"), set); } public void testCreation_fiveElements() { Set set = of("a", "b", "c", "d", "e"); - assertEquals(Sets.newHashSet("a", "b", "c", "d", "e"), set); + assertEquals(newHashSet("a", "b", "c", "d", "e"), set); } public void testCreation_sixElements() { Set set = of("a", "b", "c", "d", "e", "f"); - assertEquals(Sets.newHashSet("a", "b", "c", "d", "e", "f"), set); + assertEquals(newHashSet("a", "b", "c", "d", "e", "f"), set); } public void testCreation_sevenElements() { Set set = of("a", "b", "c", "d", "e", "f", "g"); - assertEquals(Sets.newHashSet("a", "b", "c", "d", "e", "f", "g"), set); + assertEquals(newHashSet("a", "b", "c", "d", "e", "f", "g"), set); } public void testCreation_eightElements() { Set set = of("a", "b", "c", "d", "e", "f", "g", "h"); - assertEquals(Sets.newHashSet("a", "b", "c", "d", "e", "f", "g", "h"), set); + assertEquals(newHashSet("a", "b", "c", "d", "e", "f", "g", "h"), set); } public void testCopyOf_emptyArray() { String[] array = new String[0]; Set set = copyOf(array); assertEquals(Collections.emptySet(), set); - assertSame(of(), set); + assertSame(this.of(), set); } public void testCopyOf_arrayOfOneElement() { String[] array = new String[] {"a"}; Set set = copyOf(array); - assertEquals(Collections.singleton("a"), set); + assertEquals(singleton("a"), set); } public void testCopyOf_nullArray() { - try { - copyOf((String[]) null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> copyOf((String[]) null)); } public void testCopyOf_arrayContainingOnlyNull() { - String[] array = new String[] {null}; - try { - copyOf(array); - fail(); - } catch (NullPointerException expected) { - } + @Nullable String[] array = new @Nullable String[] {null}; + assertThrows(NullPointerException.class, () -> copyOf((String[]) array)); } public void testCopyOf_collection_empty() { - // "" is required to work around a javac 1.5 bug. - Collection c = MinimalCollection.of(); + Collection c = MinimalCollection.of(); Set set = copyOf(c); assertEquals(Collections.emptySet(), set); - assertSame(of(), set); + assertSame(this.of(), set); } public void testCopyOf_collection_oneElement() { Collection c = MinimalCollection.of("a"); Set set = copyOf(c); - assertEquals(Collections.singleton("a"), set); + assertEquals(singleton("a"), set); } public void testCopyOf_collection_oneElementRepeated() { Collection c = MinimalCollection.of("a", "a", "a"); Set set = copyOf(c); - assertEquals(Collections.singleton("a"), set); + assertEquals(singleton("a"), set); } public void testCopyOf_collection_general() { @@ -175,12 +175,8 @@ public void testCopyOf_collection_general() { } public void testCopyOf_collectionContainingNull() { - Collection c = MinimalCollection.of("a", null, "b"); - try { - copyOf(c); - fail(); - } catch (NullPointerException expected) { - } + Collection<@Nullable String> c = MinimalCollection.of("a", null, "b"); + assertThrows(NullPointerException.class, () -> copyOf((Collection) c)); } enum TestEnum { @@ -198,22 +194,22 @@ public void testCopyOf_collection_enumSet() { } public void testCopyOf_iterator_empty() { - Iterator iterator = Iterators.emptyIterator(); + Iterator iterator = emptyIterator(); Set set = copyOf(iterator); assertEquals(Collections.emptySet(), set); - assertSame(of(), set); + assertSame(this.of(), set); } public void testCopyOf_iterator_oneElement() { - Iterator iterator = Iterators.singletonIterator("a"); + Iterator iterator = singletonIterator("a"); Set set = copyOf(iterator); - assertEquals(Collections.singleton("a"), set); + assertEquals(singleton("a"), set); } public void testCopyOf_iterator_oneElementRepeated() { Iterator iterator = Iterators.forArray("a", "a", "a"); Set set = copyOf(iterator); - assertEquals(Collections.singleton("a"), set); + assertEquals(singleton("a"), set); } public void testCopyOf_iterator_general() { @@ -225,12 +221,8 @@ public void testCopyOf_iterator_general() { } public void testCopyOf_iteratorContainingNull() { - Iterator c = Iterators.forArray("a", null, "b"); - try { - copyOf(c); - fail(); - } catch (NullPointerException expected) { - } + Iterator<@Nullable String> c = Iterators.forArray("a", null, "b"); + assertThrows(NullPointerException.class, () -> copyOf((Iterator) c)); } private static class CountingIterable implements Iterable { @@ -265,7 +257,7 @@ public void testCopyOf_shortcut_empty() { public void testCopyOf_shortcut_singleton() { Collection c = of("a"); - assertEquals(Collections.singleton("a"), copyOf(c)); + assertEquals(singleton("a"), copyOf(c)); assertSame(c, copyOf(c)); } @@ -282,7 +274,7 @@ public void testToString() { @GwtIncompatible // slow (~40s) public void testIterator_oneElement() { new IteratorTester( - 5, UNMODIFIABLE, Collections.singleton("a"), IteratorTester.KnownOrder.KNOWN_ORDER) { + 5, UNMODIFIABLE, singleton("a"), IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { return of("a").iterator(); @@ -396,99 +388,147 @@ public void testComplexBuilder() { abstract int getComplexBuilderSetLastElement(); public void testBuilderAddHandlesNullsCorrectly() { - ImmutableSet.Builder builder = this.builder(); - try { - builder.add((String) null); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + assertThrows(NullPointerException.class, () -> builder.add((String) null)); } - builder = this.builder(); - try { - builder.add((String[]) null); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + assertThrows(NullPointerException.class, () -> builder.add((String[]) null)); } - builder = this.builder(); - try { - builder.add("a", (String) null); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + assertThrows(NullPointerException.class, () -> builder.add("a", (String) null)); } - builder = this.builder(); - try { - builder.add("a", "b", (String) null); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + assertThrows(NullPointerException.class, () -> builder.add("a", "b", (String) null)); } - builder = this.builder(); - try { - builder.add("a", "b", "c", null); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + assertThrows(NullPointerException.class, () -> builder.add("a", "b", "c", null)); } - builder = this.builder(); - try { - builder.add("a", "b", null, "c"); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + assertThrows(NullPointerException.class, () -> builder.add("a", "b", null, "c")); } } public void testBuilderAddAllHandlesNullsCorrectly() { - ImmutableSet.Builder builder = this.builder(); - try { - builder.addAll((Iterable) null); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + assertThrows(NullPointerException.class, () -> builder.addAll((Iterable) null)); } - try { - builder.addAll((Iterator) null); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + assertThrows(NullPointerException.class, () -> builder.addAll((Iterator) null)); } - builder = this.builder(); - List listWithNulls = asList("a", null, "b"); - try { - builder.addAll(listWithNulls); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + List<@Nullable String> listWithNulls = asList("a", null, "b"); + assertThrows(NullPointerException.class, () -> builder.addAll((List) listWithNulls)); } - Iterable iterableWithNulls = MinimalIterable.of("a", null, "b"); - try { - builder.addAll(iterableWithNulls); - fail("expected NullPointerException"); // COV_NF_LINE - } catch (NullPointerException expected) { + { + ImmutableSet.Builder builder = this.builder(); + Iterable<@Nullable String> iterableWithNulls = MinimalIterable.of("a", null, "b"); + assertThrows( + NullPointerException.class, () -> builder.addAll((Iterable) iterableWithNulls)); } } /** * Verify thread safety by using a collection whose size() may be inconsistent with the actual - * number of elements. Tests using this method might fail in GWT because the GWT emulations might - * count on size() during copy. It is safe to do so in GWT because javascript is single-threaded. + * number of elements and whose elements may change over time. + * + *

    This test might fail in GWT because the GWT emulations might count on the input collection + * not to change during the copy. It is safe to do so in GWT because javascript is + * single-threaded. */ - // TODO(benyu): turn this into a test once all copyOf(Collection) are - // thread-safe @GwtIncompatible // GWT is single threaded - void verifyThreadSafe() { - List sample = Lists.newArrayList("a", "b", "c"); - for (int delta : new int[] {-1, 0, 1}) { - for (int i = 0; i < sample.size(); i++) { - Collection misleading = Helpers.misleadingSizeCollection(delta); - List expected = sample.subList(0, i); - misleading.addAll(expected); - assertEquals( - "delta: " + delta + " sample size: " + i, - Sets.newHashSet(expected), - copyOf(misleading)); + public void testCopyOf_threadSafe() { + /* + * The actual collections that we pass as inputs will be wrappers around these, so + * ImmutableSet.copyOf won't short-circuit because it won't see an ImmutableSet input. + */ + ImmutableList> distinctCandidatesByAscendingSize = + ImmutableList.of( + ImmutableSet.of(), + ImmutableSet.of("a"), + ImmutableSet.of("b", "a"), + ImmutableSet.of("c", "b", "a"), + ImmutableSet.of("d", "c", "b", "a")); + for (boolean byAscendingSize : new boolean[] {true, false}) { + Iterable> infiniteSets = + Iterables.cycle( + byAscendingSize + ? distinctCandidatesByAscendingSize + : Lists.reverse(distinctCandidatesByAscendingSize)); + for (int startIndex = 0; + startIndex < distinctCandidatesByAscendingSize.size(); + startIndex++) { + Iterable> infiniteSetsFromStartIndex = + Iterables.skip(infiniteSets, startIndex); + for (boolean inputIsSet : new boolean[] {true, false}) { + Collection input = + inputIsSet + ? new MutatedOnQuerySet<>(infiniteSetsFromStartIndex) + : new MutatedOnQueryList<>( + transform(infiniteSetsFromStartIndex, ImmutableList::copyOf)); + Set immutableCopy; + try { + immutableCopy = copyOf(input); + } catch (RuntimeException e) { + throw new RuntimeException( + Strings.lenientFormat( + "byAscendingSize %s, startIndex %s, inputIsSet %s", + byAscendingSize, startIndex, inputIsSet), + e); + } + /* + * TODO(cpovirk): Check that the values match one of candidates that + * MutatedOnQuery*.delegate() actually returned during this test? + */ + assertWithMessage( + "byAscendingSize %s, startIndex %s, inputIsSet %s", + byAscendingSize, startIndex, inputIsSet) + .that(immutableCopy) + .isIn(distinctCandidatesByAscendingSize); + } } } } + + private static final class MutatedOnQuerySet extends ForwardingSet { + final Iterator> infiniteCandidates; + + MutatedOnQuerySet(Iterable> infiniteCandidates) { + this.infiniteCandidates = infiniteCandidates.iterator(); + } + + @Override + protected Set delegate() { + return infiniteCandidates.next(); + } + } + + private static final class MutatedOnQueryList extends ForwardingList { + final Iterator> infiniteCandidates; + + MutatedOnQueryList(Iterable> infiniteCandidates) { + this.infiniteCandidates = infiniteCandidates.iterator(); + } + + @Override + protected List delegate() { + return infiniteCandidates.next(); + } + } } diff --git a/android/guava-tests/test/com/google/common/collect/AbstractImmutableSortedMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/AbstractImmutableSortedMapMapInterfaceTest.java new file mode 100644 index 000000000000..085d2ea9946f --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/AbstractImmutableSortedMapMapInterfaceTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Joiner; +import com.google.common.collect.testing.SortedMapInterfaceTest; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public abstract class AbstractImmutableSortedMapMapInterfaceTest + extends SortedMapInterfaceTest { + public AbstractImmutableSortedMapMapInterfaceTest() { + super(false, false, false, false, false); + } + + @Override + protected SortedMap makeEmptyMap() { + throw new UnsupportedOperationException(); + } + + private static final Joiner JOINER = Joiner.on(", "); + + @Override + protected void assertMoreInvariants(Map map) { + // TODO: can these be moved to MapInterfaceTest? + for (Entry entry : map.entrySet()) { + assertEquals(entry.getKey() + "=" + entry.getValue(), entry.toString()); + } + + assertEquals("{" + JOINER.join(map.entrySet()) + "}", map.toString()); + assertEquals("[" + JOINER.join(map.entrySet()) + "]", map.entrySet().toString()); + assertEquals("[" + JOINER.join(map.keySet()) + "]", map.keySet().toString()); + assertEquals("[" + JOINER.join(map.values()) + "]", map.values().toString()); + + assertEquals(new HashSet<>(map.entrySet()), map.entrySet()); + assertEquals(new HashSet<>(map.keySet()), map.keySet()); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/AbstractImmutableTableTest.java b/android/guava-tests/test/com/google/common/collect/AbstractImmutableTableTest.java index ffff9f52b667..6f1b6f7e8c6b 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractImmutableTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractImmutableTableTest.java @@ -16,8 +16,11 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests {@link ImmutableTable} @@ -25,51 +28,34 @@ * @author Gregory Kick */ @GwtCompatible +@NullMarked public abstract class AbstractImmutableTableTest extends TestCase { abstract Iterable> getTestInstances(); public final void testClear() { for (Table testInstance : getTestInstances()) { - try { - testInstance.clear(); - fail(); - } catch (UnsupportedOperationException e) { - // success - } + assertThrows(UnsupportedOperationException.class, () -> testInstance.clear()); } } public final void testPut() { for (Table testInstance : getTestInstances()) { - try { - testInstance.put('a', 1, "blah"); - fail(); - } catch (UnsupportedOperationException e) { - // success - } + assertThrows(UnsupportedOperationException.class, () -> testInstance.put('a', 1, "blah")); } } public final void testPutAll() { for (Table testInstance : getTestInstances()) { - try { - testInstance.putAll(ImmutableTable.of('a', 1, "blah")); - fail(); - } catch (UnsupportedOperationException e) { - // success - } + assertThrows( + UnsupportedOperationException.class, + () -> testInstance.putAll(ImmutableTable.of('a', 1, "blah"))); } } public final void testRemove() { for (Table testInstance : getTestInstances()) { - try { - testInstance.remove('a', 1); - fail(); - } catch (UnsupportedOperationException e) { - // success - } + assertThrows(UnsupportedOperationException.class, () -> testInstance.remove('a', 1)); } } diff --git a/android/guava-tests/test/com/google/common/collect/AbstractIteratorTest.java b/android/guava-tests/test/com/google/common/collect/AbstractIteratorTest.java index 35922105f196..7427bc207ba6 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractIteratorTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractIteratorTest.java @@ -16,13 +16,21 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.SneakyThrows.sneakyThrow; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.collect.TestExceptions.SomeCheckedException; +import com.google.common.collect.TestExceptions.SomeUncheckedException; import com.google.common.testing.GcFinalization; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code AbstractIterator}. @@ -30,7 +38,8 @@ * @author Kevin Bourrillion */ @SuppressWarnings("serial") // No serialization is used in this test -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class AbstractIteratorTest extends TestCase { public void testDefaultBehaviorOfNextAndHasNext() { @@ -42,7 +51,7 @@ public void testDefaultBehaviorOfNextAndHasNext() { private int rep; @Override - public Integer computeNext() { + public @Nullable Integer computeNext() { switch (rep++) { case 0: return 0; @@ -51,8 +60,7 @@ public Integer computeNext() { case 2: return endOfData(); default: - fail("Should not have been invoked again"); - return null; + throw new AssertionError("Should not have been invoked again"); } } }; @@ -71,11 +79,7 @@ public Integer computeNext() { // Make sure computeNext() doesn't get invoked again assertFalse(iter.hasNext()); - try { - iter.next(); - fail("no exception thrown"); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, iter::next); } public void testDefaultBehaviorOfPeek() { @@ -88,7 +92,7 @@ public void testDefaultBehaviorOfPeek() { private int rep; @Override - public Integer computeNext() { + public @Nullable Integer computeNext() { switch (rep++) { case 0: return 0; @@ -97,8 +101,7 @@ public Integer computeNext() { case 2: return endOfData(); default: - fail("Should not have been invoked again"); - return null; + throw new AssertionError("Should not have been invoked again"); } } }; @@ -112,33 +115,20 @@ public Integer computeNext() { assertEquals(1, (int) iter.peek()); assertEquals(1, (int) iter.next()); - try { - iter.peek(); - fail("peek() should throw NoSuchElementException at end"); - } catch (NoSuchElementException expected) { - } - - try { - iter.peek(); - fail("peek() should continue to throw NoSuchElementException at end"); - } catch (NoSuchElementException expected) { - } - - try { - iter.next(); - fail("next() should throw NoSuchElementException as usual"); - } catch (NoSuchElementException expected) { - } - - try { - iter.peek(); - fail("peek() should still throw NoSuchElementException after next()"); - } catch (NoSuchElementException expected) { - } + /* + * We test peek() after various calls to make sure that one bad call doesn't interfere with its + * ability to throw the correct exception in the future. + */ + assertThrows(NoSuchElementException.class, iter::peek); + assertThrows(NoSuchElementException.class, iter::peek); + assertThrows(NoSuchElementException.class, iter::next); + assertThrows(NoSuchElementException.class, iter::peek); } + @J2ktIncompatible // weak references, details of GC @GwtIncompatible // weak references + @AndroidIncompatible // depends on details of GC public void testFreesNextReference() { Iterator itr = new AbstractIterator() { @@ -158,7 +148,7 @@ public void testDefaultBehaviorOfPeekForEmptyIteration() { private boolean alreadyCalledEndOfData; @Override - public Integer computeNext() { + public @Nullable Integer computeNext() { if (alreadyCalledEndOfData) { fail("Should not have been invoked again"); } @@ -167,17 +157,12 @@ public Integer computeNext() { } }; - try { - empty.peek(); - fail("peek() should throw NoSuchElementException at end"); - } catch (NoSuchElementException expected) { - } - - try { - empty.peek(); - fail("peek() should continue to throw NoSuchElementException at end"); - } catch (NoSuchElementException expected) { - } + /* + * We test multiple calls to peek() to make sure that one bad call doesn't interfere with its + * ability to throw the correct exception in the future. + */ + assertThrows(NoSuchElementException.class, empty::peek); + assertThrows(NoSuchElementException.class, empty::peek); } public void testSneakyThrow() throws Exception { @@ -188,35 +173,22 @@ public void testSneakyThrow() throws Exception { @Override public Integer computeNext() { if (haveBeenCalled) { - fail("Should not have been called again"); + throw new AssertionError("Should not have been called again"); } else { haveBeenCalled = true; - sneakyThrow(new SomeCheckedException()); + throw sneakyThrow(new SomeCheckedException()); } - return null; // never reached } }; // The first time, the sneakily-thrown exception comes out - try { - iter.hasNext(); - fail("No exception thrown"); - } catch (Exception e) { - if (!(e instanceof SomeCheckedException)) { - throw e; - } - } - + assertThrows(SomeCheckedException.class, iter::hasNext); // But the second time, AbstractIterator itself throws an ISE - try { - iter.hasNext(); - fail("No exception thrown"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, iter::hasNext); } public void testException() { - final SomeUncheckedException exception = new SomeUncheckedException(); + SomeUncheckedException exception = new SomeUncheckedException(); Iterator iter = new AbstractIterator() { @Override @@ -226,12 +198,8 @@ public Integer computeNext() { }; // It should pass through untouched - try { - iter.hasNext(); - fail("No exception thrown"); - } catch (SomeUncheckedException e) { - assertSame(exception, e); - } + SomeUncheckedException e = assertThrows(SomeUncheckedException.class, iter::hasNext); + assertSame(exception, e); } public void testExceptionAfterEndOfData() { @@ -243,13 +211,10 @@ public Integer computeNext() { throw new SomeUncheckedException(); } }; - try { - iter.hasNext(); - fail("No exception thrown"); - } catch (SomeUncheckedException expected) { - } + assertThrows(SomeUncheckedException.class, iter::hasNext); } + @SuppressWarnings("DoNotCall") public void testCantRemove() { Iterator iter = new AbstractIterator() { @@ -267,11 +232,7 @@ public Integer computeNext() { assertEquals(0, (int) iter.next()); - try { - iter.remove(); - fail("No exception thrown"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, iter::remove); } public void testReentrantHasNext() { @@ -280,32 +241,13 @@ public void testReentrantHasNext() { @Override protected Integer computeNext() { boolean unused = hasNext(); - return null; + throw new AssertionError(); } }; - try { - iter.hasNext(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, iter::hasNext); } // Technically we should test other reentrant scenarios (9 combinations of // hasNext/next/peek), but we'll cop out for now, knowing that peek() and // next() both start by invoking hasNext() anyway. - - /** Throws a undeclared checked exception. */ - private static void sneakyThrow(Throwable t) { - class SneakyThrower { - @SuppressWarnings("unchecked") // not really safe, but that's the point - void throwIt(Throwable t) throws T { - throw (T) t; - } - } - new SneakyThrower().throwIt(t); - } - - private static class SomeCheckedException extends Exception {} - - private static class SomeUncheckedException extends RuntimeException {} } diff --git a/android/guava-tests/test/com/google/common/collect/AbstractMapEntryTest.java b/android/guava-tests/test/com/google/common/collect/AbstractMapEntryTest.java index 11c66a702375..5b52e03282e0 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractMapEntryTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractMapEntryTest.java @@ -16,10 +16,13 @@ package com.google.common.collect; +import static java.util.Collections.singletonMap; + import com.google.common.annotations.GwtCompatible; -import java.util.Collections; import java.util.Map.Entry; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code AbstractMapEntry}. @@ -27,11 +30,13 @@ * @author Mike Bostock */ @GwtCompatible +@NullMarked public class AbstractMapEntryTest extends TestCase { - private static final String NK = null; - private static final Integer NV = null; + private static final @Nullable String NK = null; + private static final @Nullable Integer NV = null; - private static Entry entry(final K key, final V value) { + private static Entry entry( + K key, V value) { return new AbstractMapEntry() { @Override public K getKey() { @@ -45,8 +50,9 @@ public V getValue() { }; } - private static Entry control(K key, V value) { - return Collections.singletonMap(key, value).entrySet().iterator().next(); + private static Entry control( + K key, V value) { + return singletonMap(key, value).entrySet().iterator().next(); } public void testToString() { @@ -61,7 +67,8 @@ public void testToStringNull() { public void testEquals() { Entry foo1 = entry("foo", 1); - assertEquals(foo1, foo1); + // Explicitly call `equals`; `assertEquals` might return fast + assertTrue(foo1.equals(foo1)); assertEquals(control("foo", 1), foo1); assertEquals(control("bar", 2), entry("bar", 2)); assertFalse(control("foo", 1).equals(entry("foo", 2))); diff --git a/android/guava-tests/test/com/google/common/collect/AbstractMapsTransformValuesTest.java b/android/guava-tests/test/com/google/common/collect/AbstractMapsTransformValuesTest.java new file mode 100644 index 000000000000..bef792c16612 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/AbstractMapsTransformValuesTest.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Maps.transformValues; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.testing.MapInterfaceTest; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Superclass for tests for {@link Maps#transformValues} overloads. + * + * @author Isaac Shum + */ +@GwtCompatible +@NullMarked +abstract class AbstractMapsTransformValuesTest extends MapInterfaceTest { + public AbstractMapsTransformValuesTest() { + super(false, true, false, true, true); + } + + @Override + protected String getKeyNotInPopulatedMap() throws UnsupportedOperationException { + return "z"; + } + + @Override + protected String getValueNotInPopulatedMap() throws UnsupportedOperationException { + return "26"; + } + + /** Helper assertion comparing two maps */ + private void assertMapsEqual(Map expected, Map map) { + assertEquals(expected, map); + assertEquals(expected.hashCode(), map.hashCode()); + assertEquals(expected.entrySet(), map.entrySet()); + + // Assert that expectedValues > mapValues and that + // mapValues > expectedValues; i.e. that expectedValues == mapValues. + Collection expectedValues = expected.values(); + Collection mapValues = map.values(); + assertEquals(expectedValues.size(), mapValues.size()); + assertTrue(expectedValues.containsAll(mapValues)); + assertTrue(mapValues.containsAll(expectedValues)); + } + + public void testTransformEmptyMapEquality() { + Map map = + transformValues(ImmutableMap.of(), Functions.toStringFunction()); + assertMapsEqual(new HashMap<>(), map); + } + + public void testTransformSingletonMapEquality() { + Map map = + transformValues(ImmutableMap.of("a", 1), Functions.toStringFunction()); + Map expected = ImmutableMap.of("a", "1"); + assertMapsEqual(expected, map); + assertEquals(expected.get("a"), map.get("a")); + } + + public void testTransformIdentityFunctionEquality() { + Map underlying = ImmutableMap.of("a", 1); + Map map = transformValues(underlying, Functions.identity()); + assertMapsEqual(underlying, map); + } + + public void testTransformPutEntryIsUnsupported() { + Map map = + transformValues(ImmutableMap.of("a", 1), Functions.toStringFunction()); + assertThrows(UnsupportedOperationException.class, () -> map.put("b", "2")); + + assertThrows(UnsupportedOperationException.class, () -> map.putAll(ImmutableMap.of("b", "2"))); + + assertThrows( + UnsupportedOperationException.class, + () -> map.entrySet().iterator().next().setValue("one")); + } + + public void testTransformRemoveEntry() { + Map underlying = new HashMap<>(); + underlying.put("a", 1); + Map map = transformValues(underlying, Functions.toStringFunction()); + assertEquals("1", map.remove("a")); + assertThat(map.remove("b")).isNull(); + } + + public void testTransformEqualityOfMapsWithNullValues() { + Map underlying = new HashMap<>(); + underlying.put("a", null); + underlying.put("b", ""); + + Map map = + transformValues( + underlying, + new Function<@Nullable String, Boolean>() { + @Override + public Boolean apply(@Nullable String from) { + return from == null; + } + }); + Map expected = ImmutableMap.of("a", true, "b", false); + assertMapsEqual(expected, map); + assertEquals(expected.get("a"), map.get("a")); + assertEquals(expected.containsKey("a"), map.containsKey("a")); + assertEquals(expected.get("b"), map.get("b")); + assertEquals(expected.containsKey("b"), map.containsKey("b")); + assertEquals(expected.get("c"), map.get("c")); + assertEquals(expected.containsKey("c"), map.containsKey("c")); + } + + public void testTransformReflectsUnderlyingMap() { + Map underlying = new HashMap<>(); + underlying.put("a", 1); + underlying.put("b", 2); + underlying.put("c", 3); + Map map = transformValues(underlying, Functions.toStringFunction()); + assertEquals(underlying.size(), map.size()); + + underlying.put("d", 4); + assertEquals(underlying.size(), map.size()); + assertEquals("4", map.get("d")); + + underlying.remove("c"); + assertEquals(underlying.size(), map.size()); + assertFalse(map.containsKey("c")); + + underlying.clear(); + assertEquals(underlying.size(), map.size()); + } + + public void testTransformChangesAreReflectedInUnderlyingMap() { + Map underlying = new LinkedHashMap<>(); + underlying.put("a", 1); + underlying.put("b", 2); + underlying.put("c", 3); + underlying.put("d", 4); + underlying.put("e", 5); + underlying.put("f", 6); + underlying.put("g", 7); + Map map = transformValues(underlying, Functions.toStringFunction()); + + map.remove("a"); + assertFalse(underlying.containsKey("a")); + + Set keys = map.keySet(); + keys.remove("b"); + assertFalse(underlying.containsKey("b")); + + Iterator keyIterator = keys.iterator(); + keyIterator.next(); + keyIterator.remove(); + assertFalse(underlying.containsKey("c")); + + Collection values = map.values(); + values.remove("4"); + assertFalse(underlying.containsKey("d")); + + Iterator valueIterator = values.iterator(); + valueIterator.next(); + valueIterator.remove(); + assertFalse(underlying.containsKey("e")); + + Set> entries = map.entrySet(); + Entry firstEntry = entries.iterator().next(); + entries.remove(firstEntry); + assertFalse(underlying.containsKey("f")); + + Iterator> entryIterator = entries.iterator(); + entryIterator.next(); + entryIterator.remove(); + assertFalse(underlying.containsKey("g")); + + assertTrue(underlying.isEmpty()); + assertTrue(map.isEmpty()); + assertTrue(keys.isEmpty()); + assertTrue(values.isEmpty()); + assertTrue(entries.isEmpty()); + } + + public void testTransformEquals() { + Map underlying = ImmutableMap.of("a", 0, "b", 1, "c", 2); + Map expected = transformValues(underlying, Functions.identity()); + + assertMapsEqual(expected, expected); + + Map equalToUnderlying = Maps.newTreeMap(); + equalToUnderlying.putAll(underlying); + Map map = transformValues(equalToUnderlying, Functions.identity()); + assertMapsEqual(expected, map); + + map = + transformValues( + ImmutableMap.of("a", 1, "b", 2, "c", 3), + new Function() { + @Override + public Integer apply(Integer from) { + return from - 1; + } + }); + assertMapsEqual(expected, map); + } + + public void testTransformEntrySetContains() { + Map<@Nullable String, @Nullable Boolean> underlying = new HashMap<>(); + underlying.put("a", null); + underlying.put("b", true); + underlying.put(null, true); + + Map<@Nullable String, @Nullable Boolean> map = + transformValues( + underlying, + new Function<@Nullable Boolean, @Nullable Boolean>() { + @Override + public @Nullable Boolean apply(@Nullable Boolean from) { + return (from == null) ? true : null; + } + }); + + Set> entries = map.entrySet(); + assertTrue(entries.contains(immutableEntry("a", true))); + assertTrue(entries.contains(Maps.immutableEntry("b", null))); + assertTrue( + entries.contains(Maps.<@Nullable String, @Nullable Boolean>immutableEntry(null, null))); + + assertFalse(entries.contains(Maps.immutableEntry("c", null))); + assertFalse(entries.contains(Maps.<@Nullable String, Boolean>immutableEntry(null, true))); + } + + @Override + public void testKeySetRemoveAllNullFromEmpty() { + try { + super.testKeySetRemoveAllNullFromEmpty(); + } catch (RuntimeException tolerated) { + // GWT's HashMap.keySet().removeAll(null) doesn't throws NPE. + } + } + + @Override + public void testEntrySetRemoveAllNullFromEmpty() { + try { + super.testEntrySetRemoveAllNullFromEmpty(); + } catch (RuntimeException tolerated) { + // GWT's HashMap.entrySet().removeAll(null) doesn't throws NPE. + } + } +} diff --git a/android/guava-tests/test/com/google/common/collect/AbstractMultimapAsMapImplementsMapTest.java b/android/guava-tests/test/com/google/common/collect/AbstractMultimapAsMapImplementsMapTest.java index 3e41dec8eb5a..f8d43bee62fc 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractMultimapAsMapImplementsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractMultimapAsMapImplementsMapTest.java @@ -16,10 +16,13 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.MapInterfaceTest; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.NullMarked; /** * Test {@link Multimap#asMap()} for an arbitrary multimap with {@link MapInterfaceTest}. @@ -28,6 +31,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public abstract class AbstractMultimapAsMapImplementsMapTest extends MapInterfaceTest> { @@ -63,28 +67,23 @@ protected Collection getValueNotInPopulatedMap() throws UnsupportedOper */ @Override public void testRemove() { - final Map> map; - final String keyToRemove; + Map> map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { return; } - keyToRemove = map.keySet().iterator().next(); + String keyToRemove = map.keySet().iterator().next(); if (supportsRemove) { int initialSize = map.size(); - map.get(keyToRemove); + // var oldValue = map.get(keyToRemove); map.remove(keyToRemove); // This line doesn't hold - see the Javadoc comments above. // assertEquals(expectedValue, oldValue); assertFalse(map.containsKey(keyToRemove)); assertEquals(initialSize - 1, map.size()); } else { - try { - map.remove(keyToRemove); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.remove(keyToRemove)); } assertInvariants(map); } diff --git a/android/guava-tests/test/com/google/common/collect/AbstractRangeSetTest.java b/android/guava-tests/test/com/google/common/collect/AbstractRangeSetTest.java index e53bea1bbcf9..7b31a66668cb 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractRangeSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractRangeSetTest.java @@ -14,11 +14,14 @@ package com.google.common.collect; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtIncompatible; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Base class for {@link RangeSet} tests. @@ -26,13 +29,14 @@ * @author Louis Wasserman */ @GwtIncompatible // TreeRangeSet +@NullUnmarked public abstract class AbstractRangeSetTest extends TestCase { public static void testInvariants(RangeSet rangeSet) { testInvariantsInternal(rangeSet); testInvariantsInternal(rangeSet.complement()); } - private static void testInvariantsInternal(RangeSet rangeSet) { + private static > void testInvariantsInternal(RangeSet rangeSet) { assertEquals(rangeSet.asRanges().isEmpty(), rangeSet.isEmpty()); assertEquals(rangeSet.asDescendingSetOfRanges().isEmpty(), rangeSet.isEmpty()); assertEquals(!rangeSet.asRanges().iterator().hasNext(), rangeSet.isEmpty()); @@ -66,7 +70,7 @@ private static void testInvariantsInternal(RangeSet ra Range span = rangeSet.span(); assertEquals(expectedSpan, span); } catch (NoSuchElementException e) { - assertNull(expectedSpan); + assertThat(expectedSpan).isNull(); } // test that asDescendingSetOfRanges is the reverse of asRanges diff --git a/android/guava-tests/test/com/google/common/collect/AbstractSequentialIteratorTest.java b/android/guava-tests/test/com/google/common/collect/AbstractSequentialIteratorTest.java index c0b7d91c4cf4..bc3cc85bbc73 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractSequentialIteratorTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractSequentialIteratorTest.java @@ -16,19 +16,24 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.TestExceptions.SomeUncheckedException; import com.google.common.collect.testing.IteratorTester; import java.util.Iterator; import java.util.NoSuchElementException; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** Tests for {@link AbstractSequentialIterator}. */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class AbstractSequentialIteratorTest extends TestCase { @GwtIncompatible // Too slow public void testDoublerExhaustive() { @@ -59,7 +64,8 @@ public void testSampleCode() { public Iterator iterator() { Iterator powersOfTwo = new AbstractSequentialIterator(1) { - protected Integer computeNext(Integer previous) { + @Override + protected @Nullable Integer computeNext(Integer previous) { return (previous == 1 << 30) ? null : previous * 2; } }; @@ -102,63 +108,52 @@ protected Integer computeNext(Integer previous) { .inOrder(); } + @SuppressWarnings("DoNotCall") public void testEmpty() { - Iterator empty = newEmpty(); + Iterator empty = new EmptyAbstractSequentialIterator<>(); assertFalse(empty.hasNext()); - try { - empty.next(); - fail(); - } catch (NoSuchElementException expected) { - } - try { - empty.remove(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(NoSuchElementException.class, empty::next); + assertThrows(UnsupportedOperationException.class, empty::remove); } public void testBroken() { - Iterator broken = newBroken(); + Iterator broken = new BrokenAbstractSequentialIterator(); assertTrue(broken.hasNext()); // We can't retrieve even the known first element: - try { - broken.next(); - fail(); - } catch (MyException expected) { - } - try { - broken.next(); - fail(); - } catch (MyException expected) { - } + assertThrows(SomeUncheckedException.class, broken::next); + assertThrows(SomeUncheckedException.class, broken::next); } - private static Iterator newDoubler(int first, final int last) { + private static Iterator newDoubler(int first, int last) { return new AbstractSequentialIterator(first) { @Override - protected Integer computeNext(Integer previous) { + protected @Nullable Integer computeNext(Integer previous) { return (previous == last) ? null : previous * 2; } }; } - private static Iterator newEmpty() { - return new AbstractSequentialIterator(null) { - @Override - protected T computeNext(T previous) { - throw new AssertionFailedError(); - } - }; - } + private static class EmptyAbstractSequentialIterator extends AbstractSequentialIterator { - private static Iterator newBroken() { - return new AbstractSequentialIterator("UNUSED") { - @Override - protected Object computeNext(Object previous) { - throw new MyException(); - } - }; + EmptyAbstractSequentialIterator() { + super(null); + } + + @Override + protected T computeNext(T previous) { + throw new AssertionFailedError(); + } } - private static class MyException extends RuntimeException {} + private static class BrokenAbstractSequentialIterator extends AbstractSequentialIterator { + + BrokenAbstractSequentialIterator() { + super("UNUSED"); + } + + @Override + protected Object computeNext(Object previous) { + throw new SomeUncheckedException(); + } + } } diff --git a/android/guava-tests/test/com/google/common/collect/AbstractTableReadTest.java b/android/guava-tests/test/com/google/common/collect/AbstractTableReadTest.java index 67d44cb9b680..ee90d0bd40df 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractTableReadTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractTableReadTest.java @@ -16,23 +16,28 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; +import java.util.Objects; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link Table} read operations. * * @author Jared Levy */ -@GwtCompatible(emulated = true) -public abstract class AbstractTableReadTest extends TestCase { - protected Table table; +@GwtCompatible +@NullMarked +public abstract class AbstractTableReadTest extends TestCase { + protected Table table; /** * Creates a table with the specified data. @@ -41,7 +46,7 @@ public abstract class AbstractTableReadTest extends TestCase { * @throws IllegalArgumentException if the size of {@code data} isn't a multiple of 3 * @throws ClassCastException if a data element has the wrong type */ - protected abstract Table create(Object... data); + protected abstract Table create(@Nullable Object... data); protected void assertSize(int expectedSize) { assertEquals(expectedSize, table.size()); @@ -96,12 +101,12 @@ public void testGet() { assertEquals((Character) 'a', table.get("foo", 1)); assertEquals((Character) 'b', table.get("bar", 1)); assertEquals((Character) 'c', table.get("foo", 3)); - assertNull(table.get("foo", 2)); - assertNull(table.get("bar", 3)); - assertNull(table.get("cat", 1)); - assertNull(table.get("foo", null)); - assertNull(table.get(null, 1)); - assertNull(table.get(null, null)); + assertThat(table.get("foo", 2)).isNull(); + assertThat(table.get("bar", 3)).isNull(); + assertThat(table.get("cat", 1)).isNull(); + assertThat(table.get("foo", null)).isNull(); + assertThat(table.get(null, 1)).isNull(); + assertThat(table.get(null, null)).isNull(); } public void testIsEmpty() { @@ -118,14 +123,13 @@ public void testSize() { public void testEquals() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - Table hashCopy = HashBasedTable.create(table); - Table reordered = - create("foo", 3, 'c', "foo", 1, 'a', "bar", 1, 'b'); - Table smaller = create("foo", 1, 'a', "bar", 1, 'b'); - Table swapOuter = - create("bar", 1, 'a', "foo", 1, 'b', "bar", 3, 'c'); - Table swapValues = - create("foo", 1, 'c', "bar", 1, 'b', "foo", 3, 'a'); + // We know that we have only added non-null Characters. + Table hashCopy = + HashBasedTable.create((Table) table); + Table reordered = create("foo", 3, 'c', "foo", 1, 'a', "bar", 1, 'b'); + Table smaller = create("foo", 1, 'a', "bar", 1, 'b'); + Table swapOuter = create("bar", 1, 'a', "foo", 1, 'b', "bar", 3, 'c'); + Table swapValues = create("foo", 1, 'c', "bar", 1, 'b', "foo", 3, 'a'); new EqualsTester() .addEqualityGroup(table, hashCopy, reordered) @@ -138,9 +142,7 @@ public void testEquals() { public void testHashCode() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); int expected = - Objects.hashCode("foo", 1, 'a') - + Objects.hashCode("bar", 1, 'b') - + Objects.hashCode("foo", 3, 'c'); + Objects.hash("foo", 1, 'a') + Objects.hash("bar", 1, 'b') + Objects.hash("foo", 3, 'c'); assertEquals(expected, table.hashCode()); } @@ -157,11 +159,7 @@ public void testRow() { // This test assumes that the implementation does not support null keys. public void testRowNull() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - try { - table.row(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> table.row(null)); } public void testColumn() { @@ -172,11 +170,7 @@ public void testColumn() { // This test assumes that the implementation does not support null keys. public void testColumnNull() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - try { - table.column(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> table.column(null)); } public void testColumnSetPartialOverlap() { @@ -184,6 +178,7 @@ public void testColumnSetPartialOverlap() { assertThat(table.columnKeySet()).containsExactly(1, 2, 3); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerInstance() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 2, 'c', "bar", 3, 'd'); diff --git a/android/guava-tests/test/com/google/common/collect/AbstractTableTest.java b/android/guava-tests/test/com/google/common/collect/AbstractTableTest.java index f634b6a8c724..25eb735a61d3 100644 --- a/android/guava-tests/test/com/google/common/collect/AbstractTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/AbstractTableTest.java @@ -17,9 +17,14 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import java.util.Map; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for a {@link Table} implementation supporting reads and writes. @@ -28,12 +33,15 @@ * @author Louis Wasserman */ @GwtCompatible -public abstract class AbstractTableTest extends AbstractTableReadTest { +@NullMarked +public abstract class AbstractTableTest + extends AbstractTableReadTest { - protected void populate(Table table, Object... data) { + protected void populate(Table table, @Nullable Object... data) { checkArgument(data.length % 3 == 0); for (int i = 0; i < data.length; i += 3) { - table.put((String) data[i], (Integer) data[i + 1], (Character) data[i + 2]); + table.put( + (String) data[i], (Integer) data[i + 1], nullableCellValue((Character) data[i + 2])); } } @@ -52,50 +60,33 @@ public void testClear() { assertEquals(0, table.size()); assertFalse(table.containsRow("foo")); } else { - try { - table.clear(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> table.clear()); } } public void testPut() { - assertNull(table.put("foo", 1, 'a')); - assertNull(table.put("bar", 1, 'b')); - assertNull(table.put("foo", 3, 'c')); - assertEquals((Character) 'a', table.put("foo", 1, 'd')); + assertThat(table.put("foo", 1, cellValue('a'))).isNull(); + assertThat(table.put("bar", 1, cellValue('b'))).isNull(); + assertThat(table.put("foo", 3, cellValue('c'))).isNull(); + assertEquals((Character) 'a', table.put("foo", 1, cellValue('d'))); assertEquals((Character) 'd', table.get("foo", 1)); assertEquals((Character) 'b', table.get("bar", 1)); assertSize(3); - assertEquals((Character) 'd', table.put("foo", 1, 'd')); + assertEquals((Character) 'd', table.put("foo", 1, cellValue('d'))); assertEquals((Character) 'd', table.get("foo", 1)); assertSize(3); } - // This test assumes that the implementation does not support nulls. public void testPutNull() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); assertSize(3); - try { - table.put(null, 2, 'd'); - fail(); - } catch (NullPointerException expected) { - } - try { - table.put("cat", null, 'd'); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> table.put(null, 2, cellValue('d'))); + assertThrows(NullPointerException.class, () -> table.put("cat", null, cellValue('d'))); if (supportsNullValues()) { - assertNull(table.put("cat", 2, null)); + assertThat(table.put("cat", 2, null)).isNull(); assertTrue(table.contains("cat", 2)); } else { - try { - table.put("cat", 2, null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> table.put("cat", 2, null)); } assertSize(3); } @@ -104,23 +95,19 @@ public void testPutNullReplace() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); if (supportsNullValues()) { - assertEquals((Character) 'b', table.put("bar", 1, null)); - assertNull(table.get("bar", 1)); + assertEquals((Character) 'b', table.put("bar", 1, nullableCellValue(null))); + assertThat(table.get("bar", 1)).isNull(); } else { - try { - table.put("bar", 1, null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> table.put("bar", 1, nullableCellValue(null))); } } public void testPutAllTable() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - Table other = HashBasedTable.create(); - other.put("foo", 1, 'd'); - other.put("bar", 2, 'e'); - other.put("cat", 2, 'f'); + Table other = HashBasedTable.create(); + other.put("foo", 1, cellValue('d')); + other.put("bar", 2, cellValue('e')); + other.put("cat", 2, cellValue('f')); table.putAll(other); assertEquals((Character) 'd', table.get("foo", 1)); assertEquals((Character) 'b', table.get("bar", 1)); @@ -133,24 +120,20 @@ public void testPutAllTable() { public void testRemove() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); if (supportsRemove()) { - assertNull(table.remove("cat", 1)); - assertNull(table.remove("bar", 3)); + assertThat(table.remove("cat", 1)).isNull(); + assertThat(table.remove("bar", 3)).isNull(); assertEquals(3, table.size()); assertEquals((Character) 'c', table.remove("foo", 3)); assertEquals(2, table.size()); assertEquals((Character) 'a', table.get("foo", 1)); assertEquals((Character) 'b', table.get("bar", 1)); - assertNull(table.get("foo", 3)); - assertNull(table.remove(null, 1)); - assertNull(table.remove("foo", null)); - assertNull(table.remove(null, null)); + assertThat(table.get("foo", 3)).isNull(); + assertThat(table.remove(null, 1)).isNull(); + assertThat(table.remove("foo", null)).isNull(); + assertThat(table.remove(null, null)).isNull(); assertSize(2); } else { - try { - table.remove("foo", 3); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> table.remove("foo", 3)); assertEquals((Character) 'c', table.get("foo", 3)); } } @@ -158,18 +141,29 @@ public void testRemove() { public void testRowClearAndPut() { if (supportsRemove()) { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - Map row = table.row("foo"); + Map row = table.row("foo"); assertEquals(ImmutableMap.of(1, 'a', 3, 'c'), row); table.remove("foo", 3); assertEquals(ImmutableMap.of(1, 'a'), row); table.remove("foo", 1); assertEquals(ImmutableMap.of(), row); - table.put("foo", 2, 'b'); + table.put("foo", 2, cellValue('b')); assertEquals(ImmutableMap.of(2, 'b'), row); row.clear(); assertEquals(ImmutableMap.of(), row); - table.put("foo", 5, 'x'); + table.put("foo", 5, cellValue('x')); assertEquals(ImmutableMap.of(5, 'x'), row); } } + + @SuppressWarnings("unchecked") // C can only be @Nullable Character or Character + protected @NonNull C cellValue(Character character) { + return (C) character; + } + + // Only safe wrt. ClassCastException. Not null-safe (can be used to test expected Table NPEs) + @SuppressWarnings("unchecked") + protected C nullableCellValue(@Nullable Character character) { + return (C) character; + } } diff --git a/android/guava-tests/test/com/google/common/collect/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/collect/AndroidIncompatible.java index 6600c1f6946f..21fd882d0bed 100644 --- a/android/guava-tests/test/com/google/common/collect/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/collect/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/collect/ArrayListMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ArrayListMultimapTest.java index 08e5ae8ccf2c..1bdc72cfdf3b 100644 --- a/android/guava-tests/test/com/google/common/collect/ArrayListMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ArrayListMultimapTest.java @@ -16,11 +16,13 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -33,16 +35,20 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@code ArrayListMultimap}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ArrayListMultimapTest extends TestCase { @GwtIncompatible // suite + @J2ktIncompatible + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -116,11 +122,7 @@ public void testSublistConcurrentModificationException() { assertTrue(sublist.isEmpty()); multimap.put("foo", 6); - try { - sublist.isEmpty(); - fail("Expected ConcurrentModificationException"); - } catch (ConcurrentModificationException expected) { - } + assertThrows(ConcurrentModificationException.class, () -> sublist.isEmpty()); } public void testCreateFromMultimap() { @@ -143,17 +145,9 @@ public void testCreateFromSizes() { } public void testCreateFromIllegalSizes() { - try { - ArrayListMultimap.create(15, -2); - fail(); - } catch (IllegalArgumentException expected) { - } - - try { - ArrayListMultimap.create(-15, 2); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> ArrayListMultimap.create(15, -2)); + + assertThrows(IllegalArgumentException.class, () -> ArrayListMultimap.create(-15, 2)); } public void testCreateFromHashMultimap() { diff --git a/android/guava-tests/test/com/google/common/collect/ArrayTableColumnMapTest.java b/android/guava-tests/test/com/google/common/collect/ArrayTableColumnMapTest.java new file mode 100644 index 000000000000..f73153084816 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ArrayTableColumnMapTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.TableCollectionTest.ColumnMapTests; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // TODO(hhchan): ArrayTable +@NullUnmarked +public class ArrayTableColumnMapTest extends ColumnMapTests { + public ArrayTableColumnMapTest() { + super(true, false, false, false); + } + + @Override + Table makeTable() { + return ArrayTable.create(asList(1, 2, 3), asList("foo", "bar", "dog")); + } + + @Override + protected Map> makeEmptyMap() { + throw new UnsupportedOperationException(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ArrayTableColumnTest.java b/android/guava-tests/test/com/google/common/collect/ArrayTableColumnTest.java new file mode 100644 index 000000000000..902e5ea7b00f --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ArrayTableColumnTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.TableCollectionTest.ColumnTests; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // TODO(hhchan): ArrayTable +@NullUnmarked +public class ArrayTableColumnTest extends ColumnTests { + public ArrayTableColumnTest() { + super(true, true, false, false, false); + } + + @Override + protected String getKeyNotInPopulatedMap() { + throw new UnsupportedOperationException(); + } + + @Override + protected Map makeEmptyMap() { + throw new UnsupportedOperationException(); + } + + @Override + Table makeTable() { + return ArrayTable.create(asList("one", "two", "three", "four"), asList('a', 'b', 'c')); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ArrayTableRowMapTest.java b/android/guava-tests/test/com/google/common/collect/ArrayTableRowMapTest.java new file mode 100644 index 000000000000..082a43596f9d --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ArrayTableRowMapTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // TODO(hhchan): ArrayTable +@NullUnmarked +public class ArrayTableRowMapTest extends RowMapTests { + public ArrayTableRowMapTest() { + super(true, false, false, false); + } + + @Override + Table makeTable() { + return ArrayTable.create(asList("foo", "bar", "dog"), asList(1, 2, 3)); + } + + @Override + protected Map> makeEmptyMap() { + throw new UnsupportedOperationException(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ArrayTableRowTest.java b/android/guava-tests/test/com/google/common/collect/ArrayTableRowTest.java new file mode 100644 index 000000000000..9d8370717961 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ArrayTableRowTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.TableCollectionTest.RowTests; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // TODO(hhchan): ArrayTable +@NullUnmarked +public class ArrayTableRowTest extends RowTests { + public ArrayTableRowTest() { + super(true, true, false, false, false); + } + + @Override + protected String getKeyNotInPopulatedMap() { + throw new UnsupportedOperationException(); + } + + @Override + protected Map makeEmptyMap() { + throw new UnsupportedOperationException(); + } + + @Override + protected Table makeTable() { + return ArrayTable.create(asList('a', 'b', 'c'), asList("one", "two", "three", "four")); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ArrayTableTest.java b/android/guava-tests/test/com/google/common/collect/ArrayTableTest.java index b7622c23137a..75aeb1784254 100644 --- a/android/guava-tests/test/com/google/common/collect/ArrayTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/ArrayTableTest.java @@ -16,29 +16,36 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Tables.immutableCell; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Table.Cell; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link ArrayTable}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) -public class ArrayTableTest extends AbstractTableTest { +@GwtCompatible +@NullMarked +public class ArrayTableTest extends AbstractTableTest<@Nullable Character> { @Override - protected ArrayTable create(Object... data) { + protected ArrayTable create(@Nullable Object... data) { // TODO: Specify different numbers of rows and columns, to detect problems // that arise when the wrong size is used. ArrayTable table = @@ -125,12 +132,12 @@ public void testEquals() { hashCopy.put("foo", 1, 'a'); hashCopy.put("bar", 1, 'b'); hashCopy.put("foo", 3, 'c'); - Table reordered = + Table reordered = create("foo", 3, 'c', "foo", 1, 'a', "bar", 1, 'b'); - Table smaller = create("foo", 1, 'a', "bar", 1, 'b'); - Table swapOuter = + Table smaller = create("foo", 1, 'a', "bar", 1, 'b'); + Table swapOuter = create("bar", 1, 'a', "foo", 1, 'b', "bar", 3, 'c'); - Table swapValues = + Table swapValues = create("foo", 1, 'c', "bar", 1, 'b', "foo", 3, 'a'); new EqualsTester() @@ -149,17 +156,17 @@ public void testHashCode() { table.put("bar", 1, 'b'); table.put("foo", 3, 'c'); int expected = - Objects.hashCode("foo", 1, 'a') - + Objects.hashCode("bar", 1, 'b') - + Objects.hashCode("foo", 3, 'c') - + Objects.hashCode("bar", 3, 0); + Objects.hash("foo", 1, 'a') + + Objects.hash("bar", 1, 'b') + + Objects.hash("foo", 3, 'c') + + Objects.hash("bar", 3, 0); assertEquals(expected, table.hashCode()); } @Override public void testRow() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - Map expected = Maps.newHashMap(); + Map expected = new HashMap<>(); expected.put(1, 'a'); expected.put(3, 'c'); expected.put(2, null); @@ -169,7 +176,7 @@ public void testRow() { @Override public void testColumn() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - Map expected = Maps.newHashMap(); + Map expected = new HashMap<>(); expected.put("foo", 'a'); expected.put("bar", 'b'); expected.put("cat", null); @@ -184,35 +191,27 @@ public void testToStringSize1() { } public void testCreateDuplicateRows() { - try { - ArrayTable.create(asList("foo", "bar", "foo"), asList(1, 2, 3)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> ArrayTable.create(asList("foo", "bar", "foo"), asList(1, 2, 3))); } public void testCreateDuplicateColumns() { - try { - ArrayTable.create(asList("foo", "bar"), asList(1, 2, 3, 2)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> ArrayTable.create(asList("foo", "bar"), asList(1, 2, 3, 2))); } public void testCreateEmptyRows() { - try { - ArrayTable.create(Arrays.asList(), asList(1, 2, 3)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> ArrayTable.create(Arrays.asList(), asList(1, 2, 3))); } public void testCreateEmptyColumns() { - try { - ArrayTable.create(asList("foo", "bar"), Arrays.asList()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> ArrayTable.create(asList("foo", "bar"), Arrays.asList())); } public void testCreateEmptyRowsXColumns() { @@ -224,11 +223,7 @@ public void testCreateEmptyRowsXColumns() { assertThat(table.rowKeyList()).isEmpty(); assertThat(table.columnKeySet()).isEmpty(); assertThat(table.rowKeySet()).isEmpty(); - try { - table.at(0, 0); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> table.at(0, 0)); } @GwtIncompatible // toArray @@ -239,9 +234,9 @@ public void testEmptyToArry() { } public void testCreateCopyArrayTable() { - Table original = + Table original = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - Table copy = ArrayTable.create(original); + Table copy = ArrayTable.create(original); assertEquals(original, copy); original.put("foo", 1, 'd'); assertEquals((Character) 'd', original.get("foo", 1)); @@ -255,12 +250,12 @@ public void testCreateCopyHashBasedTable() { original.put("foo", 1, 'a'); original.put("bar", 1, 'b'); original.put("foo", 3, 'c'); - Table copy = ArrayTable.create(original); + Table copy = ArrayTable.create(original); assertEquals(4, copy.size()); assertEquals((Character) 'a', copy.get("foo", 1)); assertEquals((Character) 'b', copy.get("bar", 1)); assertEquals((Character) 'c', copy.get("foo", 3)); - assertNull(copy.get("bar", 3)); + assertThat(copy.get("bar", 3)).isNull(); original.put("foo", 1, 'd'); assertEquals((Character) 'd', original.get("foo", 1)); assertEquals((Character) 'a', copy.get("foo", 1)); @@ -278,7 +273,7 @@ public void testCreateCopyEmptyTable() { } public void testCreateCopyEmptyArrayTable() { - Table original = + Table original = ArrayTable.create(Arrays.asList(), Arrays.asList()); ArrayTable copy = ArrayTable.create(original); assertThat(copy).isEqualTo(original); @@ -290,6 +285,7 @@ public void testSerialization() { SerializableTester.reserializeAndAssert(table); } + @J2ktIncompatible @GwtIncompatible // reflection public void testNullPointerStatic() { new NullPointerTester().testAllPublicStaticMethods(ArrayTable.class); @@ -347,8 +343,8 @@ public void testColumnKeyList() { public void testGetMissingKeys() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - assertNull(table.get("dog", 1)); - assertNull(table.get("foo", 4)); + assertThat(table.get("dog", 1)).isNull(); + assertThat(table.get("foo", 4)).isNull(); } public void testAt() { @@ -356,27 +352,11 @@ public void testAt() { create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); assertEquals((Character) 'b', table.at(1, 0)); assertEquals((Character) 'c', table.at(0, 2)); - assertNull(table.at(1, 2)); - try { - table.at(1, 3); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - table.at(1, -1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - table.at(3, 2); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - table.at(-1, 2); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThat(table.at(1, 2)).isNull(); + assertThrows(IndexOutOfBoundsException.class, () -> table.at(1, 3)); + assertThrows(IndexOutOfBoundsException.class, () -> table.at(1, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> table.at(3, 2)); + assertThrows(IndexOutOfBoundsException.class, () -> table.at(-1, 2)); } public void testSet() { @@ -384,30 +364,14 @@ public void testSet() { create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); assertEquals((Character) 'b', table.set(1, 0, 'd')); assertEquals((Character) 'd', table.get("bar", 1)); - assertNull(table.set(2, 0, 'e')); + assertThat(table.set(2, 0, 'e')).isNull(); assertEquals((Character) 'e', table.get("cat", 1)); assertEquals((Character) 'a', table.set(0, 0, null)); - assertNull(table.get("foo", 1)); - try { - table.set(1, 3, 'z'); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - table.set(1, -1, 'z'); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - table.set(3, 2, 'z'); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - table.set(-1, 2, 'z'); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThat(table.get("foo", 1)).isNull(); + assertThrows(IndexOutOfBoundsException.class, () -> table.set(1, 3, 'z')); + assertThrows(IndexOutOfBoundsException.class, () -> table.set(1, -1, 'z')); + assertThrows(IndexOutOfBoundsException.class, () -> table.set(3, 2, 'z')); + assertThrows(IndexOutOfBoundsException.class, () -> table.set(-1, 2, 'z')); assertFalse(table.containsValue('z')); } @@ -416,25 +380,18 @@ public void testEraseAll() { create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); table.eraseAll(); assertEquals(9, table.size()); - assertNull(table.get("bar", 1)); + assertThat(table.get("bar", 1)).isNull(); assertTrue(table.containsRow("foo")); assertFalse(table.containsValue('a')); } public void testPutIllegal() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); - try { - table.put("dog", 1, 'd'); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Row dog not in [foo, bar, cat]"); - } - try { - table.put("foo", 4, 'd'); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Column 4 not in [1, 2, 3]"); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> table.put("dog", 1, 'd')); + assertThat(expected).hasMessageThat().isEqualTo("Row dog not in [foo, bar, cat]"); + expected = assertThrows(IllegalArgumentException.class, () -> table.put("foo", 4, 'd')); + assertThat(expected).hasMessageThat().isEqualTo("Column 4 not in [1, 2, 3]"); assertFalse(table.containsValue('d')); } @@ -442,14 +399,14 @@ public void testErase() { ArrayTable table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); assertEquals((Character) 'b', table.erase("bar", 1)); - assertNull(table.get("bar", 1)); + assertThat(table.get("bar", 1)).isNull(); assertEquals(9, table.size()); - assertNull(table.erase("bar", 1)); - assertNull(table.erase("foo", 2)); - assertNull(table.erase("dog", 1)); - assertNull(table.erase("bar", 5)); - assertNull(table.erase(null, 1)); - assertNull(table.erase("bar", null)); + assertThat(table.erase("bar", 1)).isNull(); + assertThat(table.erase("foo", 2)).isNull(); + assertThat(table.erase("dog", 1)).isNull(); + assertThat(table.erase("bar", 5)).isNull(); + assertThat(table.erase(null, 1)).isNull(); + assertThat(table.erase("bar", null)).isNull(); } @GwtIncompatible // ArrayTable.toArray(Class) @@ -470,62 +427,50 @@ public void testToArray() { public void testCellReflectsChanges() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); Cell cell = table.cellSet().iterator().next(); - assertEquals(Tables.immutableCell("foo", 1, 'a'), cell); + assertEquals(immutableCell("foo", 1, 'a'), cell); assertEquals((Character) 'a', table.put("foo", 1, 'd')); - assertEquals(Tables.immutableCell("foo", 1, 'd'), cell); + assertEquals(immutableCell("foo", 1, 'd'), cell); } public void testRowMissing() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); Map row = table.row("dog"); assertTrue(row.isEmpty()); - try { - row.put(1, 'd'); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> row.put(1, 'd')); } public void testColumnMissing() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); Map column = table.column(4); assertTrue(column.isEmpty()); - try { - column.put("foo", 'd'); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> column.put("foo", 'd')); } public void testRowPutIllegal() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); Map map = table.row("foo"); - try { - map.put(4, 'd'); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Column 4 not in [1, 2, 3]"); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> map.put(4, 'd')); + assertThat(expected).hasMessageThat().isEqualTo("Column 4 not in [1, 2, 3]"); } public void testColumnPutIllegal() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); Map map = table.column(3); - try { - map.put("dog", 'd'); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Row dog not in [foo, bar, cat]"); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> map.put("dog", 'd')); + assertThat(expected).hasMessageThat().isEqualTo("Row dog not in [foo, bar, cat]"); } + @J2ktIncompatible @GwtIncompatible // reflection public void testNulls() { new NullPointerTester().testAllPublicInstanceMethods(create()); } - @GwtIncompatible // serialize - public void testSerializable() { + @GwtIncompatible + @J2ktIncompatible + public void testSerializable() { SerializableTester.reserializeAndAssert(create()); } } diff --git a/android/guava-tests/test/com/google/common/collect/Base.java b/android/guava-tests/test/com/google/common/collect/Base.java new file mode 100644 index 000000000000..1d9e2836202e --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/Base.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.io.Serializable; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; + +/** Simple base class to verify that we handle generics correctly. */ +@GwtCompatible +@NullUnmarked +class Base implements Comparable, Serializable { + private final String s; + + public Base(String s) { + this.s = s; + } + + @Override + public int hashCode() { // delegate to 's' + return s.hashCode(); + } + + @Override + public boolean equals(@Nullable Object other) { + if (other == null) { + return false; + } else if (other instanceof Base) { + return s.equals(((Base) other).s); + } else { + return false; + } + } + + @Override + public int compareTo(Base o) { + return s.compareTo(o.s); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; +} diff --git a/android/guava-tests/test/com/google/common/collect/BenchmarkHelpers.java b/android/guava-tests/test/com/google/common/collect/BenchmarkHelpers.java index 65d2ef6f46e7..5206cea2a0ba 100644 --- a/android/guava-tests/test/com/google/common/collect/BenchmarkHelpers.java +++ b/android/guava-tests/test/com/google/common/collect/BenchmarkHelpers.java @@ -17,11 +17,14 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkState; +import static java.util.Collections.synchronizedSet; +import static java.util.Collections.unmodifiableSet; import com.google.common.base.Equivalence; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Queue; @@ -32,12 +35,14 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; +import org.jspecify.annotations.NullUnmarked; /** * Helper classes for various benchmarks. * * @author Christopher Swenson */ +@NullUnmarked final class BenchmarkHelpers { /** So far, this is the best way to test various implementations of {@link Set} subclasses. */ public interface CollectionsImplEnum { @@ -80,13 +85,13 @@ public > Set create(Collection contents) { UnmodifiableSetImpl { @Override public > Set create(Collection contents) { - return Collections.unmodifiableSet(new HashSet(contents)); + return unmodifiableSet(new HashSet(contents)); } }, SynchronizedSetImpl { @Override public > Set create(Collection contents) { - return Collections.synchronizedSet(new HashSet(contents)); + return synchronizedSet(new HashSet(contents)); } }, ImmutableSetImpl { @@ -188,13 +193,13 @@ public enum MapImpl implements MapsImplEnum { HashMapImpl { @Override public , V> Map create(Map map) { - return Maps.newHashMap(map); + return new HashMap<>(map); } }, LinkedHashMapImpl { @Override public , V> Map create(Map map) { - return Maps.newLinkedHashMap(map); + return new LinkedHashMap<>(map); } }, ConcurrentHashMapImpl { @@ -386,7 +391,7 @@ public enum InternerImpl implements InternerImplEnum { public Interner create(Collection contents) { Interner interner = Interners.newWeakInterner(); for (E e : contents) { - interner.intern(e); + E unused = interner.intern(e); } return interner; } @@ -396,7 +401,7 @@ public Interner create(Collection contents) { public Interner create(Collection contents) { Interner interner = Interners.newStrongInterner(); for (E e : contents) { - interner.intern(e); + E unused = interner.intern(e); } return interner; } @@ -416,7 +421,7 @@ public enum ListSizeDistribution { final int min; final int max; - private ListSizeDistribution(int min, int max) { + ListSizeDistribution(int min, int max) { this.min = min; this.max = max; } @@ -425,4 +430,6 @@ public int chooseSize(Random random) { return random.nextInt(max - min + 1) + min; } } + + private BenchmarkHelpers() {} } diff --git a/android/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java b/android/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java new file mode 100644 index 000000000000..0dc9ac8dea2a --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Lists.charactersOf; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Ascii; +import com.google.common.collect.testing.SpliteratorTester; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Spliterator; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; + +/** Tests for {@code CollectSpliterators}. */ +@GwtCompatible +@NullMarked +public class CollectSpliteratorsTest extends TestCase { + public void testMap() { + SpliteratorTester.of( + () -> + CollectSpliterators.map( + Arrays.spliterator(new String[] {"a", "b", "c", "d", "e"}), Ascii::toUpperCase)) + .expect("A", "B", "C", "D", "E"); + } + + public void testFlatMap() { + SpliteratorTester.of( + () -> + CollectSpliterators.flatMap( + Arrays.spliterator(new String[] {"abc", "", "de", "f", "g", ""}), + (String str) -> charactersOf(str).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 7)) + .expect('a', 'b', 'c', 'd', 'e', 'f', 'g'); + } + + public void testFlatMap_nullStream() { + SpliteratorTester.of( + () -> + CollectSpliterators.flatMap( + Arrays.spliterator(new String[] {"abc", "", "de", "f", "g", ""}), + (String str) -> str.isEmpty() ? null : charactersOf(str).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 7)) + .expect('a', 'b', 'c', 'd', 'e', 'f', 'g'); + } + + public void testFlatMapToInt_nullStream() { + SpliteratorTester.ofInt( + () -> + CollectSpliterators.flatMapToInt( + Arrays.spliterator(new Integer[] {1, 0, 1, 2, 3}), + (Integer i) -> i == 0 ? null : IntStream.of(i).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 4)) + .expect(1, 1, 2, 3); + } + + public void testFlatMapToLong_nullStream() { + SpliteratorTester.ofLong( + () -> + CollectSpliterators.flatMapToLong( + Arrays.spliterator(new Long[] {1L, 0L, 1L, 2L, 3L}), + (Long i) -> i == 0L ? null : LongStream.of(i).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 4)) + .expect(1L, 1L, 2L, 3L); + } + + public void testFlatMapToDouble_nullStream() { + SpliteratorTester.ofDouble( + () -> + CollectSpliterators.flatMapToDouble( + Arrays.spliterator(new Double[] {1.0, 0.0, 1.0, 2.0, 3.0}), + (Double i) -> i == 0.0 ? null : DoubleStream.of(i).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 4)) + .expect(1.0, 1.0, 2.0, 3.0); + } + + public void testMultisetsSpliterator() { + Multiset multiset = TreeMultiset.create(); + multiset.add("a", 3); + multiset.add("b", 1); + multiset.add("c", 2); + + List actualValues = new ArrayList<>(); + multiset.spliterator().forEachRemaining(actualValues::add); + assertThat(multiset).containsExactly("a", "a", "a", "b", "c", "c").inOrder(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/CollectionBenchmarkSampleData.java b/android/guava-tests/test/com/google/common/collect/CollectionBenchmarkSampleData.java index cdc7a91cc1e6..47671aa92701 100644 --- a/android/guava-tests/test/com/google/common/collect/CollectionBenchmarkSampleData.java +++ b/android/guava-tests/test/com/google/common/collect/CollectionBenchmarkSampleData.java @@ -17,17 +17,20 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Collections.shuffle; -import com.google.common.primitives.Ints; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Package up sample data for common collections benchmarking. * * @author Nicholaus Shupe */ +@NullUnmarked class CollectionBenchmarkSampleData { private final boolean isUserTypeFast; private final SpecialRandom random; @@ -74,8 +77,8 @@ private Element[] createQueries(Set elementsInSet, int numQueries) { for (int i = 0; i < minCopiesOfEachGoodQuery; i++) { queryList.addAll(elementsInSet); } - List tmp = Lists.newArrayList(elementsInSet); - Collections.shuffle(tmp, random); + List tmp = new ArrayList<>(elementsInSet); + shuffle(tmp, random); queryList.addAll(tmp.subList(0, extras)); } @@ -86,7 +89,7 @@ private Element[] createQueries(Set elementsInSet, int numQueries) { queryList.add(candidate); } } - Collections.shuffle(queryList, random); + shuffle(queryList, random); return queryList.toArray(new Element[0]); } @@ -111,7 +114,7 @@ static class Element implements Comparable { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return this == obj || (obj instanceof Element && ((Element) obj).hash == hash); } @@ -122,7 +125,7 @@ public int hashCode() { @Override public int compareTo(Element that) { - return Ints.compare(hash, that.hash); + return Integer.compare(hash, that.hash); } @Override @@ -137,7 +140,7 @@ static class SlowElement extends Element { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return slowItDown() != 1 && super.equals(obj); } diff --git a/android/guava-tests/test/com/google/common/collect/Collections2FilterArrayListTest.java b/android/guava-tests/test/com/google/common/collect/Collections2FilterArrayListTest.java new file mode 100644 index 000000000000..4c3ef4b26098 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/Collections2FilterArrayListTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Predicate; +import com.google.common.collect.FilteredCollectionsTestUtil.AbstractFilteredCollectionTest; +import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; + +@NullUnmarked +public final class Collections2FilterArrayListTest + extends AbstractFilteredCollectionTest> { + @Override + Collection createUnfiltered(Iterable contents) { + return Lists.newArrayList(contents); + } + + @Override + Collection filter(Collection elements, Predicate predicate) { + return Collections2.filter(elements, predicate); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/Collections2Test.java b/android/guava-tests/test/com/google/common/collect/Collections2Test.java index 4b8761893013..4f93a718b255 100644 --- a/android/guava-tests/test/com/google/common/collect/Collections2Test.java +++ b/android/guava-tests/test/com/google/common/collect/Collections2Test.java @@ -16,30 +16,34 @@ package com.google.common.collect; +import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Lists.newLinkedList; import static com.google.common.truth.Truth.assertThat; -import static java.util.Arrays.asList; import static java.util.Collections.nCopies; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Function; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Predicate; import com.google.common.collect.testing.CollectionTestSuiteBuilder; import com.google.common.collect.testing.TestStringCollectionGenerator; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.testing.NullPointerTester; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; +import java.util.Objects; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Collections2}. @@ -47,9 +51,12 @@ * @author Chris Povirk * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class Collections2Test extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(Collections2Test.class.getSimpleName()); suite.addTest(testsForFilter()); @@ -62,37 +69,20 @@ public static Test suite() { return suite; } - static final Predicate NOT_YYY_ZZZ = - new Predicate() { - @Override - public boolean apply(String input) { - return !"yyy".equals(input) && !"zzz".equals(input); - } - }; - - static final Predicate LENGTH_1 = - new Predicate() { - @Override - public boolean apply(String input) { - return input.length() == 1; - } - }; - - static final Predicate STARTS_WITH_VOWEL = - new Predicate() { - @Override - public boolean apply(String input) { - return asList('a', 'e', 'i', 'o', 'u').contains(input.charAt(0)); - } - }; + static final Predicate<@Nullable String> NOT_YYY_ZZZ = + input -> !Objects.equals(input, "yyy") && !Objects.equals(input, "zzz"); + static final Predicate LENGTH_1 = input -> input.length() == 1; + + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForFilter() { return CollectionTestSuiteBuilder.using( new TestStringCollectionGenerator() { @Override public Collection create(String[] elements) { - List unfiltered = newArrayList(); + List unfiltered = new ArrayList<>(); unfiltered.add("yyy"); Collections.addAll(unfiltered, elements); unfiltered.add("zzz"); @@ -109,13 +99,15 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForFilterAll() { return CollectionTestSuiteBuilder.using( new TestStringCollectionGenerator() { @Override public Collection create(String[] elements) { - List unfiltered = newArrayList(); + List unfiltered = new ArrayList<>(); Collections.addAll(unfiltered, elements); return Collections2.filter(unfiltered, NOT_YYY_ZZZ); } @@ -130,13 +122,15 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForFilterLinkedList() { return CollectionTestSuiteBuilder.using( new TestStringCollectionGenerator() { @Override public Collection create(String[] elements) { - List unfiltered = newLinkedList(); + List unfiltered = new LinkedList<>(); unfiltered.add("yyy"); Collections.addAll(unfiltered, elements); unfiltered.add("zzz"); @@ -153,13 +147,15 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForFilterNoNulls() { return CollectionTestSuiteBuilder.using( new TestStringCollectionGenerator() { @Override public Collection create(String[] elements) { - List unfiltered = newArrayList(); + List unfiltered = new ArrayList<>(); unfiltered.add("yyy"); unfiltered.addAll(ImmutableList.copyOf(elements)); unfiltered.add("zzz"); @@ -176,13 +172,15 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForFilterFiltered() { return CollectionTestSuiteBuilder.using( new TestStringCollectionGenerator() { @Override public Collection create(String[] elements) { - List unfiltered = newArrayList(); + List unfiltered = new ArrayList<>(); unfiltered.add("yyy"); unfiltered.addAll(ImmutableList.copyOf(elements)); unfiltered.add("zzz"); @@ -200,25 +198,20 @@ public Collection create(String[] elements) { .createTestSuite(); } - private static final Function REMOVE_FIRST_CHAR = - new Function() { - @Override - public String apply(String from) { - return ((from == null) || "".equals(from)) ? null : from.substring(1); - } - }; - + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForTransform() { return CollectionTestSuiteBuilder.using( new TestStringCollectionGenerator() { @Override - public Collection create(String[] elements) { - List list = newArrayList(); + public Collection<@Nullable String> create(@Nullable String[] elements) { + List<@Nullable String> list = new ArrayList<>(); for (String element : elements) { list.add((element == null) ? null : "q" + element); } - return Collections2.transform(list, REMOVE_FIRST_CHAR); + return Collections2.transform( + list, from -> isNullOrEmpty(from) ? null : from.substring(1)); } }) .named("Collections2.transform") @@ -230,6 +223,7 @@ public Collection create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -237,7 +231,7 @@ public void testNullPointerExceptions() { } public void testOrderedPermutationSetEmpty() { - List list = newArrayList(); + List list = new ArrayList<>(); Collection> permutationSet = Collections2.orderedPermutations(list); assertEquals(1, permutationSet.size()); @@ -245,7 +239,7 @@ public void testOrderedPermutationSetEmpty() { Iterator> permutations = permutationSet.iterator(); - assertNextPermutation(Lists.newArrayList(), permutations); + assertNextPermutation(new ArrayList<>(), permutations); assertNoMorePermutations(permutations); } @@ -498,7 +492,7 @@ private void assertPermutationsCount(int expected, Collection> permu } public void testToStringImplWithNullEntries() throws Exception { - List list = Lists.newArrayList(); + List<@Nullable String> list = new ArrayList<>(); list.add("foo"); list.add(null); diff --git a/android/guava-tests/test/com/google/common/collect/CompactHashMapTest.java b/android/guava-tests/test/com/google/common/collect/CompactHashMapTest.java index 52c9bed52b15..ec8601fc52a0 100644 --- a/android/guava-tests/test/com/google/common/collect/CompactHashMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/CompactHashMapTest.java @@ -18,6 +18,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.max; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.TestStringMapGenerator; @@ -29,13 +30,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code CompactHashMap}. * * @author Louis Wasserman */ +@NullUnmarked public class CompactHashMapTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -111,7 +115,7 @@ public void testAllocArraysExpectedSize() { map.put(1, "1"); assertThat(map.needsAllocArrays()).isFalse(); - int expectedSize = Math.max(1, i); + int expectedSize = max(1, i); assertThat(map.entries).hasLength(expectedSize); assertThat(map.keys).hasLength(expectedSize); assertThat(map.values).hasLength(expectedSize); diff --git a/android/guava-tests/test/com/google/common/collect/CompactHashSetTest.java b/android/guava-tests/test/com/google/common/collect/CompactHashSetTest.java index 0f0216f3b77d..e472c4039357 100644 --- a/android/guava-tests/test/com/google/common/collect/CompactHashSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/CompactHashSetTest.java @@ -17,7 +17,8 @@ package com.google.common.collect; import static com.google.common.truth.Truth.assertThat; -import static java.util.stream.Collectors.*; +import static java.lang.Math.max; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.SetTestSuiteBuilder; @@ -31,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for CompactHashSet. @@ -38,7 +40,9 @@ * @author Dimitris Andreou */ @GwtIncompatible // java.util.Arrays#copyOf(Object[], int), java.lang.reflect.Array +@NullUnmarked public class CompactHashSetTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { List> allFeatures = Arrays.>asList( @@ -58,7 +62,7 @@ public static Test suite() { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return CompactHashSet.create(Arrays.asList(elements)); + return CompactHashSet.create(asList(elements)); } }) .named("CompactHashSet") @@ -69,12 +73,12 @@ protected Set create(String[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - CompactHashSet set = CompactHashSet.create(Arrays.asList(elements)); + CompactHashSet set = CompactHashSet.create(asList(elements)); for (int i = 0; i < 100; i++) { - set.add(i); + set.add("extra" + i); } for (int i = 0; i < 100; i++) { - set.remove(i); + set.remove("extra" + i); } set.trimToSize(); return set; @@ -104,7 +108,7 @@ public void testAllocArraysExpectedSize() { set.add(1); assertThat(set.needsAllocArrays()).isFalse(); - int expectedSize = Math.max(1, i); + int expectedSize = max(1, i); assertThat(set.elements).hasLength(expectedSize); } } diff --git a/android/guava-tests/test/com/google/common/collect/CompactLinkedHashMapTest.java b/android/guava-tests/test/com/google/common/collect/CompactLinkedHashMapTest.java index d1363bcda669..927f8f7450b4 100644 --- a/android/guava-tests/test/com/google/common/collect/CompactLinkedHashMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/CompactLinkedHashMapTest.java @@ -14,26 +14,32 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.max; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.TestStringMapGenerator; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code CompactLinkedHashMap}. * * @author Louis Wasserman */ +@NullUnmarked public class CompactLinkedHashMapTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -127,16 +133,16 @@ public void testTrimToSize() { } private void testHasMapEntriesInOrder(Map map, Object... alternatingKeysAndValues) { - List> entries = Lists.newArrayList(map.entrySet()); - List keys = Lists.newArrayList(map.keySet()); - List values = Lists.newArrayList(map.values()); - assertEquals(2 * entries.size(), alternatingKeysAndValues.length); - assertEquals(2 * keys.size(), alternatingKeysAndValues.length); - assertEquals(2 * values.size(), alternatingKeysAndValues.length); + List> entries = new ArrayList<>(map.entrySet()); + List keys = new ArrayList<>(map.keySet()); + List values = new ArrayList<>(map.values()); + assertThat(alternatingKeysAndValues).hasLength(2 * entries.size()); + assertThat(alternatingKeysAndValues).hasLength(2 * keys.size()); + assertThat(alternatingKeysAndValues).hasLength(2 * values.size()); for (int i = 0; i < map.size(); i++) { Object expectedKey = alternatingKeysAndValues[2 * i]; Object expectedValue = alternatingKeysAndValues[2 * i + 1]; - Entry expectedEntry = Maps.immutableEntry(expectedKey, expectedValue); + Entry expectedEntry = immutableEntry(expectedKey, expectedValue); assertEquals(expectedEntry, entries.get(i)); assertEquals(expectedKey, keys.get(i)); assertEquals(expectedValue, values.get(i)); @@ -170,7 +176,7 @@ public void testAllocArraysExpectedSize() { map.put(1, Integer.toString(1)); assertThat(map.needsAllocArrays()).isFalse(); - int expectedSize = Math.max(1, i); + int expectedSize = max(1, i); assertThat(map.entries).hasLength(expectedSize); assertThat(map.keys).hasLength(expectedSize); assertThat(map.values).hasLength(expectedSize); diff --git a/android/guava-tests/test/com/google/common/collect/CompactLinkedHashSetTest.java b/android/guava-tests/test/com/google/common/collect/CompactLinkedHashSetTest.java index 299503e0463e..67853e409484 100644 --- a/android/guava-tests/test/com/google/common/collect/CompactLinkedHashSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/CompactLinkedHashSetTest.java @@ -17,6 +17,8 @@ package com.google.common.collect; import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.max; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.SetTestSuiteBuilder; @@ -30,6 +32,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for CompactLinkedHashSet. @@ -37,7 +40,9 @@ * @author Dimitris Andreou */ @GwtIncompatible // java.util.Arrays#copyOf(Object[], int), java.lang.reflect.Array +@NullUnmarked public class CompactLinkedHashSetTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { List> allFeatures = Arrays.>asList( @@ -58,7 +63,7 @@ public static Test suite() { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return CompactLinkedHashSet.create(Arrays.asList(elements)); + return CompactLinkedHashSet.create(asList(elements)); } }) .named("CompactLinkedHashSet") @@ -85,7 +90,7 @@ public void testAllocArraysExpectedSize() { set.add(1); assertThat(set.needsAllocArrays()).isFalse(); - int expectedSize = Math.max(1, i); + int expectedSize = max(1, i); assertThat(set.elements).hasLength(expectedSize); } } diff --git a/android/guava-tests/test/com/google/common/collect/ComparatorsTest.java b/android/guava-tests/test/com/google/common/collect/ComparatorsTest.java index b30cb7646d43..6d557e47692d 100644 --- a/android/guava-tests/test/com/google/common/collect/ComparatorsTest.java +++ b/android/guava-tests/test/com/google/common/collect/ComparatorsTest.java @@ -16,15 +16,28 @@ package com.google.common.collect; +import static com.google.common.collect.Comparators.emptiesFirst; +import static com.google.common.collect.Comparators.emptiesLast; +import static com.google.common.collect.Comparators.isInOrder; +import static com.google.common.collect.Comparators.isInStrictOrder; +import static com.google.common.collect.Comparators.max; +import static com.google.common.collect.Comparators.min; +import static com.google.common.collect.testing.Helpers.testComparator; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static java.util.Comparator.reverseOrder; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.testing.Helpers; import com.google.common.testing.EqualsTester; import java.util.Collections; import java.util.Comparator; +import java.util.Optional; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code Comparators}. @@ -32,8 +45,8 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public class ComparatorsTest extends TestCase { - @SuppressWarnings("unchecked") // dang varargs public void testLexicographical() { Comparator comparator = Ordering.natural(); Comparator> lexy = Comparators.lexicographical(comparator); @@ -44,7 +57,7 @@ public void testLexicographical() { ImmutableList ab = ImmutableList.of("a", "b"); ImmutableList b = ImmutableList.of("b"); - Helpers.testComparator(lexy, empty, a, aa, ab, b); + testComparator(lexy, empty, a, aa, ab, b); new EqualsTester() .addEqualityGroup(lexy, Comparators.lexicographical(comparator)) @@ -54,46 +67,85 @@ public void testLexicographical() { } public void testIsInOrder() { - assertFalse(Comparators.isInOrder(asList(5, 3, 0, 9), Ordering.natural())); - assertFalse(Comparators.isInOrder(asList(0, 5, 3, 9), Ordering.natural())); - assertTrue(Comparators.isInOrder(asList(0, 3, 5, 9), Ordering.natural())); - assertTrue(Comparators.isInOrder(asList(0, 0, 3, 3), Ordering.natural())); - assertTrue(Comparators.isInOrder(asList(0, 3), Ordering.natural())); - assertTrue(Comparators.isInOrder(Collections.singleton(1), Ordering.natural())); - assertTrue(Comparators.isInOrder(Collections.emptyList(), Ordering.natural())); + assertFalse(isInOrder(asList(5, 3, 0, 9), Ordering.natural())); + assertFalse(isInOrder(asList(0, 5, 3, 9), Ordering.natural())); + assertTrue(isInOrder(asList(0, 3, 5, 9), Ordering.natural())); + assertTrue(isInOrder(asList(0, 0, 3, 3), Ordering.natural())); + assertTrue(isInOrder(asList(0, 3), Ordering.natural())); + assertTrue(isInOrder(singleton(1), Ordering.natural())); + assertTrue(isInOrder(ImmutableList.of(), Ordering.natural())); } public void testIsInStrictOrder() { - assertFalse(Comparators.isInStrictOrder(asList(5, 3, 0, 9), Ordering.natural())); - assertFalse(Comparators.isInStrictOrder(asList(0, 5, 3, 9), Ordering.natural())); - assertTrue(Comparators.isInStrictOrder(asList(0, 3, 5, 9), Ordering.natural())); - assertFalse(Comparators.isInStrictOrder(asList(0, 0, 3, 3), Ordering.natural())); - assertTrue(Comparators.isInStrictOrder(asList(0, 3), Ordering.natural())); - assertTrue(Comparators.isInStrictOrder(Collections.singleton(1), Ordering.natural())); - assertTrue(Comparators.isInStrictOrder(Collections.emptyList(), Ordering.natural())); + assertFalse(isInStrictOrder(asList(5, 3, 0, 9), Ordering.natural())); + assertFalse(isInStrictOrder(asList(0, 5, 3, 9), Ordering.natural())); + assertTrue(isInStrictOrder(asList(0, 3, 5, 9), Ordering.natural())); + assertFalse(isInStrictOrder(asList(0, 0, 3, 3), Ordering.natural())); + assertTrue(isInStrictOrder(asList(0, 3), Ordering.natural())); + assertTrue(isInStrictOrder(singleton(1), Ordering.natural())); + assertTrue(isInStrictOrder(ImmutableList.of(), Ordering.natural())); + } + + public void testEmptiesFirst() { + Optional empty = Optional.empty(); + Optional abc = Optional.of("abc"); + Optional z = Optional.of("z"); + + Comparator> comparator = emptiesFirst(comparing(String::length)); + testComparator(comparator, empty, z, abc); + + // Just demonstrate that no explicit type parameter is required + Comparator> unused = emptiesFirst(naturalOrder()); + } + + public void testEmptiesLast() { + Optional empty = Optional.empty(); + Optional abc = Optional.of("abc"); + Optional z = Optional.of("z"); + + Comparator> comparator = emptiesLast(comparing(String::length)); + testComparator(comparator, z, abc, empty); + + // Just demonstrate that no explicit type parameter is required + Comparator> unused = emptiesLast(naturalOrder()); } public void testMinMaxNatural() { - assertThat(Comparators.min(1, 2)).isEqualTo(1); - assertThat(Comparators.min(2, 1)).isEqualTo(1); - assertThat(Comparators.max(1, 2)).isEqualTo(2); - assertThat(Comparators.max(2, 1)).isEqualTo(2); + assertThat(min(1, 2)).isEqualTo(1); + assertThat(min(2, 1)).isEqualTo(1); + assertThat(max(1, 2)).isEqualTo(2); + assertThat(max(2, 1)).isEqualTo(2); } public void testMinMaxNatural_equalInstances() { Foo a = new Foo(1); Foo b = new Foo(1); - assertThat(Comparators.min(a, b)).isSameInstanceAs(a); - assertThat(Comparators.max(a, b)).isSameInstanceAs(a); + assertThat(min(a, b)).isSameInstanceAs(a); + assertThat(max(a, b)).isSameInstanceAs(a); } public void testMinMaxComparator() { - Comparator natural = Ordering.natural(); - Comparator reverse = Collections.reverseOrder(natural); - assertThat(Comparators.min(1, 2, reverse)).isEqualTo(2); - assertThat(Comparators.min(2, 1, reverse)).isEqualTo(2); - assertThat(Comparators.max(1, 2, reverse)).isEqualTo(1); - assertThat(Comparators.max(2, 1, reverse)).isEqualTo(1); + Comparator reverse = reverseOrder(); + assertThat(min(1, 2, reverse)).isEqualTo(2); + assertThat(min(2, 1, reverse)).isEqualTo(2); + assertThat(max(1, 2, reverse)).isEqualTo(1); + assertThat(max(2, 1, reverse)).isEqualTo(1); + } + + /** + * Fails compilation if the signature of min and max is changed to take {@code Comparator} + * instead of {@code Comparator}. + */ + public void testMinMaxWithSupertypeComparator() { + Comparator numberComparator = comparing(Number::intValue); + Integer comparand1 = 1; + Integer comparand2 = 2; + + Integer min = min(comparand1, comparand2, numberComparator); + Integer max = max(comparand1, comparand2, numberComparator); + + assertThat(min).isEqualTo(1); + assertThat(max).isEqualTo(2); } public void testMinMaxComparator_equalInstances() { @@ -101,8 +153,8 @@ public void testMinMaxComparator_equalInstances() { Comparator reverse = Collections.reverseOrder(natural); Foo a = new Foo(1); Foo b = new Foo(1); - assertThat(Comparators.min(a, b, reverse)).isSameInstanceAs(a); - assertThat(Comparators.max(a, b, reverse)).isSameInstanceAs(a); + assertThat(min(a, b, reverse)).isSameInstanceAs(a); + assertThat(max(a, b, reverse)).isSameInstanceAs(a); } private static class Foo implements Comparable { @@ -118,7 +170,7 @@ public int hashCode() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof Foo) && ((Foo) o).value.equals(value); } diff --git a/android/guava-tests/test/com/google/common/collect/ComparisonChainTest.java b/android/guava-tests/test/com/google/common/collect/ComparisonChainTest.java index 9a9f98a8cb68..e5f495528734 100644 --- a/android/guava-tests/test/com/google/common/collect/ComparisonChainTest.java +++ b/android/guava-tests/test/com/google/common/collect/ComparisonChainTest.java @@ -16,9 +16,12 @@ package com.google.common.collect; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link ComparisonChain}. @@ -26,6 +29,7 @@ * @author Kevin Bourrillion */ @GwtCompatible +@NullMarked public class ComparisonChainTest extends TestCase { private static final DontCompareMe DONT_COMPARE_ME = new DontCompareMe(); @@ -36,77 +40,84 @@ public int compareTo(DontCompareMe o) { } } + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testCompareBooleans() { - assertEquals( - 0, - ComparisonChain.start() - .compare(true, true) - .compare(true, Boolean.TRUE) - .compare(Boolean.TRUE, true) - .compare(Boolean.TRUE, Boolean.TRUE) - .result()); + assertThat( + ComparisonChain.start() + .compare(true, true) + .compare(true, Boolean.TRUE) + .compare(Boolean.TRUE, true) + .compare(Boolean.TRUE, Boolean.TRUE) + .result()) + .isEqualTo(0); } public void testDegenerate() { // kinda bogus, but who cares? - assertEquals(0, ComparisonChain.start().result()); + assertThat(ComparisonChain.start().result()).isEqualTo(0); } public void testOneEqual() { - assertEquals(0, ComparisonChain.start().compare("a", "a").result()); + assertThat(ComparisonChain.start().compare("a", "a").result()).isEqualTo(0); } public void testOneEqualUsingComparator() { - assertEquals( - 0, ComparisonChain.start().compare("a", "A", String.CASE_INSENSITIVE_ORDER).result()); + assertThat(ComparisonChain.start().compare("a", "A", String.CASE_INSENSITIVE_ORDER).result()) + .isEqualTo(0); } public void testManyEqual() { - assertEquals( - 0, - ComparisonChain.start() - .compare(1, 1) - .compare(1L, 1L) - .compareFalseFirst(true, true) - .compare(1.0, 1.0) - .compare(1.0f, 1.0f) - .compare("a", "a", Ordering.usingToString()) - .result()); + assertThat( + ComparisonChain.start() + .compare(1, 1) + .compare(1L, 1L) + .compareFalseFirst(true, true) + .compare(1.0, 1.0) + .compare(1.0f, 1.0f) + .compare("a", "a", Ordering.usingToString()) + .result()) + .isEqualTo(0); } public void testShortCircuitLess() { - assertTrue( - ComparisonChain.start().compare("a", "b").compare(DONT_COMPARE_ME, DONT_COMPARE_ME).result() - < 0); + assertThat( + ComparisonChain.start() + .compare("a", "b") + .compare(DONT_COMPARE_ME, DONT_COMPARE_ME) + .result()) + .isLessThan(0); } public void testShortCircuitGreater() { - assertTrue( - ComparisonChain.start().compare("b", "a").compare(DONT_COMPARE_ME, DONT_COMPARE_ME).result() - > 0); + assertThat( + ComparisonChain.start() + .compare("b", "a") + .compare(DONT_COMPARE_ME, DONT_COMPARE_ME) + .result()) + .isGreaterThan(0); } public void testShortCircuitSecondStep() { - assertTrue( - ComparisonChain.start() + assertThat( + ComparisonChain.start() .compare("a", "a") .compare("a", "b") .compare(DONT_COMPARE_ME, DONT_COMPARE_ME) - .result() - < 0); + .result()) + .isLessThan(0); } public void testCompareFalseFirst() { - assertTrue(ComparisonChain.start().compareFalseFirst(true, true).result() == 0); - assertTrue(ComparisonChain.start().compareFalseFirst(true, false).result() > 0); - assertTrue(ComparisonChain.start().compareFalseFirst(false, true).result() < 0); - assertTrue(ComparisonChain.start().compareFalseFirst(false, false).result() == 0); + assertThat(ComparisonChain.start().compareFalseFirst(true, true).result()).isEqualTo(0); + assertThat(ComparisonChain.start().compareFalseFirst(true, false).result()).isGreaterThan(0); + assertThat(ComparisonChain.start().compareFalseFirst(false, true).result()).isLessThan(0); + assertThat(ComparisonChain.start().compareFalseFirst(false, false).result()).isEqualTo(0); } public void testCompareTrueFirst() { - assertTrue(ComparisonChain.start().compareTrueFirst(true, true).result() == 0); - assertTrue(ComparisonChain.start().compareTrueFirst(true, false).result() < 0); - assertTrue(ComparisonChain.start().compareTrueFirst(false, true).result() > 0); - assertTrue(ComparisonChain.start().compareTrueFirst(false, false).result() == 0); + assertThat(ComparisonChain.start().compareTrueFirst(true, true).result()).isEqualTo(0); + assertThat(ComparisonChain.start().compareTrueFirst(true, false).result()).isLessThan(0); + assertThat(ComparisonChain.start().compareTrueFirst(false, true).result()).isGreaterThan(0); + assertThat(ComparisonChain.start().compareTrueFirst(false, false).result()).isEqualTo(0); } } diff --git a/android/guava-tests/test/com/google/common/collect/ConcurrentHashMultisetBasherTest.java b/android/guava-tests/test/com/google/common/collect/ConcurrentHashMultisetBasherTest.java index ddc2bf1c704b..325aa4d7b14f 100644 --- a/android/guava-tests/test/com/google/common/collect/ConcurrentHashMultisetBasherTest.java +++ b/android/guava-tests/test/com/google/common/collect/ConcurrentHashMultisetBasherTest.java @@ -16,6 +16,11 @@ package com.google.common.collect; +import static com.google.common.collect.Lists.newArrayListWithExpectedSize; +import static com.google.common.collect.Lists.transform; +import static java.lang.Math.min; +import static java.util.concurrent.Executors.newFixedThreadPool; + import com.google.common.base.Function; import com.google.common.primitives.Ints; import java.util.List; @@ -26,10 +31,10 @@ import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Basher test for {@link ConcurrentHashMultiset}: start a bunch of threads, have each of them do @@ -40,17 +45,18 @@ * @author mike nonemacher */ +@NullUnmarked public class ConcurrentHashMultisetBasherTest extends TestCase { - public void testAddAndRemove_ConcurrentHashMap() throws Exception { + public void testAddAndRemove_concurrentHashMap() throws Exception { testAddAndRemove(new ConcurrentHashMap()); } - public void testAddAndRemove_ConcurrentSkipListMap() throws Exception { + public void testAddAndRemove_concurrentSkipListMap() throws Exception { testAddAndRemove(new ConcurrentSkipListMap()); } - public void testAddAndRemove_MapMakerMap() throws Exception { + public void testAddAndRemove_mapMakerMap() throws Exception { MapMaker mapMaker = new MapMaker(); // force MapMaker to use its own MapMakerInternalMap mapMaker.useCustomMap = true; @@ -60,14 +66,14 @@ public void testAddAndRemove_MapMakerMap() throws Exception { private void testAddAndRemove(ConcurrentMap map) throws ExecutionException, InterruptedException { - final ConcurrentHashMultiset multiset = new ConcurrentHashMultiset<>(map); + ConcurrentHashMultiset multiset = new ConcurrentHashMultiset<>(map); int nThreads = 20; int tasksPerThread = 10; int nTasks = nThreads * tasksPerThread; - ExecutorService pool = Executors.newFixedThreadPool(nThreads); + ExecutorService pool = newFixedThreadPool(nThreads); ImmutableList keys = ImmutableList.of("a", "b", "c"); try { - List> futures = Lists.newArrayListWithExpectedSize(nTasks); + List> futures = newArrayListWithExpectedSize(nTasks); for (int i = 0; i < nTasks; i++) { futures.add(pool.submit(new MutateTask(multiset, keys))); } @@ -81,7 +87,7 @@ private void testAddAndRemove(ConcurrentMap map) } List actualCounts = - Lists.transform( + transform( keys, new Function() { @Override @@ -132,7 +138,7 @@ public int[] call() throws Exception { { int newValue = random.nextInt(3); int oldValue = multiset.setCount(key, newValue); - deltas[keyIndex] += (newValue - oldValue); + deltas[keyIndex] += newValue - oldValue; break; } case SET_COUNT_IF: @@ -140,7 +146,7 @@ public int[] call() throws Exception { int newValue = random.nextInt(3); int oldValue = multiset.count(key); if (multiset.setCount(key, oldValue, newValue)) { - deltas[keyIndex] += (newValue - oldValue); + deltas[keyIndex] += newValue - oldValue; } break; } @@ -148,7 +154,7 @@ public int[] call() throws Exception { { int delta = random.nextInt(6); // [0, 5] int oldValue = multiset.remove(key, delta); - deltas[keyIndex] -= Math.min(delta, oldValue); + deltas[keyIndex] -= min(delta, oldValue); break; } case REMOVE_EXACTLY: diff --git a/android/guava-tests/test/com/google/common/collect/ConcurrentHashMultisetTest.java b/android/guava-tests/test/com/google/common/collect/ConcurrentHashMultisetTest.java index 1d38c8600e55..fe00fc868560 100644 --- a/android/guava-tests/test/com/google/common/collect/ConcurrentHashMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ConcurrentHashMultisetTest.java @@ -20,6 +20,7 @@ import static com.google.common.collect.MapMakerInternalMap.Strength.WEAK; import static com.google.common.testing.SerializableTester.reserializeAndAssert; import static java.util.Arrays.asList; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -39,6 +40,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test case for {@link ConcurrentHashMultiset}. @@ -46,8 +48,10 @@ * @author Cliff L. Biffle * @author mike nonemacher */ +@NullUnmarked public class ConcurrentHashMultisetTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -73,6 +77,7 @@ public static Test suite() { return suite; } + @AndroidIncompatible // test-suite builders private static TestStringMultisetGenerator concurrentHashMultisetGenerator() { return new TestStringMultisetGenerator() { @Override @@ -82,6 +87,7 @@ protected Multiset create(String[] elements) { }; } + @AndroidIncompatible // test-suite builders private static TestStringMultisetGenerator concurrentSkipListMultisetGenerator() { return new TestStringMultisetGenerator() { @Override @@ -114,7 +120,7 @@ protected void setUp() { } public void testCount_elementPresent() { - final int COUNT = 12; + int COUNT = 12; when(backingMap.get(KEY)).thenReturn(new AtomicInteger(COUNT)); assertEquals(COUNT, multiset.count(KEY)); @@ -127,14 +133,14 @@ public void testCount_elementAbsent() { } public void testAdd_zero() { - final int INITIAL_COUNT = 32; + int INITIAL_COUNT = 32; when(backingMap.get(KEY)).thenReturn(new AtomicInteger(INITIAL_COUNT)); assertEquals(INITIAL_COUNT, multiset.add(KEY, 0)); } public void testAdd_firstFewWithSuccess() { - final int COUNT = 400; + int COUNT = 400; when(backingMap.get(KEY)).thenReturn(null); when(backingMap.putIfAbsent(eq(KEY), isA(AtomicInteger.class))).thenReturn(null); @@ -154,16 +160,12 @@ public void testAdd_laterFewWithSuccess() { } public void testAdd_laterFewWithOverflow() { - final int INITIAL_COUNT = 92384930; - final int COUNT_TO_ADD = Integer.MAX_VALUE - INITIAL_COUNT + 1; + int INITIAL_COUNT = 92384930; + int COUNT_TO_ADD = Integer.MAX_VALUE - INITIAL_COUNT + 1; when(backingMap.get(KEY)).thenReturn(new AtomicInteger(INITIAL_COUNT)); - try { - multiset.add(KEY, COUNT_TO_ADD); - fail("Must reject arguments that would cause counter overflow."); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> multiset.add(KEY, COUNT_TO_ADD)); } /** @@ -200,7 +202,7 @@ public void testAdd_withFailures() { } public void testRemove_zeroFromSome() { - final int INITIAL_COUNT = 14; + int INITIAL_COUNT = 14; when(backingMap.get(KEY)).thenReturn(new AtomicInteger(INITIAL_COUNT)); assertEquals(INITIAL_COUNT, multiset.remove(KEY, 0)); @@ -246,11 +248,7 @@ public void testRemoveExactly() { cms.add("a", 2); cms.add("b", 3); - try { - cms.removeExactly("a", -2); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> cms.removeExactly("a", -2)); assertTrue(cms.removeExactly("a", 0)); assertEquals(2, cms.count("a")); diff --git a/android/guava-tests/test/com/google/common/collect/ContiguousSetTest.java b/android/guava-tests/test/com/google/common/collect/ContiguousSetTest.java index 6b19b1edcd2d..9d226a3ee076 100644 --- a/android/guava-tests/test/com/google/common/collect/ContiguousSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ContiguousSetTest.java @@ -19,6 +19,7 @@ import static com.google.common.collect.BoundType.CLOSED; import static com.google.common.collect.BoundType.OPEN; import static com.google.common.collect.DiscreteDomain.integers; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_QUERIES; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionFeature.NON_STANDARD_TOSTRING; @@ -30,6 +31,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.NavigableSetTestSuiteBuilder; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.google.SetGenerators.ContiguousSetDescendingGenerator; @@ -43,9 +45,13 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; -/** @author Gregory Kick */ -@GwtCompatible(emulated = true) +/** + * @author Gregory Kick + */ +@GwtCompatible +@NullUnmarked public class ContiguousSetTest extends TestCase { private static final DiscreteDomain NOT_EQUAL_TO_INTEGERS = new DiscreteDomain() { @@ -76,29 +82,13 @@ public Integer maxValue() { }; public void testInvalidIntRange() { - try { - ContiguousSet.closed(2, 1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - ContiguousSet.closedOpen(2, 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> ContiguousSet.closed(2, 1)); + assertThrows(IllegalArgumentException.class, () -> ContiguousSet.closedOpen(2, 1)); } public void testInvalidLongRange() { - try { - ContiguousSet.closed(2L, 1L); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - ContiguousSet.closedOpen(2L, 1L); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> ContiguousSet.closed(2L, 1L)); + assertThrows(IllegalArgumentException.class, () -> ContiguousSet.closedOpen(2L, 1L)); } public void testEquals() { @@ -155,22 +145,36 @@ public void testSerialization() { assertEquals(enormous, enormousReserialized); } + private static final DiscreteDomain UNBOUNDED_THROWING_DOMAIN = + new DiscreteDomain() { + @Override + public Integer next(Integer value) { + throw new AssertionError(); + } + + @Override + public Integer previous(Integer value) { + throw new AssertionError(); + } + + @Override + public long distance(Integer start, Integer end) { + throw new AssertionError(); + } + }; + public void testCreate_noMin() { Range range = Range.lessThan(0); - try { - ContiguousSet.create(range, RangeTest.UNBOUNDED_DOMAIN); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> ContiguousSet.create(range, UNBOUNDED_THROWING_DOMAIN)); } public void testCreate_noMax() { Range range = Range.greaterThan(0); - try { - ContiguousSet.create(range, RangeTest.UNBOUNDED_DOMAIN); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> ContiguousSet.create(range, UNBOUNDED_THROWING_DOMAIN)); } public void testCreate_empty() { @@ -236,11 +240,7 @@ public void testSubSet() { public void testSubSet_outOfOrder() { ImmutableSortedSet set = ContiguousSet.create(Range.closed(1, 3), integers()); - try { - set.subSet(3, 2); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> set.subSet(3, 2)); } public void testSubSet_tooLarge() { @@ -280,6 +280,12 @@ public void testContains() { assertTrue(set.contains(2)); assertTrue(set.contains(3)); assertFalse(set.contains(4)); + } + + // TODO: https://youtrack.jetbrains.com/issue/KT-71001/ - Enable when Kotlin throws expected CCE. + @J2ktIncompatible + public void testContains_typeMismatch() { + ImmutableSortedSet set = ContiguousSet.create(Range.open(0, 4), integers()); assertFalse(set.contains((Object) "blah")); } @@ -291,6 +297,12 @@ public void testContainsAll() { for (Set subset : Sets.powerSet(ImmutableSet.of(1, 2, 3))) { assertFalse(set.containsAll(Sets.union(subset, ImmutableSet.of(9)))); } + } + + // TODO: https://youtrack.jetbrains.com/issue/KT-71001/ - Enable when Kotlin throws expected CCE. + @J2ktIncompatible + public void testContainsAll_typeMismatch() { + ImmutableSortedSet set = ContiguousSet.create(Range.closed(1, 3), integers()); assertFalse(set.containsAll((Collection) ImmutableSet.of("blah"))); } @@ -388,7 +400,9 @@ public void testAsList() { assertEquals(ImmutableList.of(1, 2, 3), ImmutableList.copyOf(list.toArray(new Integer[0]))); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static class BuiltTests extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); diff --git a/android/guava-tests/test/com/google/common/collect/CountTest.java b/android/guava-tests/test/com/google/common/collect/CountTest.java index 0eff420a5818..86323cc8f728 100644 --- a/android/guava-tests/test/com/google/common/collect/CountTest.java +++ b/android/guava-tests/test/com/google/common/collect/CountTest.java @@ -16,6 +16,7 @@ import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for {@code Count}. @@ -23,6 +24,7 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public class CountTest extends TestCase { public void testGet() { assertEquals(20, new Count(20).get()); diff --git a/android/guava-tests/test/com/google/common/collect/Derived.java b/android/guava-tests/test/com/google/common/collect/Derived.java new file mode 100644 index 000000000000..046853509f6b --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/Derived.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.NullUnmarked; + +/** Simple derived class to verify that we handle generics correctly. */ +@GwtCompatible +@NullUnmarked +class Derived extends Base { + public Derived(String s) { + super(s); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; +} diff --git a/android/guava-tests/test/com/google/common/collect/DiscreteDomainTest.java b/android/guava-tests/test/com/google/common/collect/DiscreteDomainTest.java index 5de3f8df2be1..c11828ad39dc 100644 --- a/android/guava-tests/test/com/google/common/collect/DiscreteDomainTest.java +++ b/android/guava-tests/test/com/google/common/collect/DiscreteDomainTest.java @@ -17,10 +17,12 @@ package com.google.common.collect; import static com.google.common.testing.SerializableTester.reserializeAndAssert; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; import java.math.BigInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link DiscreteDomain}. @@ -28,6 +30,7 @@ * @author Chris Povirk */ @GwtIncompatible // SerializableTester +@NullUnmarked public class DiscreteDomainTest extends TestCase { public void testSerialization() { reserializeAndAssert(DiscreteDomain.integers()); @@ -43,16 +46,10 @@ public void testIntegersOffset() { } public void testIntegersOffsetExceptions() { - try { - DiscreteDomain.integers().offset(0, -1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - DiscreteDomain.integers().offset(Integer.MAX_VALUE, 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> DiscreteDomain.integers().offset(0, -1)); + assertThrows( + IllegalArgumentException.class, + () -> DiscreteDomain.integers().offset(Integer.MAX_VALUE, 1)); } public void testLongsOffset() { @@ -61,16 +58,9 @@ public void testLongsOffset() { } public void testLongsOffsetExceptions() { - try { - DiscreteDomain.longs().offset(0L, -1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - DiscreteDomain.longs().offset(Long.MAX_VALUE, 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> DiscreteDomain.longs().offset(0L, -1)); + assertThrows( + IllegalArgumentException.class, () -> DiscreteDomain.longs().offset(Long.MAX_VALUE, 1)); } public void testBigIntegersOffset() { @@ -81,10 +71,45 @@ public void testBigIntegersOffset() { } public void testBigIntegersOffsetExceptions() { - try { - DiscreteDomain.bigIntegers().offset(BigInteger.ZERO, -1); - fail(); - } catch (IllegalArgumentException expected) { + assertThrows( + IllegalArgumentException.class, + () -> DiscreteDomain.bigIntegers().offset(BigInteger.ZERO, -1)); + } + + public void testCustomOffsetExceptions() { + assertThrows(IllegalArgumentException.class, () -> new MyIntegerDomain().offset(0, -1)); + assertThrows( + IllegalArgumentException.class, () -> new MyIntegerDomain().offset(Integer.MAX_VALUE, 1)); + } + + private static final class MyIntegerDomain extends DiscreteDomain { + static final DiscreteDomain DELEGATE = DiscreteDomain.integers(); + + @Override + public Integer next(Integer value) { + return DELEGATE.next(value); + } + + @Override + public Integer previous(Integer value) { + return DELEGATE.previous(value); + } + + // Do *not* override offset() to delegate: We want to test the default implementation. + + @Override + public long distance(Integer start, Integer end) { + return DELEGATE.distance(start, end); + } + + @Override + public Integer minValue() { + return DELEGATE.minValue(); + } + + @Override + public Integer maxValue() { + return DELEGATE.maxValue(); } } } diff --git a/android/guava-tests/test/com/google/common/collect/EmptyImmutableTableTest.java b/android/guava-tests/test/com/google/common/collect/EmptyImmutableTableTest.java index 4fb26e65c8fa..a258b2263dff 100644 --- a/android/guava-tests/test/com/google/common/collect/EmptyImmutableTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/EmptyImmutableTableTest.java @@ -16,16 +16,20 @@ package com.google.common.collect; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.testing.EqualsTester; +import org.jspecify.annotations.NullMarked; /** * Tests {@link EmptyImmutableTable} * * @author Gregory Kick */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class EmptyImmutableTableTest extends AbstractImmutableTableTest { private static final ImmutableTable INSTANCE = ImmutableTable.of(); @@ -65,7 +69,7 @@ public void testSize() { } public void testGet() { - assertNull(INSTANCE.get('a', 1)); + assertThat(INSTANCE.get('a', 1)).isNull(); } public void testIsEmpty() { diff --git a/android/guava-tests/test/com/google/common/collect/EnumBiMapTest.java b/android/guava-tests/test/com/google/common/collect/EnumBiMapTest.java index 25b057a72d56..7feeb953f22d 100644 --- a/android/guava-tests/test/com/google/common/collect/EnumBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/EnumBiMapTest.java @@ -16,12 +16,14 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.Helpers.orderEntriesByKey; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -40,6 +42,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Tests for {@code EnumBiMap}. @@ -47,7 +50,9 @@ * @author Mike Bostock * @author Jared Levy */ -@GwtCompatible(emulated = true) +@J2ktIncompatible // EnumBimap +@GwtCompatible +@NullMarked public class EnumBiMapTest extends TestCase { private enum Currency { DOLLAR, @@ -65,6 +70,7 @@ private enum Country { UK } + @AndroidIncompatible // test-suite builders public static final class EnumBiMapGenerator implements TestBiMapGenerator { @SuppressWarnings("unchecked") @Override @@ -80,17 +86,17 @@ public BiMap create(Object... entries) { @Override public SampleElements> samples() { return new SampleElements<>( - Helpers.mapEntry(Country.CANADA, Currency.DOLLAR), - Helpers.mapEntry(Country.CHILE, Currency.PESO), - Helpers.mapEntry(Country.UK, Currency.POUND), - Helpers.mapEntry(Country.JAPAN, Currency.YEN), - Helpers.mapEntry(Country.SWITZERLAND, Currency.FRANC)); + mapEntry(Country.CANADA, Currency.DOLLAR), + mapEntry(Country.CHILE, Currency.PESO), + mapEntry(Country.UK, Currency.POUND), + mapEntry(Country.JAPAN, Currency.YEN), + mapEntry(Country.SWITZERLAND, Currency.FRANC)); } @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -109,7 +115,9 @@ public Currency[] createValueArray(int length) { } } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -148,16 +156,12 @@ public void testCreateFromMap() { assertEquals(Currency.DOLLAR, bimap.inverse().get(Country.CANADA)); /* Map must have at least one entry if not an EnumBiMap. */ - try { - EnumBiMap.create(Collections.emptyMap()); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - } - try { - EnumBiMap.create(EnumHashBiMap.create(Currency.class)); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> EnumBiMap.create(Collections.emptyMap())); + assertThrows( + IllegalArgumentException.class, + () -> EnumBiMap.create(EnumHashBiMap.create(Currency.class))); /* Map can be empty if it's an EnumBiMap. */ Map emptyBimap = EnumBiMap.create(Currency.class, Country.class); @@ -174,7 +178,7 @@ public void testEnumBiMapConstructor() { assertEquals(bimap1, bimap2); bimap2.inverse().put(Country.SWITZERLAND, Currency.FRANC); assertEquals(Country.SWITZERLAND, bimap2.get(Currency.FRANC)); - assertNull(bimap1.get(Currency.FRANC)); + assertThat(bimap1.get(Currency.FRANC)).isNull(); assertFalse(bimap2.equals(bimap1)); /* Test that it can be empty. */ @@ -183,11 +187,13 @@ public void testEnumBiMapConstructor() { assertEquals(bimap3, emptyBimap); } + @GwtIncompatible // keyType public void testKeyType() { EnumBiMap bimap = EnumBiMap.create(Currency.class, Country.class); assertEquals(Currency.class, bimap.keyType()); } + @GwtIncompatible // valueType public void testValueType() { EnumBiMap bimap = EnumBiMap.create(Currency.class, Country.class); assertEquals(Country.class, bimap.valueType()); @@ -285,12 +291,14 @@ public void testEntrySet() { assertEquals(3, uniqueEntries.size()); } - @GwtIncompatible // serialization - public void testSerializable() { + @GwtIncompatible + @J2ktIncompatible + public void testSerializable() { SerializableTester.reserializeAndAssert( EnumBiMap.create(ImmutableMap.of(Currency.DOLLAR, Country.CANADA))); } + @J2ktIncompatible @GwtIncompatible // reflection public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(EnumBiMap.class); diff --git a/android/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java b/android/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java index 2a78f55477da..8390a9bcd73e 100644 --- a/android/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/EnumHashBiMapTest.java @@ -16,8 +16,13 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -34,13 +39,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code EnumHashBiMap}. * * @author Mike Bostock */ -@GwtCompatible(emulated = true) +@J2ktIncompatible // EnumHashBiMap +@GwtCompatible +@NullUnmarked public class EnumHashBiMapTest extends TestCase { private enum Currency { DOLLAR, @@ -58,6 +66,7 @@ private enum Country { UK } + @AndroidIncompatible // test-suite builders public static final class EnumHashBiMapGenerator implements TestBiMapGenerator { @SuppressWarnings("unchecked") @Override @@ -73,17 +82,17 @@ public BiMap create(Object... entries) { @Override public SampleElements> samples() { return new SampleElements<>( - Maps.immutableEntry(Country.CANADA, "DOLLAR"), - Maps.immutableEntry(Country.CHILE, "PESO"), - Maps.immutableEntry(Country.UK, "POUND"), - Maps.immutableEntry(Country.JAPAN, "YEN"), - Maps.immutableEntry(Country.SWITZERLAND, "FRANC")); + immutableEntry(Country.CANADA, "DOLLAR"), + immutableEntry(Country.CHILE, "PESO"), + immutableEntry(Country.UK, "POUND"), + immutableEntry(Country.JAPAN, "YEN"), + immutableEntry(Country.SWITZERLAND, "FRANC")); } @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -102,7 +111,9 @@ public String[] createValueArray(int length) { } } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -142,11 +153,9 @@ public void testCreateFromMap() { assertEquals(Currency.DOLLAR, bimap.inverse().get("dollar")); /* Map must have at least one entry if not an EnumHashBiMap. */ - try { - EnumHashBiMap.create(Collections.emptyMap()); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> EnumHashBiMap.create(Collections.emptyMap())); /* Map can be empty if it's an EnumHashBiMap. */ Map emptyBimap = EnumHashBiMap.create(Currency.class); @@ -168,7 +177,7 @@ public void testEnumHashBiMapConstructor() { assertEquals(bimap1, bimap2); bimap2.inverse().put("franc", Currency.FRANC); assertEquals("franc", bimap2.get(Currency.FRANC)); - assertNull(bimap1.get(Currency.FRANC)); + assertThat(bimap1.get(Currency.FRANC)).isNull(); assertFalse(bimap2.equals(bimap1)); /* Test that it can be empty. */ @@ -187,7 +196,7 @@ public void testEnumBiMapConstructor() { assertEquals(bimap1, bimap2); bimap2.inverse().put("franc", Currency.FRANC); assertEquals("franc", bimap2.get(Currency.FRANC)); - assertNull(bimap1.get(Currency.FRANC)); + assertThat(bimap1.get(Currency.FRANC)).isNull(); assertFalse(bimap2.equals(bimap1)); /* Test that it can be empty. */ @@ -197,6 +206,7 @@ public void testEnumBiMapConstructor() { assertEquals(bimap3, emptyBimap); } + @GwtIncompatible // keyType public void testKeyType() { EnumHashBiMap bimap = EnumHashBiMap.create(Currency.class); assertEquals(Currency.class, bimap.keyType()); @@ -216,11 +226,13 @@ public void testEntrySet() { assertEquals(3, uniqueEntries.size()); } - @GwtIncompatible // serialize - public void testSerializable() { + @GwtIncompatible + @J2ktIncompatible + public void testSerializable() { SerializableTester.reserializeAndAssert(EnumHashBiMap.create(Currency.class)); } + @J2ktIncompatible @GwtIncompatible // reflection public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(EnumHashBiMap.class); diff --git a/android/guava-tests/test/com/google/common/collect/EnumMultisetTest.java b/android/guava-tests/test/com/google/common/collect/EnumMultisetTest.java index 265db21286b7..d81a68f1bbaa 100644 --- a/android/guava-tests/test/com/google/common/collect/EnumMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/EnumMultisetTest.java @@ -16,10 +16,12 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.AnEnum; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -29,22 +31,28 @@ import com.google.common.testing.ClassSanityTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; +import com.google.errorprone.annotations.Keep; import java.util.Collection; import java.util.EnumSet; import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for an {@link EnumMultiset}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@J2ktIncompatible // EnumMultiset +@NullUnmarked public class EnumMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -62,6 +70,7 @@ public static Test suite() { return suite; } + @AndroidIncompatible // test-suite builders private static TestEnumMultisetGenerator enumMultisetGenerator() { return new TestEnumMultisetGenerator() { @Override @@ -105,11 +114,7 @@ public void testCollectionCreate() { public void testIllegalCreate() { Collection empty = EnumSet.noneOf(Color.class); - try { - EnumMultiset.create(empty); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> EnumMultiset.create(empty)); } public void testCreateEmptyWithClass() { @@ -118,11 +123,8 @@ public void testCreateEmptyWithClass() { } public void testCreateEmptyWithoutClassFails() { - try { - EnumMultiset.create(ImmutableList.of()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> EnumMultiset.create(ImmutableList.of())); } public void testToString() { @@ -154,11 +156,13 @@ public void testEntrySet() { // create(Enum1.class) is equal to create(Enum2.class) but testEquals() expects otherwise. // For the same reason, we need to skip create(Iterable, Class). private static class EnumMultisetFactory { + @Keep // used reflectively by testEquals public static > EnumMultiset create(Iterable elements) { return EnumMultiset.create(elements); } } + @J2ktIncompatible @GwtIncompatible // reflection public void testEquals() throws Exception { new ClassSanityTester() @@ -168,6 +172,7 @@ public void testEquals() throws Exception { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // reflection public void testNulls() throws Exception { new NullPointerTester() diff --git a/android/guava-tests/test/com/google/common/collect/EvictingQueueTest.java b/android/guava-tests/test/com/google/common/collect/EvictingQueueTest.java index 05fdbd0e860a..8c2d784c5771 100644 --- a/android/guava-tests/test/com/google/common/collect/EvictingQueueTest.java +++ b/android/guava-tests/test/com/google/common/collect/EvictingQueueTest.java @@ -16,29 +16,31 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.util.AbstractList; import java.util.List; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for {@link EvictingQueue}. * * @author Kurt Alfred Kluever */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class EvictingQueueTest extends TestCase { public void testCreateWithNegativeSize() throws Exception { - try { - EvictingQueue.create(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> EvictingQueue.create(-1)); } public void testCreateWithZeroSize() throws Exception { @@ -54,19 +56,11 @@ public void testCreateWithZeroSize() throws Exception { assertFalse(queue.remove("hi")); assertEquals(0, queue.size()); - try { - queue.element(); - fail(); - } catch (NoSuchElementException expected) { - } - - assertNull(queue.peek()); - assertNull(queue.poll()); - try { - queue.remove(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> queue.element()); + + assertThat(queue.peek()).isNull(); + assertThat(queue.poll()).isNull(); + assertThrows(NoSuchElementException.class, () -> queue.remove()); } public void testRemainingCapacity_maxSize0() { @@ -161,7 +155,7 @@ public void testAddAll() throws Exception { } public void testAddAll_largeList() { - final List list = ImmutableList.of("one", "two", "three", "four", "five"); + List list = ImmutableList.of("one", "two", "three", "four", "five"); List misbehavingList = new AbstractList() { @Override @@ -187,6 +181,7 @@ public String get(int index) { assertTrue(queue.isEmpty()); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/collect/FauxveridesTest.java b/android/guava-tests/test/com/google/common/collect/FauxveridesTest.java index ff0b86bf95a2..e779e6864434 100644 --- a/android/guava-tests/test/com/google/common/collect/FauxveridesTest.java +++ b/android/guava-tests/test/com/google/common/collect/FauxveridesTest.java @@ -18,22 +18,25 @@ import static com.google.common.collect.Lists.transform; import static com.google.common.collect.Sets.difference; -import static com.google.common.collect.Sets.newHashSet; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThrows; import com.google.common.base.Function; import com.google.common.base.Joiner; -import com.google.common.base.Objects; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests that all {@code public static} methods "inherited" from superclasses are "overridden" in @@ -42,6 +45,7 @@ * * @author Chris Povirk */ +@NullUnmarked public class FauxveridesTest extends TestCase { public void testImmutableBiMap() { doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class); @@ -77,31 +81,23 @@ public void testImmutableSortedMapCopyOfMap() { Map original = ImmutableMap.of(new Object(), new Object(), new Object(), new Object()); - try { - ImmutableSortedMap.copyOf(original); - fail(); - } catch (ClassCastException expected) { - } + assertThrows(ClassCastException.class, () -> ImmutableSortedMap.copyOf(original)); } public void testImmutableSortedSetCopyOfIterable() { + // false positive: `new Object()` is not equal to `new Object()` + @SuppressWarnings("DistinctVarargsChecker") Set original = ImmutableSet.of(new Object(), new Object()); - try { - ImmutableSortedSet.copyOf(original); - fail(); - } catch (ClassCastException expected) { - } + assertThrows(ClassCastException.class, () -> ImmutableSortedSet.copyOf(original)); } public void testImmutableSortedSetCopyOfIterator() { + // false positive: `new Object()` is not equal to `new Object()` + @SuppressWarnings("DistinctVarargsChecker") Set original = ImmutableSet.of(new Object(), new Object()); - try { - ImmutableSortedSet.copyOf(original.iterator()); - fail(); - } catch (ClassCastException expected) { - } + assertThrows(ClassCastException.class, () -> ImmutableSortedSet.copyOf(original.iterator())); } private void doHasAllFauxveridesTest(Class descendant, Class ancestor) { @@ -126,7 +122,7 @@ private static Set getAllFauxveridden(Class descendant, Clas private static Set getPublicStaticMethodsBetween( Class descendant, Class ancestor) { - Set methods = newHashSet(); + Set methods = new HashSet<>(); for (Class clazz : getClassesBetween(descendant, ancestor)) { methods.addAll(getPublicStaticMethods(clazz)); } @@ -134,7 +130,7 @@ private static Set getPublicStaticMethodsBetween( } private static Set getPublicStaticMethods(Class clazz) { - Set publicStaticMethods = newHashSet(); + Set publicStaticMethods = new HashSet<>(); for (Method method : clazz.getDeclaredMethods()) { int modifiers = method.getModifiers(); @@ -148,7 +144,7 @@ private static Set getPublicStaticMethods(Class clazz) { /** [descendant, ancestor) */ private static Set> getClassesBetween(Class descendant, Class ancestor) { - Set> classes = newHashSet(); + Set> classes = new HashSet<>(); while (!descendant.equals(ancestor)) { classes.add(descendant); @@ -171,12 +167,12 @@ private static final class MethodSignature implements Comparable[] parameters) { parameterSignatures = transform( - Arrays.asList(parameters), + asList(parameters), new Function, TypeParameterSignature>() { @Override public TypeParameterSignature apply(TypeVariable from) { @@ -219,7 +215,7 @@ public TypeParameterSignature apply(TypeVariable from) { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof TypeSignature) { TypeSignature other = (TypeSignature) obj; return parameterSignatures.equals(other.parameterSignatures); @@ -235,7 +231,7 @@ public int hashCode() { @Override public String toString() { - return (parameterSignatures.isEmpty()) + return parameterSignatures.isEmpty() ? "" : "<" + Joiner.on(", ").join(parameterSignatures) + "> "; } @@ -247,11 +243,11 @@ private static final class TypeParameterSignature { TypeParameterSignature(TypeVariable typeParameter) { name = typeParameter.getName(); - bounds = Arrays.asList(typeParameter.getBounds()); + bounds = asList(typeParameter.getBounds()); } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof TypeParameterSignature) { TypeParameterSignature other = (TypeParameterSignature) obj; /* @@ -271,7 +267,7 @@ public int hashCode() { @Override public String toString() { - return (bounds.equals(ImmutableList.of(Object.class))) + return bounds.equals(ImmutableList.of(Object.class)) ? name : name + " extends " + getTypesString(bounds); } diff --git a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java b/android/guava-tests/test/com/google/common/collect/FilteredBiMapTest.java similarity index 70% rename from guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java rename to android/guava-tests/test/com/google/common/collect/FilteredBiMapTest.java index 7eaf55dbd0ee..78d4e958ccc3 100644 --- a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java +++ b/android/guava-tests/test/com/google/common/collect/FilteredBiMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Guava Authors + * Copyright (C) 2007 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,13 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullMarked; -@GwtCompatible(emulated = true) -abstract class ImmutableMultisetGwtSerializationDependencies extends ImmutableCollection { - E dummy; +@GwtCompatible +@NullMarked +public class FilteredBiMapTest extends AbstractFilteredMapTest { + @Override + BiMap createUnfiltered() { + return HashBiMap.create(); + } } diff --git a/android/guava-tests/test/com/google/common/collect/FilteredCollectionsTest.java b/android/guava-tests/test/com/google/common/collect/FilteredCollectionsTestUtil.java similarity index 78% rename from android/guava-tests/test/com/google/common/collect/FilteredCollectionsTest.java rename to android/guava-tests/test/com/google/common/collect/FilteredCollectionsTestUtil.java index 3b05972eeeec..a5dfeff3053b 100644 --- a/android/guava-tests/test/com/google/common/collect/FilteredCollectionsTest.java +++ b/android/guava-tests/test/com/google/common/collect/FilteredCollectionsTestUtil.java @@ -16,27 +16,37 @@ package com.google.common.collect; +import static com.google.common.base.Predicates.not; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.testing.EqualsTester; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; -import java.util.TreeSet; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** - * Tests for filtered collection views. + * Class that contains nested abstract tests for filtered collection views, along with their + * implementation helpers. * * @author Louis Wasserman */ -public class FilteredCollectionsTest extends TestCase { +/* + * TODO(cpovirk): Should all the tests for filtered collections run under GWT, too? Currently, they + * don't. + */ +@NullUnmarked +public final class FilteredCollectionsTestUtil { private static final Predicate EVEN = new Predicate() { @Override @@ -96,7 +106,7 @@ public void testReadsThroughAdd() { C filterThenAdd = filter(unfiltered, EVEN); unfiltered.add(4); - List target = Lists.newArrayList(contents); + List target = new ArrayList<>(contents); target.add(4); C addThenFilter = filter(createUnfiltered(target), EVEN); @@ -152,11 +162,7 @@ public void testAddAllFailsAtomically() { C filtered = filter(createUnfiltered(contents), EVEN); C filteredToModify = filter(createUnfiltered(contents), EVEN); - try { - filteredToModify.addAll(toAdd); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> filteredToModify.addAll(toAdd)); assertThat(filteredToModify).containsExactlyElementsIn(filtered); } @@ -168,17 +174,9 @@ public void testAddToFilterFiltered() { C filtered1 = filter(unfiltered, EVEN); C filtered2 = filter(filtered1, PRIME_DIGIT); - try { - filtered2.add(4); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> filtered2.add(4)); - try { - filtered2.add(3); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> filtered2.add(3)); filtered2.add(2); } @@ -191,7 +189,7 @@ public void testClearFilterFiltered() { C filtered2 = filter(filtered1, PRIME_DIGIT); C inverseFiltered = - filter(createUnfiltered(contents), Predicates.not(Predicates.and(EVEN, PRIME_DIGIT))); + filter(createUnfiltered(contents), not(Predicates.and(EVEN, PRIME_DIGIT))); filtered2.clear(); assertThat(unfiltered).containsExactlyElementsIn(inverseFiltered); @@ -203,7 +201,7 @@ public abstract static class AbstractFilteredSetTest> extends AbstractFilteredCollectionTest { public void testEqualsAndHashCode() { for (List contents : SAMPLE_INPUTS) { - Set expected = Sets.newHashSet(); + Set expected = new HashSet<>(); for (Integer i : contents) { if (EVEN.apply(i)) { expected.add(i); @@ -374,79 +372,5 @@ public void testNavigation() { } } - // implementation tests - - public static final class IterablesFilterArrayListTest - extends AbstractFilteredIterableTest> { - @Override - Iterable createUnfiltered(Iterable contents) { - return Lists.newArrayList(contents); - } - - @Override - Iterable filter(Iterable elements, Predicate predicate) { - return Iterables.filter(elements, predicate); - } - } - - public static final class Collections2FilterArrayListTest - extends AbstractFilteredCollectionTest> { - @Override - Collection createUnfiltered(Iterable contents) { - return Lists.newArrayList(contents); - } - - @Override - Collection filter(Collection elements, Predicate predicate) { - return Collections2.filter(elements, predicate); - } - } - - public static final class SetsFilterHashSetTest extends AbstractFilteredSetTest> { - @Override - Set createUnfiltered(Iterable contents) { - return Sets.newHashSet(contents); - } - - @Override - Set filter(Set elements, Predicate predicate) { - return Sets.filter(elements, predicate); - } - } - - public static final class SetsFilterSortedSetTest - extends AbstractFilteredSortedSetTest> { - @Override - SortedSet createUnfiltered(Iterable contents) { - final TreeSet result = Sets.newTreeSet(contents); - // we have to make the result not Navigable - return new ForwardingSortedSet() { - @Override - protected SortedSet delegate() { - return result; - } - }; - } - - @Override - SortedSet filter(SortedSet elements, Predicate predicate) { - return Sets.filter(elements, predicate); - } - } - - public static final class SetsFilterNavigableSetTest extends AbstractFilteredNavigableSetTest { - @Override - NavigableSet createUnfiltered(Iterable contents) { - return Sets.newTreeSet(contents); - } - - @Override - NavigableSet filter( - NavigableSet elements, Predicate predicate) { - return Sets.filter(elements, predicate); - } - } - - /** No-op test so that the class has at least one method, making Maven's test runner happy. */ - public void testNoop() {} + private FilteredCollectionsTestUtil() {} } diff --git a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/HashMultimapGwtSerializationDependencies.java b/android/guava-tests/test/com/google/common/collect/FilteredMapTest.java similarity index 68% rename from guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/HashMultimapGwtSerializationDependencies.java rename to android/guava-tests/test/com/google/common/collect/FilteredMapTest.java index a38f9e807e4c..881d7fde8dac 100644 --- a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/HashMultimapGwtSerializationDependencies.java +++ b/android/guava-tests/test/com/google/common/collect/FilteredMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Guava Authors + * Copyright (C) 2007 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,15 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import java.util.Collection; +import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.NullMarked; -@GwtCompatible(emulated = true) -abstract class HashMultimapGwtSerializationDependencies extends AbstractSetMultimap { - HashMultimapGwtSerializationDependencies(Map> map) { - super(map); +@GwtCompatible +@NullMarked +public class FilteredMapTest extends AbstractFilteredMapTest { + @Override + Map createUnfiltered() { + return new HashMap<>(); } - - K dummyKey; - V dummyValue; } diff --git a/android/guava-tests/test/com/google/common/collect/FilteredMultimapTest.java b/android/guava-tests/test/com/google/common/collect/FilteredMultimapTest.java index 0e5e26574821..383dc06a7ba7 100644 --- a/android/guava-tests/test/com/google/common/collect/FilteredMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/FilteredMultimapTest.java @@ -16,11 +16,16 @@ package com.google.common.collect; +import static com.google.common.collect.Multimaps.filterKeys; +import static com.google.common.collect.Multimaps.filterValues; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Predicate; -import java.util.Arrays; import java.util.Map.Entry; +import java.util.Objects; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Multimaps} filtering methods. @@ -28,15 +33,12 @@ * @author Jared Levy */ @GwtIncompatible // nottested +@NullUnmarked public class FilteredMultimapTest extends TestCase { private static final Predicate> ENTRY_PREDICATE = - new Predicate>() { - @Override - public boolean apply(Entry entry) { - return !"badkey".equals(entry.getKey()) && !((Integer) 55556).equals(entry.getValue()); - } - }; + entry -> + !Objects.equals(entry.getKey(), "badkey") && !Objects.equals(entry.getValue(), 55556); protected Multimap create() { Multimap unfiltered = HashMultimap.create(); @@ -45,36 +47,24 @@ protected Multimap create() { return Multimaps.filterEntries(unfiltered, ENTRY_PREDICATE); } - private static final Predicate KEY_PREDICATE = - new Predicate() { - @Override - public boolean apply(String key) { - return !"badkey".equals(key); - } - }; + private static final Predicate KEY_PREDICATE = key -> !Objects.equals(key, "badkey"); public void testFilterKeys() { Multimap unfiltered = HashMultimap.create(); unfiltered.put("foo", 55556); unfiltered.put("badkey", 1); - Multimap filtered = Multimaps.filterKeys(unfiltered, KEY_PREDICATE); + Multimap filtered = filterKeys(unfiltered, KEY_PREDICATE); assertEquals(1, filtered.size()); assertTrue(filtered.containsEntry("foo", 55556)); } - private static final Predicate VALUE_PREDICATE = - new Predicate() { - @Override - public boolean apply(Integer value) { - return !((Integer) 55556).equals(value); - } - }; + private static final Predicate VALUE_PREDICATE = value -> !Objects.equals(value, 55556); public void testFilterValues() { Multimap unfiltered = HashMultimap.create(); unfiltered.put("foo", 55556); unfiltered.put("badkey", 1); - Multimap filtered = Multimaps.filterValues(unfiltered, VALUE_PREDICATE); + Multimap filtered = filterValues(unfiltered, VALUE_PREDICATE); assertEquals(1, filtered.size()); assertFalse(filtered.containsEntry("foo", 55556)); assertTrue(filtered.containsEntry("badkey", 1)); @@ -85,11 +75,11 @@ public void testFilterFiltered() { unfiltered.put("foo", 55556); unfiltered.put("badkey", 1); unfiltered.put("foo", 1); - Multimap keyFiltered = Multimaps.filterKeys(unfiltered, KEY_PREDICATE); - Multimap filtered = Multimaps.filterValues(keyFiltered, VALUE_PREDICATE); + Multimap keyFiltered = filterKeys(unfiltered, KEY_PREDICATE); + Multimap filtered = filterValues(keyFiltered, VALUE_PREDICATE); assertEquals(1, filtered.size()); assertTrue(filtered.containsEntry("foo", 1)); - assertTrue(filtered.keySet().retainAll(Arrays.asList("cat", "dog"))); + assertTrue(filtered.keySet().retainAll(asList("cat", "dog"))); assertEquals(0, filtered.size()); } diff --git a/android/guava-tests/test/com/google/common/collect/FilteredSortedMapTest.java b/android/guava-tests/test/com/google/common/collect/FilteredSortedMapTest.java new file mode 100644 index 000000000000..7fb754300d59 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/FilteredSortedMapTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.SortedMap; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class FilteredSortedMapTest extends AbstractFilteredMapTest { + @Override + SortedMap createUnfiltered() { + return Maps.newTreeMap(); + } + + public void testFirstAndLastKeyFilteredMap() { + SortedMap unfiltered = createUnfiltered(); + unfiltered.put("apple", 2); + unfiltered.put("banana", 6); + unfiltered.put("cat", 3); + unfiltered.put("dog", 5); + + SortedMap filtered = Maps.filterEntries(unfiltered, CORRECT_LENGTH); + assertEquals("banana", filtered.firstKey()); + assertEquals("cat", filtered.lastKey()); + } + + public void testHeadSubTailMap_filteredMap() { + SortedMap unfiltered = createUnfiltered(); + unfiltered.put("apple", 2); + unfiltered.put("banana", 6); + unfiltered.put("cat", 4); + unfiltered.put("dog", 3); + SortedMap filtered = Maps.filterEntries(unfiltered, CORRECT_LENGTH); + + assertEquals(ImmutableMap.of("banana", 6), filtered.headMap("dog")); + assertEquals(ImmutableMap.of(), filtered.headMap("banana")); + assertEquals(ImmutableMap.of("banana", 6, "dog", 3), filtered.headMap("emu")); + + assertEquals(ImmutableMap.of("banana", 6), filtered.subMap("banana", "dog")); + assertEquals(ImmutableMap.of("dog", 3), filtered.subMap("cat", "emu")); + + assertEquals(ImmutableMap.of("dog", 3), filtered.tailMap("cat")); + assertEquals(ImmutableMap.of("banana", 6, "dog", 3), filtered.tailMap("banana")); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/FluentIterableTest.java b/android/guava-tests/test/com/google/common/collect/FluentIterableTest.java index e623f31d2f36..79bdbceaadc0 100644 --- a/android/guava-tests/test/com/google/common/collect/FluentIterableTest.java +++ b/android/guava-tests/test/com/google/common/collect/FluentIterableTest.java @@ -16,9 +16,18 @@ package com.google.common.collect; +import static com.google.common.base.Predicates.equalTo; +import static com.google.common.collect.Iterables.removeIf; import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Sets.newHashSet; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.nCopies; +import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; @@ -33,21 +42,25 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.concurrent.TimeUnit; import junit.framework.AssertionFailedError; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link FluentIterable}. * * @author Marcin Mikosik */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class FluentIterableTest extends TestCase { @GwtIncompatible // NullPointerTester @@ -57,17 +70,12 @@ public void testNullPointerExceptions() { } public void testFromArrayAndAppend() { - FluentIterable units = - FluentIterable.from(TimeUnit.values()).append(TimeUnit.SECONDS); + FluentIterable unused = FluentIterable.from(TimeUnit.values()).append(SECONDS); } public void testFromArrayAndIteratorRemove() { FluentIterable units = FluentIterable.from(TimeUnit.values()); - try { - Iterables.removeIf(units, Predicates.equalTo(TimeUnit.SECONDS)); - fail("Expected an UnsupportedOperationException to be thrown but it wasn't."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> removeIf(units, equalTo(SECONDS))); } public void testFrom() { @@ -76,7 +84,11 @@ public void testFrom() { Lists.newArrayList(FluentIterable.from(ImmutableList.of(1, 2, 3, 4)))); } - @SuppressWarnings("deprecation") // test of deprecated method + @SuppressWarnings({ + "deprecation", // test of deprecated method + // We need to test that from(FluentIterable) really is just a null check. + "InlineMeInliner", + }) public void testFrom_alreadyFluentIterable() { FluentIterable iterable = FluentIterable.from(asList(1)); assertSame(iterable, FluentIterable.from(iterable)); @@ -101,7 +113,6 @@ public void testConcatIterable() { List list1 = newArrayList(1); List list2 = newArrayList(4); - @SuppressWarnings("unchecked") List> input = newArrayList(list1, list2); FluentIterable result = FluentIterable.concat(input); @@ -123,7 +134,6 @@ public void testConcatVarargs() { List list3 = newArrayList(7, 8); List list4 = newArrayList(9); List list5 = newArrayList(10); - @SuppressWarnings("unchecked") FluentIterable result = FluentIterable.concat(list1, list2, list3, list4, list5); assertEquals(asList(1, 4, 7, 8, 9, 10), newArrayList(result)); assertEquals("[1, 4, 7, 8, 9, 10]", result.toString()); @@ -133,17 +143,13 @@ public void testConcatNullPointerException() { List list1 = newArrayList(1); List list2 = newArrayList(4); - try { - FluentIterable.concat(list1, null, list2); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> FluentIterable.concat(list1, null, list2)); } public void testConcatPeformingFiniteCycle() { Iterable iterable = asList(1, 2, 3); int n = 4; - FluentIterable repeated = FluentIterable.concat(Collections.nCopies(n, iterable)); + FluentIterable repeated = FluentIterable.concat(nCopies(n, iterable)); assertThat(repeated).containsExactly(1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3).inOrder(); } @@ -220,7 +226,7 @@ public Iterator iterator() { } public void testContains_nullSetYes() { - Iterable set = Sets.newHashSet("a", null, "b"); + Iterable set = newHashSet("a", null, "b"); assertTrue(FluentIterable.from(set).contains(null)); } @@ -240,12 +246,12 @@ public void testContains_nullIterableNo() { } public void testContains_nonNullSetYes() { - Iterable set = Sets.newHashSet("a", null, "b"); + Iterable set = newHashSet("a", null, "b"); assertTrue(FluentIterable.from(set).contains("b")); } public void testContains_nonNullSetNo() { - Iterable set = Sets.newHashSet("a", "b"); + Iterable set = newHashSet("a", "b"); assertFalse(FluentIterable.from(set).contains("c")); } @@ -264,7 +270,7 @@ public void testOfToString() { } public void testToString() { - assertEquals("[]", FluentIterable.from(Collections.emptyList()).toString()); + assertEquals("[]", FluentIterable.from(emptyList()).toString()); assertEquals("[]", FluentIterable.of().toString()); assertEquals( @@ -323,17 +329,17 @@ public void testAppend_toEmpty() { public void testAppend_emptyList() { FluentIterable result = - FluentIterable.from(asList(1, 2, 3)).append(Lists.newArrayList()); + FluentIterable.from(asList(1, 2, 3)).append(new ArrayList()); assertEquals(asList(1, 2, 3), Lists.newArrayList(result)); } public void testAppend_nullPointerException() { - try { - FluentIterable unused = - FluentIterable.from(asList(1, 2)).append((List) null); - fail("Appending null iterable should throw NPE."); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> { + FluentIterable unused = + FluentIterable.from(asList(1, 2)).append((List) null); + }); } /* @@ -346,9 +352,9 @@ public void testAppend_nullPointerException() { public void testFilter() { FluentIterable filtered = - FluentIterable.from(asList("foo", "bar")).filter(Predicates.equalTo("foo")); + FluentIterable.from(asList("foo", "bar")).filter(equalTo("foo")); - List expected = Collections.singletonList("foo"); + List expected = singletonList("foo"); List actual = Lists.newArrayList(filtered); assertEquals(expected, actual); assertCanIterateAgain(filtered); @@ -371,9 +377,9 @@ public void testFilterByType() throws Exception { } public void testAnyMatch() { - ArrayList list = Lists.newArrayList(); + ArrayList list = new ArrayList<>(); FluentIterable iterable = FluentIterable.from(list); - Predicate predicate = Predicates.equalTo("pants"); + Predicate predicate = equalTo("pants"); assertFalse(iterable.anyMatch(predicate)); list.add("cool"); @@ -383,9 +389,9 @@ public void testAnyMatch() { } public void testAllMatch() { - List list = Lists.newArrayList(); + List list = new ArrayList<>(); FluentIterable iterable = FluentIterable.from(list); - Predicate predicate = Predicates.equalTo("cool"); + Predicate predicate = equalTo("cool"); assertTrue(iterable.allMatch(predicate)); list.add("cool"); @@ -396,8 +402,8 @@ public void testAllMatch() { public void testFirstMatch() { FluentIterable iterable = FluentIterable.from(Lists.newArrayList("cool", "pants")); - assertThat(iterable.firstMatch(Predicates.equalTo("cool"))).hasValue("cool"); - assertThat(iterable.firstMatch(Predicates.equalTo("pants"))).hasValue("pants"); + assertThat(iterable.firstMatch(equalTo("cool"))).hasValue("cool"); + assertThat(iterable.firstMatch(equalTo("pants"))).hasValue("pants"); assertThat(iterable.firstMatch(Predicates.alwaysFalse())).isAbsent(); assertThat(iterable.firstMatch(Predicates.alwaysTrue())).hasValue("cool"); } @@ -425,11 +431,7 @@ public void testTransformWith_poorlyBehavedTransform() { Iterator resultIterator = iterable.iterator(); resultIterator.next(); - try { - resultIterator.next(); - fail("Transforming null to int should throw NumberFormatException"); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> resultIterator.next()); } private static final class StringValueOfFunction implements Function { @@ -484,15 +486,11 @@ public void testFirst_list() { public void testFirst_null() { List list = Lists.newArrayList(null, "a", "b"); - try { - FluentIterable.from(list).first(); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> FluentIterable.from(list).first()); } public void testFirst_emptyList() { - List list = Collections.emptyList(); + List list = emptyList(); assertThat(FluentIterable.from(list).first()).isAbsent(); } @@ -512,7 +510,7 @@ public void testFirst_iterable() { } public void testFirst_emptyIterable() { - Set set = Sets.newHashSet(); + Set set = new HashSet<>(); assertThat(FluentIterable.from(set).first()).isAbsent(); } @@ -523,15 +521,11 @@ public void testLast_list() { public void testLast_null() { List list = Lists.newArrayList("a", "b", null); - try { - FluentIterable.from(list).last(); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> FluentIterable.from(list).last()); } public void testLast_emptyList() { - List list = Collections.emptyList(); + List list = emptyList(); assertThat(FluentIterable.from(list).last()).isAbsent(); } @@ -551,7 +545,7 @@ public void testLast_iterable() { } public void testLast_emptyIterable() { - Set set = Sets.newHashSet(); + Set set = new HashSet<>(); assertThat(FluentIterable.from(set).last()).isAbsent(); } @@ -571,12 +565,12 @@ public void testSkip_simpleList() { public void testSkip_pastEnd() { Collection set = ImmutableSet.of("a", "b"); - assertEquals(Collections.emptyList(), Lists.newArrayList(FluentIterable.from(set).skip(20))); + assertEquals(emptyList(), Lists.newArrayList(FluentIterable.from(set).skip(20))); } public void testSkip_pastEndList() { Collection list = Lists.newArrayList("a", "b"); - assertEquals(Collections.emptyList(), Lists.newArrayList(FluentIterable.from(list).skip(20))); + assertEquals(emptyList(), Lists.newArrayList(FluentIterable.from(list).skip(20))); } public void testSkip_skipNone() { @@ -599,7 +593,7 @@ public void testSkip_iterator() throws Exception { IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { - Collection collection = Sets.newLinkedHashSet(); + Collection collection = new LinkedHashSet<>(); Collections.addAll(collection, 1, 2, 3); return FluentIterable.from(collection).skip(1).iterator(); } @@ -630,7 +624,7 @@ public void testSkip_nonStructurallyModifiedList() throws Exception { } public void testSkip_structurallyModifiedSkipSome() throws Exception { - Collection set = Sets.newLinkedHashSet(); + Collection set = new LinkedHashSet<>(); Collections.addAll(set, "a", "b", "c"); FluentIterable tail = FluentIterable.from(set).skip(1); set.remove("b"); @@ -647,7 +641,7 @@ public void testSkip_structurallyModifiedSkipSomeList() throws Exception { } public void testSkip_structurallyModifiedSkipAll() throws Exception { - Collection set = Sets.newLinkedHashSet(); + Collection set = new LinkedHashSet<>(); Collections.addAll(set, "a", "b", "c"); FluentIterable tail = FluentIterable.from(set).skip(2); set.remove("a"); @@ -663,11 +657,8 @@ public void testSkip_structurallyModifiedSkipAllList() throws Exception { } public void testSkip_illegalArgument() { - try { - FluentIterable.from(asList("a", "b", "c")).skip(-1); - fail("Skipping negative number of elements should throw IllegalArgumentException."); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> FluentIterable.from(asList("a", "b", "c")).skip(-1)); } public void testLimit() { @@ -680,12 +671,12 @@ public void testLimit() { } public void testLimit_illegalArgument() { - try { - FluentIterable unused = - FluentIterable.from(Lists.newArrayList("a", "b", "c")).limit(-1); - fail("Passing negative number to limit(...) method should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> { + FluentIterable unused = + FluentIterable.from(Lists.newArrayList("a", "b", "c")).limit(-1); + }); } public void testIsEmpty() { @@ -747,25 +738,17 @@ public void testToMultiset_empty() { public void testToMap() { assertThat(fluent(1, 2, 3).toMap(Functions.toStringFunction()).entrySet()) - .containsExactly( - Maps.immutableEntry(1, "1"), Maps.immutableEntry(2, "2"), Maps.immutableEntry(3, "3")) + .containsExactly(immutableEntry(1, "1"), immutableEntry(2, "2"), immutableEntry(3, "3")) .inOrder(); } public void testToMap_nullKey() { - try { - fluent(1, null, 2).toMap(Functions.constant("foo")); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> fluent(1, null, 2).toMap(Functions.constant("foo"))); } public void testToMap_nullValue() { - try { - fluent(1, 2, 3).toMap(Functions.constant(null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> fluent(1, 2, 3).toMap(Functions.constant(null))); } public void testIndex() { @@ -788,21 +771,21 @@ public Integer apply(String input) { } public void testIndex_nullKey() { - try { - ImmutableListMultimap unused = - fluent(1, 2, 3).index(Functions.constant(null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> { + ImmutableListMultimap unused = + fluent(1, 2, 3).index(Functions.constant(null)); + }); } public void testIndex_nullValue() { - try { - ImmutableListMultimap unused = - fluent(1, null, 2).index(Functions.constant("foo")); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> { + ImmutableListMultimap unused = + fluent(1, null, 2).index(Functions.constant("foo")); + }); } public void testUniqueIndex() { @@ -820,63 +803,60 @@ public Integer apply(String input) { } public void testUniqueIndex_duplicateKey() { - try { - ImmutableMap unused = - FluentIterable.from(asList("one", "two", "three", "four")) - .uniqueIndex( - new Function() { - @Override - public Integer apply(String input) { - return input.length(); - } - }); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> { + ImmutableMap unused = + FluentIterable.from(asList("one", "two", "three", "four")) + .uniqueIndex( + new Function() { + @Override + public Integer apply(String input) { + return input.length(); + } + }); + }); } public void testUniqueIndex_nullKey() { - try { - fluent(1, 2, 3).uniqueIndex(Functions.constant(null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> fluent(1, 2, 3).uniqueIndex(Functions.constant(null))); } public void testUniqueIndex_nullValue() { - try { - ImmutableMap unused = - fluent(1, null, 2) - .uniqueIndex( - new Function() { - @Override - public Object apply(@NullableDecl Integer input) { - return String.valueOf(input); - } - }); - fail(); - } catch (NullPointerException expected) { - } - } - - public void testCopyInto_List() { + assertThrows( + NullPointerException.class, + () -> { + ImmutableMap unused = + fluent(1, null, 2) + .uniqueIndex( + new Function() { + @Override + public Object apply(@Nullable Integer input) { + return String.valueOf(input); + } + }); + }); + } + + public void testCopyInto_list() { assertThat(fluent(1, 3, 5).copyInto(Lists.newArrayList(1, 2))) .containsExactly(1, 2, 1, 3, 5) .inOrder(); } - public void testCopyInto_Set() { - assertThat(fluent(1, 3, 5).copyInto(Sets.newHashSet(1, 2))).containsExactly(1, 2, 3, 5); + public void testCopyInto_set() { + assertThat(fluent(1, 3, 5).copyInto(newHashSet(1, 2))).containsExactly(1, 2, 3, 5); } - public void testCopyInto_SetAllDuplicates() { - assertThat(fluent(1, 3, 5).copyInto(Sets.newHashSet(1, 2, 3, 5))).containsExactly(1, 2, 3, 5); + public void testCopyInto_setAllDuplicates() { + assertThat(fluent(1, 3, 5).copyInto(newHashSet(1, 2, 3, 5))).containsExactly(1, 2, 3, 5); } - public void testCopyInto_NonCollection() { - final ArrayList list = Lists.newArrayList(1, 2, 3); + public void testCopyInto_nonCollection() { + ArrayList list = Lists.newArrayList(1, 2, 3); - final ArrayList iterList = Lists.newArrayList(9, 8, 7); + ArrayList iterList = Lists.newArrayList(9, 8, 7); Iterable iterable = new Iterable() { @Override @@ -905,17 +885,13 @@ public void testGet() { } public void testGet_outOfBounds() { - try { - FluentIterable.from(Lists.newArrayList("a", "b", "c")).get(-1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, + () -> FluentIterable.from(Lists.newArrayList("a", "b", "c")).get(-1)); - try { - FluentIterable.from(Lists.newArrayList("a", "b", "c")).get(3); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, + () -> FluentIterable.from(Lists.newArrayList("a", "b", "c")).get(3)); } private static void assertCanIterateAgain(Iterable iterable) { @@ -929,7 +905,7 @@ private static FluentIterable fluent(Integer... elements) { } private static Iterable iterable(String... elements) { - final List list = asList(elements); + List list = asList(elements); return new Iterable() { @Override public Iterator iterator() { diff --git a/android/guava-tests/test/com/google/common/collect/ForMapMultimapAsMapImplementsMapTest.java b/android/guava-tests/test/com/google/common/collect/ForMapMultimapAsMapImplementsMapTest.java index 402410b297f0..6a53b9a70786 100644 --- a/android/guava-tests/test/com/google/common/collect/ForMapMultimapAsMapImplementsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForMapMultimapAsMapImplementsMapTest.java @@ -19,7 +19,9 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.MapInterfaceTest; import java.util.Collection; +import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.NullMarked; /** * Test {@link Multimap#asMap()} for a {@link Multimaps#forMap} multimap with {@link @@ -28,6 +30,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class ForMapMultimapAsMapImplementsMapTest extends AbstractMultimapAsMapImplementsMapTest { public ForMapMultimapAsMapImplementsMapTest() { @@ -36,13 +39,13 @@ public ForMapMultimapAsMapImplementsMapTest() { @Override protected Map> makeEmptyMap() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); return Multimaps.forMap(map).asMap(); } @Override protected Map> makePopulatedMap() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("foo", 1); map.put("bar", 2); map.put("cow", 3); diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingCollectionTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingCollectionTest.java index 63dd135afaec..13bf5a6fd79e 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingCollectionTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingCollectionTest.java @@ -26,9 +26,11 @@ import com.google.common.collect.testing.features.CollectionSize; import com.google.common.testing.ForwardingWrapperTester; import java.util.Collection; +import java.util.LinkedList; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link ForwardingCollection}. @@ -37,6 +39,7 @@ * @author Hayward Chan * @author Louis Wasserman */ +@NullUnmarked public class ForwardingCollectionTest extends TestCase { static final class StandardImplForwardingCollection extends ForwardingCollection { private final Collection backingCollection; @@ -101,6 +104,7 @@ public String toString() { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -111,7 +115,7 @@ public static Test suite() { @Override protected Collection create(String[] elements) { return new StandardImplForwardingCollection<>( - Lists.newLinkedList(asList(elements))); + new LinkedList<>(asList(elements))); } }) .named("ForwardingCollection[LinkedList] with standard implementations") @@ -128,7 +132,7 @@ protected Collection create(String[] elements) { return new StandardImplForwardingCollection<>(MinimalCollection.of(elements)); } }) - .named("ForwardingCollection[MinimalCollection] with standard" + " implementations") + .named("ForwardingCollection[MinimalCollection] with standard implementations") .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES) .createTestSuite()); @@ -148,7 +152,7 @@ public Collection apply(Collection delegate) { }); } - private static Collection wrap(final Collection delegate) { + private static Collection wrap(Collection delegate) { return new ForwardingCollection() { @Override protected Collection delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingConcurrentMapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingConcurrentMapTest.java index aa1c61c88d35..654188e7324e 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingConcurrentMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingConcurrentMapTest.java @@ -16,15 +16,19 @@ package com.google.common.collect; +import static com.google.common.truth.Truth.assertThat; + import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link ForwardingConcurrentMap}. * * @author Jared Levy */ +@NullUnmarked public class ForwardingConcurrentMapTest extends TestCase { private static class TestMap extends ForwardingConcurrentMap { @@ -41,7 +45,7 @@ public void testPutIfAbsent() { map.put("foo", 1); assertEquals(Integer.valueOf(1), map.putIfAbsent("foo", 2)); assertEquals(Integer.valueOf(1), map.get("foo")); - assertNull(map.putIfAbsent("bar", 3)); + assertThat(map.putIfAbsent("bar", 3)).isNull(); assertEquals(Integer.valueOf(3), map.get("bar")); } @@ -59,7 +63,7 @@ public void testReplace() { TestMap map = new TestMap(); map.put("foo", 1); assertEquals(Integer.valueOf(1), map.replace("foo", 2)); - assertNull(map.replace("bar", 3)); + assertThat(map.replace("bar", 3)).isNull(); assertEquals(Integer.valueOf(2), map.get("foo")); assertFalse(map.containsKey("bar")); } diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingDequeTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingDequeTest.java index 7541f91a2dd2..bd7e64842796 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingDequeTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingDequeTest.java @@ -20,12 +20,14 @@ import com.google.common.testing.ForwardingWrapperTester; import java.util.Deque; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code ForwardingDeque}. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class ForwardingDequeTest extends TestCase { @SuppressWarnings("rawtypes") @@ -33,15 +35,15 @@ public void testForwarding() { new ForwardingWrapperTester() .testForwarding( Deque.class, - new Function() { + new Function>() { @Override - public Deque apply(Deque delegate) { - return wrap(delegate); + public Deque apply(Deque delegate) { + return wrap((Deque) delegate); } }); } - private static Deque wrap(final Deque delegate) { + private static Deque wrap(Deque delegate) { return new ForwardingDeque() { @Override protected Deque delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingListIteratorTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingListIteratorTest.java index df6fe5003bd0..55b5dda264fa 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingListIteratorTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingListIteratorTest.java @@ -20,12 +20,14 @@ import com.google.common.testing.ForwardingWrapperTester; import java.util.ListIterator; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code ForwardingListIterator}. * * @author Robert Konigsberg */ +@NullUnmarked public class ForwardingListIteratorTest extends TestCase { @SuppressWarnings("rawtypes") @@ -33,15 +35,15 @@ public void testForwarding() { new ForwardingWrapperTester() .testForwarding( ListIterator.class, - new Function() { + new Function>() { @Override - public ListIterator apply(ListIterator delegate) { - return wrap(delegate); + public ListIterator apply(ListIterator delegate) { + return wrap((ListIterator) delegate); } }); } - private static ListIterator wrap(final ListIterator delegate) { + private static ListIterator wrap(ListIterator delegate) { return new ForwardingListIterator() { @Override protected ListIterator delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingListMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingListMultimapTest.java index 3c5ebae76e46..18c0e72cac4c 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingListMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingListMultimapTest.java @@ -20,12 +20,14 @@ import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link ForwardingListMultimap}. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class ForwardingListMultimapTest extends TestCase { @SuppressWarnings("rawtypes") @@ -33,10 +35,10 @@ public void testForwarding() { new ForwardingWrapperTester() .testForwarding( ListMultimap.class, - new Function() { + new Function>() { @Override - public ListMultimap apply(ListMultimap delegate) { - return wrap(delegate); + public ListMultimap apply(ListMultimap delegate) { + return wrap((ListMultimap) delegate); } }); } @@ -50,7 +52,7 @@ public void testEquals() { .testEquals(); } - private static ListMultimap wrap(final ListMultimap delegate) { + private static ListMultimap wrap(ListMultimap delegate) { return new ForwardingListMultimap() { @Override protected ListMultimap delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingListTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingListTest.java index 7d3466eedd2c..ac3707a9caf9 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingListTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingListTest.java @@ -31,6 +31,8 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code ForwardingList}. @@ -38,6 +40,7 @@ * @author Robert Konigsberg * @author Louis Wasserman */ +@NullUnmarked public class ForwardingListTest extends TestCase { static final class StandardImplForwardingList extends ForwardingList { private final List backingList; @@ -112,7 +115,7 @@ public String toString() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -152,6 +155,7 @@ public List subList(int fromIndex, int toIndex) { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -209,7 +213,7 @@ public void testEquals() { .testEquals(); } - private static List wrap(final List delegate) { + private static List wrap(List delegate) { return new ForwardingList() { @Override protected List delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingMapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingMapTest.java index cc5d73967607..dd29c8e26028 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingMapTest.java @@ -16,6 +16,7 @@ package com.google.common.collect; +import static com.google.common.collect.Iterators.emptyIterator; import static java.lang.reflect.Modifier.STATIC; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; @@ -29,9 +30,7 @@ import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; -import com.google.common.reflect.AbstractInvocationHandler; import com.google.common.reflect.Parameter; -import com.google.common.reflect.Reflection; import com.google.common.reflect.TypeToken; import com.google.common.testing.ArbitraryInstances; import com.google.common.testing.EqualsTester; @@ -40,13 +39,20 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link ForwardingMap}. @@ -54,6 +60,7 @@ * @author Hayward Chan * @author Louis Wasserman */ +@NullUnmarked public class ForwardingMapTest extends TestCase { static class StandardImplForwardingMap extends ForwardingMap { private final Map backingMap; @@ -83,12 +90,12 @@ public void putAll(Map map) { } @Override - public V remove(Object object) { + public @Nullable V remove(Object object) { return standardRemove(object); } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -133,6 +140,7 @@ public boolean isEmpty() { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -143,7 +151,7 @@ public static Test suite() { @Override protected Map create(Entry[] entries) { - Map map = Maps.newLinkedHashMap(); + Map map = new LinkedHashMap<>(); for (Entry entry : entries) { map.put(entry.getKey(), entry.getValue()); } @@ -170,7 +178,7 @@ protected Map create(Entry[] entries) { for (Entry entry : entries) { builder.put(entry.getKey(), entry.getValue()); } - return new StandardImplForwardingMap<>(builder.build()); + return new StandardImplForwardingMap<>(builder.buildOrThrow()); } }) .named("ForwardingMap[ImmutableMap] with standard implementations") @@ -208,7 +216,7 @@ public void testEquals() { public void testStandardEntrySet() throws InvocationTargetException { @SuppressWarnings("unchecked") - final Map map = mock(Map.class); + Map map = mock(Map.class); Map forward = new ForwardingMap() { @@ -222,7 +230,7 @@ public Set> entrySet() { return new StandardEntrySet() { @Override public Iterator> iterator() { - return Iterators.emptyIterator(); + return emptyIterator(); } }; } @@ -241,7 +249,7 @@ public Iterator> iterator() { public void testStandardKeySet() throws InvocationTargetException { @SuppressWarnings("unchecked") - final Map map = mock(Map.class); + Map map = mock(Map.class); Map forward = new ForwardingMap() { @@ -269,7 +277,7 @@ public Set keySet() { public void testStandardValues() throws InvocationTargetException { @SuppressWarnings("unchecked") - final Map map = mock(Map.class); + Map map = mock(Map.class); Map forward = new ForwardingMap() { @@ -295,12 +303,12 @@ public Collection values() { } public void testToStringWithNullKeys() throws Exception { - Map hashmap = Maps.newHashMap(); + Map hashmap = new HashMap<>(); hashmap.put("foo", "bar"); hashmap.put(null, "baz"); StandardImplForwardingMap forwardingMap = - new StandardImplForwardingMap<>(Maps.newHashMap()); + new StandardImplForwardingMap<>(new HashMap<>()); forwardingMap.put("foo", "bar"); forwardingMap.put(null, "baz"); @@ -308,19 +316,19 @@ public void testToStringWithNullKeys() throws Exception { } public void testToStringWithNullValues() throws Exception { - Map hashmap = Maps.newHashMap(); + Map hashmap = new HashMap<>(); hashmap.put("foo", "bar"); hashmap.put("baz", null); StandardImplForwardingMap forwardingMap = - new StandardImplForwardingMap<>(Maps.newHashMap()); + new StandardImplForwardingMap<>(new HashMap<>()); forwardingMap.put("foo", "bar"); forwardingMap.put("baz", null); assertEquals(hashmap.toString(), forwardingMap.toString()); } - private static Map wrap(final Map delegate) { + private static Map wrap(Map delegate) { return new ForwardingMap() { @Override protected Map delegate() { @@ -329,38 +337,25 @@ protected Map delegate() { }; } - private static final ImmutableMap JUF_METHODS = - ImmutableMap.of( - "java.util.function.Predicate", "test", - "java.util.function.Consumer", "accept", - "java.util.function.IntFunction", "apply"); - - private static Object getDefaultValue(final TypeToken type) { + private static @Nullable Object getDefaultValue(TypeToken type) { Class rawType = type.getRawType(); Object defaultValue = ArbitraryInstances.get(rawType); if (defaultValue != null) { return defaultValue; } - final String typeName = rawType.getCanonicalName(); - if (JUF_METHODS.containsKey(typeName)) { - // Generally, methods that accept java.util.function.* instances - // don't like to get null values. We generate them dynamically - // using Proxy so that we can have Java 7 compliant code. - return Reflection.newProxy( - rawType, - new AbstractInvocationHandler() { - @Override - public Object handleInvocation(Object proxy, Method method, Object[] args) { - // Crude, but acceptable until we can use Java 8. Other - // methods have default implementations, and it is hard to - // distinguish. - if (method.getName().equals(JUF_METHODS.get(typeName))) { - return getDefaultValue(type.method(method).getReturnType()); - } - throw new IllegalStateException("Unexpected " + method + " invoked on " + proxy); - } - }); + // TODO(cpovirk): Support these types in ArbitraryInstances itself? + if (rawType.equals(Predicate.class)) { + return (Predicate) v -> (boolean) getDefaultValue(TypeToken.of(boolean.class)); + } else if (rawType.equals(IntFunction.class)) { + try { + Method method = IntFunction.class.getMethod("apply", int.class); + return (IntFunction) v -> getDefaultValue(type.method(method).getReturnType()); + } catch (NoSuchMethodException e) { + throw newLinkageError(e); + } + } else if (rawType.equals(Consumer.class)) { + return (Consumer) v -> {}; } else { return null; } @@ -392,4 +387,8 @@ private static void callAllPublicMethods(TypeToken type, T object) } } } + + private static LinkageError newLinkageError(Throwable cause) { + return new LinkageError(cause.toString(), cause); + } } diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingMultimapTest.java index b842feae10b2..a3afcaa30c32 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingMultimapTest.java @@ -20,12 +20,14 @@ import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link ForwardingMultimap}. * * @author Hayward Chan */ +@NullUnmarked public class ForwardingMultimapTest extends TestCase { @SuppressWarnings("rawtypes") @@ -33,10 +35,10 @@ public void testForwarding() { new ForwardingWrapperTester() .testForwarding( Multimap.class, - new Function() { + new Function>() { @Override - public Multimap apply(Multimap delegate) { - return wrap(delegate); + public Multimap apply(Multimap delegate) { + return wrap((Multimap) delegate); } }); } @@ -50,7 +52,7 @@ public void testEquals() { .testEquals(); } - private static Multimap wrap(final Multimap delegate) { + private static Multimap wrap(Multimap delegate) { return new ForwardingMultimap() { @Override protected Multimap delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingMultisetTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingMultisetTest.java index 327885d538f4..0a87e91de989 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingMultisetTest.java @@ -16,6 +16,8 @@ package com.google.common.collect; +import static java.util.Arrays.asList; + import com.google.common.base.Function; import com.google.common.collect.Multiset.Entry; import com.google.common.collect.testing.SetTestSuiteBuilder; @@ -26,13 +28,14 @@ import com.google.common.collect.testing.google.TestStringMultisetGenerator; import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ForwardingMultiset}. @@ -40,6 +43,7 @@ * @author Hayward Chan * @author Louis Wasserman */ +@NullUnmarked public class ForwardingMultisetTest extends TestCase { static final class StandardImplForwardingMultiset extends ForwardingMultiset { @@ -115,7 +119,7 @@ public String toString() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -155,6 +159,7 @@ public int size() { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -166,10 +171,10 @@ public static Test suite() { @Override protected Multiset create(String[] elements) { return new StandardImplForwardingMultiset<>( - LinkedHashMultiset.create(Arrays.asList(elements))); + LinkedHashMultiset.create(asList(elements))); } }) - .named("ForwardingMultiset[LinkedHashMultiset] with standard " + "implementations") + .named("ForwardingMultiset[LinkedHashMultiset] with standard implementations") .withFeatures( CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES, @@ -184,7 +189,7 @@ protected Multiset create(String[] elements) { return new StandardImplForwardingMultiset<>(ImmutableMultiset.copyOf(elements)); } }) - .named("ForwardingMultiset[ImmutableMultiset] with standard " + "implementations") + .named("ForwardingMultiset[ImmutableMultiset] with standard implementations") .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_QUERIES) .createTestSuite()); suite.addTest( @@ -197,8 +202,7 @@ protected Multiset create(String[] elements) { */ @Override protected Set create(String[] elements) { - final Multiset inner = - LinkedHashMultiset.create(Arrays.asList(elements)); + Multiset inner = LinkedHashMultiset.create(asList(elements)); return new ForwardingMultiset() { @Override protected Multiset delegate() { @@ -222,7 +226,7 @@ public boolean add(String element) { @Override public Set> entrySet() { - final Set> backingSet = super.entrySet(); + Set> backingSet = super.entrySet(); return new ForwardingSet>() { @Override protected Set> delegate() { @@ -277,7 +281,7 @@ public boolean retainAll(Collection collection) { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { throw new UnsupportedOperationException(); } @@ -355,7 +359,7 @@ public void testEquals() { .testEquals(); } - private static Multiset wrap(final Multiset delegate) { + private static Multiset wrap(Multiset delegate) { return new ForwardingMultiset() { @Override protected Multiset delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingNavigableMapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingNavigableMapTest.java index b70cd0718ef1..d67193fb5bc5 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingNavigableMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingNavigableMapTest.java @@ -17,6 +17,7 @@ package com.google.common.collect; import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Function; import com.google.common.collect.testing.NavigableMapTestSuiteBuilder; @@ -39,6 +40,8 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code ForwardingNavigableMap}. @@ -46,6 +49,7 @@ * @author Robert Konigsberg * @author Louis Wasserman */ +@NullUnmarked public class ForwardingNavigableMapTest extends TestCase { static class StandardImplForwardingNavigableMap extends ForwardingNavigableMap { private final NavigableMap backingMap; @@ -75,12 +79,12 @@ public void putAll(Map map) { } @Override - public V remove(Object object) { + public @Nullable V remove(Object object) { return standardRemove(object); } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -134,47 +138,47 @@ public SortedMap subMap(K fromKey, K toKey) { } @Override - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(K key) { return standardLowerEntry(key); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(K key) { return standardLowerKey(key); } @Override - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(K key) { return standardFloorEntry(key); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(K key) { return standardFloorKey(key); } @Override - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(K key) { return standardCeilingEntry(key); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(K key) { return standardCeilingKey(key); } @Override - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(K key) { return standardHigherEntry(key); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(K key) { return standardHigherKey(key); } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return standardFirstEntry(); } @@ -184,12 +188,12 @@ public Entry firstEntry() { */ @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return standardPollFirstEntry(); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return standardPollLastEntry(); } @@ -242,11 +246,12 @@ protected NavigableMap delegate() { } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return standardLastEntry(); } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -291,7 +296,7 @@ protected SortedMap create(Entry[] entries) { public void testStandardLastEntry() { NavigableMap forwarding = new StandardLastEntryForwardingNavigableMap<>(new SafeTreeMap()); - assertNull(forwarding.lastEntry()); + assertThat(forwarding.lastEntry()).isNull(); forwarding.put("b", 2); assertEquals(immutableEntry("b", 2), forwarding.lastEntry()); forwarding.put("c", 3); @@ -324,7 +329,7 @@ public void testEquals() { .testEquals(); } - private static NavigableMap wrap(final NavigableMap delegate) { + private static NavigableMap wrap(NavigableMap delegate) { return new ForwardingNavigableMap() { @Override protected NavigableMap delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingNavigableSetTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingNavigableSetTest.java index fd47bf8fd53c..7a10c294792b 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingNavigableSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingNavigableSetTest.java @@ -16,6 +16,8 @@ package com.google.common.collect; +import static java.util.Arrays.asList; + import com.google.common.base.Function; import com.google.common.collect.testing.SafeTreeSet; import com.google.common.collect.testing.SetTestSuiteBuilder; @@ -24,7 +26,7 @@ import com.google.common.collect.testing.features.CollectionSize; import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -34,12 +36,15 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code ForwardingNavigableSet}. * * @author Louis Wasserman */ +@NullUnmarked public class ForwardingNavigableSetTest extends TestCase { static class StandardImplForwardingNavigableSet extends ForwardingNavigableSet { private final NavigableSet backingSet; @@ -54,7 +59,7 @@ protected NavigableSet delegate() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -119,32 +124,32 @@ public SortedSet subSet(T fromElement, T toElement) { } @Override - public T lower(T e) { + public @Nullable T lower(T e) { return standardLower(e); } @Override - public T floor(T e) { + public @Nullable T floor(T e) { return standardFloor(e); } @Override - public T ceiling(T e) { + public @Nullable T ceiling(T e) { return standardCeiling(e); } @Override - public T higher(T e) { + public @Nullable T higher(T e) { return standardHigher(e); } @Override - public T pollFirst() { + public @Nullable T pollFirst() { return standardPollFirst(); } @Override - public T pollLast() { + public @Nullable T pollLast() { return standardPollLast(); } @@ -159,6 +164,7 @@ public SortedSet tailSet(T fromElement) { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -169,12 +175,12 @@ public static Test suite() { @Override protected Set create(String[] elements) { return new StandardImplForwardingNavigableSet<>( - new SafeTreeSet(Arrays.asList(elements))); + new SafeTreeSet(asList(elements))); } @Override public List order(List insertionOrder) { - return Lists.newArrayList(Sets.newTreeSet(insertionOrder)); + return new ArrayList<>(Sets.newTreeSet(insertionOrder)); } }) .named("ForwardingNavigableSet[SafeTreeSet] with standard implementations") @@ -195,7 +201,7 @@ protected Set create(String[] elements) { @Override public List order(List insertionOrder) { - return Lists.newArrayList(Sets.newTreeSet(insertionOrder)); + return new ArrayList<>(Sets.newTreeSet(insertionOrder)); } }) .named( @@ -233,7 +239,7 @@ public void testEquals() { .testEquals(); } - private static NavigableSet wrap(final NavigableSet delegate) { + private static NavigableSet wrap(NavigableSet delegate) { return new ForwardingNavigableSet() { @Override protected NavigableSet delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingObjectTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingObjectTest.java index 05bd9f5ca5ed..be0bd612a850 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingObjectTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingObjectTest.java @@ -16,19 +16,23 @@ package com.google.common.collect; +import static com.google.common.collect.Sets.newHashSet; + import com.google.common.testing.EqualsTester; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code ForwardingObject}. * * @author Mike Bostock */ +@NullUnmarked public class ForwardingObjectTest extends TestCase { public void testEqualsReflexive() { - final Object delegate = new Object(); + Object delegate = new Object(); ForwardingObject forward = new ForwardingObject() { @Override @@ -40,7 +44,7 @@ protected Object delegate() { } public void testEqualsSymmetric() { - final Set delegate = Sets.newHashSet("foo"); + Set delegate = newHashSet("foo"); ForwardingObject forward = new ForwardingObject() { @Override diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingQueueTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingQueueTest.java index 248f132777a4..d5c394504ca1 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingQueueTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingQueueTest.java @@ -25,10 +25,13 @@ import com.google.common.collect.testing.features.CollectionSize; import com.google.common.testing.ForwardingWrapperTester; import java.util.Collection; +import java.util.LinkedList; import java.util.Queue; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code ForwardingQueue}. @@ -36,6 +39,7 @@ * @author Robert Konigsberg * @author Louis Wasserman */ +@NullUnmarked public class ForwardingQueueTest extends TestCase { static final class StandardImplForwardingQueue extends ForwardingQueue { @@ -106,16 +110,17 @@ public boolean offer(T o) { } @Override - public T peek() { + public @Nullable T peek() { return standardPeek(); } @Override - public T poll() { + public @Nullable T poll() { return standardPoll(); } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -126,7 +131,7 @@ public static Test suite() { @Override protected Queue create(String[] elements) { - return new StandardImplForwardingQueue<>(Lists.newLinkedList(asList(elements))); + return new StandardImplForwardingQueue<>(new LinkedList<>(asList(elements))); } }) .named("ForwardingQueue[LinkedList] with standard implementations") @@ -152,7 +157,7 @@ public Queue apply(Queue delegate) { }); } - private static Queue wrap(final Queue delegate) { + private static Queue wrap(Queue delegate) { return new ForwardingQueue() { @Override protected Queue delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingSetMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingSetMultimapTest.java index 03f0f3151efa..46e0a3be6edb 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingSetMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingSetMultimapTest.java @@ -20,12 +20,14 @@ import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link ForwardingSetMultimap}. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class ForwardingSetMultimapTest extends TestCase { @SuppressWarnings("rawtypes") @@ -33,10 +35,10 @@ public void testForwarding() { new ForwardingWrapperTester() .testForwarding( SetMultimap.class, - new Function() { + new Function>() { @Override - public SetMultimap apply(SetMultimap delegate) { - return wrap(delegate); + public SetMultimap apply(SetMultimap delegate) { + return wrap((SetMultimap) delegate); } }); } @@ -50,7 +52,7 @@ public void testEquals() { .testEquals(); } - private static SetMultimap wrap(final SetMultimap delegate) { + private static SetMultimap wrap(SetMultimap delegate) { return new ForwardingSetMultimap() { @Override protected SetMultimap delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingSetTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingSetTest.java index 89bf7b31d728..6d84cf8c7e6e 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingSetTest.java @@ -27,10 +27,13 @@ import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code ForwardingSet}. @@ -38,6 +41,7 @@ * @author Robert Konigsberg * @author Louis Wasserman */ +@NullUnmarked public class ForwardingSetTest extends TestCase { static class StandardImplForwardingSet extends ForwardingSet { private final Set backingSet; @@ -52,7 +56,7 @@ protected Set delegate() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -112,6 +116,7 @@ public String toString() { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -121,7 +126,7 @@ public static Test suite() { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return new StandardImplForwardingSet<>(Sets.newLinkedHashSet(asList(elements))); + return new StandardImplForwardingSet<>(new LinkedHashSet<>(asList(elements))); } }) .named("ForwardingSet[LinkedHashSet] with standard implementations") @@ -167,7 +172,7 @@ public void testEquals() { .testEquals(); } - private static Set wrap(final Set delegate) { + private static Set wrap(Set delegate) { return new ForwardingSet() { @Override protected Set delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingSortedMapImplementsMapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingSortedMapImplementsMapTest.java index b694d5d68988..9d9056926944 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingSortedMapImplementsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingSortedMapImplementsMapTest.java @@ -17,10 +17,12 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.MapInterfaceTest; import com.google.common.collect.testing.SortedMapInterfaceTest; import java.util.SortedMap; import java.util.TreeMap; +import org.jspecify.annotations.NullMarked; /** * Tests for {@link ForwardingSortedMap} using {@link MapInterfaceTest}. @@ -28,6 +30,7 @@ * @author George van den Driessche */ @GwtCompatible +@NullMarked public class ForwardingSortedMapImplementsMapTest extends SortedMapInterfaceTest { private static class SimpleForwardingSortedMap extends ForwardingSortedMap { @@ -50,12 +53,12 @@ public ForwardingSortedMapImplementsMapTest() { @Override protected SortedMap makeEmptyMap() { return new SimpleForwardingSortedMap<>( - new TreeMap(Ordering.natural().nullsFirst())); + new TreeMap(Ordering.natural().nullsFirst())); } @Override protected SortedMap makePopulatedMap() { - final SortedMap sortedMap = makeEmptyMap(); + SortedMap sortedMap = makeEmptyMap(); sortedMap.put("one", 1); sortedMap.put("two", 2); sortedMap.put("three", 3); @@ -72,6 +75,7 @@ protected Integer getValueNotInPopulatedMap() throws UnsupportedOperationExcepti return -1; } + @J2ktIncompatible // https://youtrack.jetbrains.com/issue/KT-58242/ undefined behavior (crash) @Override public void testContainsKey() { try { @@ -80,6 +84,7 @@ public void testContainsKey() { } } + @J2ktIncompatible // https://youtrack.jetbrains.com/issue/KT-58242/ undefined behavior (crash) @Override public void testEntrySetContainsEntryIncompatibleKey() { try { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingSortedMapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingSortedMapTest.java index 59e7ece53eac..c913852be0d7 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingSortedMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingSortedMapTest.java @@ -36,12 +36,15 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code ForwardingSortedMap}. * * @author Robert KonigsbergSortedMapFeature */ +@NullUnmarked public class ForwardingSortedMapTest extends TestCase { static class StandardImplForwardingSortedMap extends ForwardingSortedMap { private final SortedMap backingSortedMap; @@ -71,12 +74,12 @@ public void putAll(Map map) { } @Override - public V remove(Object object) { + public @Nullable V remove(Object object) { return standardRemove(object); } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -126,6 +129,7 @@ public SortedMap subMap(K fromKey, K toKey) { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -191,7 +195,7 @@ protected SortedMap create(Entry[] entries) { return new StandardImplForwardingSortedMap<>(builder.build()); } }) - .named("ForwardingSortedMap[ImmutableSortedMap] with standard " + "implementations") + .named("ForwardingSortedMap[ImmutableSortedMap] with standard implementations") .withFeatures( CollectionSize.ANY, MapFeature.REJECTS_DUPLICATES_AT_CREATION, @@ -223,7 +227,7 @@ public void testEquals() { .testEquals(); } - private static SortedMap wrap(final SortedMap delegate) { + private static SortedMap wrap(SortedMap delegate) { return new ForwardingSortedMap() { @Override protected SortedMap delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingSortedMultisetTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingSortedMultisetTest.java index d0673f1d0d7c..c1a712cd6e10 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingSortedMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingSortedMultisetTest.java @@ -14,6 +14,8 @@ package com.google.common.collect; +import static java.util.Arrays.asList; + import com.google.common.base.Function; import com.google.common.collect.Multiset.Entry; import com.google.common.collect.testing.features.CollectionFeature; @@ -22,7 +24,6 @@ import com.google.common.collect.testing.google.TestStringMultisetGenerator; import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -30,13 +31,15 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ForwardingSortedMultiset}. * * @author Louis Wasserman */ +@NullUnmarked public class ForwardingSortedMultisetTest extends TestCase { static class StandardImplForwardingSortedMultiset extends ForwardingSortedMultiset { private final SortedMultiset backingMultiset; @@ -93,12 +96,12 @@ public SortedMultiset subMultiset( } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { return standardCount(element); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -123,7 +126,7 @@ public void clear() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return standardContains(object); } @@ -143,7 +146,7 @@ public Iterator iterator() { } @Override - public boolean remove(@NullableDecl Object object) { + public boolean remove(@Nullable Object object) { return standardRemove(object); } @@ -173,6 +176,7 @@ public T[] toArray(T[] array) { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -183,7 +187,7 @@ public static Test suite() { @Override protected Multiset create(String[] elements) { return new StandardImplForwardingSortedMultiset<>( - TreeMultiset.create(Arrays.asList(elements))); + TreeMultiset.create(asList(elements))); } @Override @@ -224,7 +228,7 @@ public void testEquals() { .testEquals(); } - private static SortedMultiset wrap(final SortedMultiset delegate) { + private static SortedMultiset wrap(SortedMultiset delegate) { return new ForwardingSortedMultiset() { @Override protected SortedMultiset delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingSortedSetMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingSortedSetMultimapTest.java index 7f411c7da667..1ee0b1e6294f 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingSortedSetMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingSortedSetMultimapTest.java @@ -20,12 +20,14 @@ import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link ForwardingSortedSetMultimap}. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class ForwardingSortedSetMultimapTest extends TestCase { @SuppressWarnings("rawtypes") @@ -33,10 +35,10 @@ public void testForwarding() { new ForwardingWrapperTester() .testForwarding( SortedSetMultimap.class, - new Function() { + new Function>() { @Override - public SortedSetMultimap apply(SortedSetMultimap delegate) { - return wrap(delegate); + public SortedSetMultimap apply(SortedSetMultimap delegate) { + return wrap((SortedSetMultimap) delegate); } }); } @@ -50,7 +52,7 @@ public void testEquals() { .testEquals(); } - private static SortedSetMultimap wrap(final SortedSetMultimap delegate) { + private static SortedSetMultimap wrap(SortedSetMultimap delegate) { return new ForwardingSortedSetMultimap() { @Override protected SortedSetMultimap delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingSortedSetTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingSortedSetTest.java index b83ee6b97112..0598544a13fa 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingSortedSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingSortedSetTest.java @@ -16,6 +16,8 @@ package com.google.common.collect; +import static java.util.Arrays.asList; + import com.google.common.base.Function; import com.google.common.collect.testing.SafeTreeSet; import com.google.common.collect.testing.SortedSetTestSuiteBuilder; @@ -24,19 +26,22 @@ import com.google.common.collect.testing.features.CollectionSize; import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.SortedSet; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code ForwardingSortedSet}. * * @author Louis Wasserman */ +@NullUnmarked public class ForwardingSortedSetTest extends TestCase { static class StandardImplForwardingSortedSet extends ForwardingSortedSet { private final SortedSet backingSortedSet; @@ -51,7 +56,7 @@ protected SortedSet delegate() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { return standardEquals(object); } @@ -116,6 +121,7 @@ public SortedSet subSet(T fromElement, T toElement) { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -126,12 +132,12 @@ public static Test suite() { @Override protected SortedSet create(String[] elements) { return new StandardImplForwardingSortedSet<>( - new SafeTreeSet(Arrays.asList(elements))); + new SafeTreeSet(asList(elements))); } @Override public List order(List insertionOrder) { - return Lists.newArrayList(Sets.newTreeSet(insertionOrder)); + return new ArrayList<>(Sets.newTreeSet(insertionOrder)); } }) .named("ForwardingSortedSet[SafeTreeSet] with standard implementations") @@ -166,7 +172,7 @@ public void testEquals() { .testEquals(); } - private static SortedSet wrap(final SortedSet delegate) { + private static SortedSet wrap(SortedSet delegate) { return new ForwardingSortedSet() { @Override protected SortedSet delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/ForwardingTableTest.java b/android/guava-tests/test/com/google/common/collect/ForwardingTableTest.java index 91374e6c9b0a..61c619cf3309 100644 --- a/android/guava-tests/test/com/google/common/collect/ForwardingTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/ForwardingTableTest.java @@ -20,12 +20,14 @@ import com.google.common.testing.EqualsTester; import com.google.common.testing.ForwardingWrapperTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests {@link ForwardingTable}. * * @author Gregory Kick */ +@NullUnmarked public class ForwardingTableTest extends TestCase { @SuppressWarnings("rawtypes") @@ -33,10 +35,10 @@ public void testForwarding() { new ForwardingWrapperTester() .testForwarding( Table.class, - new Function() { + new Function>() { @Override - public Table apply(Table delegate) { - return wrap(delegate); + public Table apply(Table delegate) { + return wrap((Table) delegate); } }); } @@ -50,7 +52,7 @@ public void testEquals() { .testEquals(); } - private static Table wrap(final Table delegate) { + private static Table wrap(Table delegate) { return new ForwardingTable() { @Override protected Table delegate() { diff --git a/android/guava-tests/test/com/google/common/collect/GeneralRangeTest.java b/android/guava-tests/test/com/google/common/collect/GeneralRangeTest.java index dfa73d62ba88..5dd5e3719110 100644 --- a/android/guava-tests/test/com/google/common/collect/GeneralRangeTest.java +++ b/android/guava-tests/test/com/google/common/collect/GeneralRangeTest.java @@ -16,51 +16,55 @@ import static com.google.common.collect.BoundType.CLOSED; import static com.google.common.collect.BoundType.OPEN; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.unmodifiableList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import java.util.Arrays; import java.util.List; +import java.util.Objects; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code GeneralRange}. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class GeneralRangeTest extends TestCase { - private static final Ordering ORDERING = Ordering.natural().nullsFirst(); + private static final Ordering<@Nullable Integer> ORDERING = + Ordering.natural().nullsFirst(); - private static final List IN_ORDER_VALUES = Arrays.asList(null, 1, 2, 3, 4, 5); + private static final List<@Nullable Integer> IN_ORDER_VALUES = + unmodifiableList(Arrays.<@Nullable Integer>asList(null, 1, 2, 3, 4, 5)); public void testCreateEmptyRangeFails() { for (BoundType lboundType : BoundType.values()) { for (BoundType uboundType : BoundType.values()) { - try { - GeneralRange.range(ORDERING, 4, lboundType, 2, uboundType); - fail("Expected IAE"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> GeneralRange.range(ORDERING, 4, lboundType, 2, uboundType)); } } } public void testCreateEmptyRangeOpenOpenFails() { for (Integer i : IN_ORDER_VALUES) { - try { - GeneralRange.range(ORDERING, i, OPEN, i, OPEN); - fail("Expected IAE"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> GeneralRange.<@Nullable Integer>range(ORDERING, i, OPEN, i, OPEN)); } } public void testCreateEmptyRangeClosedOpenSucceeds() { for (Integer i : IN_ORDER_VALUES) { - GeneralRange range = GeneralRange.range(ORDERING, i, CLOSED, i, OPEN); + GeneralRange<@Nullable Integer> range = GeneralRange.range(ORDERING, i, CLOSED, i, OPEN); for (Integer j : IN_ORDER_VALUES) { assertFalse(range.contains(j)); } @@ -69,7 +73,7 @@ public void testCreateEmptyRangeClosedOpenSucceeds() { public void testCreateEmptyRangeOpenClosedSucceeds() { for (Integer i : IN_ORDER_VALUES) { - GeneralRange range = GeneralRange.range(ORDERING, i, OPEN, i, CLOSED); + GeneralRange<@Nullable Integer> range = GeneralRange.range(ORDERING, i, OPEN, i, CLOSED); for (Integer j : IN_ORDER_VALUES) { assertFalse(range.contains(j)); } @@ -78,15 +82,15 @@ public void testCreateEmptyRangeOpenClosedSucceeds() { public void testCreateSingletonRangeSucceeds() { for (Integer i : IN_ORDER_VALUES) { - GeneralRange range = GeneralRange.range(ORDERING, i, CLOSED, i, CLOSED); + GeneralRange<@Nullable Integer> range = GeneralRange.range(ORDERING, i, CLOSED, i, CLOSED); for (Integer j : IN_ORDER_VALUES) { - assertEquals(Objects.equal(i, j), range.contains(j)); + assertEquals(Objects.equals(i, j), range.contains(j)); } } } public void testSingletonRange() { - GeneralRange range = GeneralRange.range(ORDERING, 3, CLOSED, 3, CLOSED); + GeneralRange<@Nullable Integer> range = GeneralRange.range(ORDERING, 3, CLOSED, 3, CLOSED); for (Integer i : IN_ORDER_VALUES) { assertEquals(ORDERING.compare(i, 3) == 0, range.contains(i)); } @@ -94,7 +98,7 @@ public void testSingletonRange() { public void testLowerRange() { for (BoundType lBoundType : BoundType.values()) { - GeneralRange range = GeneralRange.downTo(ORDERING, 3, lBoundType); + GeneralRange<@Nullable Integer> range = GeneralRange.downTo(ORDERING, 3, lBoundType); for (Integer i : IN_ORDER_VALUES) { assertEquals( ORDERING.compare(i, 3) > 0 || (ORDERING.compare(i, 3) == 0 && lBoundType == CLOSED), @@ -109,7 +113,7 @@ public void testLowerRange() { public void testUpperRange() { for (BoundType lBoundType : BoundType.values()) { - GeneralRange range = GeneralRange.upTo(ORDERING, 3, lBoundType); + GeneralRange<@Nullable Integer> range = GeneralRange.upTo(ORDERING, 3, lBoundType); for (Integer i : IN_ORDER_VALUES) { assertEquals( ORDERING.compare(i, 3) < 0 || (ORDERING.compare(i, 3) == 0 && lBoundType == CLOSED), @@ -126,7 +130,8 @@ public void testDoublyBoundedAgainstRange() { for (BoundType lboundType : BoundType.values()) { for (BoundType uboundType : BoundType.values()) { Range range = Range.range(2, lboundType, 4, uboundType); - GeneralRange gRange = GeneralRange.range(ORDERING, 2, lboundType, 4, uboundType); + GeneralRange<@Nullable Integer> gRange = + GeneralRange.range(ORDERING, 2, lboundType, 4, uboundType); for (Integer i : IN_ORDER_VALUES) { assertEquals(i != null && range.contains(i), gRange.contains(i)); } @@ -146,7 +151,7 @@ public void testIntersectAgainstBiggerRange() { assertEquals( GeneralRange.range(ORDERING, 2, CLOSED, 4, OPEN), - range.intersect(GeneralRange.range(ORDERING, null, OPEN, 5, CLOSED))); + range.intersect(GeneralRange.<@Nullable Integer>range(ORDERING, null, OPEN, 5, CLOSED))); assertEquals( GeneralRange.range(ORDERING, 2, OPEN, 4, OPEN), @@ -219,6 +224,7 @@ public void testReverse() { GeneralRange.range(ORDERING, 3, CLOSED, 5, OPEN).reverse()); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { new NullPointerTester().testAllPublicStaticMethods(GeneralRange.class); diff --git a/android/guava-tests/test/com/google/common/collect/HashBasedTableColumnMapTest.java b/android/guava-tests/test/com/google/common/collect/HashBasedTableColumnMapTest.java new file mode 100644 index 000000000000..16657ccc4799 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/HashBasedTableColumnMapTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnMapTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class HashBasedTableColumnMapTest extends ColumnMapTests { + public HashBasedTableColumnMapTest() { + super(false, true, true, false); + } + + @Override + Table makeTable() { + return HashBasedTable.create(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/HashBasedTableColumnTest.java b/android/guava-tests/test/com/google/common/collect/HashBasedTableColumnTest.java new file mode 100644 index 000000000000..238c04bba9d6 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/HashBasedTableColumnTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class HashBasedTableColumnTest extends ColumnTests { + public HashBasedTableColumnTest() { + super(false, true, true, true, false); + } + + @Override + Table makeTable() { + return HashBasedTable.create(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/HashBasedTableRowMapTest.java b/android/guava-tests/test/com/google/common/collect/HashBasedTableRowMapTest.java new file mode 100644 index 000000000000..923ffe197faa --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/HashBasedTableRowMapTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class HashBasedTableRowMapTest extends RowMapTests { + public HashBasedTableRowMapTest() { + super(false, true, true, true); + } + + @Override + Table makeTable() { + return HashBasedTable.create(); + } +} diff --git a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java b/android/guava-tests/test/com/google/common/collect/HashBasedTableRowTest.java similarity index 61% rename from guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java rename to android/guava-tests/test/com/google/common/collect/HashBasedTableRowTest.java index 55af586e9101..1b6f4a44a97f 100644 --- a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java +++ b/android/guava-tests/test/com/google/common/collect/HashBasedTableRowTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Guava Authors + * Copyright (C) 2008 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,16 +17,18 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import java.util.Collection; -import java.util.Map; +import com.google.common.collect.TableCollectionTest.RowTests; +import org.jspecify.annotations.NullMarked; -@GwtCompatible(emulated = true) -abstract class ArrayListMultimapGwtSerializationDependencies - extends AbstractListMultimap { - ArrayListMultimapGwtSerializationDependencies(Map> map) { - super(map); +@GwtCompatible +@NullMarked +public class HashBasedTableRowTest extends RowTests { + public HashBasedTableRowTest() { + super(false, true, true, true, true); } - K dummyKey; - V dummyValue; + @Override + Table makeTable() { + return HashBasedTable.create(); + } } diff --git a/android/guava-tests/test/com/google/common/collect/HashBasedTableTest.java b/android/guava-tests/test/com/google/common/collect/HashBasedTableTest.java index 8a608aeeea60..51a65521e20d 100644 --- a/android/guava-tests/test/com/google/common/collect/HashBasedTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/HashBasedTableTest.java @@ -16,23 +16,28 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link HashBasedTable}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) -public class HashBasedTableTest extends AbstractTableTest { +@GwtCompatible +@NullMarked +public class HashBasedTableTest extends AbstractTableTest { @Override - protected Table create(Object... data) { + protected Table create(@Nullable Object... data) { Table table = HashBasedTable.create(); table.put("foo", 4, 'a'); table.put("cat", 1, 'b'); @@ -70,17 +75,9 @@ public void testCreateWithValidSizes() { } public void testCreateWithInvalidSizes() { - try { - HashBasedTable.create(100, -5); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HashBasedTable.create(100, -5)); - try { - HashBasedTable.create(-5, 20); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HashBasedTable.create(-5, 20)); } public void testCreateCopy() { @@ -91,12 +88,14 @@ public void testCreateCopy() { assertEquals((Character) 'a', copy.get("foo", 1)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); SerializableTester.reserializeAndAssert(table); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerStatic() { new NullPointerTester().testAllPublicStaticMethods(HashBasedTable.class); diff --git a/android/guava-tests/test/com/google/common/collect/HashBiMapTest.java b/android/guava-tests/test/com/google/common/collect/HashBiMapTest.java index 9541434286d4..be6e3cd9369f 100644 --- a/android/guava-tests/test/com/google/common/collect/HashBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/HashBiMapTest.java @@ -16,10 +16,12 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -32,15 +34,19 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Tests for {@link HashBiMap}. * * @author Mike Bostock */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class HashBiMapTest extends TestCase { + @J2ktIncompatible + @AndroidIncompatible // test-suite builders public static final class HashBiMapGenerator extends TestStringBiMapGenerator { @Override protected BiMap create(Entry[] entries) { @@ -52,7 +58,9 @@ protected BiMap create(Entry[] entries) { } } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -91,7 +99,7 @@ public void testBashIt() throws Exception { BiMap inverse = bimap.inverse(); for (int i = 0; i < N; i++) { - assertNull(bimap.put(2 * i, 2 * i + 1)); + assertThat(bimap.put(2 * i, 2 * i + 1)).isNull(); } for (int i = 0; i < N; i++) { assertEquals(2 * i + 1, (int) bimap.get(2 * i)); @@ -138,9 +146,7 @@ public void testInsertionOrder() { map.put("quux", 3); assertThat(map.entrySet()) .containsExactly( - Maps.immutableEntry("foo", 1), - Maps.immutableEntry("bar", 2), - Maps.immutableEntry("quux", 3)) + immutableEntry("foo", 1), immutableEntry("bar", 2), immutableEntry("quux", 3)) .inOrder(); } @@ -152,7 +158,7 @@ public void testInsertionOrderAfterRemoveFirst() { map.remove("foo"); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("bar", 2), Maps.immutableEntry("quux", 3)) + .containsExactly(immutableEntry("bar", 2), immutableEntry("quux", 3)) .inOrder(); } @@ -164,7 +170,7 @@ public void testInsertionOrderAfterRemoveMiddle() { map.remove("bar"); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("foo", 1), Maps.immutableEntry("quux", 3)) + .containsExactly(immutableEntry("foo", 1), immutableEntry("quux", 3)) .inOrder(); } @@ -176,7 +182,7 @@ public void testInsertionOrderAfterRemoveLast() { map.remove("quux"); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("foo", 1), Maps.immutableEntry("bar", 2)) + .containsExactly(immutableEntry("foo", 1), immutableEntry("bar", 2)) .inOrder(); } @@ -188,7 +194,7 @@ public void testInsertionOrderAfterForcePut() { map.forcePut("quux", 1); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("bar", 2), Maps.immutableEntry("quux", 1)) + .containsExactly(immutableEntry("bar", 2), immutableEntry("quux", 1)) .inOrder(); } @@ -200,7 +206,7 @@ public void testInsertionOrderAfterInverseForcePut() { map.inverse().forcePut(1, "quux"); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("bar", 2), Maps.immutableEntry("quux", 1)) + .containsExactly(immutableEntry("bar", 2), immutableEntry("quux", 1)) .inOrder(); } @@ -210,7 +216,7 @@ public void testInverseInsertionOrderAfterInverse() { map.put("quux", 1); assertThat(map.inverse().entrySet()) - .containsExactly(Maps.immutableEntry(2, "bar"), Maps.immutableEntry(1, "quux")) + .containsExactly(immutableEntry(2, "bar"), immutableEntry(1, "quux")) .inOrder(); } @@ -222,7 +228,7 @@ public void testInverseInsertionOrderAfterInverseForcePut() { map.inverse().forcePut(1, "quux"); assertThat(map.inverse().entrySet()) - .containsExactly(Maps.immutableEntry(2, "bar"), Maps.immutableEntry(1, "quux")) + .containsExactly(immutableEntry(2, "bar"), immutableEntry(1, "quux")) .inOrder(); } @@ -236,9 +242,7 @@ public void testInverseInsertionOrderAfterInverseForcePutPresentKey() { map.inverse().forcePut(4, "bar"); assertThat(map.entrySet()) .containsExactly( - Maps.immutableEntry("foo", 1), - Maps.immutableEntry("bar", 4), - Maps.immutableEntry("quux", 3)) + immutableEntry("foo", 1), immutableEntry("bar", 4), immutableEntry("quux", 3)) .inOrder(); } @@ -249,10 +253,10 @@ public void testInverseEntrySetValueNewKey() { Iterator> inverseEntryItr = map.inverse().entrySet().iterator(); Entry entry = inverseEntryItr.next(); entry.setValue(3); - assertEquals(Maps.immutableEntry("b", 2), inverseEntryItr.next()); + assertEquals(immutableEntry("b", 2), inverseEntryItr.next()); assertFalse(inverseEntryItr.hasNext()); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry(2, "b"), Maps.immutableEntry(3, "a")) + .containsExactly(immutableEntry(2, "b"), immutableEntry(3, "a")) .inOrder(); } } diff --git a/android/guava-tests/test/com/google/common/collect/HashMultimapTest.java b/android/guava-tests/test/com/google/common/collect/HashMultimapTest.java index 6cde6c56fda9..e9ad82d018cd 100644 --- a/android/guava-tests/test/com/google/common/collect/HashMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/HashMultimapTest.java @@ -16,8 +16,11 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -27,16 +30,20 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@link HashMultimap}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class HashMultimapTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -99,17 +106,9 @@ public void testCreateFromSizes() { } public void testCreateFromIllegalSizes() { - try { - HashMultimap.create(-20, 15); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HashMultimap.create(-20, 15)); - try { - HashMultimap.create(20, -15); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HashMultimap.create(20, -15)); } public void testEmptyMultimapsEqual() { diff --git a/android/guava-tests/test/com/google/common/collect/HashMultisetTest.java b/android/guava-tests/test/com/google/common/collect/HashMultisetTest.java index 030c927a209c..33ea608e9b79 100644 --- a/android/guava-tests/test/com/google/common/collect/HashMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/HashMultisetTest.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.google.MultisetFeature; @@ -27,10 +28,10 @@ import com.google.common.collect.testing.google.TestStringMultisetGenerator; import com.google.common.testing.SerializableTester; import java.io.Serializable; -import java.util.Arrays; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link HashMultiset}. @@ -38,10 +39,13 @@ * @author Kevin Bourrillion * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class HashMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -59,6 +63,8 @@ public static Test suite() { return suite; } + @J2ktIncompatible + @AndroidIncompatible // test-suite builders private static TestStringMultisetGenerator hashMultisetGenerator() { return new TestStringMultisetGenerator() { @Override @@ -85,11 +91,12 @@ public void testCreateWithSize() { } public void testCreateFromIterable() { - Multiset multiset = HashMultiset.create(Arrays.asList("foo", "bar", "foo")); + Multiset multiset = HashMultiset.create(asList("foo", "bar", "foo")); assertEquals(3, multiset.size()); assertEquals(2, multiset.count("foo")); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializationContainingSelf() { Multiset> multiset = HashMultiset.create(); @@ -99,17 +106,19 @@ public void testSerializationContainingSelf() { assertSame(copy, copy.iterator().next()); } + @J2ktIncompatible @GwtIncompatible // Only used by @GwtIncompatible code private static class MultisetHolder implements Serializable { - public Multiset member; + private final Multiset member; MultisetHolder(Multiset multiset) { this.member = multiset; } - private static final long serialVersionUID = 1L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1L; } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializationIndirectSelfReference() { Multiset multiset = HashMultiset.create(); diff --git a/android/guava-tests/test/com/google/common/collect/HashingTest.java b/android/guava-tests/test/com/google/common/collect/HashingTest.java index 5dfac4726ab9..07162702e118 100644 --- a/android/guava-tests/test/com/google/common/collect/HashingTest.java +++ b/android/guava-tests/test/com/google/common/collect/HashingTest.java @@ -18,9 +18,11 @@ import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** Tests for {@code Hashing}. */ @GwtCompatible +@NullMarked public class HashingTest extends TestCase { public void testSmear() { assertEquals(1459320713, smear(754102528)); diff --git a/guava/src/com/google/common/collect/ImmutableEntry.java b/android/guava-tests/test/com/google/common/collect/ImmutableBiMapInverseMapInterfaceTest.java similarity index 52% rename from guava/src/com/google/common/collect/ImmutableEntry.java rename to android/guava-tests/test/com/google/common/collect/ImmutableBiMapInverseMapInterfaceTest.java index 0f435e97069a..2bcf2a7e0793 100644 --- a/guava/src/com/google/common/collect/ImmutableEntry.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableBiMapInverseMapInterfaceTest.java @@ -17,34 +17,30 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import java.io.Serializable; -import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; -/** @see com.google.common.collect.Maps#immutableEntry(Object, Object) */ -@GwtCompatible(serializable = true) -class ImmutableEntry extends AbstractMapEntry implements Serializable { - final @Nullable K key; - final @Nullable V value; - - ImmutableEntry(@Nullable K key, @Nullable V value) { - this.key = key; - this.value = value; +@GwtCompatible +@NullUnmarked +public class ImmutableBiMapInverseMapInterfaceTest + extends AbstractImmutableBiMapMapInterfaceTest { + @Override + protected Map makeEmptyMap() { + return ImmutableBiMap.of(); } @Override - public final @Nullable K getKey() { - return key; + protected Map makePopulatedMap() { + return ImmutableBiMap.of(1, "one", 2, "two", 3, "three").inverse(); } @Override - public final @Nullable V getValue() { - return value; + protected String getKeyNotInPopulatedMap() { + return "minus one"; } @Override - public final V setValue(V value) { - throw new UnsupportedOperationException(); + protected Integer getValueNotInPopulatedMap() { + return -1; } - - private static final long serialVersionUID = 0; } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableBiMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableBiMapMapInterfaceTest.java new file mode 100644 index 000000000000..63bd444579db --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableBiMapMapInterfaceTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class ImmutableBiMapMapInterfaceTest + extends AbstractImmutableBiMapMapInterfaceTest { + @Override + protected Map makeEmptyMap() { + return ImmutableBiMap.of(); + } + + @Override + protected Map makePopulatedMap() { + return ImmutableBiMap.of("one", 1, "two", 2, "three", 3); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableBiMapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableBiMapTest.java index b4f99725702a..5affab2afe1b 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableBiMapTest.java @@ -16,13 +16,16 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Sets.newHashSet; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.singletonMap; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Joiner; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableBiMap.Builder; -import com.google.common.collect.testing.MapInterfaceTest; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -32,6 +35,7 @@ import com.google.common.collect.testing.google.BiMapInverseTester; import com.google.common.collect.testing.google.BiMapTestSuiteBuilder; import com.google.common.testing.SerializableTester; +import java.util.AbstractMap; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -40,26 +44,26 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ImmutableBiMap}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableBiMapTest extends TestCase { // TODO: Reduce duplication of ImmutableMapTest code + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); - suite.addTestSuite(MapTests.class); - suite.addTestSuite(InverseMapTests.class); - suite.addTestSuite(CreationTests.class); - suite.addTestSuite(BiMapSpecificTests.class); - suite.addTest( BiMapTestSuiteBuilder.using(new ImmutableBiMapGenerator()) .named("ImmutableBiMap") @@ -92,512 +96,541 @@ public static Test suite() { MapFeature.ALLOWS_ANY_NULL_QUERIES) .suppressing(BiMapInverseTester.getInverseSameAfterSerializingMethods()) .createTestSuite()); + suite.addTestSuite(ImmutableBiMapTest.class); return suite; } - public abstract static class AbstractMapTests extends MapInterfaceTest { - public AbstractMapTests() { - super(false, false, false, false, false); - } - - @Override - protected Map makeEmptyMap() { - throw new UnsupportedOperationException(); - } - - private static final Joiner joiner = Joiner.on(", "); - - @Override - protected void assertMoreInvariants(Map map) { + // Creation tests - BiMap bimap = (BiMap) map; - - for (Entry entry : map.entrySet()) { - assertEquals(entry.getKey() + "=" + entry.getValue(), entry.toString()); - assertEquals(entry.getKey(), bimap.inverse().get(entry.getValue())); - } - - assertEquals("{" + joiner.join(map.entrySet()) + "}", map.toString()); - assertEquals("[" + joiner.join(map.entrySet()) + "]", map.entrySet().toString()); - assertEquals("[" + joiner.join(map.keySet()) + "]", map.keySet().toString()); - assertEquals("[" + joiner.join(map.values()) + "]", map.values().toString()); - - assertEquals(Sets.newHashSet(map.entrySet()), map.entrySet()); - assertEquals(Sets.newHashSet(map.keySet()), map.keySet()); - } + public void testEmptyBuilder() { + ImmutableBiMap map = new Builder().build(); + assertEquals(Collections.emptyMap(), map); + assertEquals(Collections.emptyMap(), map.inverse()); + assertSame(ImmutableBiMap.of(), map); } - public static class MapTests extends AbstractMapTests { - @Override - protected Map makeEmptyMap() { - return ImmutableBiMap.of(); - } - - @Override - protected Map makePopulatedMap() { - return ImmutableBiMap.of("one", 1, "two", 2, "three", 3); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; - } + public void testSingletonBuilder() { + ImmutableBiMap map = new Builder().put("one", 1).build(); + assertMapEquals(map, "one", 1); + assertMapEquals(map.inverse(), 1, "one"); } - public static class InverseMapTests extends AbstractMapTests { - @Override - protected Map makeEmptyMap() { - return ImmutableBiMap.of(); - } + public void testBuilder_withImmutableEntry() { + ImmutableBiMap map = + new Builder().put(immutableEntry("one", 1)).build(); + assertMapEquals(map, "one", 1); + } - @Override - protected Map makePopulatedMap() { - return ImmutableBiMap.of(1, "one", 2, "two", 3, "three").inverse(); - } + public void testBuilder() { + ImmutableBiMap map = + ImmutableBiMap.builder() + .put("one", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .build(); + assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); + } - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } + @GwtIncompatible + public void testBuilderExactlySizedReusesArray() { + ImmutableBiMap.Builder builder = ImmutableBiMap.builderWithExpectedSize(10); + Object[] builderArray = builder.alternatingKeysAndValues; + for (int i = 0; i < 10; i++) { + builder.put(i, i); + } + Object[] builderArrayAfterPuts = builder.alternatingKeysAndValues; + RegularImmutableBiMap map = + (RegularImmutableBiMap) builder.build(); + Object[] mapInternalArray = map.alternatingKeysAndValues; + assertSame(builderArray, builderArrayAfterPuts); + assertSame(builderArray, mapInternalArray); + } - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; - } + public void testBuilder_orderEntriesByValue() { + ImmutableBiMap map = + ImmutableBiMap.builder() + .orderEntriesByValue(Ordering.natural()) + .put("three", 3) + .put("one", 1) + .put("five", 5) + .put("four", 4) + .put("two", 2) + .build(); + assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); } - public static class CreationTests extends TestCase { - public void testEmptyBuilder() { - ImmutableBiMap map = new Builder().build(); - assertEquals(Collections.emptyMap(), map); - assertEquals(Collections.emptyMap(), map.inverse()); - assertSame(ImmutableBiMap.of(), map); - } + public void testBuilder_orderEntriesByValueAfterExactSizeBuild() { + ImmutableBiMap.Builder builder = + new ImmutableBiMap.Builder(2).put("four", 4).put("one", 1); + ImmutableMap keyOrdered = builder.build(); + ImmutableMap valueOrdered = + builder.orderEntriesByValue(Ordering.natural()).build(); + assertMapEquals(keyOrdered, "four", 4, "one", 1); + assertMapEquals(valueOrdered, "one", 1, "four", 4); + } - public void testSingletonBuilder() { - ImmutableBiMap map = new Builder().put("one", 1).build(); - assertMapEquals(map, "one", 1); - assertMapEquals(map.inverse(), 1, "one"); - } + public void testBuilder_orderEntriesByValue_usedTwiceFails() { + ImmutableBiMap.Builder builder = + new Builder().orderEntriesByValue(Ordering.natural()); + assertThrows( + IllegalStateException.class, () -> builder.orderEntriesByValue(Ordering.natural())); + } - public void testBuilder_withImmutableEntry() { - ImmutableBiMap map = - new Builder().put(Maps.immutableEntry("one", 1)).build(); - assertMapEquals(map, "one", 1); - } + public void testBuilderPutAllWithEmptyMap() { + ImmutableBiMap map = + new Builder().putAll(Collections.emptyMap()).build(); + assertEquals(Collections.emptyMap(), map); + } - public void testBuilder() { - ImmutableBiMap map = - ImmutableBiMap.builder() - .put("one", 1) - .put("two", 2) - .put("three", 3) - .put("four", 4) - .put("five", 5) - .build(); - assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); - assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); - } + public void testBuilderPutAll() { + Map toPut = new LinkedHashMap<>(); + toPut.put("one", 1); + toPut.put("two", 2); + toPut.put("three", 3); + Map moreToPut = new LinkedHashMap<>(); + moreToPut.put("four", 4); + moreToPut.put("five", 5); + + ImmutableBiMap map = + new Builder().putAll(toPut).putAll(moreToPut).build(); + assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); + } - @GwtIncompatible - public void testBuilderExactlySizedReusesArray() { - ImmutableBiMap.Builder builder = ImmutableBiMap.builderWithExpectedSize(10); - Object[] builderArray = builder.alternatingKeysAndValues; - for (int i = 0; i < 10; i++) { - builder.put(i, i); - } - Object[] builderArrayAfterPuts = builder.alternatingKeysAndValues; - RegularImmutableBiMap map = - (RegularImmutableBiMap) builder.build(); - Object[] mapInternalArray = map.alternatingKeysAndValues; - assertSame(builderArray, builderArrayAfterPuts); - assertSame(builderArray, mapInternalArray); - } + public void testBuilderReuse() { + Builder builder = new Builder<>(); + ImmutableBiMap mapOne = builder.put("one", 1).put("two", 2).build(); + ImmutableBiMap mapTwo = builder.put("three", 3).put("four", 4).build(); - public void testBuilder_orderEntriesByValue() { - ImmutableBiMap map = - ImmutableBiMap.builder() - .orderEntriesByValue(Ordering.natural()) - .put("three", 3) - .put("one", 1) - .put("five", 5) - .put("four", 4) - .put("two", 2) - .build(); - assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); - assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); - } + assertMapEquals(mapOne, "one", 1, "two", 2); + assertMapEquals(mapOne.inverse(), 1, "one", 2, "two"); + assertMapEquals(mapTwo, "one", 1, "two", 2, "three", 3, "four", 4); + assertMapEquals(mapTwo.inverse(), 1, "one", 2, "two", 3, "three", 4, "four"); + } - public void testBuilder_orderEntriesByValueAfterExactSizeBuild() { - ImmutableBiMap.Builder builder = - new ImmutableBiMap.Builder(2).put("four", 4).put("one", 1); - ImmutableMap keyOrdered = builder.build(); - ImmutableMap valueOrdered = - builder.orderEntriesByValue(Ordering.natural()).build(); - assertMapEquals(keyOrdered, "four", 4, "one", 1); - assertMapEquals(valueOrdered, "one", 1, "four", 4); - } + public void testBuilderPutNullKey() { + Builder builder = new Builder<>(); + assertThrows(NullPointerException.class, () -> builder.put(null, 1)); + } - public void testBuilder_orderEntriesByValue_usedTwiceFails() { - ImmutableBiMap.Builder builder = - new Builder().orderEntriesByValue(Ordering.natural()); - try { - builder.orderEntriesByValue(Ordering.natural()); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - } + public void testBuilderPutNullValue() { + Builder builder = new Builder<>(); + assertThrows(NullPointerException.class, () -> builder.put("one", null)); + } - public void testBuilderPutAllWithEmptyMap() { - ImmutableBiMap map = - new Builder().putAll(Collections.emptyMap()).build(); - assertEquals(Collections.emptyMap(), map); - } + public void testBuilderPutNullKeyViaPutAll() { + Builder builder = new Builder<>(); + assertThrows( + NullPointerException.class, + () -> builder.putAll(Collections.singletonMap(null, 1))); + } - public void testBuilderPutAll() { - Map toPut = new LinkedHashMap<>(); - toPut.put("one", 1); - toPut.put("two", 2); - toPut.put("three", 3); - Map moreToPut = new LinkedHashMap<>(); - moreToPut.put("four", 4); - moreToPut.put("five", 5); - - ImmutableBiMap map = - new Builder().putAll(toPut).putAll(moreToPut).build(); - assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); - assertMapEquals(map.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); - } + public void testBuilderPutNullValueViaPutAll() { + Builder builder = new Builder<>(); + assertThrows( + NullPointerException.class, + () -> builder.putAll(Collections.singletonMap("one", null))); + } - public void testBuilderReuse() { - Builder builder = new Builder<>(); - ImmutableBiMap mapOne = builder.put("one", 1).put("two", 2).build(); - ImmutableBiMap mapTwo = builder.put("three", 3).put("four", 4).build(); + @SuppressWarnings("AlwaysThrows") + public void testPuttingTheSameKeyTwiceThrowsOnBuild() { + Builder builder = + new Builder() + .put("one", 1) + .put("one", 1); // throwing on this line would be even better - assertMapEquals(mapOne, "one", 1, "two", 2); - assertMapEquals(mapOne.inverse(), 1, "one", 2, "two"); - assertMapEquals(mapTwo, "one", 1, "two", 2, "three", 3, "four", 4); - assertMapEquals(mapTwo.inverse(), 1, "one", 2, "two", 3, "three", 4, "four"); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> builder.build()); + assertThat(expected).hasMessageThat().contains("one"); + } - public void testBuilderPutNullKey() { - Builder builder = new Builder<>(); - try { - builder.put(null, 1); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOf() { + assertMapEquals(ImmutableBiMap.of("one", 1), "one", 1); + assertMapEquals(ImmutableBiMap.of("one", 1).inverse(), 1, "one"); + assertMapEquals(ImmutableBiMap.of("one", 1, "two", 2), "one", 1, "two", 2); + assertMapEquals(ImmutableBiMap.of("one", 1, "two", 2).inverse(), 1, "one", 2, "two"); + assertMapEquals( + ImmutableBiMap.of("one", 1, "two", 2, "three", 3), "one", 1, "two", 2, "three", 3); + assertMapEquals( + ImmutableBiMap.of("one", 1, "two", 2, "three", 3).inverse(), + 1, + "one", + 2, + "two", + 3, + "three"); + assertMapEquals( + ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4); + assertMapEquals( + ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4).inverse(), + 1, + "one", + 2, + "two", + 3, + "three", + 4, + "four"); + assertMapEquals( + ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5); + assertMapEquals( + ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5).inverse(), + 1, + "one", + 2, + "two", + 3, + "three", + 4, + "four", + 5, + "five"); + assertMapEquals( + ImmutableBiMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6); + assertMapEquals( + ImmutableBiMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6, + "seven", + 7); + assertMapEquals( + ImmutableBiMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6, + "seven", + 7, + "eight", + 8); + assertMapEquals( + ImmutableBiMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8, + "nine", 9), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6, + "seven", + 7, + "eight", + 8, + "nine", + 9); + assertMapEquals( + ImmutableBiMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8, + "nine", 9, + "ten", 10), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6, + "seven", + 7, + "eight", + 8, + "nine", + 9, + "ten", + 10); + } - public void testBuilderPutNullValue() { - Builder builder = new Builder<>(); - try { - builder.put("one", null); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOfNullKey() { + assertThrows(NullPointerException.class, () -> ImmutableBiMap.of(null, 1)); - public void testBuilderPutNullKeyViaPutAll() { - Builder builder = new Builder<>(); - try { - builder.putAll(Collections.singletonMap(null, 1)); - fail(); - } catch (NullPointerException expected) { - } - } + assertThrows(NullPointerException.class, () -> ImmutableBiMap.of("one", 1, null, 2)); + } - public void testBuilderPutNullValueViaPutAll() { - Builder builder = new Builder<>(); - try { - builder.putAll(Collections.singletonMap("one", null)); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOfNullValue() { + assertThrows(NullPointerException.class, () -> ImmutableBiMap.of("one", null)); - public void testPuttingTheSameKeyTwiceThrowsOnBuild() { - Builder builder = - new Builder() - .put("one", 1) - .put("one", 1); // throwing on this line would be even better - - try { - builder.build(); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()).contains("one"); - } - } + assertThrows(NullPointerException.class, () -> ImmutableBiMap.of("one", 1, "two", null)); + } - public void testOf() { - assertMapEquals(ImmutableBiMap.of("one", 1), "one", 1); - assertMapEquals(ImmutableBiMap.of("one", 1).inverse(), 1, "one"); - assertMapEquals(ImmutableBiMap.of("one", 1, "two", 2), "one", 1, "two", 2); - assertMapEquals(ImmutableBiMap.of("one", 1, "two", 2).inverse(), 1, "one", 2, "two"); - assertMapEquals( - ImmutableBiMap.of("one", 1, "two", 2, "three", 3), "one", 1, "two", 2, "three", 3); - assertMapEquals( - ImmutableBiMap.of("one", 1, "two", 2, "three", 3).inverse(), - 1, - "one", - 2, - "two", - 3, - "three"); - assertMapEquals( - ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4), - "one", - 1, - "two", - 2, - "three", - 3, - "four", - 4); - assertMapEquals( - ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4).inverse(), - 1, - "one", - 2, - "two", - 3, - "three", - 4, - "four"); - assertMapEquals( - ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5), - "one", - 1, - "two", - 2, - "three", - 3, - "four", - 4, - "five", - 5); - assertMapEquals( - ImmutableBiMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5).inverse(), - 1, - "one", - 2, - "two", - 3, - "three", - 4, - "four", - 5, - "five"); - } + @SuppressWarnings({"AlwaysThrows", "DistinctVarargsChecker"}) + public void testOfWithDuplicateKey() { + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> ImmutableBiMap.of("one", 1, "one", 1)); + assertThat(expected).hasMessageThat().contains("one"); + } - public void testOfNullKey() { - try { - ImmutableBiMap.of(null, 1); - fail(); - } catch (NullPointerException expected) { - } - - try { - ImmutableBiMap.of("one", 1, null, 2); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOfEntries() { + assertMapEquals(ImmutableBiMap.ofEntries(entry("one", 1), entry("two", 2)), "one", 1, "two", 2); + } - public void testOfNullValue() { - try { - ImmutableBiMap.of("one", null); - fail(); - } catch (NullPointerException expected) { - } - - try { - ImmutableBiMap.of("one", 1, "two", null); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOfEntriesNull() { + Entry<@Nullable Integer, Integer> nullKey = entry(null, 23); + assertThrows( + NullPointerException.class, + () -> ImmutableBiMap.ofEntries((Entry) nullKey)); + Entry nullValue = + ImmutableBiMapTest.<@Nullable Integer>entry(23, null); + assertThrows( + NullPointerException.class, + () -> ImmutableBiMap.ofEntries((Entry) nullValue)); + } - public void testOfWithDuplicateKey() { - try { - ImmutableBiMap.of("one", 1, "one", 1); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()).contains("one"); - } - } + private static Entry entry(T key, T value) { + return new AbstractMap.SimpleImmutableEntry<>(key, value); + } - public void testCopyOfEmptyMap() { - ImmutableBiMap copy = - ImmutableBiMap.copyOf(Collections.emptyMap()); - assertEquals(Collections.emptyMap(), copy); - assertSame(copy, ImmutableBiMap.copyOf(copy)); - assertSame(ImmutableBiMap.of(), copy); - } + public void testCopyOfEmptyMap() { + ImmutableBiMap copy = + ImmutableBiMap.copyOf(Collections.emptyMap()); + assertEquals(Collections.emptyMap(), copy); + assertSame(copy, ImmutableBiMap.copyOf(copy)); + assertSame(ImmutableBiMap.of(), copy); + } - public void testCopyOfSingletonMap() { - ImmutableBiMap copy = - ImmutableBiMap.copyOf(Collections.singletonMap("one", 1)); - assertMapEquals(copy, "one", 1); - assertSame(copy, ImmutableBiMap.copyOf(copy)); - } + public void testCopyOfSingletonMap() { + ImmutableBiMap copy = ImmutableBiMap.copyOf(singletonMap("one", 1)); + assertMapEquals(copy, "one", 1); + assertSame(copy, ImmutableBiMap.copyOf(copy)); + } - public void testCopyOf() { - Map original = new LinkedHashMap<>(); - original.put("one", 1); - original.put("two", 2); - original.put("three", 3); + public void testCopyOf() { + Map original = new LinkedHashMap<>(); + original.put("one", 1); + original.put("two", 2); + original.put("three", 3); - ImmutableBiMap copy = ImmutableBiMap.copyOf(original); - assertMapEquals(copy, "one", 1, "two", 2, "three", 3); - assertSame(copy, ImmutableBiMap.copyOf(copy)); - } + ImmutableBiMap copy = ImmutableBiMap.copyOf(original); + assertMapEquals(copy, "one", 1, "two", 2, "three", 3); + assertSame(copy, ImmutableBiMap.copyOf(copy)); + } - public void testEmpty() { - ImmutableBiMap bimap = ImmutableBiMap.of(); - assertEquals(Collections.emptyMap(), bimap); - assertEquals(Collections.emptyMap(), bimap.inverse()); - } + public void testEmpty() { + ImmutableBiMap bimap = ImmutableBiMap.of(); + assertEquals(Collections.emptyMap(), bimap); + assertEquals(Collections.emptyMap(), bimap.inverse()); + } - public void testFromHashMap() { - Map hashMap = Maps.newLinkedHashMap(); - hashMap.put("one", 1); - hashMap.put("two", 2); - ImmutableBiMap bimap = - ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); - assertMapEquals(bimap, "one", 1, "two", 2); - assertMapEquals(bimap.inverse(), 1, "one", 2, "two"); - } + public void testFromHashMap() { + Map hashMap = new LinkedHashMap<>(); + hashMap.put("one", 1); + hashMap.put("two", 2); + ImmutableBiMap bimap = ImmutableBiMap.copyOf(hashMap); + assertMapEquals(bimap, "one", 1, "two", 2); + assertMapEquals(bimap.inverse(), 1, "one", 2, "two"); + } - public void testFromImmutableMap() { - ImmutableBiMap bimap = - ImmutableBiMap.copyOf( - new ImmutableMap.Builder() - .put("one", 1) - .put("two", 2) - .put("three", 3) - .put("four", 4) - .put("five", 5) - .build()); - assertMapEquals(bimap, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); - assertMapEquals(bimap.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); - } + public void testFromImmutableMap() { + ImmutableBiMap bimap = + ImmutableBiMap.copyOf( + new ImmutableMap.Builder() + .put("one", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .buildOrThrow()); + assertMapEquals(bimap, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + assertMapEquals(bimap.inverse(), 1, "one", 2, "two", 3, "three", 4, "four", 5, "five"); + } - public void testDuplicateValues() { - ImmutableMap map = - new ImmutableMap.Builder() - .put("one", 1) - .put("two", 2) - .put("uno", 1) - .put("dos", 2) - .build(); - - try { - ImmutableBiMap.copyOf(map); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()).contains("1"); - } - } + public void testDuplicateValues() { + ImmutableMap map = + new ImmutableMap.Builder() + .put("one", 1) + .put("two", 2) + .put("uno", 1) + .put("dos", 2) + .buildOrThrow(); + + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> ImmutableBiMap.copyOf(map)); + assertThat(expected).hasMessageThat().containsMatch("1|2"); + } - // TODO(b/172823566): Use mainline testToImmutableBiMap once CollectorTester is usable to java7. - public void testToImmutableBiMap_java7_combine() { - ImmutableBiMap.Builder zis = - ImmutableBiMap.builder().put("one", 1); - ImmutableBiMap.Builder zat = - ImmutableBiMap.builder().put("two", 2).put("three", 3); - ImmutableBiMap biMap = zis.combine(zat).build(); - assertMapEquals(biMap, "one", 1, "two", 2, "three", 3); - } + // TODO(b/172823566): Use mainline testToImmutableBiMap once CollectorTester is usable to java7. + public void testToImmutableBiMap_java7_combine() { + ImmutableBiMap.Builder zis = + ImmutableBiMap.builder().put("one", 1); + ImmutableBiMap.Builder zat = + ImmutableBiMap.builder().put("two", 2).put("three", 3); + ImmutableBiMap biMap = zis.combine(zat).build(); + assertMapEquals(biMap, "one", 1, "two", 2, "three", 3); + } - // TODO(b/172823566): Use mainline testToImmutableBiMap once CollectorTester is usable to java7. - public void testToImmutableBiMap_exceptionOnDuplicateKey_java7_combine() { - ImmutableBiMap.Builder zis = - ImmutableBiMap.builder().put("one", 1).put("two", 2); - ImmutableBiMap.Builder zat = - ImmutableBiMap.builder().put("two", 22).put("three", 3); - try { - zis.combine(zat).build(); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // expected - } - } + // TODO(b/172823566): Use mainline testToImmutableBiMap once CollectorTester is usable to java7. + public void testToImmutableBiMap_exceptionOnDuplicateKey_java7_combine() { + ImmutableBiMap.Builder zis = + ImmutableBiMap.builder().put("one", 1).put("two", 2); + ImmutableBiMap.Builder zat = + ImmutableBiMap.builder().put("two", 22).put("three", 3); + assertThrows(IllegalArgumentException.class, () -> zis.combine(zat).build()); } - public static class BiMapSpecificTests extends TestCase { + // BiMap-specific tests - public void testForcePut() { - BiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); - try { - bimap.forcePut("three", 3); - fail(); - } catch (UnsupportedOperationException expected) { - } - } + @SuppressWarnings("DoNotCall") + public void testForcePut() { + BiMap bimap = ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); + assertThrows(UnsupportedOperationException.class, () -> bimap.forcePut("three", 3)); + } - public void testKeySet() { - ImmutableBiMap bimap = - ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4)); - Set keys = bimap.keySet(); - assertEquals(Sets.newHashSet("one", "two", "three", "four"), keys); - assertThat(keys).containsExactly("one", "two", "three", "four").inOrder(); - } + public void testKeySet() { + ImmutableBiMap bimap = + ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4)); + Set keys = bimap.keySet(); + assertEquals(newHashSet("one", "two", "three", "four"), keys); + assertThat(keys).containsExactly("one", "two", "three", "four").inOrder(); + } - public void testValues() { - ImmutableBiMap bimap = - ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4)); - Set values = bimap.values(); - assertEquals(Sets.newHashSet(1, 2, 3, 4), values); - assertThat(values).containsExactly(1, 2, 3, 4).inOrder(); - } + public void testValues() { + ImmutableBiMap bimap = + ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4)); + Set values = bimap.values(); + assertEquals(newHashSet(1, 2, 3, 4), values); + assertThat(values).containsExactly(1, 2, 3, 4).inOrder(); + } - public void testDoubleInverse() { - ImmutableBiMap bimap = - ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); - assertSame(bimap, bimap.inverse().inverse()); - } + public void testDoubleInverse() { + ImmutableBiMap bimap = + ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); + assertSame(bimap, bimap.inverse().inverse()); + } - @GwtIncompatible // SerializableTester - public void testEmptySerialization() { - ImmutableBiMap bimap = ImmutableBiMap.of(); - assertSame(bimap, SerializableTester.reserializeAndAssert(bimap)); - } + @J2ktIncompatible + @GwtIncompatible // SerializableTester + public void testEmptySerialization() { + ImmutableBiMap bimap = ImmutableBiMap.of(); + assertSame(bimap, SerializableTester.reserializeAndAssert(bimap)); + } - @GwtIncompatible // SerializableTester - public void testSerialization() { - ImmutableBiMap bimap = - ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); - ImmutableBiMap copy = SerializableTester.reserializeAndAssert(bimap); - assertEquals(Integer.valueOf(1), copy.get("one")); - assertEquals("one", copy.inverse().get(1)); - assertSame(copy, copy.inverse().inverse()); - } + @J2ktIncompatible + @GwtIncompatible // SerializableTester + public void testSerialization() { + ImmutableBiMap bimap = + ImmutableBiMap.copyOf(ImmutableMap.of("one", 1, "two", 2)); + ImmutableBiMap copy = SerializableTester.reserializeAndAssert(bimap); + assertEquals(Integer.valueOf(1), copy.get("one")); + assertEquals("one", copy.inverse().get(1)); + assertSame(copy, copy.inverse().inverse()); + } - @GwtIncompatible // SerializableTester - public void testInverseSerialization() { - ImmutableBiMap bimap = - ImmutableBiMap.copyOf(ImmutableMap.of(1, "one", 2, "two")).inverse(); - ImmutableBiMap copy = SerializableTester.reserializeAndAssert(bimap); - assertEquals(Integer.valueOf(1), copy.get("one")); - assertEquals("one", copy.inverse().get(1)); - assertSame(copy, copy.inverse().inverse()); - } + @J2ktIncompatible + @GwtIncompatible // SerializableTester + public void testInverseSerialization() { + ImmutableBiMap bimap = + ImmutableBiMap.copyOf(ImmutableMap.of(1, "one", 2, "two")).inverse(); + ImmutableBiMap copy = SerializableTester.reserializeAndAssert(bimap); + assertEquals(Integer.valueOf(1), copy.get("one")); + assertEquals("one", copy.inverse().get(1)); + assertSame(copy, copy.inverse().inverse()); } private static void assertMapEquals(Map map, Object... alternatingKeysAndValues) { - int i = 0; - for (Entry entry : map.entrySet()) { - assertEquals(alternatingKeysAndValues[i++], entry.getKey()); - assertEquals(alternatingKeysAndValues[i++], entry.getValue()); + Map expected = new LinkedHashMap<>(); + for (int i = 0; i < alternatingKeysAndValues.length; i += 2) { + expected.put(alternatingKeysAndValues[i], alternatingKeysAndValues[i + 1]); } + assertThat(map).containsExactlyEntriesIn(expected).inOrder(); } /** No-op test so that the class has at least one method, making Maven's test runner happy. */ diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableClassToInstanceMapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableClassToInstanceMapTest.java index bce73b8f759b..19654d1513c8 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableClassToInstanceMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableClassToInstanceMapTest.java @@ -17,6 +17,10 @@ package com.google.common.collect; import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertThrows; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -26,20 +30,24 @@ import com.google.common.collect.testing.features.MapFeature; import com.google.common.testing.SerializableTester; import java.io.Serializable; -import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link ImmutableClassToInstanceMap}. * * @author Kevin Bourrillion */ +@NullUnmarked public class ImmutableClassToInstanceMapTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(ImmutableClassToInstanceMapTest.class); @@ -50,13 +58,13 @@ public static Test suite() { // Other tests will verify what real, warning-free usage looks like // but here we have to do some serious fudging @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) public Map create(Object... elements) { ImmutableClassToInstanceMap.Builder builder = ImmutableClassToInstanceMap.builder(); for (Object object : elements) { - Entry entry = (Entry) object; - builder.put(entry.getKey(), entry.getValue()); + Entry entry = (Entry) object; + builder.put((Class) entry.getKey(), (Impl) entry.getValue()); } return (Map) builder.build(); } @@ -81,7 +89,7 @@ public void testSerialization_empty() { } public void testCopyOf_map_empty() { - Map, Object> in = Collections.emptyMap(); + Map, Object> in = emptyMap(); ClassToInstanceMap map = ImmutableClassToInstanceMap.copyOf(in); assertTrue(map.isEmpty()); assertSame(map, ImmutableClassToInstanceMap.of()); @@ -98,7 +106,7 @@ public void testOf_one() { } public void testCopyOf_map_valid() { - Map, Number> in = Maps.newHashMap(); + Map, Number> in = new HashMap<>(); in.put(Number.class, 0); in.put(Double.class, Math.PI); ClassToInstanceMap map = ImmutableClassToInstanceMap.copyOf(in); @@ -108,30 +116,21 @@ public void testCopyOf_map_valid() { assertEquals(0, zero); Double pi = map.getInstance(Double.class); - assertEquals(Math.PI, pi, 0.0); + assertThat(pi).isEqualTo(Math.PI); assertSame(map, ImmutableClassToInstanceMap.copyOf(map)); } public void testCopyOf_map_nulls() { - Map, Number> nullKey = Collections.singletonMap(null, (Number) 1.0); - try { - ImmutableClassToInstanceMap.copyOf(nullKey); - fail(); - } catch (NullPointerException expected) { - } + Map, Number> nullKey = singletonMap(null, (Number) 1.0); + assertThrows(NullPointerException.class, () -> ImmutableClassToInstanceMap.copyOf(nullKey)); - Map, Number> nullValue = - Collections.singletonMap(Number.class, null); - try { - ImmutableClassToInstanceMap.copyOf(nullValue); - fail(); - } catch (NullPointerException expected) { - } + Map, Number> nullValue = singletonMap(Number.class, null); + assertThrows(NullPointerException.class, () -> ImmutableClassToInstanceMap.copyOf(nullValue)); } public void testCopyOf_imap_empty() { - Map, Object> in = Collections.emptyMap(); + Map, Object> in = emptyMap(); ClassToInstanceMap map = ImmutableClassToInstanceMap.copyOf(in); assertTrue(map.isEmpty()); } @@ -146,7 +145,7 @@ public void testCopyOf_imap_valid() { assertEquals(0, zero); Double pi = map.getInstance(Double.class); - assertEquals(Math.PI, pi, 0.0); + assertThat(pi).isEqualTo(Math.PI); } public void testPrimitiveAndWrapper() { @@ -161,11 +160,12 @@ public void testPrimitiveAndWrapper() { assertEquals(1, (int) ictim.getInstance(int.class)); } + @SuppressWarnings("rawtypes") // TODO(cpovirk): Can we at least use Class in some places? abstract static class TestClassToInstanceMapGenerator implements TestMapGenerator { @Override - public Class[] createKeyArray(int length) { - return new Class[length]; + public Class[] createKeyArray(int length) { + return new Class[length]; } @Override @@ -186,7 +186,7 @@ public SampleElements> samples() { @Override @SuppressWarnings("unchecked") public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -213,7 +213,7 @@ static final class Impl implements One, Two, Three, Four, Five, Serializable { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return obj instanceof Impl && value == ((Impl) obj).value; } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableCollectionTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableCollectionTest.java index 260c43459b3d..59d794dfa6f2 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableCollectionTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableCollectionTest.java @@ -17,12 +17,14 @@ package com.google.common.collect; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code ImmutableCollection}. * * @author Louis Wasserman */ +@NullUnmarked public class ImmutableCollectionTest extends TestCase { public void testCapacityExpansion() { assertEquals(1, ImmutableCollection.Builder.expandedCapacity(0, 1)); diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableEnumMapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableEnumMapTest.java index 189db72e6b5e..e5766b41466c 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableEnumMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableEnumMapTest.java @@ -17,35 +17,41 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_QUERIES; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.collect.testing.AnEnum; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.TestEnumMapGenerator; import com.google.common.collect.testing.features.CollectionSize; +import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Tests for {@code ImmutableEnumMap}. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableEnumMapTest extends TestCase { + @J2ktIncompatible + @AndroidIncompatible // test-suite builders public static class ImmutableEnumMapGenerator extends TestEnumMapGenerator { @Override protected Map create(Entry[] entries) { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); for (Entry entry : entries) { map.put(entry.getKey(), entry.getValue()); } @@ -53,7 +59,9 @@ protected Map create(Entry[] entries) { } } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -80,7 +88,7 @@ public AnEnum apply(AnEnum ae) { } }); ImmutableMap copy = Maps.immutableEnumMap(map); - assertThat(copy.entrySet()).containsExactly(Helpers.mapEntry(AnEnum.A, AnEnum.A)); + assertThat(copy.entrySet()).containsExactly(mapEntry(AnEnum.A, AnEnum.A)); } public void testEmptyImmutableEnumMap() { @@ -93,10 +101,7 @@ public void testImmutableEnumMapOrdering() { Maps.immutableEnumMap(ImmutableMap.of(AnEnum.C, "c", AnEnum.A, "a", AnEnum.E, "e")); assertThat(map.entrySet()) - .containsExactly( - Helpers.mapEntry(AnEnum.A, "a"), - Helpers.mapEntry(AnEnum.C, "c"), - Helpers.mapEntry(AnEnum.E, "e")) + .containsExactly(mapEntry(AnEnum.A, "a"), mapEntry(AnEnum.C, "c"), mapEntry(AnEnum.E, "e")) .inOrder(); } } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableListCopyOfConcurrentlyModifiedInputTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableListCopyOfConcurrentlyModifiedInputTest.java new file mode 100644 index 000000000000..0ee7675e0deb --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableListCopyOfConcurrentlyModifiedInputTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Iterables.unmodifiableIterable; +import static com.google.common.reflect.Reflection.newProxy; +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtIncompatible; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // reflection +@NullUnmarked +public class ImmutableListCopyOfConcurrentlyModifiedInputTest extends TestCase { + enum WrapWithIterable { + WRAP, + NO_WRAP + } + + private static void runConcurrentlyMutatedTest( + Collection initialContents, + Iterable actionsToPerformConcurrently, + WrapWithIterable wrap) { + ConcurrentlyMutatedList concurrentlyMutatedList = + newConcurrentlyMutatedList(initialContents, actionsToPerformConcurrently); + + Iterable iterableToCopy = + wrap == WrapWithIterable.WRAP + ? unmodifiableIterable(concurrentlyMutatedList) + : concurrentlyMutatedList; + + ImmutableList copyOfIterable = ImmutableList.copyOf(iterableToCopy); + + assertTrue(concurrentlyMutatedList.getAllStates().contains(copyOfIterable)); + } + + private static void runConcurrentlyMutatedTest(WrapWithIterable wrap) { + /* + * TODO: Iterate over many array sizes and all possible operation lists, + * performing adds and removes in different ways. + */ + runConcurrentlyMutatedTest(elements(), ops(add(1), add(2)), wrap); + + runConcurrentlyMutatedTest(elements(), ops(add(1), nop()), wrap); + + runConcurrentlyMutatedTest(elements(), ops(add(1), remove()), wrap); + + runConcurrentlyMutatedTest(elements(), ops(nop(), add(1)), wrap); + + runConcurrentlyMutatedTest(elements(1), ops(remove(), nop()), wrap); + + runConcurrentlyMutatedTest(elements(1), ops(remove(), add(2)), wrap); + + runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), remove()), wrap); + + runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), nop()), wrap); + + runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), add(3)), wrap); + + runConcurrentlyMutatedTest(elements(1, 2), ops(nop(), remove()), wrap); + + runConcurrentlyMutatedTest(elements(1, 2, 3), ops(remove(), remove()), wrap); + } + + private static ImmutableList elements(Integer... elements) { + return ImmutableList.copyOf(elements); + } + + private static ImmutableList ops(ListFrobber... elements) { + return ImmutableList.copyOf(elements); + } + + public void testCopyOf_concurrentlyMutatedList() { + runConcurrentlyMutatedTest(WrapWithIterable.NO_WRAP); + } + + public void testCopyOf_concurrentlyMutatedIterable() { + runConcurrentlyMutatedTest(WrapWithIterable.WRAP); + } + + /** An operation to perform on a list. */ + interface ListFrobber { + void perform(List list); + } + + static ListFrobber add(int element) { + return new ListFrobber() { + @Override + public void perform(List list) { + list.add(0, element); + } + }; + } + + static ListFrobber remove() { + return new ListFrobber() { + @Override + public void perform(List list) { + list.remove(0); + } + }; + } + + static ListFrobber nop() { + return new ListFrobber() { + @Override + public void perform(List list) {} + }; + } + + /** A list that mutates itself after every call to each of its {@link List} methods. */ + interface ConcurrentlyMutatedList extends List { + /** + * The elements of a {@link ConcurrentlyMutatedList} are added and removed over time. This + * method returns every state that the list has passed through at some point. + */ + Set> getAllStates(); + } + + /** + * Returns a {@link ConcurrentlyMutatedList} that performs the given operations as its concurrent + * modifications. The mutations occur in the same thread as the triggering method call. + */ + private static ConcurrentlyMutatedList newConcurrentlyMutatedList( + Collection initialContents, Iterable actionsToPerformConcurrently) { + InvocationHandler invocationHandler = + new InvocationHandler() { + final CopyOnWriteArrayList delegate = + new CopyOnWriteArrayList<>(initialContents); + + final Method getAllStatesMethod = + getOnlyElement(asList(ConcurrentlyMutatedList.class.getDeclaredMethods())); + + final Iterator remainingActions = actionsToPerformConcurrently.iterator(); + + final Set> allStates = new HashSet<>(); + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return method.equals(getAllStatesMethod) + ? getAllStates() + : invokeListMethod(method, args); + } + + private Set> getAllStates() { + return allStates; + } + + private Object invokeListMethod(Method method, Object[] args) throws Throwable { + try { + Object returnValue = method.invoke(delegate, args); + mutateDelegate(); + return returnValue; + } catch (InvocationTargetException e) { + throw e.getCause(); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + + private void mutateDelegate() { + allStates.add(ImmutableList.copyOf(delegate)); + remainingActions.next().perform(delegate); + allStates.add(ImmutableList.copyOf(delegate)); + } + }; + + @SuppressWarnings("unchecked") + ConcurrentlyMutatedList list = + newProxy(ConcurrentlyMutatedList.class, invocationHandler); + return list; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableListMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableListMultimapTest.java index 768ebecde560..a1493263a039 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableListMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableListMultimapTest.java @@ -16,19 +16,24 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_ANY_NULL_QUERIES; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableListMultimap.Builder; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.google.ListMultimapTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringListMultimapGenerator; import com.google.common.collect.testing.google.UnmodifiableCollectionTests; import com.google.common.testing.EqualsTester; +import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.util.Arrays; import java.util.Collection; @@ -37,14 +42,19 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ImmutableListMultimap}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableListMultimapTest extends TestCase { + @J2ktIncompatible + @AndroidIncompatible // test-suite builders public static class ImmutableListMultimapGenerator extends TestStringListMultimapGenerator { @Override protected ListMultimap create(Entry[] entries) { @@ -56,6 +66,8 @@ protected ListMultimap create(Entry[] entries) { } } + @J2ktIncompatible + @AndroidIncompatible // test-suite builders public static class ImmutableListMultimapCopyOfEntriesGenerator extends TestStringListMultimapGenerator { @Override @@ -64,7 +76,9 @@ protected ListMultimap create(Entry[] entries) { } } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -81,6 +95,44 @@ public static Test suite() { return suite; } + public void testBuilderWithExpectedKeysNegative() { + assertThrows( + IllegalArgumentException.class, () -> ImmutableListMultimap.builderWithExpectedKeys(-1)); + } + + public void testBuilderWithExpectedKeysZero() { + ImmutableListMultimap.Builder builder = + ImmutableListMultimap.builderWithExpectedKeys(0); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedKeysPositive() { + ImmutableListMultimap.Builder builder = + ImmutableListMultimap.builderWithExpectedKeys(1); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedValuesPerKeyNegative() { + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + assertThrows(IllegalArgumentException.class, () -> builder.expectedValuesPerKey(-1)); + } + + public void testBuilderWithExpectedValuesPerKeyZero() { + ImmutableListMultimap.Builder builder = + ImmutableListMultimap.builder().expectedValuesPerKey(0); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedValuesPerKeyPositive() { + ImmutableListMultimap.Builder builder = + ImmutableListMultimap.builder().expectedValuesPerKey(1); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + public void testBuilder_withImmutableEntry() { ImmutableListMultimap multimap = new Builder().put(Maps.immutableEntry("one", 1)).build(); @@ -89,25 +141,19 @@ public void testBuilder_withImmutableEntry() { public void testBuilder_withImmutableEntryAndNullContents() { Builder builder = new Builder<>(); - try { - builder.put(Maps.immutableEntry("one", (Integer) null)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.put(Maps.immutableEntry((String) null, 1)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> builder.put(Maps.immutableEntry("one", (Integer) null))); + assertThrows( + NullPointerException.class, () -> builder.put(Maps.immutableEntry((String) null, 1))); } private static class StringHolder { - String string; + @Nullable String string; } public void testBuilder_withMutableEntry() { ImmutableListMultimap.Builder builder = new Builder<>(); - final StringHolder holder = new StringHolder(); + StringHolder holder = new StringHolder(); holder.string = "one"; Entry entry = new AbstractMapEntry() { @@ -133,8 +179,8 @@ public void testBuilderPutAllIterable() { builder.putAll("bar", Arrays.asList(4, 5)); builder.putAll("foo", Arrays.asList(6, 7)); Multimap multimap = builder.build(); - assertEquals(Arrays.asList(1, 2, 3, 6, 7), multimap.get("foo")); - assertEquals(Arrays.asList(4, 5), multimap.get("bar")); + assertThat(multimap.get("foo")).containsExactly(1, 2, 3, 6, 7).inOrder(); + assertThat(multimap.get("bar")).containsExactly(4, 5).inOrder(); assertEquals(7, multimap.size()); } @@ -143,9 +189,9 @@ public void testBuilderPutAllVarargs() { builder.putAll("foo", 1, 2, 3); builder.putAll("bar", 4, 5); builder.putAll("foo", 6, 7); - Multimap multimap = builder.build(); - assertEquals(Arrays.asList(1, 2, 3, 6, 7), multimap.get("foo")); - assertEquals(Arrays.asList(4, 5), multimap.get("bar")); + ImmutableListMultimap multimap = builder.build(); + assertThat(multimap.get("foo")).containsExactly(1, 2, 3, 6, 7).inOrder(); + assertThat(multimap.get("bar")).containsExactly(4, 5).inOrder(); assertEquals(7, multimap.size()); } @@ -162,9 +208,9 @@ public void testBuilderPutAllMultimap() { ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); builder.putAll(toPut); builder.putAll(moreToPut); - Multimap multimap = builder.build(); - assertEquals(Arrays.asList(1, 2, 3, 6, 7), multimap.get("foo")); - assertEquals(Arrays.asList(4, 5), multimap.get("bar")); + ImmutableListMultimap multimap = builder.build(); + assertThat(multimap.get("foo")).containsExactly(1, 2, 3, 6, 7).inOrder(); + assertThat(multimap.get("bar")).containsExactly(4, 5).inOrder(); assertEquals(7, multimap.size()); } @@ -205,62 +251,33 @@ public void testBuilderPutAllMultimapWithDuplicates() { ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); builder.putAll(toPut); builder.putAll(moreToPut); - Multimap multimap = builder.build(); - assertEquals(Arrays.asList(1, 2, 1, 6, 7, 2), multimap.get("foo")); - assertEquals(Arrays.asList(4, 5, 4), multimap.get("bar")); + ImmutableListMultimap multimap = builder.build(); + assertThat(multimap.get("foo")).containsExactly(1, 2, 1, 6, 7, 2).inOrder(); + assertThat(multimap.get("bar")).containsExactly(4, 5, 4).inOrder(); assertEquals(9, multimap.size()); } public void testBuilderPutNullKey() { - Multimap toPut = LinkedListMultimap.create(); - toPut.put("foo", null); + Multimap<@Nullable String, Integer> toPut = LinkedListMultimap.create(); + toPut.put(null, 1); ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); - try { - builder.put(null, 1); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll(null, Arrays.asList(1, 2, 3)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll(null, 1, 2, 3); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll(toPut); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.put(null, 1)); + assertThrows(NullPointerException.class, () -> builder.putAll(null, Arrays.asList(1, 2, 3))); + assertThrows(NullPointerException.class, () -> builder.putAll(null, 1, 2, 3)); + assertThrows( + NullPointerException.class, () -> builder.putAll((Multimap) toPut)); } public void testBuilderPutNullValue() { - Multimap toPut = LinkedListMultimap.create(); - toPut.put(null, 1); + Multimap toPut = LinkedListMultimap.create(); + toPut.put("foo", null); ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); - try { - builder.put("foo", null); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll("foo", Arrays.asList(1, null, 3)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll("foo", 1, null, 3); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll(toPut); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.put("foo", null)); + assertThrows( + NullPointerException.class, () -> builder.putAll("foo", Arrays.asList(1, null, 3))); + assertThrows(NullPointerException.class, () -> builder.putAll("foo", 1, null, 3)); + assertThrows( + NullPointerException.class, () -> builder.putAll((Multimap) toPut)); } public void testBuilderOrderKeysBy() { @@ -339,9 +356,8 @@ public void testCopyOf() { input.put("foo", 1); input.put("bar", 2); input.put("foo", 3); - Multimap multimap = ImmutableListMultimap.copyOf(input); - assertEquals(multimap, input); - assertEquals(input, multimap); + ImmutableListMultimap multimap = ImmutableListMultimap.copyOf(input); + new EqualsTester().addEqualityGroup(input, multimap).testEquals(); } public void testCopyOfWithDuplicates() { @@ -350,16 +366,14 @@ public void testCopyOfWithDuplicates() { input.put("bar", 2); input.put("foo", 3); input.put("foo", 1); - Multimap multimap = ImmutableListMultimap.copyOf(input); - assertEquals(multimap, input); - assertEquals(input, multimap); + ImmutableListMultimap multimap = ImmutableListMultimap.copyOf(input); + new EqualsTester().addEqualityGroup(input, multimap).testEquals(); } public void testCopyOfEmpty() { ArrayListMultimap input = ArrayListMultimap.create(); - Multimap multimap = ImmutableListMultimap.copyOf(input); - assertEquals(multimap, input); - assertEquals(input, multimap); + ImmutableListMultimap multimap = ImmutableListMultimap.copyOf(input); + new EqualsTester().addEqualityGroup(input, multimap).testEquals(); } public void testCopyOfImmutableListMultimap() { @@ -368,23 +382,19 @@ public void testCopyOfImmutableListMultimap() { } public void testCopyOfNullKey() { - ArrayListMultimap input = ArrayListMultimap.create(); + ArrayListMultimap<@Nullable String, Integer> input = ArrayListMultimap.create(); input.put(null, 1); - try { - ImmutableListMultimap.copyOf(input); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> ImmutableListMultimap.copyOf((ArrayListMultimap) input)); } public void testCopyOfNullValue() { - ArrayListMultimap input = ArrayListMultimap.create(); - input.putAll("foo", Arrays.asList(1, null, 3)); - try { - ImmutableListMultimap.copyOf(input); - fail(); - } catch (NullPointerException expected) { - } + ArrayListMultimap input = ArrayListMultimap.create(); + input.putAll("foo", Arrays.<@Nullable Integer>asList(1, null, 3)); + assertThrows( + NullPointerException.class, + () -> ImmutableListMultimap.copyOf((ArrayListMultimap) input)); } // TODO(b/172823566): Use mainline testToImmutableListMultimap once CollectorTester is usable. @@ -402,17 +412,17 @@ public void testToImmutableListMultimap_java7_combine() { } public void testEmptyMultimapReads() { - Multimap multimap = ImmutableListMultimap.of(); + ImmutableListMultimap multimap = ImmutableListMultimap.of(); assertFalse(multimap.containsKey("foo")); assertFalse(multimap.containsValue(1)); assertFalse(multimap.containsEntry("foo", 1)); assertTrue(multimap.entries().isEmpty()); assertTrue(multimap.equals(ArrayListMultimap.create())); - assertEquals(Collections.emptyList(), multimap.get("foo")); + assertEquals(emptyList(), multimap.get("foo")); assertEquals(0, multimap.hashCode()); assertTrue(multimap.isEmpty()); assertEquals(HashMultiset.create(), multimap.keys()); - assertEquals(Collections.emptySet(), multimap.keySet()); + assertEquals(emptySet(), multimap.keySet()); assertEquals(0, multimap.size()); assertTrue(multimap.values().isEmpty()); assertEquals("{}", multimap.toString()); @@ -553,6 +563,7 @@ private static void assertMultimapEquals( } } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { Multimap multimap = createMultimap(); @@ -566,9 +577,20 @@ public void testSerialization() { assertEquals(HashMultiset.create(multimap.values()), HashMultiset.create(valuesCopy)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testEmptySerialization() { Multimap multimap = ImmutableListMultimap.of(); assertSame(multimap, SerializableTester.reserialize(multimap)); } + + @J2ktIncompatible + @GwtIncompatible // reflection + public void testNulls() throws Exception { + NullPointerTester tester = new NullPointerTester(); + tester.testAllPublicStaticMethods(ImmutableListMultimap.class); + tester.ignore(ImmutableListMultimap.class.getMethod("get", Object.class)); + tester.testAllPublicInstanceMethods(ImmutableListMultimap.of()); + tester.testAllPublicInstanceMethods(ImmutableListMultimap.of("a", 1)); + } } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableListTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableListTest.java index 52ed7fa3ffe2..ddaf4080e32a 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableListTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableListTest.java @@ -16,17 +16,21 @@ package com.google.common.collect; -import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.common.collect.Iterables.unmodifiableIterable; -import static com.google.common.collect.Sets.newHashSet; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Iterators.singletonIterator; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.testing.Helpers.misleadingSizeCollection; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_QUERIES; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; -import static java.lang.reflect.Proxy.newProxyInstance; +import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.nCopies; +import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.MinimalCollection; import com.google.common.collect.testing.MinimalIterable; @@ -41,19 +45,15 @@ import com.google.common.collect.testing.testers.ListHashCodeTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link ImmutableList}. @@ -62,10 +62,13 @@ * @author George van den Driessche * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableListTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -112,727 +115,523 @@ public static Test suite() { return suite; } - public static class CreationTests extends TestCase { - public void testCreation_noArgs() { - List list = ImmutableList.of(); - assertEquals(Collections.emptyList(), list); - } - - public void testCreation_oneElement() { - List list = ImmutableList.of("a"); - assertEquals(Collections.singletonList("a"), list); - } - - public void testCreation_twoElements() { - List list = ImmutableList.of("a", "b"); - assertEquals(Lists.newArrayList("a", "b"), list); - } - - public void testCreation_threeElements() { - List list = ImmutableList.of("a", "b", "c"); - assertEquals(Lists.newArrayList("a", "b", "c"), list); - } - - public void testCreation_fourElements() { - List list = ImmutableList.of("a", "b", "c", "d"); - assertEquals(Lists.newArrayList("a", "b", "c", "d"), list); - } - - public void testCreation_fiveElements() { - List list = ImmutableList.of("a", "b", "c", "d", "e"); - assertEquals(Lists.newArrayList("a", "b", "c", "d", "e"), list); - } - - public void testCreation_sixElements() { - List list = ImmutableList.of("a", "b", "c", "d", "e", "f"); - assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f"), list); - } + // Creation tests - public void testCreation_sevenElements() { - List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g"); - assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g"), list); - } - - public void testCreation_eightElements() { - List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h"); - assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h"), list); - } - - public void testCreation_nineElements() { - List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i"); - assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i"), list); - } + public void testCreation_noArgs() { + List list = ImmutableList.of(); + assertEquals(emptyList(), list); + } - public void testCreation_tenElements() { - List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"); - assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"), list); - } + public void testCreation_oneElement() { + List list = ImmutableList.of("a"); + assertEquals(singletonList("a"), list); + } - public void testCreation_elevenElements() { - List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"); - assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"), list); - } + public void testCreation_twoElements() { + List list = ImmutableList.of("a", "b"); + assertEquals(Lists.newArrayList("a", "b"), list); + } - // Varargs versions + public void testCreation_threeElements() { + List list = ImmutableList.of("a", "b", "c"); + assertEquals(Lists.newArrayList("a", "b", "c"), list); + } - public void testCreation_twelveElements() { - List list = - ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"); - assertEquals( - Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"), list); - } + public void testCreation_fourElements() { + List list = ImmutableList.of("a", "b", "c", "d"); + assertEquals(Lists.newArrayList("a", "b", "c", "d"), list); + } - public void testCreation_thirteenElements() { - List list = - ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"); - assertEquals( - Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"), - list); - } + public void testCreation_fiveElements() { + List list = ImmutableList.of("a", "b", "c", "d", "e"); + assertEquals(Lists.newArrayList("a", "b", "c", "d", "e"), list); + } - public void testCreation_fourteenElements() { - List list = - ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n"); - assertEquals( - Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n"), - list); - } + public void testCreation_sixElements() { + List list = ImmutableList.of("a", "b", "c", "d", "e", "f"); + assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f"), list); + } - public void testCreation_singletonNull() { - try { - ImmutableList.of((String) null); - fail(); - } catch (NullPointerException expected) { - } - } + public void testCreation_sevenElements() { + List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g"); + assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g"), list); + } - public void testCreation_withNull() { - try { - ImmutableList.of("a", null, "b"); - fail(); - } catch (NullPointerException expected) { - } - } + public void testCreation_eightElements() { + List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h"); + assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h"), list); + } - public void testCreation_generic() { - List a = ImmutableList.of("a"); - // only verify that there is no compile warning - ImmutableList> unused = ImmutableList.of(a, a); - } + public void testCreation_nineElements() { + List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i"); + assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i"), list); + } - public void testCreation_arrayOfArray() { - String[] array = new String[] {"a"}; - List list = ImmutableList.of(array); - assertEquals(Collections.singletonList(array), list); - } + public void testCreation_tenElements() { + List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"); + assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"), list); + } - public void testCopyOf_emptyArray() { - String[] array = new String[0]; - List list = ImmutableList.copyOf(array); - assertEquals(Collections.emptyList(), list); - } + public void testCreation_elevenElements() { + List list = ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"); + assertEquals(Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"), list); + } - public void testCopyOf_arrayOfOneElement() { - String[] array = new String[] {"a"}; - List list = ImmutableList.copyOf(array); - assertEquals(Collections.singletonList("a"), list); - } + // Varargs versions - public void testCopyOf_nullArray() { - try { - ImmutableList.copyOf((String[]) null); - fail(); - } catch (NullPointerException expected) { - } - } + public void testCreation_twelveElements() { + List list = + ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"); + assertEquals( + Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"), list); + } - public void testCopyOf_arrayContainingOnlyNull() { - String[] array = new String[] {null}; - try { - ImmutableList.copyOf(array); - fail(); - } catch (NullPointerException expected) { - } - } + public void testCreation_thirteenElements() { + List list = + ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"); + assertEquals( + Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"), list); + } - public void testCopyOf_collection_empty() { - // "" is required to work around a javac 1.5 bug. - Collection c = MinimalCollection.of(); - List list = ImmutableList.copyOf(c); - assertEquals(Collections.emptyList(), list); - } + public void testCreation_fourteenElements() { + List list = + ImmutableList.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n"); + assertEquals( + Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n"), + list); + } - public void testCopyOf_collection_oneElement() { - Collection c = MinimalCollection.of("a"); - List list = ImmutableList.copyOf(c); - assertEquals(Collections.singletonList("a"), list); - } + public void testCreation_singletonNull() { + assertThrows(NullPointerException.class, () -> ImmutableList.of((String) null)); + } - public void testCopyOf_collection_general() { - Collection c = MinimalCollection.of("a", "b", "a"); - List list = ImmutableList.copyOf(c); - assertEquals(asList("a", "b", "a"), list); - List mutableList = asList("a", "b"); - list = ImmutableList.copyOf(mutableList); - mutableList.set(0, "c"); - assertEquals(asList("a", "b"), list); - } + public void testCreation_withNull() { + assertThrows(NullPointerException.class, () -> ImmutableList.of("a", null, "b")); + } - public void testCopyOf_collectionContainingNull() { - Collection c = MinimalCollection.of("a", null, "b"); - try { - ImmutableList.copyOf(c); - fail(); - } catch (NullPointerException expected) { - } - } + public void testCreation_generic() { + List a = ImmutableList.of("a"); + // only verify that there is no compile warning + ImmutableList> unused = ImmutableList.of(a, a); + } - public void testCopyOf_iterator_empty() { - Iterator iterator = Iterators.emptyIterator(); - List list = ImmutableList.copyOf(iterator); - assertEquals(Collections.emptyList(), list); - } + public void testCreation_arrayOfArray() { + String[] array = new String[] {"a"}; + List list = ImmutableList.of(array); + assertEquals(singletonList(array), list); + } - public void testCopyOf_iterator_oneElement() { - Iterator iterator = Iterators.singletonIterator("a"); - List list = ImmutableList.copyOf(iterator); - assertEquals(Collections.singletonList("a"), list); - } + public void testCopyOf_emptyArray() { + String[] array = new String[0]; + List list = ImmutableList.copyOf(array); + assertEquals(emptyList(), list); + } - public void testCopyOf_iterator_general() { - Iterator iterator = asList("a", "b", "a").iterator(); - List list = ImmutableList.copyOf(iterator); - assertEquals(asList("a", "b", "a"), list); - } + public void testCopyOf_arrayOfOneElement() { + String[] array = new String[] {"a"}; + List list = ImmutableList.copyOf(array); + assertEquals(singletonList("a"), list); + } - public void testCopyOf_iteratorContainingNull() { - Iterator iterator = asList("a", null, "b").iterator(); - try { - ImmutableList.copyOf(iterator); - fail(); - } catch (NullPointerException expected) { - } - } + public void testCopyOf_nullArray() { + assertThrows(NullPointerException.class, () -> ImmutableList.copyOf((String[]) null)); + } - public void testCopyOf_iteratorNull() { - try { - ImmutableList.copyOf((Iterator) null); - fail(); - } catch (NullPointerException expected) { - } - } + public void testCopyOf_arrayContainingOnlyNull() { + @Nullable String[] array = new @Nullable String[] {null}; + assertThrows(NullPointerException.class, () -> ImmutableList.copyOf((String[]) array)); + } - public void testCopyOf_concurrentlyMutating() { - List sample = Lists.newArrayList("a", "b", "c"); - for (int delta : new int[] {-1, 0, 1}) { - for (int i = 0; i < sample.size(); i++) { - Collection misleading = Helpers.misleadingSizeCollection(delta); - List expected = sample.subList(0, i); - misleading.addAll(expected); - assertEquals(expected, ImmutableList.copyOf(misleading)); - assertEquals(expected, ImmutableList.copyOf((Iterable) misleading)); - } - } - } + public void testCopyOf_collection_empty() { + Collection c = MinimalCollection.of(); + List list = ImmutableList.copyOf(c); + assertEquals(emptyList(), list); + } - private static class CountingIterable implements Iterable { - int count = 0; + public void testCopyOf_collection_oneElement() { + Collection c = MinimalCollection.of("a"); + List list = ImmutableList.copyOf(c); + assertEquals(singletonList("a"), list); + } - @Override - public Iterator iterator() { - count++; - return asList("a", "b", "a").iterator(); - } - } + public void testCopyOf_collection_general() { + Collection c = MinimalCollection.of("a", "b", "a"); + List list = ImmutableList.copyOf(c); + assertEquals(asList("a", "b", "a"), list); + List mutableList = asList("a", "b"); + list = ImmutableList.copyOf(mutableList); + mutableList.set(0, "c"); + assertEquals(asList("a", "b"), list); + } - public void testCopyOf_plainIterable() { - CountingIterable iterable = new CountingIterable(); - List list = ImmutableList.copyOf(iterable); - assertEquals(asList("a", "b", "a"), list); - } + public void testCopyOf_collectionContainingNull() { + Collection<@Nullable String> c = MinimalCollection.of("a", null, "b"); + assertThrows(NullPointerException.class, () -> ImmutableList.copyOf((Collection) c)); + } - public void testCopyOf_plainIterable_iteratesOnce() { - CountingIterable iterable = new CountingIterable(); - ImmutableList unused = ImmutableList.copyOf(iterable); - assertEquals(1, iterable.count); - } + public void testCopyOf_iterator_empty() { + Iterator iterator = emptyIterator(); + List list = ImmutableList.copyOf(iterator); + assertEquals(emptyList(), list); + } - public void testCopyOf_shortcut_empty() { - Collection c = ImmutableList.of(); - assertSame(c, ImmutableList.copyOf(c)); - } + public void testCopyOf_iterator_oneElement() { + Iterator iterator = singletonIterator("a"); + List list = ImmutableList.copyOf(iterator); + assertEquals(singletonList("a"), list); + } - public void testCopyOf_shortcut_singleton() { - Collection c = ImmutableList.of("a"); - assertSame(c, ImmutableList.copyOf(c)); - } + public void testCopyOf_iterator_general() { + Iterator iterator = asList("a", "b", "a").iterator(); + List list = ImmutableList.copyOf(iterator); + assertEquals(asList("a", "b", "a"), list); + } - public void testCopyOf_shortcut_immutableList() { - Collection c = ImmutableList.of("a", "b", "c"); - assertSame(c, ImmutableList.copyOf(c)); - } + public void testCopyOf_iteratorContainingNull() { + Iterator<@Nullable String> iterator = + Arrays.<@Nullable String>asList("a", null, "b").iterator(); + assertThrows( + NullPointerException.class, () -> ImmutableList.copyOf((Iterator) iterator)); + } - public void testBuilderAddArrayHandlesNulls() { - String[] elements = {"a", null, "b"}; - ImmutableList.Builder builder = ImmutableList.builder(); - try { - builder.add(elements); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } - ImmutableList result = builder.build(); - - /* - * Maybe it rejects all elements, or maybe it adds "a" before failing. - * Either way is fine with us. - */ - if (result.isEmpty()) { - return; - } - assertTrue(ImmutableList.of("a").equals(result)); - assertEquals(1, result.size()); - } + public void testCopyOf_iteratorNull() { + assertThrows(NullPointerException.class, () -> ImmutableList.copyOf((Iterator) null)); + } - public void testBuilderAddCollectionHandlesNulls() { - List elements = Arrays.asList("a", null, "b"); - ImmutableList.Builder builder = ImmutableList.builder(); - try { - builder.addAll(elements); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { + public void testCopyOf_concurrentlyMutating() { + List sample = Lists.newArrayList("a", "b", "c"); + for (int delta : new int[] {-1, 0, 1}) { + for (int i = 0; i < sample.size(); i++) { + Collection misleading = misleadingSizeCollection(delta); + List expected = sample.subList(0, i); + misleading.addAll(expected); + assertEquals(expected, ImmutableList.copyOf(misleading)); + assertEquals(expected, ImmutableList.copyOf((Iterable) misleading)); } - ImmutableList result = builder.build(); - assertEquals(ImmutableList.of("a"), result); - assertEquals(1, result.size()); } + } - public void testSortedCopyOf_natural() { - Collection c = MinimalCollection.of(4, 16, 10, -1, 5); - ImmutableList list = ImmutableList.sortedCopyOf(c); - assertEquals(asList(-1, 4, 5, 10, 16), list); - } + private static class CountingIterable implements Iterable { + int count = 0; - public void testSortedCopyOf_natural_empty() { - Collection c = MinimalCollection.of(); - ImmutableList list = ImmutableList.sortedCopyOf(c); - assertEquals(asList(), list); + @Override + public Iterator iterator() { + count++; + return asList("a", "b", "a").iterator(); } + } - public void testSortedCopyOf_natural_singleton() { - Collection c = MinimalCollection.of(100); - ImmutableList list = ImmutableList.sortedCopyOf(c); - assertEquals(asList(100), list); - } + public void testCopyOf_plainIterable() { + CountingIterable iterable = new CountingIterable(); + List list = ImmutableList.copyOf(iterable); + assertEquals(asList("a", "b", "a"), list); + } - public void testSortedCopyOf_natural_containsNull() { - Collection c = MinimalCollection.of(1, 3, null, 2); - try { - ImmutableList.sortedCopyOf(c); - fail("Expected NPE"); - } catch (NullPointerException expected) { - } - } + public void testCopyOf_plainIterable_iteratesOnce() { + CountingIterable iterable = new CountingIterable(); + ImmutableList unused = ImmutableList.copyOf(iterable); + assertEquals(1, iterable.count); + } - public void testSortedCopyOf() { - Collection c = MinimalCollection.of("a", "b", "A", "c"); - List list = ImmutableList.sortedCopyOf(String.CASE_INSENSITIVE_ORDER, c); - assertEquals(asList("a", "A", "b", "c"), list); - } + public void testCopyOf_shortcut_empty() { + Collection c = ImmutableList.of(); + assertSame(c, ImmutableList.copyOf(c)); + } - public void testSortedCopyOf_empty() { - Collection c = MinimalCollection.of(); - List list = ImmutableList.sortedCopyOf(String.CASE_INSENSITIVE_ORDER, c); - assertEquals(asList(), list); - } + public void testCopyOf_shortcut_singleton() { + Collection c = ImmutableList.of("a"); + assertSame(c, ImmutableList.copyOf(c)); + } - public void testSortedCopyOf_singleton() { - Collection c = MinimalCollection.of("a"); - List list = ImmutableList.sortedCopyOf(String.CASE_INSENSITIVE_ORDER, c); - assertEquals(asList("a"), list); - } + public void testCopyOf_shortcut_immutableList() { + Collection c = ImmutableList.of("a", "b", "c"); + assertSame(c, ImmutableList.copyOf(c)); + } - public void testSortedCopyOf_containsNull() { - Collection c = MinimalCollection.of("a", "b", "A", null, "c"); - try { - ImmutableList.sortedCopyOf(String.CASE_INSENSITIVE_ORDER, c); - fail("Expected NPE"); - } catch (NullPointerException expected) { - } - } + public void testBuilderAddArrayHandlesNulls() { + @Nullable String[] elements = new @Nullable String[] {"a", null, "b"}; + ImmutableList.Builder builder = ImmutableList.builder(); + assertThrows(NullPointerException.class, () -> builder.add((String[]) elements)); + ImmutableList result = builder.build(); - // TODO(b/172823566): Use mainline testToImmutableList once CollectorTester is usable to java7. - public void testToImmutableList_java7_combine() { - ImmutableList.Builder zis = ImmutableList.builder().add("a", "b"); - ImmutableList.Builder zat = ImmutableList.builder().add("c", "d"); - ImmutableList list = zis.combine(zat).build(); - assertEquals(asList("a", "b", "c", "d"), list); + /* + * Maybe it rejects all elements, or maybe it adds "a" before failing. + * Either way is fine with us. + */ + if (result.isEmpty()) { + return; } + assertTrue(ImmutableList.of("a").equals(result)); + assertEquals(1, result.size()); } - @GwtIncompatible // reflection - public static class ConcurrentTests extends TestCase { - enum WrapWithIterable { - WRAP, - NO_WRAP - } + public void testBuilderAddCollectionHandlesNulls() { + List<@Nullable String> elements = asList("a", null, "b"); + ImmutableList.Builder builder = ImmutableList.builder(); + assertThrows(NullPointerException.class, () -> builder.addAll((List) elements)); + ImmutableList result = builder.build(); + assertEquals(ImmutableList.of("a"), result); + assertEquals(1, result.size()); + } - private static void runConcurrentlyMutatedTest( - Collection initialContents, - Iterable actionsToPerformConcurrently, - WrapWithIterable wrap) { - ConcurrentlyMutatedList concurrentlyMutatedList = - newConcurrentlyMutatedList(initialContents, actionsToPerformConcurrently); + public void testSortedCopyOf_natural() { + Collection c = MinimalCollection.of(4, 16, 10, -1, 5); + ImmutableList list = ImmutableList.sortedCopyOf(c); + assertEquals(asList(-1, 4, 5, 10, 16), list); + } - Iterable iterableToCopy = - wrap == WrapWithIterable.WRAP - ? unmodifiableIterable(concurrentlyMutatedList) - : concurrentlyMutatedList; + public void testSortedCopyOf_natural_empty() { + Collection c = MinimalCollection.of(); + ImmutableList list = ImmutableList.sortedCopyOf(c); + assertEquals(asList(), list); + } - ImmutableList copyOfIterable = ImmutableList.copyOf(iterableToCopy); + public void testSortedCopyOf_natural_singleton() { + Collection c = MinimalCollection.of(100); + ImmutableList list = ImmutableList.sortedCopyOf(c); + assertEquals(asList(100), list); + } - assertTrue(concurrentlyMutatedList.getAllStates().contains(copyOfIterable)); - } + public void testSortedCopyOf_natural_containsNull() { + Collection<@Nullable Integer> c = MinimalCollection.of(1, 3, null, 2); + assertThrows( + NullPointerException.class, () -> ImmutableList.sortedCopyOf((Collection) c)); + } - private static void runConcurrentlyMutatedTest(WrapWithIterable wrap) { - /* - * TODO: Iterate over many array sizes and all possible operation lists, - * performing adds and removes in different ways. - */ - runConcurrentlyMutatedTest(elements(), ops(add(1), add(2)), wrap); + public void testSortedCopyOf() { + Collection c = MinimalCollection.of("a", "b", "A", "c"); + List list = ImmutableList.sortedCopyOf(String.CASE_INSENSITIVE_ORDER, c); + assertEquals(asList("a", "A", "b", "c"), list); + } - runConcurrentlyMutatedTest(elements(), ops(add(1), nop()), wrap); + public void testSortedCopyOf_empty() { + Collection c = MinimalCollection.of(); + List list = ImmutableList.sortedCopyOf(String.CASE_INSENSITIVE_ORDER, c); + assertEquals(asList(), list); + } - runConcurrentlyMutatedTest(elements(), ops(add(1), remove()), wrap); + public void testSortedCopyOf_singleton() { + Collection c = MinimalCollection.of("a"); + List list = ImmutableList.sortedCopyOf(String.CASE_INSENSITIVE_ORDER, c); + assertEquals(asList("a"), list); + } - runConcurrentlyMutatedTest(elements(), ops(nop(), add(1)), wrap); + public void testSortedCopyOf_containsNull() { + Collection<@Nullable String> c = MinimalCollection.of("a", "b", "A", null, "c"); + assertThrows( + NullPointerException.class, + () -> ImmutableList.sortedCopyOf(String.CASE_INSENSITIVE_ORDER, (Collection) c)); + } - runConcurrentlyMutatedTest(elements(1), ops(remove(), nop()), wrap); + // TODO(b/172823566): Use mainline testToImmutableList once CollectorTester is usable to java7. + public void testToImmutableList_java7_combine() { + ImmutableList.Builder zis = ImmutableList.builder().add("a", "b"); + ImmutableList.Builder zat = ImmutableList.builder().add("c", "d"); + ImmutableList list = zis.combine(zat).build(); + assertEquals(asList("a", "b", "c", "d"), list); + } - runConcurrentlyMutatedTest(elements(1), ops(remove(), add(2)), wrap); + // Basic tests - runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), remove()), wrap); + @J2ktIncompatible + @GwtIncompatible // NullPointerTester + public void testNullPointers() { + NullPointerTester tester = new NullPointerTester(); + tester.testAllPublicStaticMethods(ImmutableList.class); + tester.testAllPublicInstanceMethods(ImmutableList.of(1, 2, 3)); + } - runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), nop()), wrap); + @J2ktIncompatible + @GwtIncompatible // SerializableTester + public void testSerialization_empty() { + Collection c = ImmutableList.of(); + assertSame(c, SerializableTester.reserialize(c)); + } - runConcurrentlyMutatedTest(elements(1, 2), ops(remove(), add(3)), wrap); + @J2ktIncompatible + @GwtIncompatible // SerializableTester + public void testSerialization_singleton() { + Collection c = ImmutableList.of("a"); + SerializableTester.reserializeAndAssert(c); + } - runConcurrentlyMutatedTest(elements(1, 2), ops(nop(), remove()), wrap); + @J2ktIncompatible + @GwtIncompatible // SerializableTester + public void testSerialization_multiple() { + Collection c = ImmutableList.of("a", "b", "c"); + SerializableTester.reserializeAndAssert(c); + } - runConcurrentlyMutatedTest(elements(1, 2, 3), ops(remove(), remove()), wrap); - } + public void testEquals_immutableList() { + Collection c = ImmutableList.of("a", "b", "c"); + assertTrue(c.equals(ImmutableList.of("a", "b", "c"))); + assertFalse(c.equals(ImmutableList.of("a", "c", "b"))); + assertFalse(c.equals(ImmutableList.of("a", "b"))); + assertFalse(c.equals(ImmutableList.of("a", "b", "c", "d"))); + } - private static ImmutableList elements(Integer... elements) { - return ImmutableList.copyOf(elements); - } + public void testBuilderAdd() { + ImmutableList list = + new ImmutableList.Builder().add("a").add("b").add("a").add("c").build(); + assertEquals(asList("a", "b", "a", "c"), list); + } - private static ImmutableList ops(ListFrobber... elements) { - return ImmutableList.copyOf(elements); + @GwtIncompatible("Builder impl") + public void testBuilderForceCopy() { + ImmutableList.Builder builder = ImmutableList.builder(); + Object[] prevArray = null; + for (int i = 0; i < 10; i++) { + builder.add(i); + assertNotSame(builder.contents, prevArray); + prevArray = builder.contents; + ImmutableList unused = builder.build(); } + } - public void testCopyOf_concurrentlyMutatedList() { - runConcurrentlyMutatedTest(WrapWithIterable.NO_WRAP); - } + @GwtIncompatible + public void testBuilderExactlySizedReusesArray() { + ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(10); + Object[] builderArray = builder.contents; + for (int i = 0; i < 10; i++) { + builder.add(i); + } + Object[] builderArrayAfterAdds = builder.contents; + RegularImmutableList list = (RegularImmutableList) builder.build(); + Object[] listInternalArray = list.array; + assertSame(builderArray, builderArrayAfterAdds); + assertSame(builderArray, listInternalArray); + } - public void testCopyOf_concurrentlyMutatedIterable() { - runConcurrentlyMutatedTest(WrapWithIterable.WRAP); - } + public void testBuilderAdd_varargs() { + ImmutableList list = + new ImmutableList.Builder().add("a", "b", "a", "c").build(); + assertEquals(asList("a", "b", "a", "c"), list); + } - /** An operation to perform on a list. */ - interface ListFrobber { - void perform(List list); - } + public void testBuilderAddAll_iterable() { + List a = asList("a", "b"); + List b = asList("c", "d"); + ImmutableList list = new ImmutableList.Builder().addAll(a).addAll(b).build(); + assertEquals(asList("a", "b", "c", "d"), list); + b.set(0, "f"); + assertEquals(asList("a", "b", "c", "d"), list); + } - static ListFrobber add(final int element) { - return new ListFrobber() { - @Override - public void perform(List list) { - list.add(0, element); - } - }; - } + public void testBuilderAddAll_iterator() { + List a = asList("a", "b"); + List b = asList("c", "d"); + ImmutableList list = + new ImmutableList.Builder().addAll(a.iterator()).addAll(b.iterator()).build(); + assertEquals(asList("a", "b", "c", "d"), list); + b.set(0, "f"); + assertEquals(asList("a", "b", "c", "d"), list); + } - static ListFrobber remove() { - return new ListFrobber() { - @Override - public void perform(List list) { - list.remove(0); + public void testComplexBuilder() { + List colorElem = asList(0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF); + ImmutableList.Builder webSafeColorsBuilder = ImmutableList.builder(); + for (Integer red : colorElem) { + for (Integer green : colorElem) { + for (Integer blue : colorElem) { + webSafeColorsBuilder.add((red << 16) + (green << 8) + blue); } - }; - } - - static ListFrobber nop() { - return new ListFrobber() { - @Override - public void perform(List list) {} - }; - } - - /** A list that mutates itself after every call to each of its {@link List} methods. */ - interface ConcurrentlyMutatedList extends List { - /** - * The elements of a {@link ConcurrentlyMutatedList} are added and removed over time. This - * method returns every state that the list has passed through at some point. - */ - Set> getAllStates(); - } - - /** - * Returns a {@link ConcurrentlyMutatedList} that performs the given operations as its - * concurrent modifications. The mutations occur in the same thread as the triggering method - * call. - */ - private static ConcurrentlyMutatedList newConcurrentlyMutatedList( - final Collection initialContents, - final Iterable actionsToPerformConcurrently) { - InvocationHandler invocationHandler = - new InvocationHandler() { - final CopyOnWriteArrayList delegate = - new CopyOnWriteArrayList<>(initialContents); - - final Method getAllStatesMethod = - getOnlyElement(asList(ConcurrentlyMutatedList.class.getDeclaredMethods())); - - final Iterator remainingActions = actionsToPerformConcurrently.iterator(); - - final Set> allStates = newHashSet(); - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - return method.equals(getAllStatesMethod) - ? getAllStates() - : invokeListMethod(method, args); - } - - private Set> getAllStates() { - return allStates; - } - - private Object invokeListMethod(Method method, Object[] args) throws Throwable { - try { - Object returnValue = method.invoke(delegate, args); - mutateDelegate(); - return returnValue; - } catch (InvocationTargetException e) { - throw e.getCause(); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - - private void mutateDelegate() { - allStates.add(ImmutableList.copyOf(delegate)); - remainingActions.next().perform(delegate); - allStates.add(ImmutableList.copyOf(delegate)); - } - }; - - @SuppressWarnings("unchecked") - ConcurrentlyMutatedList list = - (ConcurrentlyMutatedList) - newProxyInstance( - ImmutableListTest.CreationTests.class.getClassLoader(), - new Class[] {ConcurrentlyMutatedList.class}, - invocationHandler); - return list; + } } + ImmutableList webSafeColors = webSafeColorsBuilder.build(); + assertEquals(216, webSafeColors.size()); + Integer[] webSafeColorArray = webSafeColors.toArray(new Integer[webSafeColors.size()]); + assertEquals(0x000000, (int) webSafeColorArray[0]); + assertEquals(0x000033, (int) webSafeColorArray[1]); + assertEquals(0x000066, (int) webSafeColorArray[2]); + assertEquals(0x003300, (int) webSafeColorArray[6]); + assertEquals(0x330000, (int) webSafeColorArray[36]); + assertEquals(0x000066, (int) webSafeColors.get(2)); + assertEquals(0x003300, (int) webSafeColors.get(6)); + ImmutableList addedColor = webSafeColorsBuilder.add(0x00BFFF).build(); + assertEquals( + "Modifying the builder should not have changed any already built sets", + 216, + webSafeColors.size()); + assertEquals("the new array should be one bigger than webSafeColors", 217, addedColor.size()); + Integer[] appendColorArray = addedColor.toArray(new Integer[addedColor.size()]); + assertEquals(0x00BFFF, (int) appendColorArray[216]); } - public static class BasicTests extends TestCase { - - @GwtIncompatible // NullPointerTester - public void testNullPointers() { - NullPointerTester tester = new NullPointerTester(); - tester.testAllPublicStaticMethods(ImmutableList.class); - tester.testAllPublicInstanceMethods(ImmutableList.of(1, 2, 3)); - } - - @GwtIncompatible // SerializableTester - public void testSerialization_empty() { - Collection c = ImmutableList.of(); - assertSame(c, SerializableTester.reserialize(c)); - } - - @GwtIncompatible // SerializableTester - public void testSerialization_singleton() { - Collection c = ImmutableList.of("a"); - SerializableTester.reserializeAndAssert(c); - } - - @GwtIncompatible // SerializableTester - public void testSerialization_multiple() { - Collection c = ImmutableList.of("a", "b", "c"); - SerializableTester.reserializeAndAssert(c); - } - - public void testEquals_immutableList() { - Collection c = ImmutableList.of("a", "b", "c"); - assertTrue(c.equals(ImmutableList.of("a", "b", "c"))); - assertFalse(c.equals(ImmutableList.of("a", "c", "b"))); - assertFalse(c.equals(ImmutableList.of("a", "b"))); - assertFalse(c.equals(ImmutableList.of("a", "b", "c", "d"))); - } - - public void testBuilderAdd() { - ImmutableList list = - new ImmutableList.Builder().add("a").add("b").add("a").add("c").build(); - assertEquals(asList("a", "b", "a", "c"), list); - } - - @GwtIncompatible("Builder impl") - public void testBuilderForceCopy() { - ImmutableList.Builder builder = ImmutableList.builder(); - Object[] prevArray = null; - for (int i = 0; i < 10; i++) { - builder.add(i); - assertNotSame(builder.contents, prevArray); - prevArray = builder.contents; - ImmutableList unused = builder.build(); - } - } + public void testBuilderAddHandlesNullsCorrectly() { + ImmutableList.Builder builder = ImmutableList.builder(); + assertThrows(NullPointerException.class, () -> builder.add((String) null)); - @GwtIncompatible - public void testBuilderExactlySizedReusesArray() { - ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(10); - Object[] builderArray = builder.contents; - for (int i = 0; i < 10; i++) { - builder.add(i); - } - Object[] builderArrayAfterAdds = builder.contents; - RegularImmutableList list = (RegularImmutableList) builder.build(); - Object[] listInternalArray = list.array; - assertSame(builderArray, builderArrayAfterAdds); - assertSame(builderArray, listInternalArray); - } + assertThrows(NullPointerException.class, () -> builder.add((String[]) null)); - public void testBuilderAdd_varargs() { - ImmutableList list = - new ImmutableList.Builder().add("a", "b", "a", "c").build(); - assertEquals(asList("a", "b", "a", "c"), list); - } + assertThrows(NullPointerException.class, () -> builder.add("a", null, "b")); + } - public void testBuilderAddAll_iterable() { - List a = asList("a", "b"); - List b = asList("c", "d"); - ImmutableList list = new ImmutableList.Builder().addAll(a).addAll(b).build(); - assertEquals(asList("a", "b", "c", "d"), list); - b.set(0, "f"); - assertEquals(asList("a", "b", "c", "d"), list); + public void testBuilderAddAllHandlesNullsCorrectly() { + { + ImmutableList.Builder builder = ImmutableList.builder(); + assertThrows(NullPointerException.class, () -> builder.addAll((Iterable) null)); } - public void testBuilderAddAll_iterator() { - List a = asList("a", "b"); - List b = asList("c", "d"); - ImmutableList list = - new ImmutableList.Builder().addAll(a.iterator()).addAll(b.iterator()).build(); - assertEquals(asList("a", "b", "c", "d"), list); - b.set(0, "f"); - assertEquals(asList("a", "b", "c", "d"), list); + { + ImmutableList.Builder builder = ImmutableList.builder(); + assertThrows(NullPointerException.class, () -> builder.addAll((Iterator) null)); } - public void testComplexBuilder() { - List colorElem = asList(0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF); - ImmutableList.Builder webSafeColorsBuilder = ImmutableList.builder(); - for (Integer red : colorElem) { - for (Integer green : colorElem) { - for (Integer blue : colorElem) { - webSafeColorsBuilder.add((red << 16) + (green << 8) + blue); - } - } - } - ImmutableList webSafeColors = webSafeColorsBuilder.build(); - assertEquals(216, webSafeColors.size()); - Integer[] webSafeColorArray = webSafeColors.toArray(new Integer[webSafeColors.size()]); - assertEquals(0x000000, (int) webSafeColorArray[0]); - assertEquals(0x000033, (int) webSafeColorArray[1]); - assertEquals(0x000066, (int) webSafeColorArray[2]); - assertEquals(0x003300, (int) webSafeColorArray[6]); - assertEquals(0x330000, (int) webSafeColorArray[36]); - assertEquals(0x000066, (int) webSafeColors.get(2)); - assertEquals(0x003300, (int) webSafeColors.get(6)); - ImmutableList addedColor = webSafeColorsBuilder.add(0x00BFFF).build(); - assertEquals( - "Modifying the builder should not have changed any already" + " built sets", - 216, - webSafeColors.size()); - assertEquals("the new array should be one bigger than webSafeColors", 217, addedColor.size()); - Integer[] appendColorArray = addedColor.toArray(new Integer[addedColor.size()]); - assertEquals(0x00BFFF, (int) appendColorArray[216]); + { + ImmutableList.Builder builder = ImmutableList.builder(); + List<@Nullable String> listWithNulls = asList("a", null, "b"); + assertThrows(NullPointerException.class, () -> builder.addAll((List) listWithNulls)); } - public void testBuilderAddHandlesNullsCorrectly() { + { ImmutableList.Builder builder = ImmutableList.builder(); - try { - builder.add((String) null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } - - try { - builder.add((String[]) null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } - - try { - builder.add("a", null, "b"); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + Iterator<@Nullable String> iteratorWithNulls = + Arrays.<@Nullable String>asList("a", null, "b").iterator(); + assertThrows( + NullPointerException.class, () -> builder.addAll((Iterator) iteratorWithNulls)); } - public void testBuilderAddAllHandlesNullsCorrectly() { + { ImmutableList.Builder builder = ImmutableList.builder(); - try { - builder.addAll((Iterable) null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } - - try { - builder.addAll((Iterator) null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } - - builder = ImmutableList.builder(); - List listWithNulls = asList("a", null, "b"); - try { - builder.addAll(listWithNulls); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } - - builder = ImmutableList.builder(); - Iterator iteratorWithNulls = asList("a", null, "b").iterator(); - try { - builder.addAll(iteratorWithNulls); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } - - Iterable iterableWithNulls = MinimalIterable.of("a", null, "b"); - try { - builder.addAll(iterableWithNulls); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + Iterable<@Nullable String> iterableWithNulls = MinimalIterable.of("a", null, "b"); + assertThrows( + NullPointerException.class, () -> builder.addAll((Iterable) iterableWithNulls)); } + } - public void testAsList() { - ImmutableList list = ImmutableList.of("a", "b"); - assertSame(list, list.asList()); - } + // We need to test that asList() really does return the original list. + @SuppressWarnings("InlineMeInliner") + public void testAsList() { + ImmutableList list = ImmutableList.of("a", "b"); + assertSame(list, list.asList()); + } - @GwtIncompatible("builder internals") - public void testReusedBuilder() { - ImmutableList.Builder builder = new ImmutableList.Builder(); - for (int i = 0; i < 10; i++) { - builder.add("foo"); - } - builder.add("bar"); - RegularImmutableList list = (RegularImmutableList) builder.build(); - builder.add("baz"); - assertTrue(list.array != builder.contents); + @GwtIncompatible("builder internals") + public void testReusedBuilder() { + ImmutableList.Builder builder = new ImmutableList.Builder(); + for (int i = 0; i < 10; i++) { + builder.add("foo"); } + builder.add("bar"); + RegularImmutableList list = (RegularImmutableList) builder.build(); + builder.add("baz"); + assertTrue(list.array != builder.contents); + } + + @SuppressWarnings("ModifiedButNotUsed") + @GwtIncompatible // actually allocates nCopies + @J2ktIncompatible // actually allocates nCopies + public void testAddOverflowCollection() { + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < 100; i++) { + builder.add("a"); + } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> builder.addAll(nCopies(Integer.MAX_VALUE - 50, "a"))); + assertThat(expected) + .hasMessageThat() + .contains("cannot store more than Integer.MAX_VALUE elements"); } } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableMapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableMapTest.java index fd27416c5922..e7d5862d7e72 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableMapTest.java @@ -16,21 +16,21 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.testing.SerializableTester.reserialize; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Joiner; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.testing.CollectionTestSuiteBuilder; import com.google.common.collect.testing.ListTestSuiteBuilder; -import com.google.common.collect.testing.MapInterfaceTest; import com.google.common.collect.testing.MapTestSuiteBuilder; -import com.google.common.collect.testing.MinimalSet; -import com.google.common.collect.testing.SampleElements.Colliders; -import com.google.common.collect.testing.SampleElements.Unhashables; -import com.google.common.collect.testing.UnhashableObject; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -45,19 +45,25 @@ import com.google.common.collect.testing.google.MapGenerators.ImmutableMapValuesAsSingletonSetGenerator; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; -import com.google.common.testing.SerializableTester; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ImmutableMap}. @@ -65,10 +71,14 @@ * @author Kevin Bourrillion * @author Jesse Wilson */ -@GwtCompatible(emulated = true) +@GwtCompatible +@SuppressWarnings("AlwaysThrows") +@NullMarked public class ImmutableMapTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(ImmutableMapTest.class); @@ -166,550 +176,644 @@ public static Test suite() { return suite; } - public abstract static class AbstractMapTests extends MapInterfaceTest { - public AbstractMapTests() { - super(false, false, false, false, false); - } - - @Override - protected Map makeEmptyMap() { - throw new UnsupportedOperationException(); - } - - private static final Joiner joiner = Joiner.on(", "); + // Creation tests - @Override - protected void assertMoreInvariants(Map map) { - // TODO: can these be moved to MapInterfaceTest? - for (Entry entry : map.entrySet()) { - assertEquals(entry.getKey() + "=" + entry.getValue(), entry.toString()); - } - - assertEquals("{" + joiner.join(map.entrySet()) + "}", map.toString()); - assertEquals("[" + joiner.join(map.entrySet()) + "]", map.entrySet().toString()); - assertEquals("[" + joiner.join(map.keySet()) + "]", map.keySet().toString()); - assertEquals("[" + joiner.join(map.values()) + "]", map.values().toString()); - - assertEquals(MinimalSet.from(map.entrySet()), map.entrySet()); - assertEquals(Sets.newHashSet(map.keySet()), map.keySet()); - } + public void testEmptyBuilder() { + ImmutableMap map = new Builder().buildOrThrow(); + assertEquals(Collections.emptyMap(), map); } - public static class MapTests extends AbstractMapTests { - @Override - protected Map makeEmptyMap() { - return ImmutableMap.of(); - } - - @Override - protected Map makePopulatedMap() { - return ImmutableMap.of("one", 1, "two", 2, "three", 3); - } + public void testSingletonBuilder() { + ImmutableMap map = new Builder().put("one", 1).buildOrThrow(); + assertMapEquals(map, "one", 1); + } - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } + public void testBuilder() { + ImmutableMap map = + new Builder() + .put("one", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .buildOrThrow(); + assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + } - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; + @GwtIncompatible + public void testBuilderExactlySizedReusesArray() { + ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(10); + Object[] builderArray = builder.alternatingKeysAndValues; + for (int i = 0; i < 10; i++) { + builder.put(i, i); } + Object[] builderArrayAfterPuts = builder.alternatingKeysAndValues; + RegularImmutableMap map = + (RegularImmutableMap) builder.buildOrThrow(); + Object[] mapInternalArray = map.alternatingKeysAndValues; + assertSame(builderArray, builderArrayAfterPuts); + assertSame(builderArray, mapInternalArray); } - public static class SingletonMapTests extends AbstractMapTests { - @Override - protected Map makePopulatedMap() { - return ImmutableMap.of("one", 1); - } + public void testBuilder_orderEntriesByValue() { + ImmutableMap map = + new Builder() + .orderEntriesByValue(Ordering.natural()) + .put("three", 3) + .put("one", 1) + .put("five", 5) + .put("four", 4) + .put("two", 2) + .buildOrThrow(); + assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + } - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } + public void testBuilder_orderEntriesByValueAfterExactSizeBuild() { + Builder builder = new Builder(2).put("four", 4).put("one", 1); + ImmutableMap keyOrdered = builder.buildOrThrow(); + ImmutableMap valueOrdered = + builder.orderEntriesByValue(Ordering.natural()).buildOrThrow(); + assertMapEquals(keyOrdered, "four", 4, "one", 1); + assertMapEquals(valueOrdered, "one", 1, "four", 4); + } - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; - } + public void testBuilder_orderEntriesByValue_usedTwiceFails() { + ImmutableMap.Builder builder = + new Builder().orderEntriesByValue(Ordering.natural()); + assertThrows( + IllegalStateException.class, () -> builder.orderEntriesByValue(Ordering.natural())); } - @GwtIncompatible // SerializableTester - public static class ReserializedMapTests extends AbstractMapTests { - @Override - protected Map makePopulatedMap() { - return SerializableTester.reserialize(ImmutableMap.of("one", 1, "two", 2, "three", 3)); - } + @GwtIncompatible // we haven't implemented this + public void testBuilder_orderEntriesByValue_keepingLast() { + ImmutableMap.Builder builder = + new Builder() + .orderEntriesByValue(Ordering.natural()) + .put("three", 3) + .put("one", 1) + .put("five", 5) + .put("four", 3) + .put("four", 5) + .put("four", 4) // this should win because it's last + .put("two", 2); + assertMapEquals( + builder.buildKeepingLast(), "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + assertThrows(IllegalArgumentException.class, () -> builder.buildOrThrow()); + } - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } + @GwtIncompatible // we haven't implemented this + public void testBuilder_orderEntriesByValueAfterExactSizeBuild_keepingLastWithoutDuplicates() { + ImmutableMap.Builder builder = + new Builder(3) + .orderEntriesByValue(Ordering.natural()) + .put("three", 3) + .put("one", 1); + assertMapEquals(builder.buildKeepingLast(), "one", 1, "three", 3); + } - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; - } + @GwtIncompatible // we haven't implemented this + public void testBuilder_orderEntriesByValue_keepingLast_builderSizeFieldPreserved() { + ImmutableMap.Builder builder = + new Builder() + .orderEntriesByValue(Ordering.natural()) + .put("one", 1) + .put("one", 1); + assertMapEquals(builder.buildKeepingLast(), "one", 1); + assertThrows(IllegalArgumentException.class, () -> builder.buildOrThrow()); } - public static class MapTestsWithBadHashes extends AbstractMapTests { + public void testBuilder_withImmutableEntry() { + ImmutableMap map = + new Builder().put(immutableEntry("one", 1)).buildOrThrow(); + assertMapEquals(map, "one", 1); + } - @Override - protected Map makeEmptyMap() { - throw new UnsupportedOperationException(); - } + public void testBuilder_withImmutableEntryAndNullContents() { + Builder builder = new Builder<>(); + assertThrows( + NullPointerException.class, () -> builder.put(immutableEntry("one", (Integer) null))); + assertThrows(NullPointerException.class, () -> builder.put(immutableEntry((String) null, 1))); + } - @Override - protected Map makePopulatedMap() { - Colliders colliders = new Colliders(); - return ImmutableMap.of( - colliders.e0(), 0, - colliders.e1(), 1, - colliders.e2(), 2, - colliders.e3(), 3); - } + private static class StringHolder { + @Nullable String string; + } - @Override - protected Object getKeyNotInPopulatedMap() { - return new Colliders().e4(); - } + public void testBuilder_withMutableEntry() { + ImmutableMap.Builder builder = new Builder<>(); + StringHolder holder = new StringHolder(); + holder.string = "one"; + Entry entry = + new AbstractMapEntry() { + @Override + public String getKey() { + return holder.string; + } + + @Override + public Integer getValue() { + return 1; + } + }; + + builder.put(entry); + holder.string = "two"; + assertMapEquals(builder.buildOrThrow(), "one", 1); + } - @Override - protected Integer getValueNotInPopulatedMap() { - return 4; - } + public void testBuilderPutAllWithEmptyMap() { + ImmutableMap map = + new Builder() + .putAll(Collections.emptyMap()) + .buildOrThrow(); + assertEquals(Collections.emptyMap(), map); } - @GwtIncompatible // GWT's ImmutableMap emulation is backed by java.util.HashMap. - public static class MapTestsWithUnhashableValues - extends AbstractMapTests { - @Override - protected Map makeEmptyMap() { - return ImmutableMap.of(); - } + public void testBuilderPutAll() { + Map toPut = new LinkedHashMap<>(); + toPut.put("one", 1); + toPut.put("two", 2); + toPut.put("three", 3); + Map moreToPut = new LinkedHashMap<>(); + moreToPut.put("four", 4); + moreToPut.put("five", 5); - @Override - protected Map makePopulatedMap() { - Unhashables unhashables = new Unhashables(); - return ImmutableMap.of(0, unhashables.e0(), 1, unhashables.e1(), 2, unhashables.e2()); - } + ImmutableMap map = + new Builder().putAll(toPut).putAll(moreToPut).buildOrThrow(); + assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + } - @Override - protected Integer getKeyNotInPopulatedMap() { - return 3; - } + public void testBuilderReuse() { + Builder builder = new Builder<>(); + ImmutableMap mapOne = builder.put("one", 1).put("two", 2).buildOrThrow(); + ImmutableMap mapTwo = builder.put("three", 3).put("four", 4).buildOrThrow(); - @Override - protected UnhashableObject getValueNotInPopulatedMap() { - return new Unhashables().e3(); - } + assertMapEquals(mapOne, "one", 1, "two", 2); + assertMapEquals(mapTwo, "one", 1, "two", 2, "three", 3, "four", 4); } - @GwtIncompatible // GWT's ImmutableMap emulation is backed by java.util.HashMap. - public static class MapTestsWithSingletonUnhashableValue extends MapTestsWithUnhashableValues { - @Override - protected Map makePopulatedMap() { - Unhashables unhashables = new Unhashables(); - return ImmutableMap.of(0, unhashables.e0()); - } + public void testBuilderPutNullKeyFailsAtomically() { + Builder builder = new Builder<>(); + assertThrows(NullPointerException.class, () -> builder.put(null, 1)); + builder.put("foo", 2); + assertMapEquals(builder.buildOrThrow(), "foo", 2); } - public static class CreationTests extends TestCase { - public void testEmptyBuilder() { - ImmutableMap map = new Builder().build(); - assertEquals(Collections.emptyMap(), map); - } - - public void testSingletonBuilder() { - ImmutableMap map = new Builder().put("one", 1).build(); - assertMapEquals(map, "one", 1); - } + public void testBuilderPutImmutableEntryWithNullKeyFailsAtomically() { + Builder builder = new Builder<>(); + assertThrows(NullPointerException.class, () -> builder.put(immutableEntry((String) null, 1))); + builder.put("foo", 2); + assertMapEquals(builder.buildOrThrow(), "foo", 2); + } - public void testBuilder() { - ImmutableMap map = - new Builder() - .put("one", 1) - .put("two", 2) - .put("three", 3) - .put("four", 4) - .put("five", 5) - .build(); - assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); - } + // for GWT compatibility + static class SimpleEntry extends AbstractMapEntry { + public K key; + public V value; - @GwtIncompatible - public void testBuilderExactlySizedReusesArray() { - ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(10); - Object[] builderArray = builder.alternatingKeysAndValues; - for (int i = 0; i < 10; i++) { - builder.put(i, i); - } - Object[] builderArrayAfterPuts = builder.alternatingKeysAndValues; - RegularImmutableMap map = - (RegularImmutableMap) builder.build(); - Object[] mapInternalArray = map.alternatingKeysAndValues; - assertSame(builderArray, builderArrayAfterPuts); - assertSame(builderArray, mapInternalArray); + SimpleEntry(K key, V value) { + this.key = key; + this.value = value; } - public void testBuilder_orderEntriesByValue() { - ImmutableMap map = - new Builder() - .orderEntriesByValue(Ordering.natural()) - .put("three", 3) - .put("one", 1) - .put("five", 5) - .put("four", 4) - .put("two", 2) - .build(); - assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + @Override + public K getKey() { + return key; } - public void testBuilder_orderEntriesByValueAfterExactSizeBuild() { - Builder builder = - new Builder(2).put("four", 4).put("one", 1); - ImmutableMap keyOrdered = builder.build(); - ImmutableMap valueOrdered = - builder.orderEntriesByValue(Ordering.natural()).build(); - assertMapEquals(keyOrdered, "four", 4, "one", 1); - assertMapEquals(valueOrdered, "one", 1, "four", 4); + @Override + public V getValue() { + return value; } + } - public void testBuilder_orderEntriesByValue_usedTwiceFails() { - ImmutableMap.Builder builder = - new Builder().orderEntriesByValue(Ordering.natural()); - try { - builder.orderEntriesByValue(Ordering.natural()); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - } + public void testBuilderPutMutableEntryWithNullKeyFailsAtomically() { + Builder builder = new Builder<>(); + assertThrows( + NullPointerException.class, () -> builder.put(new SimpleEntry(null, 1))); + builder.put("foo", 2); + assertMapEquals(builder.buildOrThrow(), "foo", 2); + } - public void testBuilder_withImmutableEntry() { - ImmutableMap map = - new Builder().put(Maps.immutableEntry("one", 1)).build(); - assertMapEquals(map, "one", 1); - } + public void testBuilderPutNullKey() { + Builder builder = new Builder<>(); + assertThrows(NullPointerException.class, () -> builder.put(null, 1)); + } - public void testBuilder_withImmutableEntryAndNullContents() { - Builder builder = new Builder<>(); - try { - builder.put(Maps.immutableEntry("one", (Integer) null)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.put(Maps.immutableEntry((String) null, 1)); - fail(); - } catch (NullPointerException expected) { - } - } + public void testBuilderPutNullValue() { + Builder builder = new Builder<>(); + assertThrows(NullPointerException.class, () -> builder.put("one", null)); + } - private static class StringHolder { - String string; - } + public void testBuilderPutNullKeyViaPutAll() { + Builder builder = new Builder<>(); + assertThrows( + NullPointerException.class, + () -> builder.putAll(Collections.singletonMap(null, 1))); + } - public void testBuilder_withMutableEntry() { - ImmutableMap.Builder builder = new Builder<>(); - final StringHolder holder = new StringHolder(); - holder.string = "one"; - Entry entry = - new AbstractMapEntry() { - @Override - public String getKey() { - return holder.string; - } - - @Override - public Integer getValue() { - return 1; - } - }; - - builder.put(entry); - holder.string = "two"; - assertMapEquals(builder.build(), "one", 1); - } + public void testBuilderPutNullValueViaPutAll() { + Builder builder = new Builder<>(); + assertThrows( + NullPointerException.class, + () -> builder.putAll(Collections.singletonMap("one", null))); + } - public void testBuilderPutAllWithEmptyMap() { - ImmutableMap map = - new Builder().putAll(Collections.emptyMap()).build(); - assertEquals(Collections.emptyMap(), map); - } + public void testPuttingTheSameKeyTwiceThrowsOnBuild() { + Builder builder = + new Builder() + .put("one", 1) + .put("one", 1); // throwing on this line might be better but it's too late to change - public void testBuilderPutAll() { - Map toPut = new LinkedHashMap<>(); - toPut.put("one", 1); - toPut.put("two", 2); - toPut.put("three", 3); - Map moreToPut = new LinkedHashMap<>(); - moreToPut.put("four", 4); - moreToPut.put("five", 5); - - ImmutableMap map = - new Builder().putAll(toPut).putAll(moreToPut).build(); - assertMapEquals(map, "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); - } + assertThrows(IllegalArgumentException.class, () -> builder.buildOrThrow()); + } - public void testBuilderReuse() { - Builder builder = new Builder<>(); - ImmutableMap mapOne = builder.put("one", 1).put("two", 2).build(); - ImmutableMap mapTwo = builder.put("three", 3).put("four", 4).build(); + public void testBuildKeepingLast_allowsOverwrite() { + Builder builder = + new Builder() + .put(1, "un") + .put(2, "deux") + .put(70, "soixante-dix") + .put(70, "septante") + .put(70, "seventy") + .put(1, "one") + .put(2, "two"); + ImmutableMap map = builder.buildKeepingLast(); + assertMapEquals(map, 1, "one", 2, "two", 70, "seventy"); + } - assertMapEquals(mapOne, "one", 1, "two", 2); - assertMapEquals(mapTwo, "one", 1, "two", 2, "three", 3, "four", 4); - } + public void testBuildKeepingLast_smallTableSameHash() { + String key1 = "QED"; + String key2 = "R&D"; + assertThat(key1.hashCode()).isEqualTo(key2.hashCode()); + ImmutableMap map = + ImmutableMap.builder() + .put(key1, 1) + .put(key2, 2) + .put(key1, 3) + .put(key2, 4) + .buildKeepingLast(); + assertMapEquals(map, key1, 3, key2, 4); + } - public void testBuilderPutNullKeyFailsAtomically() { - Builder builder = new Builder<>(); - try { - builder.put(null, 1); - fail(); - } catch (NullPointerException expected) { - } - builder.put("foo", 2); - assertMapEquals(builder.build(), "foo", 2); + // The java7 branch has different code depending on whether the entry indexes fit in a byte, + // short, or int. The small table in testBuildKeepingLast_allowsOverwrite will test the byte + // case. This method tests the short case. + public void testBuildKeepingLast_shortTable() { + Builder builder = ImmutableMap.builder(); + Map expected = new LinkedHashMap<>(); + for (int i = 0; i < 1000; i++) { + // Truncate to even key, so we have put(0, "0") then put(0, "1"). Half the entries are + // duplicates. + Integer key = i & ~1; + String value = String.valueOf(i); + builder.put(key, value); + expected.put(key, value); } + ImmutableMap map = builder.buildKeepingLast(); + assertThat(map).hasSize(500); + assertThat(map).containsExactlyEntriesIn(expected).inOrder(); + } - public void testBuilderPutImmutableEntryWithNullKeyFailsAtomically() { - Builder builder = new Builder<>(); - try { - builder.put(Maps.immutableEntry((String) null, 1)); - fail(); - } catch (NullPointerException expected) { - } - builder.put("foo", 2); - assertMapEquals(builder.build(), "foo", 2); + // This method tests the int case. + public void testBuildKeepingLast_bigTable() { + Builder builder = ImmutableMap.builder(); + Map expected = new LinkedHashMap<>(); + for (int i = 0; i < 200_000; i++) { + // Truncate to even key, so we have put(0, "0") then put(0, "1"). Half the entries are + // duplicates. + Integer key = i & ~1; + String value = String.valueOf(i); + builder.put(key, value); + expected.put(key, value); } + ImmutableMap map = builder.buildKeepingLast(); + assertThat(map).hasSize(100_000); + assertThat(map).containsExactlyEntriesIn(expected).inOrder(); + } - // for GWT compatibility - static class SimpleEntry extends AbstractMapEntry { - public K key; - public V value; + private static class ClassWithTerribleHashCode implements Comparable { + private final int value; - SimpleEntry(K key, V value) { - this.key = key; - this.value = value; - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } + ClassWithTerribleHashCode(int value) { + this.value = value; } - public void testBuilderPutMutableEntryWithNullKeyFailsAtomically() { - Builder builder = new Builder<>(); - try { - builder.put(new SimpleEntry(null, 1)); - fail(); - } catch (NullPointerException expected) { - } - builder.put("foo", 2); - assertMapEquals(builder.build(), "foo", 2); + @Override + public int compareTo(ClassWithTerribleHashCode that) { + return Integer.compare(this.value, that.value); } - public void testBuilderPutNullKey() { - Builder builder = new Builder<>(); - try { - builder.put(null, 1); - fail(); - } catch (NullPointerException expected) { - } + @Override + public boolean equals(@Nullable Object x) { + return x instanceof ClassWithTerribleHashCode + && ((ClassWithTerribleHashCode) x).value == value; } - public void testBuilderPutNullValue() { - Builder builder = new Builder<>(); - try { - builder.put("one", null); - fail(); - } catch (NullPointerException expected) { - } + @Override + public int hashCode() { + return 23; } - public void testBuilderPutNullKeyViaPutAll() { - Builder builder = new Builder<>(); - try { - builder.putAll(Collections.singletonMap(null, 1)); - fail(); - } catch (NullPointerException expected) { - } + @Override + public String toString() { + return "ClassWithTerribleHashCode(" + value + ")"; } + } - public void testBuilderPutNullValueViaPutAll() { - Builder builder = new Builder<>(); - try { - builder.putAll(Collections.singletonMap("one", null)); - fail(); - } catch (NullPointerException expected) { - } + @GwtIncompatible + public void testBuildKeepingLast_collisions() { + Map expected = new LinkedHashMap<>(); + Builder builder = new Builder<>(); + int size = 18; + for (int i = 0; i < size; i++) { + ClassWithTerribleHashCode key = new ClassWithTerribleHashCode(i); + builder.put(key, i); + builder.put(key, -i); + expected.put(key, -i); } + ImmutableMap map = builder.buildKeepingLast(); + assertThat(map).containsExactlyEntriesIn(expected).inOrder(); + } - public void testPuttingTheSameKeyTwiceThrowsOnBuild() { - Builder builder = - new Builder() - .put("one", 1) - .put("one", 1); // throwing on this line would be even better + @GwtIncompatible // Pattern, Matcher + public void testBuilder_keepingLast_thenOrThrow() { + ImmutableMap.Builder builder = + new Builder() + .put("three", 3) + .put("one", 1) + .put("five", 5) + .put("four", 3) + .put("four", 5) + .put("four", 4) // this should win because it's last + .put("two", 2); + assertMapEquals( + builder.buildKeepingLast(), "three", 3, "one", 1, "five", 5, "four", 4, "two", 2); + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> builder.buildOrThrow()); + // We don't really care which values the exception message contains, but they should be + // different from each other. If buildKeepingLast() collapsed duplicates, that might end up not + // being true. + Pattern pattern = Pattern.compile("Multiple entries with same key: four=(.*) and four=(.*)"); + assertThat(expected).hasMessageThat().matches(pattern); + Matcher matcher = pattern.matcher(expected.getMessage()); + assertThat(matcher.matches()).isTrue(); + assertThat(matcher.group(1)).isNotEqualTo(matcher.group(2)); + } - try { - builder.build(); - fail(); - } catch (IllegalArgumentException expected) { - } - } + public void testOf() { + assertMapEquals(ImmutableMap.of("one", 1), "one", 1); + assertMapEquals(ImmutableMap.of("one", 1, "two", 2), "one", 1, "two", 2); + assertMapEquals( + ImmutableMap.of("one", 1, "two", 2, "three", 3), "one", 1, "two", 2, "three", 3); + assertMapEquals( + ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4); + assertMapEquals( + ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5); + assertMapEquals( + ImmutableMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6); + assertMapEquals( + ImmutableMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6, + "seven", + 7); + assertMapEquals( + ImmutableMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6, + "seven", + 7, + "eight", + 8); + assertMapEquals( + ImmutableMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8, + "nine", 9), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6, + "seven", + 7, + "eight", + 8, + "nine", + 9); + assertMapEquals( + ImmutableMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8, + "nine", 9, + "ten", 10), + "one", + 1, + "two", + 2, + "three", + 3, + "four", + 4, + "five", + 5, + "six", + 6, + "seven", + 7, + "eight", + 8, + "nine", + 9, + "ten", + 10); + } - public void testOf() { - assertMapEquals(ImmutableMap.of("one", 1), "one", 1); - assertMapEquals(ImmutableMap.of("one", 1, "two", 2), "one", 1, "two", 2); - assertMapEquals( - ImmutableMap.of("one", 1, "two", 2, "three", 3), "one", 1, "two", 2, "three", 3); - assertMapEquals( - ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4), - "one", - 1, - "two", - 2, - "three", - 3, - "four", - 4); - assertMapEquals( - ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5), - "one", - 1, - "two", - 2, - "three", - 3, - "four", - 4, - "five", - 5); - } + public void testOfNullKey() { + assertThrows(NullPointerException.class, () -> ImmutableMap.of(null, 1)); - public void testOfNullKey() { - try { - ImmutableMap.of(null, 1); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> ImmutableMap.of("one", 1, null, 2)); + } - try { - ImmutableMap.of("one", 1, null, 2); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOfNullValue() { + assertThrows(NullPointerException.class, () -> ImmutableMap.of("one", null)); - public void testOfNullValue() { - try { - ImmutableMap.of("one", null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> ImmutableMap.of("one", 1, "two", null)); + } - try { - ImmutableMap.of("one", 1, "two", null); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOfWithDuplicateKey() { + assertThrows(IllegalArgumentException.class, () -> ImmutableMap.of("one", 1, "one", 1)); + } - public void testOfWithDuplicateKey() { - try { - ImmutableMap.of("one", 1, "one", 1); - fail(); - } catch (IllegalArgumentException expected) { - } - } + public void testCopyOfEmptyMap() { + ImmutableMap copy = + ImmutableMap.copyOf(Collections.emptyMap()); + assertEquals(Collections.emptyMap(), copy); + assertSame(copy, ImmutableMap.copyOf(copy)); + } - public void testCopyOfEmptyMap() { - ImmutableMap copy = - ImmutableMap.copyOf(Collections.emptyMap()); - assertEquals(Collections.emptyMap(), copy); - assertSame(copy, ImmutableMap.copyOf(copy)); - } + public void testCopyOfSingletonMap() { + ImmutableMap copy = ImmutableMap.copyOf(singletonMap("one", 1)); + assertMapEquals(copy, "one", 1); + assertSame(copy, ImmutableMap.copyOf(copy)); + } - public void testCopyOfSingletonMap() { - ImmutableMap copy = ImmutableMap.copyOf(Collections.singletonMap("one", 1)); - assertMapEquals(copy, "one", 1); - assertSame(copy, ImmutableMap.copyOf(copy)); - } + public void testCopyOf() { + Map original = new LinkedHashMap<>(); + original.put("one", 1); + original.put("two", 2); + original.put("three", 3); - public void testCopyOf() { - Map original = new LinkedHashMap<>(); - original.put("one", 1); - original.put("two", 2); - original.put("three", 3); + ImmutableMap copy = ImmutableMap.copyOf(original); + assertMapEquals(copy, "one", 1, "two", 2, "three", 3); + assertSame(copy, ImmutableMap.copyOf(copy)); + } - ImmutableMap copy = ImmutableMap.copyOf(original); - assertMapEquals(copy, "one", 1, "two", 2, "three", 3); - assertSame(copy, ImmutableMap.copyOf(copy)); - } + // TODO(b/172823566): Use mainline testToImmutableMap once CollectorTester is usable to java7. + public void testToImmutableMap_java7_combine() { + ImmutableMap.Builder zis = + ImmutableMap.builder().put("one", 1); + ImmutableMap.Builder zat = + ImmutableMap.builder().put("two", 2).put("three", 3); + assertMapEquals(zis.combine(zat).build(), "one", 1, "two", 2, "three", 3); + } - // TODO(b/172823566): Use mainline testToImmutableMap once CollectorTester is usable to java7. - public void testToImmutableMap_java7_combine() { - ImmutableMap.Builder zis = - ImmutableMap.builder().put("one", 1); - ImmutableMap.Builder zat = - ImmutableMap.builder().put("two", 2).put("three", 3); - assertMapEquals(zis.combine(zat).build(), "one", 1, "two", 2, "three", 3); - } + // TODO(b/172823566): Use mainline testToImmutableMap once CollectorTester is usable to java7. + public void testToImmutableMap_exceptionOnDuplicateKey_java7_combine() { + ImmutableMap.Builder zis = + ImmutableMap.builder().put("one", 1).put("two", 2); + ImmutableMap.Builder zat = + ImmutableMap.builder().put("two", 22).put("three", 3); + assertThrows(IllegalArgumentException.class, () -> zis.combine(zat).build()); + } - // TODO(b/172823566): Use mainline testToImmutableMap once CollectorTester is usable to java7. - public void testToImmutableMap_exceptionOnDuplicateKey_java7_combine() { - ImmutableMap.Builder zis = - ImmutableMap.builder().put("one", 1).put("two", 2); - ImmutableMap.Builder zat = - ImmutableMap.builder().put("two", 22).put("three", 3); - try { - zis.combine(zat).build(); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // expected + public static void hashtableTestHelper(ImmutableList sizes) { + for (int size : sizes) { + Builder builder = ImmutableMap.builderWithExpectedSize(size); + for (int i = 0; i < size; i++) { + Integer integer = i; + builder.put(integer, integer); } - } - - public static void hashtableTestHelper(ImmutableList sizes) { - for (int size : sizes) { - Builder builder = ImmutableMap.builderWithExpectedSize(size); - for (int i = 0; i < size; i++) { - Integer integer = i; - builder.put(integer, integer); - } - ImmutableMap map = builder.build(); - assertEquals(size, map.size()); - int entries = 0; - for (Integer key : map.keySet()) { - assertEquals(entries, key.intValue()); - assertSame(key, map.get(key)); - entries++; - } - assertEquals(size, entries); + ImmutableMap map = builder.build(); + assertEquals(size, map.size()); + int entries = 0; + for (Integer key : map.keySet()) { + assertEquals(entries, key.intValue()); + assertSame(key, map.get(key)); + entries++; } + assertEquals(size, entries); } + } - public void testByteArrayHashtable() { - hashtableTestHelper(ImmutableList.of(2, 89)); - } + public void testByteArrayHashtable() { + hashtableTestHelper(ImmutableList.of(2, 89)); + } - public void testShortArrayHashtable() { - hashtableTestHelper(ImmutableList.of(90, 22937)); - } + public void testShortArrayHashtable() { + hashtableTestHelper(ImmutableList.of(90, 22937)); + } - public void testIntArrayHashtable() { - hashtableTestHelper(ImmutableList.of(22938)); - } + public void testIntArrayHashtable() { + hashtableTestHelper(ImmutableList.of(22938)); } + // Non-creation tests + public void testNullGet() { ImmutableMap map = ImmutableMap.of("one", 1); - assertNull(map.get(null)); + assertThat(map.get(null)).isNull(); } public void testAsMultimap() { @@ -734,6 +838,7 @@ public void testAsMultimapCaches() { assertSame(multimap1, multimap2); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -745,23 +850,22 @@ public void testNullPointers() { } private static void assertMapEquals(Map map, Object... alternatingKeysAndValues) { - assertEquals(map.size(), alternatingKeysAndValues.length / 2); - int i = 0; - for (Entry entry : map.entrySet()) { - assertEquals(alternatingKeysAndValues[i++], entry.getKey()); - assertEquals(alternatingKeysAndValues[i++], entry.getValue()); + Map expected = new LinkedHashMap<>(); + for (int i = 0; i < alternatingKeysAndValues.length; i += 2) { + expected.put(alternatingKeysAndValues[i], alternatingKeysAndValues[i + 1]); } + assertThat(map).containsExactlyEntriesIn(expected).inOrder(); } private static class IntHolder implements Serializable { - public int value; + private int value; - public IntHolder(int value) { + IntHolder(int value) { this.value = value; } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof IntHolder) && ((IntHolder) o).value == value; } @@ -770,7 +874,7 @@ public int hashCode() { return value; } - private static final long serialVersionUID = 5; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 5; } public void testMutableValues() { @@ -778,12 +882,13 @@ public void testMutableValues() { IntHolder holderB = new IntHolder(2); Map map = ImmutableMap.of("a", holderA, "b", holderB); holderA.value = 3; - assertTrue(map.entrySet().contains(Maps.immutableEntry("a", new IntHolder(3)))); + assertTrue(map.entrySet().contains(immutableEntry("a", new IntHolder(3)))); Map intMap = ImmutableMap.of("a", 3, "b", 2); assertEquals(intMap.hashCode(), map.entrySet().hashCode()); assertEquals(intMap.hashCode(), map.hashCode()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testViewSerialization() { Map map = ImmutableMap.of("one", 1, "two", 2, "three", 3); @@ -791,10 +896,11 @@ public void testViewSerialization() { LenientSerializableTester.reserializeAndAssertLenient(map.keySet()); Collection reserializedValues = reserialize(map.values()); - assertEquals(Lists.newArrayList(map.values()), Lists.newArrayList(reserializedValues)); + assertEquals(new ArrayList<>(map.values()), new ArrayList<>(reserializedValues)); assertTrue(reserializedValues instanceof ImmutableCollection); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testKeySetIsSerializable_regularImmutableMap() { class NonSerializableClass {} @@ -806,6 +912,7 @@ class NonSerializableClass {} LenientSerializableTester.reserializeAndAssertLenient(set); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testValuesCollectionIsSerializable_regularImmutableMap() { class NonSerializableClass {} @@ -818,10 +925,11 @@ class NonSerializableClass {} } // TODO: Re-enable this test after moving to new serialization format in ImmutableMap. + @J2ktIncompatible @GwtIncompatible // SerializableTester @SuppressWarnings("unchecked") public void ignore_testSerializationNoDuplication_regularImmutableMap() throws Exception { - // Tests that searializing a map, its keySet, and values only writes the underlying data once. + // Tests that serializing a map, its keySet, and values only writes the underlying data once. Object[] entries = new Object[2000]; for (int i = 0; i < entries.length; i++) { @@ -849,15 +957,102 @@ public void ignore_testSerializationNoDuplication_regularImmutableMap() throws E public void testEquals() { new EqualsTester() - .addEqualityGroup(ImmutableMap.of(), ImmutableMap.builder().build()) - .addEqualityGroup(ImmutableMap.of(1, 1), ImmutableMap.builder().put(1, 1).build()) - .addEqualityGroup(ImmutableMap.of(1, 1, 2, 2)) - .addEqualityGroup(ImmutableMap.of(1, 1, 2, 2, 3, 3)) - .addEqualityGroup(ImmutableMap.of(1, 4, 2, 2, 3, 3)) - .addEqualityGroup(ImmutableMap.of(1, 1, 2, 4, 3, 3)) - .addEqualityGroup(ImmutableMap.of(1, 1, 2, 2, 3, 4)) - .addEqualityGroup(ImmutableMap.of(1, 2, 2, 3, 3, 1)) - .addEqualityGroup(ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4)) + .addEqualityGroup( + ImmutableMap.of(), + ImmutableMap.builder().buildOrThrow(), + ImmutableMap.ofEntries(), + map()) + .addEqualityGroup( + ImmutableMap.of(1, 1), + ImmutableMap.builder().put(1, 1).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 1)), + map(1, 1)) + .addEqualityGroup( + ImmutableMap.of(1, 1, 2, 2), + ImmutableMap.builder().put(1, 1).put(2, 2).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 1), entry(2, 2)), + map(1, 1, 2, 2)) + .addEqualityGroup( + ImmutableMap.of(1, 1, 2, 2, 3, 3), + ImmutableMap.builder().put(1, 1).put(2, 2).put(3, 3).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 1), entry(2, 2), entry(3, 3)), + map(1, 1, 2, 2, 3, 3)) + .addEqualityGroup( + ImmutableMap.of(1, 4, 2, 2, 3, 3), + ImmutableMap.builder().put(1, 4).put(2, 2).put(3, 3).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 4), entry(2, 2), entry(3, 3)), + map(1, 4, 2, 2, 3, 3)) + .addEqualityGroup( + ImmutableMap.of(1, 1, 2, 4, 3, 3), + ImmutableMap.builder().put(1, 1).put(2, 4).put(3, 3).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 1), entry(2, 4), entry(3, 3)), + map(1, 1, 2, 4, 3, 3)) + .addEqualityGroup( + ImmutableMap.of(1, 1, 2, 2, 3, 4), + ImmutableMap.builder().put(1, 1).put(2, 2).put(3, 4).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 1), entry(2, 2), entry(3, 4)), + map(1, 1, 2, 2, 3, 4)) + .addEqualityGroup( + ImmutableMap.of(1, 2, 2, 3, 3, 1), + ImmutableMap.builder().put(1, 2).put(2, 3).put(3, 1).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 2), entry(2, 3), entry(3, 1)), + map(1, 2, 2, 3, 3, 1)) + .addEqualityGroup( + ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4), + ImmutableMap.builder().put(1, 1).put(2, 2).put(3, 3).put(4, 4).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 1), entry(2, 2), entry(3, 3), entry(4, 4)), + map(1, 1, 2, 2, 3, 3, 4, 4)) + .addEqualityGroup( + ImmutableMap.of(1, 1, 2, 2, 3, 3, 4, 4, 5, 5), + ImmutableMap.builder().put(1, 1).put(2, 2).put(3, 3).put(4, 4).put(5, 5).buildOrThrow(), + ImmutableMap.ofEntries(entry(1, 1), entry(2, 2), entry(3, 3), entry(4, 4), entry(5, 5)), + map(1, 1, 2, 2, 3, 3, 4, 4, 5, 5)) .testEquals(); } + + public void testOfEntriesNull() { + Entry<@Nullable Integer, @Nullable Integer> nullKey = entry(null, 23); + assertThrows( + NullPointerException.class, + () -> ImmutableMap.ofEntries((Entry) nullKey)); + Entry<@Nullable Integer, @Nullable Integer> nullValue = entry(23, null); + assertThrows( + NullPointerException.class, + () -> ImmutableMap.ofEntries((Entry) nullValue)); + } + + private static Map map(T... keysAndValues) { + assertThat(keysAndValues.length % 2).isEqualTo(0); + LinkedHashMap map = new LinkedHashMap<>(); + for (int i = 0; i < keysAndValues.length; i += 2) { + T key = keysAndValues[i]; + T value = keysAndValues[i + 1]; + T old = map.put(key, value); + assertWithMessage("Key %s set to %s and %s", key, value, old).that(old).isNull(); + } + return map; + } + + private static Entry entry(T key, T value) { + return new AbstractMap.SimpleImmutableEntry<>(key, value); + } + + public void testCopyOfMutableEntryList() { + List> entryList = + asList(new AbstractMap.SimpleEntry<>("a", "1"), new AbstractMap.SimpleEntry<>("b", "2")); + ImmutableMap map = ImmutableMap.copyOf(entryList); + assertThat(map).containsExactly("a", "1", "b", "2").inOrder(); + entryList.get(0).setValue("3"); + assertThat(map).containsExactly("a", "1", "b", "2").inOrder(); + } + + public void testBuilderPutAllEntryList() { + List> entryList = + asList(new AbstractMap.SimpleEntry<>("a", "1"), new AbstractMap.SimpleEntry<>("b", "2")); + ImmutableMap map = + ImmutableMap.builder().putAll(entryList).buildOrThrow(); + assertThat(map).containsExactly("a", "1", "b", "2").inOrder(); + entryList.get(0).setValue("3"); + assertThat(map).containsExactly("a", "1", "b", "2").inOrder(); + } } diff --git a/android/guava/src/com/google/common/collect/ImmutableEntry.java b/android/guava-tests/test/com/google/common/collect/ImmutableMapWithBadHashesMapInterfaceTest.java similarity index 52% rename from android/guava/src/com/google/common/collect/ImmutableEntry.java rename to android/guava-tests/test/com/google/common/collect/ImmutableMapWithBadHashesMapInterfaceTest.java index cc869b31aa21..d7e4910b2af8 100644 --- a/android/guava/src/com/google/common/collect/ImmutableEntry.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableMapWithBadHashesMapInterfaceTest.java @@ -17,36 +17,36 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; - -/** @see com.google.common.collect.Maps#immutableEntry(Object, Object) */ -@GwtCompatible(serializable = true) -class ImmutableEntry extends AbstractMapEntry implements Serializable { - @NullableDecl final K key; - @NullableDecl final V value; - - ImmutableEntry(@NullableDecl K key, @NullableDecl V value) { - this.key = key; - this.value = value; +import com.google.common.collect.testing.SampleElements.Colliders; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class ImmutableMapWithBadHashesMapInterfaceTest + extends AbstractImmutableMapMapInterfaceTest { + @Override + protected Map makeEmptyMap() { + throw new UnsupportedOperationException(); } @Override - @NullableDecl - public final K getKey() { - return key; + protected Map makePopulatedMap() { + Colliders colliders = new Colliders(); + return ImmutableMap.of( + colliders.e0(), 0, + colliders.e1(), 1, + colliders.e2(), 2, + colliders.e3(), 3); } @Override - @NullableDecl - public final V getValue() { - return value; + protected Object getKeyNotInPopulatedMap() { + return new Colliders().e4(); } @Override - public final V setValue(V value) { - throw new UnsupportedOperationException(); + protected Integer getValueNotInPopulatedMap() { + return 4; } - - private static final long serialVersionUID = 0; } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableMultimapAsMapImplementsMapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableMultimapAsMapImplementsMapTest.java index 8c1f020b5ca8..0d7889408235 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableMultimapAsMapImplementsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableMultimapAsMapImplementsMapTest.java @@ -20,6 +20,7 @@ import com.google.common.collect.testing.MapInterfaceTest; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.NullMarked; /** * Test {@link Multimap#asMap()} for an {@link ImmutableMultimap} with {@link MapInterfaceTest}. @@ -27,6 +28,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class ImmutableMultimapAsMapImplementsMapTest extends AbstractMultimapAsMapImplementsMapTest { diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableMultimapTest.java index 52632580c3e8..2d3c34e0234e 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableMultimapTest.java @@ -16,51 +16,94 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMultimap.Builder; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.SampleElements.Unhashables; import com.google.common.collect.testing.UnhashableObject; import com.google.common.testing.EqualsTester; -import java.util.Arrays; +import com.google.common.testing.NullPointerTester; import java.util.Map.Entry; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ImmutableMultimap}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableMultimapTest extends TestCase { + @SuppressWarnings("JUnitIncompatibleType") public void testBuilder_withImmutableEntry() { ImmutableMultimap multimap = - new Builder().put(Maps.immutableEntry("one", 1)).build(); - assertEquals(Arrays.asList(1), multimap.get("one")); + new Builder().put(immutableEntry("one", 1)).build(); + assertEquals(asList(1), multimap.get("one")); } public void testBuilder_withImmutableEntryAndNullContents() { Builder builder = new Builder<>(); - try { - builder.put(Maps.immutableEntry("one", (Integer) null)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.put(Maps.immutableEntry((String) null, 1)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> builder.put(immutableEntry("one", (Integer) null))); + assertThrows(NullPointerException.class, () -> builder.put(immutableEntry((String) null, 1))); + } + + public void testBuilderWithExpectedKeysNegative() { + assertThrows( + IllegalArgumentException.class, () -> ImmutableMultimap.builderWithExpectedKeys(-1)); + } + + public void testBuilderWithExpectedKeysZero() { + ImmutableMultimap.Builder builder = + ImmutableMultimap.builderWithExpectedKeys(0); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedKeysPositive() { + ImmutableMultimap.Builder builder = + ImmutableMultimap.builderWithExpectedKeys(1); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedValuesPerKeyNegative() { + assertThrows( + IllegalArgumentException.class, () -> ImmutableMultimap.builder().expectedValuesPerKey(-1)); + } + + public void testBuilderWithExpectedValuesPerKeyZero() { + ImmutableMultimap.Builder builder = + ImmutableMultimap.builder().expectedValuesPerKey(0); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedValuesPerKeyPositive() { + ImmutableMultimap.Builder builder = + ImmutableMultimap.builder().expectedValuesPerKey(1); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(immutableEntry("key", "value")); } private static class StringHolder { - String string; + @Nullable String string; } + @SuppressWarnings("JUnitIncompatibleType") public void testBuilder_withMutableEntry() { ImmutableMultimap.Builder builder = new Builder<>(); - final StringHolder holder = new StringHolder(); + StringHolder holder = new StringHolder(); holder.string = "one"; Entry entry = new AbstractMapEntry() { @@ -77,7 +120,7 @@ public Integer getValue() { builder.put(entry); holder.string = "two"; - assertEquals(Arrays.asList(1), builder.build().get("one")); + assertEquals(asList(1), builder.build().get("one")); } // TODO: test ImmutableMultimap builder and factory methods @@ -124,4 +167,14 @@ public void testEquals() { ImmutableMultimap.of(1, "a", 2, "b"), ImmutableMultimap.of(2, "b", 1, "a")) .testEquals(); } + + @J2ktIncompatible + @GwtIncompatible // reflection + public void testNulls() throws Exception { + NullPointerTester tester = new NullPointerTester(); + tester.testAllPublicStaticMethods(ImmutableMultimap.class); + tester.ignore(ImmutableListMultimap.class.getMethod("get", Object.class)); + tester.testAllPublicInstanceMethods(ImmutableMultimap.of()); + tester.testAllPublicInstanceMethods(ImmutableMultimap.of("a", 1)); + } } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableMultisetTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableMultisetTest.java index 2b320ce1eac0..bfb7981c1c27 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableMultisetTest.java @@ -17,11 +17,15 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Iterators.singletonIterator; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.MinimalCollection; import com.google.common.collect.testing.SetTestSuiteBuilder; @@ -36,6 +40,7 @@ import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; @@ -44,16 +49,21 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ImmutableMultiset}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite // TODO(cpovirk): add to collect/gwt/suites + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(ImmutableMultisetTest.class); @@ -196,6 +206,7 @@ public void testCreation_arrayOfOneElement() { assertEquals(HashMultiset.create(asList("a")), multiset); } + @SuppressWarnings("ArrayAsKeyOfSetOrMap") public void testCreation_arrayOfArray() { String[] array = new String[] {"a"}; Multiset multiset = ImmutableMultiset.of(array); @@ -205,17 +216,12 @@ public void testCreation_arrayOfArray() { } public void testCreation_arrayContainingOnlyNull() { - String[] array = new String[] {null}; - try { - ImmutableMultiset.copyOf(array); - fail(); - } catch (NullPointerException expected) { - } + @Nullable String[] array = new @Nullable String[] {null}; + assertThrows(NullPointerException.class, () -> ImmutableMultiset.copyOf((String[]) array)); } public void testCopyOf_collection_empty() { - // "" is required to work around a javac 1.5 bug. - Collection c = MinimalCollection.of(); + Collection c = MinimalCollection.of(); Multiset multiset = ImmutableMultiset.copyOf(c); assertTrue(multiset.isEmpty()); } @@ -233,12 +239,9 @@ public void testCopyOf_collection_general() { } public void testCopyOf_collectionContainingNull() { - Collection c = MinimalCollection.of("a", null, "b"); - try { - ImmutableMultiset.copyOf(c); - fail(); - } catch (NullPointerException expected) { - } + Collection<@Nullable String> c = MinimalCollection.of("a", null, "b"); + assertThrows( + NullPointerException.class, () -> ImmutableMultiset.copyOf((Collection) c)); } public void testCopyOf_multiset_empty() { @@ -260,22 +263,19 @@ public void testCopyOf_multiset_general() { } public void testCopyOf_multisetContainingNull() { - Multiset c = HashMultiset.create(asList("a", null, "b")); - try { - ImmutableMultiset.copyOf(c); - fail(); - } catch (NullPointerException expected) { - } + Multiset<@Nullable String> c = + HashMultiset.create(Arrays.<@Nullable String>asList("a", null, "b")); + assertThrows(NullPointerException.class, () -> ImmutableMultiset.copyOf((Multiset) c)); } public void testCopyOf_iterator_empty() { - Iterator iterator = Iterators.emptyIterator(); + Iterator iterator = emptyIterator(); Multiset multiset = ImmutableMultiset.copyOf(iterator); assertTrue(multiset.isEmpty()); } public void testCopyOf_iterator_oneElement() { - Iterator iterator = Iterators.singletonIterator("a"); + Iterator iterator = singletonIterator("a"); Multiset multiset = ImmutableMultiset.copyOf(iterator); assertEquals(HashMultiset.create(asList("a")), multiset); } @@ -287,12 +287,10 @@ public void testCopyOf_iterator_general() { } public void testCopyOf_iteratorContainingNull() { - Iterator iterator = asList("a", null, "b").iterator(); - try { - ImmutableMultiset.copyOf(iterator); - fail(); - } catch (NullPointerException expected) { - } + Iterator<@Nullable String> iterator = + Arrays.<@Nullable String>asList("a", null, "b").iterator(); + assertThrows( + NullPointerException.class, () -> ImmutableMultiset.copyOf((Iterator) iterator)); } private static class CountingIterable implements Iterable { @@ -402,86 +400,65 @@ public void testBuilderSetCount() { public void testBuilderAddHandlesNullsCorrectly() { ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); - try { - builder.add((String) null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.add((String) null)); } public void testBuilderAddAllHandlesNullsCorrectly() { - ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); - try { - builder.addAll((Collection) null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { + { + ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); + assertThrows(NullPointerException.class, () -> builder.addAll((Collection) null)); } - builder = ImmutableMultiset.builder(); - List listWithNulls = asList("a", null, "b"); - try { - builder.addAll(listWithNulls); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { + { + ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); + List<@Nullable String> listWithNulls = asList("a", null, "b"); + assertThrows(NullPointerException.class, () -> builder.addAll((List) listWithNulls)); } - builder = ImmutableMultiset.builder(); - Multiset multisetWithNull = LinkedHashMultiset.create(asList("a", null, "b")); - try { - builder.addAll(multisetWithNull); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { + { + ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); + Multiset<@Nullable String> multisetWithNull = + LinkedHashMultiset.create(Arrays.<@Nullable String>asList("a", null, "b")); + assertThrows( + NullPointerException.class, () -> builder.addAll((Multiset) multisetWithNull)); } } public void testBuilderAddCopiesHandlesNullsCorrectly() { ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); - try { - builder.addCopies(null, 2); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.addCopies(null, 2)); } public void testBuilderAddCopiesIllegal() { ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); - try { - builder.addCopies("a", -2); - fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.addCopies("a", -2)); } public void testBuilderSetCountHandlesNullsCorrectly() { ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); - try { - builder.setCount(null, 2); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.setCount(null, 2)); } public void testBuilderSetCountIllegal() { ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); - try { - builder.setCount("a", -2); - fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.setCount("a", -2)); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicStaticMethods(ImmutableMultiset.class); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization_empty() { Collection c = ImmutableMultiset.of(); assertSame(c, SerializableTester.reserialize(c)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization_multiple() { Collection c = ImmutableMultiset.of("a", "b", "a"); @@ -489,6 +466,7 @@ public void testSerialization_multiple() { assertThat(copy).containsExactly("a", "a", "b").inOrder(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization_elementSet() { Multiset c = ImmutableMultiset.of("a", "b", "a"); @@ -496,6 +474,7 @@ public void testSerialization_elementSet() { assertThat(copy).containsExactly("a", "b").inOrder(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization_entrySet() { Multiset c = ImmutableMultiset.of("a", "b", "c"); @@ -503,11 +482,14 @@ public void testSerialization_entrySet() { } public void testEquals_immutableMultiset() { - Collection c = ImmutableMultiset.of("a", "b", "a"); - assertEquals(c, ImmutableMultiset.of("a", "b", "a")); - assertEquals(c, ImmutableMultiset.of("a", "a", "b")); - assertThat(c).isNotEqualTo(ImmutableMultiset.of("a", "b")); - assertThat(c).isNotEqualTo(ImmutableMultiset.of("a", "b", "c", "d")); + new EqualsTester() + .addEqualityGroup( + ImmutableMultiset.of("a", "b", "a"), + ImmutableMultiset.of("a", "b", "a"), + ImmutableMultiset.of("a", "a", "b")) + .addEqualityGroup(ImmutableMultiset.of("a", "b")) + .addEqualityGroup(ImmutableMultiset.of("a", "b", "c", "d")) + .testEquals(); } public void testIterationOrder() { @@ -531,6 +513,7 @@ public void testAsList() { assertEquals(4, list.lastIndexOf("b")); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization_asList() { ImmutableMultiset multiset = ImmutableMultiset.of("a", "a", "b", "b", "b"); diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableRangeMapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableRangeMapTest.java index 59f3a1b59cf9..249925434b6e 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableRangeMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableRangeMapTest.java @@ -15,13 +15,16 @@ package com.google.common.collect; import static com.google.common.collect.BoundType.OPEN; +import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; import com.google.common.testing.SerializableTester; import java.util.Map.Entry; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code ImmutableRangeMap}. @@ -29,6 +32,7 @@ * @author Louis Wasserman */ @GwtIncompatible // NavigableMap +@NullUnmarked public class ImmutableRangeMapTest extends TestCase { private static final ImmutableList> RANGES; private static final int MIN_BOUND = 0; @@ -65,18 +69,10 @@ public class ImmutableRangeMapTest extends TestCase { public void testBuilderRejectsEmptyRanges() { for (int i = MIN_BOUND; i <= MAX_BOUND; i++) { + int ii = i; ImmutableRangeMap.Builder builder = ImmutableRangeMap.builder(); - try { - builder.put(Range.closedOpen(i, i), 1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // success - } - try { - builder.put(Range.openClosed(i, i), 1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.put(Range.closedOpen(ii, ii), 1)); + assertThrows(IllegalArgumentException.class, () -> builder.put(Range.openClosed(ii, ii), 1)); } } @@ -120,11 +116,7 @@ public void testGet() { } public void testSpanEmpty() { - try { - ImmutableRangeMap.of().span(); - fail("Expected NoSuchElementException"); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> ImmutableRangeMap.of().span()); } public void testSpanSingleRange() { @@ -157,9 +149,9 @@ public void testGetEntry() { for (int i = MIN_BOUND; i <= MAX_BOUND; i++) { Entry, Integer> expectedEntry = null; if (range1.contains(i)) { - expectedEntry = Maps.immutableEntry(range1, 1); + expectedEntry = immutableEntry(range1, 1); } else if (range2.contains(i)) { - expectedEntry = Maps.immutableEntry(range2, 2); + expectedEntry = immutableEntry(range2, 2); } assertEquals(expectedEntry, rangeMap.getEntry(i)); diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableRangeSetTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableRangeSetTest.java index 5ca1f586199d..ab764372ec2a 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableRangeSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableRangeSetTest.java @@ -15,6 +15,7 @@ package com.google.common.collect; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.NavigableSetTestSuiteBuilder; @@ -28,6 +29,7 @@ import java.util.Set; import junit.framework.Test; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link ImmutableRangeSet}. @@ -35,8 +37,10 @@ * @author Louis Wasserman */ @GwtIncompatible // ImmutableRangeSet +@NullUnmarked public class ImmutableRangeSetTest extends AbstractRangeSetTest { + @AndroidIncompatible // test-suite builders static final class ImmutableRangeSetIntegerAsSetGenerator implements TestSetGenerator { @Override public SampleElements samples() { @@ -64,6 +68,7 @@ public Set create(Object... elements) { } } + @AndroidIncompatible // test-suite builders static final class ImmutableRangeSetBigIntegerAsSetGenerator implements TestSetGenerator { @Override @@ -97,6 +102,7 @@ public Set create(Object... elements) { } } + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(ImmutableRangeSetTest.class); @@ -312,6 +318,7 @@ public void testMultipleBoundedAboveRanges() { assertEquals(expectedComplement, rangeSet.complement()); } + @SuppressWarnings("DoNotCall") public void testAddUnsupported() { RangeSet rangeSet = ImmutableRangeSet.builder() @@ -319,14 +326,10 @@ public void testAddUnsupported() { .add(Range.closedOpen(1, 3)) .build(); - try { - rangeSet.add(Range.open(3, 4)); - fail(); - } catch (UnsupportedOperationException expected) { - // success - } + assertThrows(UnsupportedOperationException.class, () -> rangeSet.add(Range.open(3, 4))); } + @SuppressWarnings("DoNotCall") public void testAddAllUnsupported() { RangeSet rangeSet = ImmutableRangeSet.builder() @@ -334,14 +337,12 @@ public void testAddAllUnsupported() { .add(Range.closedOpen(1, 3)) .build(); - try { - rangeSet.addAll(ImmutableRangeSet.of()); - fail(); - } catch (UnsupportedOperationException expected) { - // success - } + assertThrows( + UnsupportedOperationException.class, + () -> rangeSet.addAll(ImmutableRangeSet.of())); } + @SuppressWarnings("DoNotCall") public void testRemoveUnsupported() { RangeSet rangeSet = ImmutableRangeSet.builder() @@ -349,14 +350,10 @@ public void testRemoveUnsupported() { .add(Range.closedOpen(1, 3)) .build(); - try { - rangeSet.remove(Range.closed(6, 7)); - fail(); - } catch (UnsupportedOperationException expected) { - // success - } + assertThrows(UnsupportedOperationException.class, () -> rangeSet.remove(Range.closed(6, 7))); } + @SuppressWarnings("DoNotCall") public void testRemoveAllUnsupported() { RangeSet rangeSet = ImmutableRangeSet.builder() @@ -364,24 +361,17 @@ public void testRemoveAllUnsupported() { .add(Range.closedOpen(1, 3)) .build(); - try { - rangeSet.removeAll(ImmutableRangeSet.of()); - fail(); - } catch (UnsupportedOperationException expected) { - // success - } + assertThrows( + UnsupportedOperationException.class, + () -> rangeSet.removeAll(ImmutableRangeSet.of())); - try { - rangeSet.removeAll(ImmutableRangeSet.of(Range.closed(6, 8))); - fail(); - } catch (UnsupportedOperationException expected) { - // success - } + assertThrows( + UnsupportedOperationException.class, + () -> rangeSet.removeAll(ImmutableRangeSet.of(Range.closed(6, 8)))); } @AndroidIncompatible // slow public void testExhaustive() { - @SuppressWarnings("unchecked") ImmutableSet> ranges = ImmutableSet.of( Range.all(), @@ -424,11 +414,7 @@ public void testExhaustive() { } if (anyOverlaps) { - try { - RangeSet copy = ImmutableRangeSet.copyOf(subset); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> ImmutableRangeSet.copyOf(subset)); } else { RangeSet copy = ImmutableRangeSet.copyOf(subset); assertEquals(mutable, copy); diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapAsMapImplementsMapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapAsMapImplementsMapTest.java index c503a17573ef..f8dfc39a2990 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapAsMapImplementsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapAsMapImplementsMapTest.java @@ -20,6 +20,7 @@ import com.google.common.collect.testing.MapInterfaceTest; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.NullMarked; /** * Test {@link Multimap#asMap()} for an {@link ImmutableSetMultimap} with {@link MapInterfaceTest}. @@ -27,6 +28,7 @@ * @author Mike Ward */ @GwtCompatible +@NullMarked public class ImmutableSetMultimapAsMapImplementsMapTest extends AbstractMultimapAsMapImplementsMapTest { diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapTest.java index 8690e948bd15..6985078a773a 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapTest.java @@ -16,19 +16,23 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; import static com.google.common.collect.testing.features.MapFeature.ALLOWS_ANY_NULL_QUERIES; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.emptySet; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableSetMultimap.Builder; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.google.SetMultimapTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringSetMultimapGenerator; import com.google.common.collect.testing.google.UnmodifiableCollectionTests; import com.google.common.testing.EqualsTester; +import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.util.Arrays; import java.util.Collection; @@ -37,14 +41,19 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ImmutableSetMultimap}. * * @author Mike Ward */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableSetMultimapTest extends TestCase { + @J2ktIncompatible + @AndroidIncompatible // test-suite builders private static final class ImmutableSetMultimapGenerator extends TestStringSetMultimapGenerator { @Override protected SetMultimap create(Entry[] entries) { @@ -56,6 +65,8 @@ protected SetMultimap create(Entry[] entries) { } } + @J2ktIncompatible + @AndroidIncompatible // test-suite builders private static final class ImmutableSetMultimapCopyOfEntriesGenerator extends TestStringSetMultimapGenerator { @Override @@ -64,7 +75,9 @@ protected SetMultimap create(Entry[] entries) { } } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(ImmutableSetMultimapTest.class); @@ -81,6 +94,98 @@ public static Test suite() { return suite; } + public void testBuilderWithExpectedKeysNegative() { + assertThrows( + IllegalArgumentException.class, () -> ImmutableSetMultimap.builderWithExpectedKeys(-1)); + } + + public void testBuilderWithExpectedKeysZero() { + ImmutableSetMultimap.Builder builder = + ImmutableSetMultimap.builderWithExpectedKeys(0); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedKeysPositive() { + ImmutableSetMultimap.Builder builder = + ImmutableSetMultimap.builderWithExpectedKeys(1); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedValuesPerKeyNegative() { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + assertThrows(IllegalArgumentException.class, () -> builder.expectedValuesPerKey(-1)); + } + + public void testBuilderWithExpectedValuesPerKeyZero() { + ImmutableSetMultimap.Builder builder = + ImmutableSetMultimap.builder().expectedValuesPerKey(0); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedValuesPerKeyPositive() { + ImmutableSetMultimap.Builder builder = + ImmutableSetMultimap.builder().expectedValuesPerKey(1); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedValuesPerKeyNegativeOrderValuesBy() { + ImmutableSetMultimap.Builder builder = + ImmutableSetMultimap.builder().orderValuesBy(Ordering.natural()); + assertThrows(IllegalArgumentException.class, () -> builder.expectedValuesPerKey(-1)); + } + + public void testBuilderWithExpectedValuesPerKeyZeroOrderValuesBy() { + ImmutableSetMultimap.Builder builder = + ImmutableSetMultimap.builder() + .orderValuesBy(Ordering.natural()) + .expectedValuesPerKey(0); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + public void testBuilderWithExpectedValuesPerKeyPositiveOrderValuesBy() { + ImmutableSetMultimap.Builder builder = + ImmutableSetMultimap.builder() + .orderValuesBy(Ordering.natural()) + .expectedValuesPerKey(1); + builder.put("key", "value"); + assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value")); + } + + static class HashHostileComparable implements Comparable { + final String string; + + public HashHostileComparable(String string) { + this.string = string; + } + + @Override + public int hashCode() { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTo(HashHostileComparable o) { + return string.compareTo(o.string); + } + } + + public void testSortedBuilderWithExpectedValuesPerKeyPositive() { + ImmutableSetMultimap.Builder builder = + ImmutableSetMultimap.builder() + .expectedValuesPerKey(2) + .orderValuesBy(Ordering.natural()); + HashHostileComparable v1 = new HashHostileComparable("value1"); + HashHostileComparable v2 = new HashHostileComparable("value2"); + builder.put("key", v1); + builder.put("key", v2); + assertThat(builder.build().entries()).hasSize(2); + } + public void testBuilder_withImmutableEntry() { ImmutableSetMultimap multimap = new Builder().put(Maps.immutableEntry("one", 1)).build(); @@ -89,25 +194,19 @@ public void testBuilder_withImmutableEntry() { public void testBuilder_withImmutableEntryAndNullContents() { Builder builder = new Builder<>(); - try { - builder.put(Maps.immutableEntry("one", (Integer) null)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.put(Maps.immutableEntry((String) null, 1)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> builder.put(Maps.immutableEntry("one", (Integer) null))); + assertThrows( + NullPointerException.class, () -> builder.put(Maps.immutableEntry((String) null, 1))); } private static class StringHolder { - String string; + @Nullable String string; } public void testBuilder_withMutableEntry() { ImmutableSetMultimap.Builder builder = new Builder<>(); - final StringHolder holder = new StringHolder(); + StringHolder holder = new StringHolder(); holder.string = "one"; Entry entry = new AbstractMapEntry() { @@ -200,55 +299,26 @@ public void testBuilderPutAllMultimapWithDuplicates() { } public void testBuilderPutNullKey() { - Multimap toPut = LinkedListMultimap.create(); - toPut.put("foo", null); + Multimap<@Nullable String, Integer> toPut = LinkedListMultimap.create(); + toPut.put(null, 1); ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); - try { - builder.put(null, 1); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll(null, Arrays.asList(1, 2, 3)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll(null, 1, 2, 3); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll(toPut); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.put(null, 1)); + assertThrows(NullPointerException.class, () -> builder.putAll(null, Arrays.asList(1, 2, 3))); + assertThrows(NullPointerException.class, () -> builder.putAll(null, 1, 2, 3)); + assertThrows( + NullPointerException.class, () -> builder.putAll((Multimap) toPut)); } public void testBuilderPutNullValue() { - Multimap toPut = LinkedListMultimap.create(); - toPut.put(null, 1); + Multimap toPut = LinkedListMultimap.create(); + toPut.put("foo", null); ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); - try { - builder.put("foo", null); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll("foo", Arrays.asList(1, null, 3)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll("foo", 4, null, 6); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.putAll(toPut); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.put("foo", null)); + assertThrows( + NullPointerException.class, () -> builder.putAll("foo", Arrays.asList(1, null, 3))); + assertThrows(NullPointerException.class, () -> builder.putAll("foo", 4, null, 6)); + assertThrows( + NullPointerException.class, () -> builder.putAll((Multimap) toPut)); } public void testBuilderOrderKeysBy() { @@ -381,23 +451,19 @@ public void testCopyOfImmutableSetMultimap() { } public void testCopyOfNullKey() { - HashMultimap input = HashMultimap.create(); + HashMultimap<@Nullable String, Integer> input = HashMultimap.create(); input.put(null, 1); - try { - ImmutableSetMultimap.copyOf(input); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> ImmutableSetMultimap.copyOf((Multimap) input)); } public void testCopyOfNullValue() { - HashMultimap input = HashMultimap.create(); - input.putAll("foo", Arrays.asList(1, null, 3)); - try { - ImmutableSetMultimap.copyOf(input); - fail(); - } catch (NullPointerException expected) { - } + HashMultimap input = HashMultimap.create(); + input.putAll("foo", Arrays.<@Nullable Integer>asList(1, null, 3)); + assertThrows( + NullPointerException.class, + () -> ImmutableSetMultimap.copyOf((Multimap) input)); } // TODO(b/172823566): Use mainline testToImmutableSetMultimap once CollectorTester is usable. @@ -421,11 +487,11 @@ public void testEmptyMultimapReads() { assertFalse(multimap.containsEntry("foo", 1)); assertTrue(multimap.entries().isEmpty()); assertTrue(multimap.equals(HashMultimap.create())); - assertEquals(Collections.emptySet(), multimap.get("foo")); + assertEquals(emptySet(), multimap.get("foo")); assertEquals(0, multimap.hashCode()); assertTrue(multimap.isEmpty()); assertEquals(HashMultiset.create(), multimap.keys()); - assertEquals(Collections.emptySet(), multimap.keySet()); + assertEquals(emptySet(), multimap.keySet()); assertEquals(0, multimap.size()); assertTrue(multimap.values().isEmpty()); assertEquals("{}", multimap.toString()); @@ -547,6 +613,7 @@ private static void assertMultimapEquals( } } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { Multimap multimap = createMultimap(); @@ -560,12 +627,14 @@ public void testSerialization() { assertEquals(HashMultiset.create(multimap.values()), HashMultiset.create(valuesCopy)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testEmptySerialization() { Multimap multimap = ImmutableSetMultimap.of(); assertSame(multimap, SerializableTester.reserialize(multimap)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSortedSerialization() { Multimap multimap = @@ -592,4 +661,14 @@ private ImmutableSetMultimap createMultimap() { .put("foo", 3) .build(); } + + @J2ktIncompatible + @GwtIncompatible // reflection + public void testNulls() throws Exception { + NullPointerTester tester = new NullPointerTester(); + tester.testAllPublicStaticMethods(ImmutableSetMultimap.class); + tester.ignore(ImmutableSetMultimap.class.getMethod("get", Object.class)); + tester.testAllPublicInstanceMethods(ImmutableSetMultimap.of()); + tester.testAllPublicInstanceMethods(ImmutableSetMultimap.of("a", 1)); + } } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSetTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSetTest.java index 2ddd1ee6c184..77bbb88c1f25 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSetTest.java @@ -17,9 +17,12 @@ package com.google.common.collect; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.SetTestSuiteBuilder; @@ -34,12 +37,13 @@ import com.google.common.collect.testing.google.SetGenerators.ImmutableSetUnsizedBuilderGenerator; import com.google.common.collect.testing.google.SetGenerators.ImmutableSetWithBadHashesGenerator; import com.google.common.testing.EqualsTester; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.Set; import junit.framework.Test; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link ImmutableSet}. @@ -48,10 +52,13 @@ * @author Jared Levy * @author Nick Kralevich */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableSetTest extends AbstractImmutableSetTest { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -168,7 +175,6 @@ protected > Set of(E e1, E e2, E e3, E e4, E return ImmutableSet.of(e1, e2, e3, e4, e5); } - @SuppressWarnings("unchecked") @Override protected > Set of( E e1, E e2, E e3, E e4, E e5, E e6, E... rest) { @@ -198,20 +204,22 @@ protected > Set copyOf(Iterator public void testCreation_allDuplicates() { ImmutableSet set = ImmutableSet.copyOf(Lists.newArrayList("a", "a")); assertTrue(set instanceof SingletonImmutableSet); - assertEquals(Lists.newArrayList("a"), Lists.newArrayList(set)); + assertEquals(Lists.newArrayList("a"), new ArrayList<>(set)); } public void testCreation_oneDuplicate() { // now we'll get the varargs overload + @SuppressWarnings("DistinctVarargsChecker") // deliberately testing deduplication ImmutableSet set = ImmutableSet.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "a"); assertEquals( Lists.newArrayList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"), - Lists.newArrayList(set)); + new ArrayList<>(set)); } public void testCreation_manyDuplicates() { // now we'll get the varargs overload + @SuppressWarnings("DistinctVarargsChecker") // deliberately testing deduplication ImmutableSet set = ImmutableSet.of("a", "b", "c", "c", "c", "c", "b", "b", "a", "a", "c", "c", "c", "a"); assertThat(set).containsExactly("a", "b", "c").inOrder(); @@ -263,7 +271,7 @@ public void testPresizedBuilderForceCopy() { public void testCreation_arrayOfArray() { String[] array = new String[] {"a"}; Set set = ImmutableSet.of(array); - assertEquals(Collections.singleton(array), set); + assertEquals(singleton(array), set); } @GwtIncompatible // ImmutableSet.chooseTableSize @@ -279,11 +287,7 @@ public void testChooseTableSize() { assertEquals(1 << 30, ImmutableSet.chooseTableSize((1 << 30) - 1)); // Now we've gone too far - try { - ImmutableSet.chooseTableSize(1 << 30); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> ImmutableSet.chooseTableSize(1 << 30)); } @GwtIncompatible // RegularImmutableSet.table not in emulation @@ -329,11 +333,6 @@ public void testToImmutableSet_java7() { assertThat(set).containsExactly("a", "b", "c", "d").inOrder(); } - @GwtIncompatible // GWT is single threaded - public void testCopyOf_threadSafe() { - verifyThreadSafe(); - } - @Override > Builder builder() { return ImmutableSet.builder(); @@ -344,6 +343,7 @@ int getComplexBuilderSetLastElement() { return LAST_COLOR_ADDED; } + @SuppressWarnings("DistinctVarargsChecker") // deliberately testing deduplication public void testEquals() { new EqualsTester() .addEqualityGroup(ImmutableSet.of(), ImmutableSet.of()) diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapHeadMapInclusiveMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapHeadMapInclusiveMapInterfaceTest.java new file mode 100644 index 000000000000..40848428bf64 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapHeadMapInclusiveMapInterfaceTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class ImmutableSortedMapHeadMapInclusiveMapInterfaceTest + extends AbstractImmutableSortedMapMapInterfaceTest { + @Override + protected SortedMap makePopulatedMap() { + return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).headMap("c", true); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "d"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return 4; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapHeadMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapHeadMapMapInterfaceTest.java new file mode 100644 index 000000000000..c22d8063072d --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapHeadMapMapInterfaceTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class ImmutableSortedMapHeadMapMapInterfaceTest + extends AbstractImmutableSortedMapMapInterfaceTest { + @Override + protected SortedMap makePopulatedMap() { + return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).headMap("d"); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "d"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return 4; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapSubMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapSubMapMapInterfaceTest.java new file mode 100644 index 000000000000..31faf4bf4c26 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapSubMapMapInterfaceTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class ImmutableSortedMapSubMapMapInterfaceTest + extends AbstractImmutableSortedMapMapInterfaceTest { + @Override + protected SortedMap makePopulatedMap() { + return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).subMap("b", "d"); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "a"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return 4; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTailMapExclusiveMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTailMapExclusiveMapInterfaceTest.java new file mode 100644 index 000000000000..8752bd4f79bd --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTailMapExclusiveMapInterfaceTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class ImmutableSortedMapTailMapExclusiveMapInterfaceTest + extends AbstractImmutableSortedMapMapInterfaceTest { + @Override + protected SortedMap makePopulatedMap() { + return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).tailMap("a", false); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "a"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return 1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTailMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTailMapMapInterfaceTest.java new file mode 100644 index 000000000000..5a31f11b46fc --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTailMapMapInterfaceTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class ImmutableSortedMapTailMapMapInterfaceTest + extends AbstractImmutableSortedMapMapInterfaceTest { + @Override + protected SortedMap makePopulatedMap() { + return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).tailMap("b"); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "a"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return 1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTest.java index 9f3a7706cd86..642b6a9390c2 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMapTest.java @@ -16,16 +16,18 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.singletonMap; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Joiner; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableSortedMap.Builder; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.NavigableMapTestSuiteBuilder; -import com.google.common.collect.testing.SortedMapInterfaceTest; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; @@ -37,16 +39,20 @@ import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link ImmutableSortedMap}. @@ -55,11 +61,15 @@ * @author Jesse Wilson * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@SuppressWarnings("AlwaysThrows") +@NullMarked public class ImmutableSortedMapTest extends TestCase { // TODO: Avoid duplicating code in ImmutableMapTest + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(ImmutableSortedMapTest.class); @@ -118,588 +128,490 @@ public static Test suite() { return suite; } - public abstract static class AbstractMapTests extends SortedMapInterfaceTest { - public AbstractMapTests() { - super(false, false, false, false, false); - } - - @Override - protected SortedMap makeEmptyMap() { - throw new UnsupportedOperationException(); - } - - private static final Joiner joiner = Joiner.on(", "); + // Creation tests - @Override - protected void assertMoreInvariants(Map map) { - // TODO: can these be moved to MapInterfaceTest? - for (Entry entry : map.entrySet()) { - assertEquals(entry.getKey() + "=" + entry.getValue(), entry.toString()); - } - - assertEquals("{" + joiner.join(map.entrySet()) + "}", map.toString()); - assertEquals("[" + joiner.join(map.entrySet()) + "]", map.entrySet().toString()); - assertEquals("[" + joiner.join(map.keySet()) + "]", map.keySet().toString()); - assertEquals("[" + joiner.join(map.values()) + "]", map.values().toString()); - - assertEquals(Sets.newHashSet(map.entrySet()), map.entrySet()); - assertEquals(Sets.newHashSet(map.keySet()), map.keySet()); - } + public void testEmptyBuilder() { + ImmutableSortedMap map = + ImmutableSortedMap.naturalOrder().build(); + assertEquals(Collections.emptyMap(), map); } - public static class MapTests extends AbstractMapTests { - @Override - protected SortedMap makeEmptyMap() { - return ImmutableSortedMap.of(); - } - - @Override - protected SortedMap makePopulatedMap() { - return ImmutableSortedMap.of("one", 1, "two", 2, "three", 3); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; - } + public void testSingletonBuilder() { + ImmutableSortedMap map = + ImmutableSortedMap.naturalOrder().put("one", 1).build(); + assertMapEquals(map, "one", 1); } - public static class SingletonMapTests extends AbstractMapTests { - @Override - protected SortedMap makePopulatedMap() { - return ImmutableSortedMap.of("one", 1); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; - } + public void testBuilder() { + ImmutableSortedMap map = + ImmutableSortedMap.naturalOrder() + .put("one", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .build(); + assertMapEquals(map, "five", 5, "four", 4, "one", 1, "three", 3, "two", 2); } - @GwtIncompatible // SerializableTester - public static class ReserializedMapTests extends AbstractMapTests { - @Override - protected SortedMap makePopulatedMap() { - return SerializableTester.reserialize(ImmutableSortedMap.of("one", 1, "two", 2, "three", 3)); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "minus one"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return -1; - } + @SuppressWarnings("DoNotCall") + public void testBuilder_orderEntriesByValueFails() { + ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); + assertThrows( + UnsupportedOperationException.class, () -> builder.orderEntriesByValue(Ordering.natural())); } - public static class HeadMapTests extends AbstractMapTests { - @Override - protected SortedMap makePopulatedMap() { - return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).headMap("d"); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "d"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return 4; - } + public void testBuilder_withImmutableEntry() { + ImmutableSortedMap map = + ImmutableSortedMap.naturalOrder().put(immutableEntry("one", 1)).build(); + assertMapEquals(map, "one", 1); } - public static class HeadMapInclusiveTests extends AbstractMapTests { - @Override - protected SortedMap makePopulatedMap() { - return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).headMap("c", true); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "d"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return 4; - } + public void testBuilder_withImmutableEntryAndNullContents() { + Builder builder = ImmutableSortedMap.naturalOrder(); + assertThrows( + NullPointerException.class, () -> builder.put(immutableEntry("one", (Integer) null))); + assertThrows(NullPointerException.class, () -> builder.put(immutableEntry((String) null, 1))); } - public static class TailMapTests extends AbstractMapTests { - @Override - protected SortedMap makePopulatedMap() { - return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).tailMap("b"); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "a"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return 1; - } + private static class StringHolder { + @Nullable String string; } - public static class TailExclusiveMapTests extends AbstractMapTests { - @Override - protected SortedMap makePopulatedMap() { - return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).tailMap("a", false); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "a"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return 1; - } + public void testBuilder_withMutableEntry() { + ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); + StringHolder holder = new StringHolder(); + holder.string = "one"; + Entry entry = + new AbstractMapEntry() { + @Override + public String getKey() { + return holder.string; + } + + @Override + public Integer getValue() { + return 1; + } + }; + + builder.put(entry); + holder.string = "two"; + assertMapEquals(builder.build(), "one", 1); } - public static class SubMapTests extends AbstractMapTests { - @Override - protected SortedMap makePopulatedMap() { - return ImmutableSortedMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5).subMap("b", "d"); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "a"; - } - - @Override - protected Integer getValueNotInPopulatedMap() { - return 4; - } + public void testBuilderPutAllWithEmptyMap() { + ImmutableSortedMap map = + ImmutableSortedMap.naturalOrder() + .putAll(Collections.emptyMap()) + .build(); + assertEquals(Collections.emptyMap(), map); } - public static class CreationTests extends TestCase { - public void testEmptyBuilder() { - ImmutableSortedMap map = - ImmutableSortedMap.naturalOrder().build(); - assertEquals(Collections.emptyMap(), map); - } - - public void testSingletonBuilder() { - ImmutableSortedMap map = - ImmutableSortedMap.naturalOrder().put("one", 1).build(); - assertMapEquals(map, "one", 1); - } - - public void testBuilder() { - ImmutableSortedMap map = - ImmutableSortedMap.naturalOrder() - .put("one", 1) - .put("two", 2) - .put("three", 3) - .put("four", 4) - .put("five", 5) - .build(); - assertMapEquals(map, "five", 5, "four", 4, "one", 1, "three", 3, "two", 2); - } - - @SuppressWarnings("DoNotCall") - public void testBuilder_orderEntriesByValueFails() { - ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); - try { - builder.orderEntriesByValue(Ordering.natural()); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } - } + public void testBuilderPutAll() { + Map toPut = new LinkedHashMap<>(); + toPut.put("one", 1); + toPut.put("two", 2); + toPut.put("three", 3); + Map moreToPut = new LinkedHashMap<>(); + moreToPut.put("four", 4); + moreToPut.put("five", 5); + + ImmutableSortedMap map = + ImmutableSortedMap.naturalOrder().putAll(toPut).putAll(moreToPut).build(); + assertMapEquals(map, "five", 5, "four", 4, "one", 1, "three", 3, "two", 2); + } - public void testBuilder_withImmutableEntry() { - ImmutableSortedMap map = - ImmutableSortedMap.naturalOrder() - .put(Maps.immutableEntry("one", 1)) - .build(); - assertMapEquals(map, "one", 1); - } + public void testBuilderReuse() { + Builder builder = ImmutableSortedMap.naturalOrder(); + ImmutableSortedMap mapOne = builder.put("one", 1).put("two", 2).build(); + ImmutableSortedMap mapTwo = builder.put("three", 3).put("four", 4).build(); - public void testBuilder_withImmutableEntryAndNullContents() { - Builder builder = ImmutableSortedMap.naturalOrder(); - try { - builder.put(Maps.immutableEntry("one", (Integer) null)); - fail(); - } catch (NullPointerException expected) { - } - try { - builder.put(Maps.immutableEntry((String) null, 1)); - fail(); - } catch (NullPointerException expected) { - } - } - - private static class StringHolder { - String string; - } + assertMapEquals(mapOne, "one", 1, "two", 2); + assertMapEquals(mapTwo, "four", 4, "one", 1, "three", 3, "two", 2); + } - public void testBuilder_withMutableEntry() { - ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); - final StringHolder holder = new StringHolder(); - holder.string = "one"; - Entry entry = - new AbstractMapEntry() { - @Override - public String getKey() { - return holder.string; - } - - @Override - public Integer getValue() { - return 1; - } - }; - - builder.put(entry); - holder.string = "two"; - assertMapEquals(builder.build(), "one", 1); - } + public void testBuilderPutNullKey() { + Builder builder = ImmutableSortedMap.naturalOrder(); + assertThrows(NullPointerException.class, () -> builder.put(null, 1)); + } - public void testBuilderPutAllWithEmptyMap() { - ImmutableSortedMap map = - ImmutableSortedMap.naturalOrder() - .putAll(Collections.emptyMap()) - .build(); - assertEquals(Collections.emptyMap(), map); - } + public void testBuilderPutNullValue() { + Builder builder = ImmutableSortedMap.naturalOrder(); + assertThrows(NullPointerException.class, () -> builder.put("one", null)); + } - public void testBuilderPutAll() { - Map toPut = new LinkedHashMap<>(); - toPut.put("one", 1); - toPut.put("two", 2); - toPut.put("three", 3); - Map moreToPut = new LinkedHashMap<>(); - moreToPut.put("four", 4); - moreToPut.put("five", 5); - - ImmutableSortedMap map = - ImmutableSortedMap.naturalOrder() - .putAll(toPut) - .putAll(moreToPut) - .build(); - assertMapEquals(map, "five", 5, "four", 4, "one", 1, "three", 3, "two", 2); - } + public void testBuilderPutNullKeyViaPutAll() { + Builder builder = ImmutableSortedMap.naturalOrder(); + assertThrows( + NullPointerException.class, + () -> builder.putAll(Collections.singletonMap(null, 1))); + } - public void testBuilderReuse() { - Builder builder = ImmutableSortedMap.naturalOrder(); - ImmutableSortedMap mapOne = builder.put("one", 1).put("two", 2).build(); - ImmutableSortedMap mapTwo = builder.put("three", 3).put("four", 4).build(); + public void testBuilderPutNullValueViaPutAll() { + Builder builder = ImmutableSortedMap.naturalOrder(); + assertThrows( + NullPointerException.class, + () -> builder.putAll(Collections.singletonMap("one", null))); + } - assertMapEquals(mapOne, "one", 1, "two", 2); - assertMapEquals(mapTwo, "four", 4, "one", 1, "three", 3, "two", 2); - } + public void testPuttingTheSameKeyTwiceThrowsOnBuild() { + Builder builder = + ImmutableSortedMap.naturalOrder() + .put("one", 1) + .put("one", 2); // throwing on this line would be even better - public void testBuilderPutNullKey() { - Builder builder = ImmutableSortedMap.naturalOrder(); - try { - builder.put(null, 1); - fail(); - } catch (NullPointerException expected) { - } - } + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } - public void testBuilderPutNullValue() { - Builder builder = ImmutableSortedMap.naturalOrder(); - try { - builder.put("one", null); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOf() { + assertMapEquals(ImmutableSortedMap.of("one", 1), "one", 1); + assertMapEquals(ImmutableSortedMap.of("one", 1, "two", 2), "one", 1, "two", 2); + assertMapEquals( + ImmutableSortedMap.of("one", 1, "two", 2, "three", 3), "one", 1, "three", 3, "two", 2); + assertMapEquals( + ImmutableSortedMap.of("one", 1, "two", 2, "three", 3, "four", 4), + "four", + 4, + "one", + 1, + "three", + 3, + "two", + 2); + assertMapEquals( + ImmutableSortedMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5), + "five", + 5, + "four", + 4, + "one", + 1, + "three", + 3, + "two", + 2); + assertMapEquals( + ImmutableSortedMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6), + "five", + 5, + "four", + 4, + "one", + 1, + "six", + 6, + "three", + 3, + "two", + 2); + assertMapEquals( + ImmutableSortedMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7), + "five", + 5, + "four", + 4, + "one", + 1, + "seven", + 7, + "six", + 6, + "three", + 3, + "two", + 2); + assertMapEquals( + ImmutableSortedMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8), + "eight", + 8, + "five", + 5, + "four", + 4, + "one", + 1, + "seven", + 7, + "six", + 6, + "three", + 3, + "two", + 2); + assertMapEquals( + ImmutableSortedMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8, + "nine", 9), + "eight", + 8, + "five", + 5, + "four", + 4, + "nine", + 9, + "one", + 1, + "seven", + 7, + "six", + 6, + "three", + 3, + "two", + 2); + assertMapEquals( + ImmutableSortedMap.of( + "one", 1, + "two", 2, + "three", 3, + "four", 4, + "five", 5, + "six", 6, + "seven", 7, + "eight", 8, + "nine", 9, + "ten", 10), + "eight", + 8, + "five", + 5, + "four", + 4, + "nine", + 9, + "one", + 1, + "seven", + 7, + "six", + 6, + "ten", + 10, + "three", + 3, + "two", + 2); + } - public void testBuilderPutNullKeyViaPutAll() { - Builder builder = ImmutableSortedMap.naturalOrder(); - try { - builder.putAll(Collections.singletonMap(null, 1)); - fail(); - } catch (NullPointerException expected) { - } - } + public void testOfNullKey() { + Integer n = null; + assertThrows(NullPointerException.class, () -> ImmutableSortedMap.of(n, 1)); - public void testBuilderPutNullValueViaPutAll() { - Builder builder = ImmutableSortedMap.naturalOrder(); - try { - builder.putAll(Collections.singletonMap("one", null)); - fail(); - } catch (NullPointerException expected) { - } - } + assertThrows(NullPointerException.class, () -> ImmutableSortedMap.of("one", 1, null, 2)); + } - public void testPuttingTheSameKeyTwiceThrowsOnBuild() { - Builder builder = - ImmutableSortedMap.naturalOrder() - .put("one", 1) - .put("one", 2); // throwing on this line would be even better + public void testOfNullValue() { + assertThrows(NullPointerException.class, () -> ImmutableSortedMap.of("one", null)); - try { - builder.build(); - fail(); - } catch (IllegalArgumentException expected) { - } - } + assertThrows(NullPointerException.class, () -> ImmutableSortedMap.of("one", 1, "two", null)); + } - public void testOf() { - assertMapEquals(ImmutableSortedMap.of("one", 1), "one", 1); - assertMapEquals(ImmutableSortedMap.of("one", 1, "two", 2), "one", 1, "two", 2); - assertMapEquals( - ImmutableSortedMap.of("one", 1, "two", 2, "three", 3), "one", 1, "three", 3, "two", 2); - assertMapEquals( - ImmutableSortedMap.of("one", 1, "two", 2, "three", 3, "four", 4), - "four", - 4, - "one", - 1, - "three", - 3, - "two", - 2); - assertMapEquals( - ImmutableSortedMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5), - "five", - 5, - "four", - 4, - "one", - 1, - "three", - 3, - "two", - 2); - } + @SuppressWarnings("DistinctVarargsChecker") + public void testOfWithDuplicateKey() { + assertThrows(IllegalArgumentException.class, () -> ImmutableSortedMap.of("one", 1, "one", 1)); + } - public void testOfNullKey() { - Integer n = null; - try { - ImmutableSortedMap.of(n, 1); - fail(); - } catch (NullPointerException expected) { - } + public void testCopyOfEmptyMap() { + ImmutableSortedMap copy = + ImmutableSortedMap.copyOf(Collections.emptyMap()); + assertEquals(Collections.emptyMap(), copy); + assertSame(copy, ImmutableSortedMap.copyOf(copy)); + assertSame(Ordering.natural(), copy.comparator()); + } - try { - ImmutableSortedMap.of("one", 1, null, 2); - fail(); - } catch (NullPointerException expected) { - } - } + public void testCopyOfSingletonMap() { + ImmutableSortedMap copy = ImmutableSortedMap.copyOf(singletonMap("one", 1)); + assertMapEquals(copy, "one", 1); + assertSame(copy, ImmutableSortedMap.copyOf(copy)); + assertSame(Ordering.natural(), copy.comparator()); + } - public void testOfNullValue() { - try { - ImmutableSortedMap.of("one", null); - fail(); - } catch (NullPointerException expected) { - } + public void testCopyOf() { + Map original = new LinkedHashMap<>(); + original.put("one", 1); + original.put("two", 2); + original.put("three", 3); - try { - ImmutableSortedMap.of("one", 1, "two", null); - fail(); - } catch (NullPointerException expected) { - } - } + ImmutableSortedMap copy = ImmutableSortedMap.copyOf(original); + assertMapEquals(copy, "one", 1, "three", 3, "two", 2); + assertSame(copy, ImmutableSortedMap.copyOf(copy)); + assertSame(Ordering.natural(), copy.comparator()); + } - public void testOfWithDuplicateKey() { - try { - ImmutableSortedMap.of("one", 1, "one", 1); - fail(); - } catch (IllegalArgumentException expected) { - } - } + public void testCopyOfExplicitComparator() { + Comparator comparator = Ordering.natural().reverse(); + Map original = new LinkedHashMap<>(); + original.put("one", 1); + original.put("two", 2); + original.put("three", 3); + + ImmutableSortedMap copy = ImmutableSortedMap.copyOf(original, comparator); + assertMapEquals(copy, "two", 2, "three", 3, "one", 1); + assertSame(copy, ImmutableSortedMap.copyOf(copy, comparator)); + assertSame(comparator, copy.comparator()); + } - public void testCopyOfEmptyMap() { - ImmutableSortedMap copy = - ImmutableSortedMap.copyOf(Collections.emptyMap()); - assertEquals(Collections.emptyMap(), copy); - assertSame(copy, ImmutableSortedMap.copyOf(copy)); - assertSame(Ordering.natural(), copy.comparator()); - } + public void testCopyOfImmutableSortedSetDifferentComparator() { + Comparator comparator = Ordering.natural().reverse(); + Map original = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3); + ImmutableSortedMap copy = ImmutableSortedMap.copyOf(original, comparator); + assertMapEquals(copy, "two", 2, "three", 3, "one", 1); + assertSame(copy, ImmutableSortedMap.copyOf(copy, comparator)); + assertSame(comparator, copy.comparator()); + } - public void testCopyOfSingletonMap() { - ImmutableSortedMap copy = - ImmutableSortedMap.copyOf(Collections.singletonMap("one", 1)); - assertMapEquals(copy, "one", 1); - assertSame(copy, ImmutableSortedMap.copyOf(copy)); - assertSame(Ordering.natural(), copy.comparator()); - } + public void testCopyOfSortedNatural() { + SortedMap original = Maps.newTreeMap(); + original.put("one", 1); + original.put("two", 2); + original.put("three", 3); - public void testCopyOf() { - Map original = new LinkedHashMap<>(); - original.put("one", 1); - original.put("two", 2); - original.put("three", 3); + ImmutableSortedMap copy = ImmutableSortedMap.copyOfSorted(original); + assertMapEquals(copy, "one", 1, "three", 3, "two", 2); + assertSame(copy, ImmutableSortedMap.copyOfSorted(copy)); + assertSame(Ordering.natural(), copy.comparator()); + } - ImmutableSortedMap copy = ImmutableSortedMap.copyOf(original); - assertMapEquals(copy, "one", 1, "three", 3, "two", 2); - assertSame(copy, ImmutableSortedMap.copyOf(copy)); - assertSame(Ordering.natural(), copy.comparator()); - } + public void testCopyOfSortedExplicit() { + Comparator comparator = Ordering.natural().reverse(); + SortedMap original = Maps.newTreeMap(comparator); + original.put("one", 1); + original.put("two", 2); + original.put("three", 3); + + ImmutableSortedMap copy = ImmutableSortedMap.copyOfSorted(original); + assertMapEquals(copy, "two", 2, "three", 3, "one", 1); + assertSame(copy, ImmutableSortedMap.copyOfSorted(copy)); + assertSame(comparator, copy.comparator()); + } - public void testCopyOfExplicitComparator() { - Comparator comparator = Ordering.natural().reverse(); - Map original = new LinkedHashMap<>(); - original.put("one", 1); - original.put("two", 2); - original.put("three", 3); - - ImmutableSortedMap copy = ImmutableSortedMap.copyOf(original, comparator); - assertMapEquals(copy, "two", 2, "three", 3, "one", 1); - assertSame(copy, ImmutableSortedMap.copyOf(copy, comparator)); - assertSame(comparator, copy.comparator()); - } + private static class IntegerDiv10 implements Comparable { + final int value; - public void testCopyOfImmutableSortedSetDifferentComparator() { - Comparator comparator = Ordering.natural().reverse(); - Map original = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3); - ImmutableSortedMap copy = ImmutableSortedMap.copyOf(original, comparator); - assertMapEquals(copy, "two", 2, "three", 3, "one", 1); - assertSame(copy, ImmutableSortedMap.copyOf(copy, comparator)); - assertSame(comparator, copy.comparator()); + IntegerDiv10(int value) { + this.value = value; } - public void testCopyOfSortedNatural() { - SortedMap original = Maps.newTreeMap(); - original.put("one", 1); - original.put("two", 2); - original.put("three", 3); - - ImmutableSortedMap copy = ImmutableSortedMap.copyOfSorted(original); - assertMapEquals(copy, "one", 1, "three", 3, "two", 2); - assertSame(copy, ImmutableSortedMap.copyOfSorted(copy)); - assertSame(Ordering.natural(), copy.comparator()); + @Override + public int compareTo(IntegerDiv10 o) { + return value / 10 - o.value / 10; } - public void testCopyOfSortedExplicit() { - Comparator comparator = Ordering.natural().reverse(); - SortedMap original = Maps.newTreeMap(comparator); - original.put("one", 1); - original.put("two", 2); - original.put("three", 3); - - ImmutableSortedMap copy = ImmutableSortedMap.copyOfSorted(original); - assertMapEquals(copy, "two", 2, "three", 3, "one", 1); - assertSame(copy, ImmutableSortedMap.copyOfSorted(copy)); - assertSame(comparator, copy.comparator()); + @Override + public String toString() { + return Integer.toString(value); } + } - private static class IntegerDiv10 implements Comparable { - final int value; - - IntegerDiv10(int value) { - this.value = value; - } - - @Override - public int compareTo(IntegerDiv10 o) { - return value / 10 - o.value / 10; - } - - @Override - public String toString() { - return Integer.toString(value); - } - } + public void testCopyOfDuplicateKey() { + Map original = + ImmutableMap.of( + new IntegerDiv10(3), "three", + new IntegerDiv10(20), "twenty", + new IntegerDiv10(11), "eleven", + new IntegerDiv10(35), "thirty five", + new IntegerDiv10(12), "twelve"); - public void testCopyOfDuplicateKey() { - Map original = - ImmutableMap.of( - new IntegerDiv10(3), "three", - new IntegerDiv10(20), "twenty", - new IntegerDiv10(11), "eleven", - new IntegerDiv10(35), "thirty five", - new IntegerDiv10(12), "twelve"); - - try { - ImmutableSortedMap.copyOf(original); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - } + assertThrows(IllegalArgumentException.class, () -> ImmutableSortedMap.copyOf(original)); + } - public void testImmutableMapCopyOfImmutableSortedMap() { - IntegerDiv10 three = new IntegerDiv10(3); - IntegerDiv10 eleven = new IntegerDiv10(11); - IntegerDiv10 twelve = new IntegerDiv10(12); - IntegerDiv10 twenty = new IntegerDiv10(20); - Map original = - ImmutableSortedMap.of(three, "three", eleven, "eleven", twenty, "twenty"); - Map copy = ImmutableMap.copyOf(original); - assertTrue(original.containsKey(twelve)); - assertFalse(copy.containsKey(twelve)); - } + public void testImmutableMapCopyOfImmutableSortedMap() { + IntegerDiv10 three = new IntegerDiv10(3); + IntegerDiv10 eleven = new IntegerDiv10(11); + IntegerDiv10 twelve = new IntegerDiv10(12); + IntegerDiv10 twenty = new IntegerDiv10(20); + Map original = + ImmutableSortedMap.of(three, "three", eleven, "eleven", twenty, "twenty"); + Map copy = ImmutableMap.copyOf(original); + assertTrue(original.containsKey(twelve)); + assertFalse(copy.containsKey(twelve)); + } - public void testBuilderReverseOrder() { - ImmutableSortedMap map = - ImmutableSortedMap.reverseOrder() - .put("one", 1) - .put("two", 2) - .put("three", 3) - .put("four", 4) - .put("five", 5) - .build(); - assertMapEquals(map, "two", 2, "three", 3, "one", 1, "four", 4, "five", 5); - assertEquals(Ordering.natural().reverse(), map.comparator()); - } + public void testBuilderReverseOrder() { + ImmutableSortedMap map = + ImmutableSortedMap.reverseOrder() + .put("one", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .build(); + assertMapEquals(map, "two", 2, "three", 3, "one", 1, "four", 4, "five", 5); + assertEquals(Ordering.natural().reverse(), map.comparator()); + } - public void testBuilderComparator() { - Comparator comparator = Ordering.natural().reverse(); - ImmutableSortedMap map = - new ImmutableSortedMap.Builder(comparator) - .put("one", 1) - .put("two", 2) - .put("three", 3) - .put("four", 4) - .put("five", 5) - .build(); - assertMapEquals(map, "two", 2, "three", 3, "one", 1, "four", 4, "five", 5); - assertSame(comparator, map.comparator()); - } + public void testBuilderComparator() { + Comparator comparator = Ordering.natural().reverse(); + ImmutableSortedMap map = + new ImmutableSortedMap.Builder(comparator) + .put("one", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .build(); + assertMapEquals(map, "two", 2, "three", 3, "one", 1, "four", 4, "five", 5); + assertSame(comparator, map.comparator()); + } - // TODO(b/172823566): Use mainline testToImmutableSortedMap once CollectorTester is usable. - public void testToImmutableSortedMap_java7_combine() { - ImmutableSortedMap.Builder zis = - ImmutableSortedMap.naturalOrder().put("one", 1).put("four", 4); - ImmutableSortedMap.Builder zat = - ImmutableSortedMap.naturalOrder().put("two", 2).put("three", 3); - ImmutableSortedMap sortedMap = zis.combine(zat).build(); - assertMapEquals(sortedMap, "four", 4, "one", 1, "three", 3, "two", 2); - } + // TODO(b/172823566): Use mainline testToImmutableSortedMap once CollectorTester is usable. + public void testToImmutableSortedMap_java7_combine() { + ImmutableSortedMap.Builder zis = + ImmutableSortedMap.naturalOrder().put("one", 1).put("four", 4); + ImmutableSortedMap.Builder zat = + ImmutableSortedMap.naturalOrder().put("two", 2).put("three", 3); + ImmutableSortedMap sortedMap = zis.combine(zat).build(); + assertMapEquals(sortedMap, "four", 4, "one", 1, "three", 3, "two", 2); + } - // TODO(b/172823566): Use mainline testToImmutableSortedMap once CollectorTester is usable. - public void testToImmutableSortedMap_exceptionOnDuplicateKey_java7_combine() { - ImmutableSortedMap.Builder zis = - ImmutableSortedMap.naturalOrder().put("one", 1).put("two", 2); - ImmutableSortedMap.Builder zat = - ImmutableSortedMap.naturalOrder().put("two", 22).put("three", 3); - try { - ImmutableSortedMap.Builder combined = zis.combine(zat); - combined.build(); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // expected - } - } + // TODO(b/172823566): Use mainline testToImmutableSortedMap once CollectorTester is usable. + public void testToImmutableSortedMap_exceptionOnDuplicateKey_java7_combine() { + ImmutableSortedMap.Builder zis = + ImmutableSortedMap.naturalOrder().put("one", 1).put("two", 2); + ImmutableSortedMap.Builder zat = + ImmutableSortedMap.naturalOrder().put("two", 22).put("three", 3); + ImmutableSortedMap.Builder combined = zis.combine(zat); + assertThrows(IllegalArgumentException.class, () -> combined.build()); } + // Other tests + public void testNullGet() { ImmutableSortedMap map = ImmutableSortedMap.of("one", 1); - assertNull(map.get(null)); + assertThat(map.get(null)).isNull(); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -713,16 +625,14 @@ public void testNullPointers() { public void testNullValuesInCopyOfMap() { for (int i = 1; i <= 10; i++) { for (int j = 0; j < i; j++) { - Map source = new TreeMap<>(); + Map source = new TreeMap<>(); for (int k = 0; k < i; k++) { source.put(k, k); } source.put(j, null); - try { - ImmutableSortedMap.copyOf(source); - fail("Expected NullPointerException in copyOf(" + source + ")"); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> ImmutableSortedMap.copyOf((Map) source)); } } } @@ -730,38 +640,35 @@ public void testNullValuesInCopyOfMap() { public void testNullValuesInCopyOfEntries() { for (int i = 1; i <= 10; i++) { for (int j = 0; j < i; j++) { - Map source = new TreeMap<>(); + Map source = new TreeMap<>(); for (int k = 0; k < i; k++) { source.put(k, k); } source.put(j, null); - try { - ImmutableSortedMap.copyOf(source.entrySet()); - fail("Expected NullPointerException in copyOf(" + source.entrySet() + ")"); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> ImmutableSortedMap.copyOf((Set>) source.entrySet())); } } } private static void assertMapEquals(Map map, Object... alternatingKeysAndValues) { - assertEquals(map.size(), alternatingKeysAndValues.length / 2); - int i = 0; - for (Entry entry : map.entrySet()) { - assertEquals(alternatingKeysAndValues[i++], entry.getKey()); - assertEquals(alternatingKeysAndValues[i++], entry.getValue()); + Map expected = new LinkedHashMap<>(); + for (int i = 0; i < alternatingKeysAndValues.length; i += 2) { + expected.put(alternatingKeysAndValues[i], alternatingKeysAndValues[i + 1]); } + assertThat(map).containsExactlyEntriesIn(expected).inOrder(); } private static class IntHolder implements Serializable { - public int value; + private int value; - public IntHolder(int value) { + IntHolder(int value) { this.value = value; } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof IntHolder) && ((IntHolder) o).value == value; } @@ -770,7 +677,7 @@ public int hashCode() { return value; } - private static final long serialVersionUID = 5; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 5; } public void testMutableValues() { @@ -778,88 +685,79 @@ public void testMutableValues() { IntHolder holderB = new IntHolder(2); Map map = ImmutableSortedMap.of("a", holderA, "b", holderB); holderA.value = 3; - assertTrue(map.entrySet().contains(Maps.immutableEntry("a", new IntHolder(3)))); + assertTrue(map.entrySet().contains(immutableEntry("a", new IntHolder(3)))); Map intMap = ImmutableSortedMap.of("a", 3, "b", 2); assertEquals(intMap.hashCode(), map.entrySet().hashCode()); assertEquals(intMap.hashCode(), map.hashCode()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testViewSerialization() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3); SerializableTester.reserializeAndAssert(map.entrySet()); SerializableTester.reserializeAndAssert(map.keySet()); assertEquals( - Lists.newArrayList(map.values()), - Lists.newArrayList(SerializableTester.reserialize(map.values()))); + new ArrayList<>(map.values()), + new ArrayList<>(SerializableTester.reserialize(map.values()))); } - @SuppressWarnings("unchecked") // varargs public void testHeadMapInclusive() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3).headMap("three", true); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("one", 1), Maps.immutableEntry("three", 3)) + .containsExactly(immutableEntry("one", 1), immutableEntry("three", 3)) .inOrder(); } - @SuppressWarnings("unchecked") // varargs public void testHeadMapExclusive() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3).headMap("three", false); - assertThat(map.entrySet()).containsExactly(Maps.immutableEntry("one", 1)); + assertThat(map.entrySet()).containsExactly(immutableEntry("one", 1)); } - @SuppressWarnings("unchecked") // varargs public void testTailMapInclusive() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3).tailMap("three", true); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("three", 3), Maps.immutableEntry("two", 2)) + .containsExactly(immutableEntry("three", 3), immutableEntry("two", 2)) .inOrder(); } - @SuppressWarnings("unchecked") // varargs public void testTailMapExclusive() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3).tailMap("three", false); - assertThat(map.entrySet()).containsExactly(Maps.immutableEntry("two", 2)); + assertThat(map.entrySet()).containsExactly(immutableEntry("two", 2)); } - @SuppressWarnings("unchecked") // varargs public void testSubMapExclusiveExclusive() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3).subMap("one", false, "two", false); - assertThat(map.entrySet()).containsExactly(Maps.immutableEntry("three", 3)); + assertThat(map.entrySet()).containsExactly(immutableEntry("three", 3)); } - @SuppressWarnings("unchecked") // varargs public void testSubMapInclusiveExclusive() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3).subMap("one", true, "two", false); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("one", 1), Maps.immutableEntry("three", 3)) + .containsExactly(immutableEntry("one", 1), immutableEntry("three", 3)) .inOrder(); } - @SuppressWarnings("unchecked") // varargs public void testSubMapExclusiveInclusive() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3).subMap("one", false, "two", true); assertThat(map.entrySet()) - .containsExactly(Maps.immutableEntry("three", 3), Maps.immutableEntry("two", 2)) + .containsExactly(immutableEntry("three", 3), immutableEntry("two", 2)) .inOrder(); } - @SuppressWarnings("unchecked") // varargs public void testSubMapInclusiveInclusive() { Map map = ImmutableSortedMap.of("one", 1, "two", 2, "three", 3).subMap("one", true, "two", true); assertThat(map.entrySet()) .containsExactly( - Maps.immutableEntry("one", 1), - Maps.immutableEntry("three", 3), - Maps.immutableEntry("two", 2)) + immutableEntry("one", 1), immutableEntry("three", 3), immutableEntry("two", 2)) .inOrder(); } @@ -870,21 +768,21 @@ public int compareTo(SelfComparableExample o) { } } - public void testBuilderGenerics_SelfComparable() { - ImmutableSortedMap.Builder natural = + public void testBuilderGenerics_selfComparable() { + ImmutableSortedMap.Builder unusedNatural = ImmutableSortedMap.naturalOrder(); - ImmutableSortedMap.Builder reverse = + ImmutableSortedMap.Builder unusedReverse = ImmutableSortedMap.reverseOrder(); } private static class SuperComparableExample extends SelfComparableExample {} - public void testBuilderGenerics_SuperComparable() { - ImmutableSortedMap.Builder natural = + public void testBuilderGenerics_superComparable() { + ImmutableSortedMap.Builder unusedNatural = ImmutableSortedMap.naturalOrder(); - ImmutableSortedMap.Builder reverse = + ImmutableSortedMap.Builder unusedReverse = ImmutableSortedMap.reverseOrder(); } } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMultisetTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMultisetTest.java index cda23ddf27b5..2326bc4fb31d 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableSortedMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSortedMultisetTest.java @@ -15,10 +15,12 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Iterators.singletonIterator; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static org.junit.Assert.assertThrows; -import com.google.common.base.Function; import com.google.common.collect.ImmutableSortedMultiset.Builder; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.MinimalCollection; @@ -33,19 +35,23 @@ import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link ImmutableSortedMultiset}. * * @author Louis Wasserman */ +@NullUnmarked public class ImmutableSortedMultisetTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(ImmutableSortedMultisetTest.class); @@ -95,7 +101,7 @@ public List order(List insertionOrder) { new TestStringListGenerator() { @Override protected List create(String[] elements) { - Set set = Sets.newHashSet(); + Set set = new HashSet<>(); ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); for (String s : elements) { @@ -175,15 +181,7 @@ public void testCreation_arrayOfOneElement() { public void testCreation_arrayOfArray() { Comparator comparator = - Ordering.natural() - .lexicographical() - .onResultOf( - new Function>() { - @Override - public Iterable apply(String[] input) { - return Arrays.asList(input); - } - }); + Ordering.natural().lexicographical().onResultOf(Arrays::asList); String[] array = new String[] {"a"}; Multiset multiset = ImmutableSortedMultiset.orderedBy(comparator).add(array).build(); Multiset expected = HashMultiset.create(); @@ -193,16 +191,11 @@ public Iterable apply(String[] input) { public void testCreation_arrayContainingOnlyNull() { String[] array = new String[] {null}; - try { - ImmutableSortedMultiset.copyOf(array); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> ImmutableSortedMultiset.copyOf(array)); } public void testCopyOf_collection_empty() { - // "" is required to work around a javac 1.5 bug. - Collection c = MinimalCollection.of(); + Collection c = MinimalCollection.of(); Multiset multiset = ImmutableSortedMultiset.copyOf(c); assertTrue(multiset.isEmpty()); } @@ -221,11 +214,7 @@ public void testCopyOf_collection_general() { public void testCopyOf_collectionContainingNull() { Collection c = MinimalCollection.of("a", null, "b"); - try { - ImmutableSortedMultiset.copyOf(c); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> ImmutableSortedMultiset.copyOf(c)); } public void testCopyOf_multiset_empty() { @@ -248,21 +237,17 @@ public void testCopyOf_multiset_general() { public void testCopyOf_multisetContainingNull() { Multiset c = HashMultiset.create(asList("a", null, "b")); - try { - ImmutableSortedMultiset.copyOf(c); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> ImmutableSortedMultiset.copyOf(c)); } public void testCopyOf_iterator_empty() { - Iterator iterator = Iterators.emptyIterator(); + Iterator iterator = emptyIterator(); Multiset multiset = ImmutableSortedMultiset.copyOf(iterator); assertTrue(multiset.isEmpty()); } public void testCopyOf_iterator_oneElement() { - Iterator iterator = Iterators.singletonIterator("a"); + Iterator iterator = singletonIterator("a"); Multiset multiset = ImmutableSortedMultiset.copyOf(iterator); assertEquals(HashMultiset.create(asList("a")), multiset); } @@ -275,11 +260,7 @@ public void testCopyOf_iterator_general() { public void testCopyOf_iteratorContainingNull() { Iterator iterator = asList("a", null, "b").iterator(); - try { - ImmutableSortedMultiset.copyOf(iterator); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> ImmutableSortedMultiset.copyOf(iterator)); } private static class CountingIterable implements Iterable { @@ -403,73 +384,47 @@ public void testBuilderSetCountThenAdd() { public void testBuilderAddHandlesNullsCorrectly() { ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); - try { - builder.add((String) null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.add((String) null)); } public void testBuilderAddAllHandlesNullsCorrectly() { - ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); - try { - builder.addAll((Collection) null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { + { + ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); + assertThrows(NullPointerException.class, () -> builder.addAll((Collection) null)); } - builder = ImmutableSortedMultiset.naturalOrder(); - List listWithNulls = asList("a", null, "b"); - try { - builder.addAll(listWithNulls); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { + { + ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); + List listWithNulls = asList("a", null, "b"); + assertThrows(NullPointerException.class, () -> builder.addAll(listWithNulls)); } - builder = ImmutableSortedMultiset.naturalOrder(); - Multiset multisetWithNull = LinkedHashMultiset.create(asList("a", null, "b")); - try { - builder.addAll(multisetWithNull); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { + { + ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); + Multiset multisetWithNull = LinkedHashMultiset.create(asList("a", null, "b")); + assertThrows(NullPointerException.class, () -> builder.addAll(multisetWithNull)); } } public void testBuilderAddCopiesHandlesNullsCorrectly() { ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); - try { - builder.addCopies(null, 2); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.addCopies(null, 2)); } public void testBuilderAddCopiesIllegal() { ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); - try { - builder.addCopies("a", -2); - fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.addCopies("a", -2)); } public void testBuilderSetCountHandlesNullsCorrectly() { ImmutableSortedMultiset.Builder builder = new ImmutableSortedMultiset.Builder<>(Ordering.natural().nullsFirst()); - try { - builder.setCount(null, 2); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> builder.setCount(null, 2)); } public void testBuilderSetCountIllegal() { ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset.naturalOrder(); - try { - builder.setCount("a", -2); - fail("expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.setCount("a", -2)); } public void testNullPointers() { diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableSortedSetTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableSortedSetTest.java index 2ebff40fe97a..751139f5162c 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableSortedSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableSortedSetTest.java @@ -16,11 +16,19 @@ package com.google.common.collect; +import static com.google.common.collect.Comparators.isInOrder; +import static com.google.common.collect.Iterables.elementsEqual; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Sets.newHashSet; import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.min; import static java.util.Arrays.asList; +import static java.util.Arrays.sort; +import static java.util.Collections.emptyList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.NavigableSetTestSuiteBuilder; import com.google.common.collect.testing.features.CollectionFeature; @@ -37,7 +45,6 @@ import com.google.common.collect.testing.testers.SetHashCodeTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -48,16 +55,20 @@ import java.util.TreeSet; import junit.framework.Test; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@link ImmutableSortedSet}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ImmutableSortedSetTest extends AbstractImmutableSetTest { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); @@ -201,7 +212,6 @@ protected > SortedSet of(E e1, E e2, E e3, E return ImmutableSortedSet.of(e1, e2, e3, e4, e5); } - @SuppressWarnings("unchecked") @Override protected > SortedSet of( E e1, E e2, E e3, E e4, E e5, E e6, E... rest) { @@ -229,6 +239,7 @@ protected > SortedSet copyOf(Iterator set = of(); - try { - set.first(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> set.first()); } public void testEmpty_last() { SortedSet set = of(); - try { - set.last(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> set.last()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testEmpty_serialization() { SortedSet set = of(); @@ -288,8 +292,8 @@ public void testSingle_headSet() { SortedSet set = of("e"); assertTrue(set.headSet("g") instanceof ImmutableSortedSet); assertThat(set.headSet("g")).contains("e"); - assertSame(of(), set.headSet("c")); - assertSame(of(), set.headSet("e")); + assertSame(this.of(), set.headSet("c")); + assertSame(this.of(), set.headSet("e")); } public void testSingle_tailSet() { @@ -297,7 +301,7 @@ public void testSingle_tailSet() { assertTrue(set.tailSet("c") instanceof ImmutableSortedSet); assertThat(set.tailSet("c")).contains("e"); assertThat(set.tailSet("e")).contains("e"); - assertSame(of(), set.tailSet("g")); + assertSame(this.of(), set.tailSet("g")); } public void testSingle_subSet() { @@ -305,9 +309,9 @@ public void testSingle_subSet() { assertTrue(set.subSet("c", "g") instanceof ImmutableSortedSet); assertThat(set.subSet("c", "g")).contains("e"); assertThat(set.subSet("e", "g")).contains("e"); - assertSame(of(), set.subSet("f", "g")); - assertSame(of(), set.subSet("c", "e")); - assertSame(of(), set.subSet("c", "d")); + assertSame(this.of(), set.subSet("f", "g")); + assertSame(this.of(), set.subSet("c", "e")); + assertSame(this.of(), set.subSet("c", "d")); } public void testSingle_first() { @@ -320,6 +324,7 @@ public void testSingle_last() { assertEquals("e", set.last()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSingle_serialization() { SortedSet set = of("e"); @@ -392,8 +397,8 @@ public void testOf_headSet() { assertTrue(set.headSet("e") instanceof ImmutableSortedSet); assertThat(set.headSet("e")).containsExactly("b", "c", "d").inOrder(); assertThat(set.headSet("g")).containsExactly("b", "c", "d", "e", "f").inOrder(); - assertSame(of(), set.headSet("a")); - assertSame(of(), set.headSet("b")); + assertSame(this.of(), set.headSet("a")); + assertSame(this.of(), set.headSet("b")); } public void testOf_tailSet() { @@ -401,7 +406,7 @@ public void testOf_tailSet() { assertTrue(set.tailSet("e") instanceof ImmutableSortedSet); assertThat(set.tailSet("e")).containsExactly("e", "f").inOrder(); assertThat(set.tailSet("a")).containsExactly("b", "c", "d", "e", "f").inOrder(); - assertSame(of(), set.tailSet("g")); + assertSame(this.of(), set.tailSet("g")); } public void testOf_subSet() { @@ -409,16 +414,13 @@ public void testOf_subSet() { assertTrue(set.subSet("c", "e") instanceof ImmutableSortedSet); assertThat(set.subSet("c", "e")).containsExactly("c", "d").inOrder(); assertThat(set.subSet("a", "g")).containsExactly("b", "c", "d", "e", "f").inOrder(); - assertSame(of(), set.subSet("a", "b")); - assertSame(of(), set.subSet("g", "h")); - assertSame(of(), set.subSet("c", "c")); - try { - set.subSet("e", "c"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertSame(this.of(), set.subSet("a", "b")); + assertSame(this.of(), set.subSet("g", "h")); + assertSame(this.of(), set.subSet("c", "c")); + assertThrows(IllegalArgumentException.class, () -> set.subSet("e", "c")); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testOf_subSetSerialization() { SortedSet set = of("e", "f", "b", "d", "c"); @@ -435,11 +437,12 @@ public void testOf_last() { assertEquals("f", set.last()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testOf_serialization() { SortedSet set = of("e", "f", "b", "d", "c"); SortedSet copy = SerializableTester.reserializeAndAssert(set); - assertTrue(Iterables.elementsEqual(set, copy)); + assertTrue(elementsEqual(set, copy)); assertEquals(set.comparator(), copy.comparator()); } @@ -533,11 +536,7 @@ public void testExplicit_subSet() { assertTrue(set.subSet("", "b").isEmpty()); assertTrue(set.subSet("vermont", "california").isEmpty()); assertTrue(set.subSet("aaa", "zzz").isEmpty()); - try { - set.subSet("quick", "the"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> set.subSet("quick", "the")); } public void testExplicit_first() { @@ -556,6 +555,7 @@ public void testExplicit_last() { assertEquals("jumped", set.last()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testExplicitEmpty_serialization() { SortedSet set = ImmutableSortedSet.orderedBy(STRING_LENGTH).build(); @@ -565,6 +565,7 @@ public void testExplicitEmpty_serialization() { assertSame(set.comparator(), copy.comparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testExplicit_serialization() { SortedSet set = @@ -572,7 +573,7 @@ public void testExplicit_serialization() { .add("in", "the", "quick", "jumped", "over", "a") .build(); SortedSet copy = SerializableTester.reserializeAndAssert(set); - assertTrue(Iterables.elementsEqual(set, copy)); + assertTrue(elementsEqual(set, copy)); assertSame(set.comparator(), copy.comparator()); } @@ -738,8 +739,8 @@ public void testEquals_bothDefaultOrdering() { assertEquals(Sets.newTreeSet(asList("a", "b", "c")), set); assertFalse(set.equals(Sets.newTreeSet(asList("a", "b", "d")))); assertFalse(Sets.newTreeSet(asList("a", "b", "d")).equals(set)); - assertFalse(set.equals(Sets.newHashSet(4, 5, 6))); - assertFalse(Sets.newHashSet(4, 5, 6).equals(set)); + assertFalse(set.equals(newHashSet(4, 5, 6))); + assertFalse(newHashSet(4, 5, 6).equals(set)); } public void testEquals_bothExplicitOrdering() { @@ -747,21 +748,21 @@ public void testEquals_bothExplicitOrdering() { assertEquals(Sets.newTreeSet(asList("in", "the", "a")), set); assertFalse(set.equals(Sets.newTreeSet(asList("in", "the", "house")))); assertFalse(Sets.newTreeSet(asList("in", "the", "house")).equals(set)); - assertFalse(set.equals(Sets.newHashSet(4, 5, 6))); - assertFalse(Sets.newHashSet(4, 5, 6).equals(set)); + assertFalse(set.equals(newHashSet(4, 5, 6))); + assertFalse(newHashSet(4, 5, 6).equals(set)); Set complex = Sets.newTreeSet(STRING_LENGTH); Collections.addAll(complex, "in", "the", "a"); assertEquals(set, complex); } - public void testEquals_bothDefaultOrdering_StringVsInt() { + public void testEquals_bothDefaultOrdering_stringVsInt() { SortedSet set = of("a", "b", "c"); assertFalse(set.equals(Sets.newTreeSet(asList(4, 5, 6)))); assertNotEqualLenient(Sets.newTreeSet(asList(4, 5, 6)), set); } - public void testEquals_bothExplicitOrdering_StringVsInt() { + public void testEquals_bothExplicitOrdering_stringVsInt() { SortedSet set = of("in", "the", "a"); assertFalse(set.equals(Sets.newTreeSet(asList(4, 5, 6)))); assertNotEqualLenient(Sets.newTreeSet(asList(4, 5, 6)), set); @@ -769,7 +770,7 @@ public void testEquals_bothExplicitOrdering_StringVsInt() { public void testContainsAll_notSortedSet() { SortedSet set = of("a", "b", "f"); - assertTrue(set.containsAll(Collections.emptyList())); + assertTrue(set.containsAll(emptyList())); assertTrue(set.containsAll(asList("b"))); assertTrue(set.containsAll(asList("b", "b"))); assertTrue(set.containsAll(asList("b", "f"))); @@ -793,7 +794,7 @@ public void testContainsAll_sameComparator() { } @SuppressWarnings("CollectionIncompatibleType") // testing incompatible types - public void testContainsAll_sameComparator_StringVsInt() { + public void testContainsAll_sameComparator_stringVsInt() { SortedSet set = of("a", "b", "f"); SortedSet unexpected = Sets.newTreeSet(Ordering.natural()); unexpected.addAll(asList(1, 2, 3)); @@ -814,6 +815,7 @@ public void testContainsAll_differentComparator() { assertFalse(set.containsAll(Sets.newTreeSet(asList("f", "d", "a")))); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testDifferentComparator_serialization() { // don't use Collections.reverseOrder(); it didn't reserialize to the same instance in JDK5 @@ -821,14 +823,14 @@ public void testDifferentComparator_serialization() { SortedSet set = new ImmutableSortedSet.Builder(comparator).add("a", "b", "c").build(); SortedSet copy = SerializableTester.reserializeAndAssert(set); - assertTrue(Iterables.elementsEqual(set, copy)); + assertTrue(elementsEqual(set, copy)); assertEquals(set.comparator(), copy.comparator()); } public void testReverseOrder() { SortedSet set = ImmutableSortedSet.reverseOrder().add("a", "b", "c").build(); assertThat(set).containsExactly("c", "b", "a").inOrder(); - assertTrue(Comparators.isInOrder(Arrays.asList("c", "b", "a"), set.comparator())); + assertTrue(isInOrder(asList("c", "b", "a"), set.comparator())); } private static final Comparator TO_STRING = @@ -878,17 +880,16 @@ public void testLegacyComparable_of() { public void testLegacyComparable_copyOf_collection() { ImmutableSortedSet set = ImmutableSortedSet.copyOf(LegacyComparable.VALUES_BACKWARD); - assertTrue(Iterables.elementsEqual(LegacyComparable.VALUES_FORWARD, set)); + assertTrue(elementsEqual(LegacyComparable.VALUES_FORWARD, set)); } public void testLegacyComparable_copyOf_iterator() { ImmutableSortedSet set = ImmutableSortedSet.copyOf(LegacyComparable.VALUES_BACKWARD.iterator()); - assertTrue(Iterables.elementsEqual(LegacyComparable.VALUES_FORWARD, set)); + assertTrue(elementsEqual(LegacyComparable.VALUES_FORWARD, set)); } public void testLegacyComparable_builder_natural() { - @SuppressWarnings("unchecked") // Note: IntelliJ wrongly reports an error for this statement ImmutableSortedSet.Builder builder = ImmutableSortedSet.naturalOrder(); @@ -898,11 +899,10 @@ public void testLegacyComparable_builder_natural() { builder.add(LegacyComparable.Y, LegacyComparable.Z); ImmutableSortedSet set = builder.build(); - assertTrue(Iterables.elementsEqual(LegacyComparable.VALUES_FORWARD, set)); + assertTrue(elementsEqual(LegacyComparable.VALUES_FORWARD, set)); } public void testLegacyComparable_builder_reverse() { - @SuppressWarnings("unchecked") // Note: IntelliJ wrongly reports an error for this statement ImmutableSortedSet.Builder builder = ImmutableSortedSet.reverseOrder(); @@ -912,16 +912,12 @@ public void testLegacyComparable_builder_reverse() { builder.add(LegacyComparable.Y, LegacyComparable.Z); ImmutableSortedSet set = builder.build(); - assertTrue(Iterables.elementsEqual(LegacyComparable.VALUES_BACKWARD, set)); + assertTrue(elementsEqual(LegacyComparable.VALUES_BACKWARD, set)); } - @SuppressWarnings({"deprecation", "static-access"}) + @SuppressWarnings({"deprecation", "static-access", "DoNotCall"}) public void testBuilderMethod() { - try { - ImmutableSortedSet.builder(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> ImmutableSortedSet.builder()); } public void testAsList() { @@ -931,6 +927,7 @@ public void testAsList() { assertSame(list, ImmutableList.copyOf(set)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester, ImmutableSortedAsList public void testAsListReturnTypeAndSerialization() { ImmutableSet set = ImmutableSortedSet.of("a", "e", "i", "o", "u"); @@ -946,6 +943,7 @@ public void testSubsetAsList() { assertEquals(list, ImmutableList.copyOf(set)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester, ImmutableSortedAsList public void testSubsetAsListReturnTypeAndSerialization() { ImmutableSet set = ImmutableSortedSet.of("a", "e", "i", "o", "u").subSet("c", "r"); @@ -954,7 +952,7 @@ public void testSubsetAsListReturnTypeAndSerialization() { assertEquals(list, copy); } - public void testAsListInconsistentComprator() { + public void testAsListInconsistentComparator() { ImmutableSet set = ImmutableSortedSet.orderedBy(STRING_LENGTH) .add("in", "the", "quick", "jumped", "over", "a") @@ -976,7 +974,7 @@ private static Iterator asIterator(E... elements) { } // In GWT, java.util.TreeSet throws ClassCastException when the comparator - // throws it, unlike JDK6. Therefore, we accept ClassCastException as a + // throws it, unlike the JDK. Therefore, we accept ClassCastException as a // valid result thrown by java.util.TreeSet#equals. private static void assertNotEqualLenient(TreeSet unexpected, SortedSet actual) { try { @@ -988,7 +986,7 @@ private static void assertNotEqualLenient(TreeSet unexpected, SortedSet ac public void testHeadSetInclusive() { String[] strings = NUMBER_NAMES.toArray(new String[0]); ImmutableSortedSet set = ImmutableSortedSet.copyOf(strings); - Arrays.sort(strings); + sort(strings); for (int i = 0; i < strings.length; i++) { assertThat(set.headSet(strings[i], true)) .containsExactlyElementsIn(sortedNumberNames(0, i + 1)) @@ -999,7 +997,7 @@ public void testHeadSetInclusive() { public void testHeadSetExclusive() { String[] strings = NUMBER_NAMES.toArray(new String[0]); ImmutableSortedSet set = ImmutableSortedSet.copyOf(strings); - Arrays.sort(strings); + sort(strings); for (int i = 0; i < strings.length; i++) { assertThat(set.headSet(strings[i], false)) .containsExactlyElementsIn(sortedNumberNames(0, i)) @@ -1010,7 +1008,7 @@ public void testHeadSetExclusive() { public void testTailSetInclusive() { String[] strings = NUMBER_NAMES.toArray(new String[0]); ImmutableSortedSet set = ImmutableSortedSet.copyOf(strings); - Arrays.sort(strings); + sort(strings); for (int i = 0; i < strings.length; i++) { assertThat(set.tailSet(strings[i], true)) .containsExactlyElementsIn(sortedNumberNames(i, strings.length)) @@ -1021,7 +1019,7 @@ public void testTailSetInclusive() { public void testTailSetExclusive() { String[] strings = NUMBER_NAMES.toArray(new String[0]); ImmutableSortedSet set = ImmutableSortedSet.copyOf(strings); - Arrays.sort(strings); + sort(strings); for (int i = 0; i < strings.length; i++) { assertThat(set.tailSet(strings[i], false)) .containsExactlyElementsIn(sortedNumberNames(i + 1, strings.length)) @@ -1070,11 +1068,11 @@ public void testCeiling_elementAbsent() { public void testSubSetExclusiveExclusive() { String[] strings = NUMBER_NAMES.toArray(new String[0]); ImmutableSortedSet set = ImmutableSortedSet.copyOf(strings); - Arrays.sort(strings); + sort(strings); for (int i = 0; i < strings.length; i++) { for (int j = i; j < strings.length; j++) { assertThat(set.subSet(strings[i], false, strings[j], false)) - .containsExactlyElementsIn(sortedNumberNames(Math.min(i + 1, j), j)) + .containsExactlyElementsIn(sortedNumberNames(min(i + 1, j), j)) .inOrder(); } } @@ -1083,7 +1081,7 @@ public void testSubSetExclusiveExclusive() { public void testSubSetInclusiveExclusive() { String[] strings = NUMBER_NAMES.toArray(new String[0]); ImmutableSortedSet set = ImmutableSortedSet.copyOf(strings); - Arrays.sort(strings); + sort(strings); for (int i = 0; i < strings.length; i++) { for (int j = i; j < strings.length; j++) { assertThat(set.subSet(strings[i], true, strings[j], false)) @@ -1096,7 +1094,7 @@ public void testSubSetInclusiveExclusive() { public void testSubSetExclusiveInclusive() { String[] strings = NUMBER_NAMES.toArray(new String[0]); ImmutableSortedSet set = ImmutableSortedSet.copyOf(strings); - Arrays.sort(strings); + sort(strings); for (int i = 0; i < strings.length; i++) { for (int j = i; j < strings.length; j++) { assertThat(set.subSet(strings[i], false, strings[j], true)) @@ -1109,7 +1107,7 @@ public void testSubSetExclusiveInclusive() { public void testSubSetInclusiveInclusive() { String[] strings = NUMBER_NAMES.toArray(new String[0]); ImmutableSortedSet set = ImmutableSortedSet.copyOf(strings); - Arrays.sort(strings); + sort(strings); for (int i = 0; i < strings.length; i++) { for (int j = i; j < strings.length; j++) { assertThat(set.subSet(strings[i], true, strings[j], true)) @@ -1127,7 +1125,7 @@ private static ImmutableList sortedNumberNames(int i, int j) { ImmutableList.of("one", "two", "three", "four", "five", "six", "seven"); private static final ImmutableList SORTED_NUMBER_NAMES = - Ordering.natural().immutableSortedCopy(NUMBER_NAMES); + Ordering.natural().immutableSortedCopy(NUMBER_NAMES); private static class SelfComparableExample implements Comparable { @Override @@ -1136,7 +1134,7 @@ public int compareTo(SelfComparableExample o) { } } - public void testBuilderGenerics_SelfComparable() { + public void testBuilderGenerics_selfComparable() { // testing simple creation ImmutableSortedSet.Builder natural = ImmutableSortedSet.naturalOrder(); assertThat(natural).isNotNull(); @@ -1146,7 +1144,7 @@ public void testBuilderGenerics_SelfComparable() { private static class SuperComparableExample extends SelfComparableExample {} - public void testBuilderGenerics_SuperComparable() { + public void testBuilderGenerics_superComparable() { // testing simple creation ImmutableSortedSet.Builder natural = ImmutableSortedSet.naturalOrder(); assertThat(natural).isNotNull(); @@ -1178,4 +1176,24 @@ public void testReusedBuilder() { builder.add("baz"); assertTrue(list.array != builder.contents); } + + public void testBuilderAsymptotics() { + int[] compares = {0}; + Comparator countingComparator = + (i, j) -> { + compares[0]++; + return i.compareTo(j); + }; + ImmutableSortedSet.Builder builder = + new ImmutableSortedSet.Builder(countingComparator, 10); + for (int i = 0; i < 9; i++) { + builder.add(i); + } + for (int j = 0; j < 1000; j++) { + builder.add(9); + } + ImmutableSortedSet unused = builder.build(); + assertThat(compares[0]).isAtMost(10000); + // hopefully something quadratic would have more digits + } } diff --git a/android/guava-tests/test/com/google/common/collect/ImmutableTableTest.java b/android/guava-tests/test/com/google/common/collect/ImmutableTableTest.java index 9bdc99cab25f..d475c00558f5 100644 --- a/android/guava-tests/test/com/google/common/collect/ImmutableTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/ImmutableTableTest.java @@ -16,26 +16,32 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Tables.immutableCell; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.SerializableTester; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests common methods in {@link ImmutableTable} * * @author Gregory Kick */ -@GwtCompatible(emulated = true) -public class ImmutableTableTest extends AbstractTableReadTest { +@GwtCompatible +@NullMarked +public class ImmutableTableTest extends AbstractTableReadTest { @Override - protected Table create(Object... data) { + protected Table create(@Nullable Object... data) { ImmutableTable.Builder builder = ImmutableTable.builder(); for (int i = 0; i < data.length; i = i + 3) { builder.put((String) data[i], (Integer) data[i + 1], (Character) data[i + 2]); } - return builder.build(); + return builder.buildOrThrow(); } // TODO(b/172823566): Use mainline testToImmutableMap once CollectorTester is usable to java7. @@ -59,8 +65,8 @@ public void testToImmutableTable_java7_combine() { public void testBuilder() { ImmutableTable.Builder builder = new ImmutableTable.Builder<>(); - assertEquals(ImmutableTable.of(), builder.build()); - assertEquals(ImmutableTable.of('a', 1, "foo"), builder.put('a', 1, "foo").build()); + assertEquals(ImmutableTable.of(), builder.buildOrThrow()); + assertEquals(ImmutableTable.of('a', 1, "foo"), builder.put('a', 1, "foo").buildOrThrow()); Table expectedTable = HashBasedTable.create(); expectedTable.put('a', 1, "foo"); expectedTable.put('b', 1, "bar"); @@ -68,45 +74,33 @@ public void testBuilder() { Table otherTable = HashBasedTable.create(); otherTable.put('b', 1, "bar"); otherTable.put('a', 2, "baz"); - assertEquals(expectedTable, builder.putAll(otherTable).build()); + assertEquals(expectedTable, builder.putAll(otherTable).buildOrThrow()); } public void testBuilder_withImmutableCell() { ImmutableTable.Builder builder = new ImmutableTable.Builder<>(); assertEquals( - ImmutableTable.of('a', 1, "foo"), builder.put(Tables.immutableCell('a', 1, "foo")).build()); + ImmutableTable.of('a', 1, "foo"), builder.put(immutableCell('a', 1, "foo")).buildOrThrow()); } public void testBuilder_withImmutableCellAndNullContents() { ImmutableTable.Builder builder = new ImmutableTable.Builder<>(); - try { - builder.put(Tables.immutableCell((Character) null, 1, "foo")); - fail(); - } catch (NullPointerException e) { - // success - } - try { - builder.put(Tables.immutableCell('a', (Integer) null, "foo")); - fail(); - } catch (NullPointerException e) { - // success - } - try { - builder.put(Tables.immutableCell('a', 1, (String) null)); - fail(); - } catch (NullPointerException e) { - // success - } + assertThrows( + NullPointerException.class, () -> builder.put(immutableCell((Character) null, 1, "foo"))); + assertThrows( + NullPointerException.class, () -> builder.put(immutableCell('a', (Integer) null, "foo"))); + assertThrows( + NullPointerException.class, () -> builder.put(immutableCell('a', 1, (String) null))); } private static class StringHolder { - String string; + @Nullable String string; } public void testBuilder_withMutableCell() { ImmutableTable.Builder builder = new ImmutableTable.Builder<>(); - final StringHolder holder = new StringHolder(); + StringHolder holder = new StringHolder(); holder.string = "foo"; Table.Cell mutableCell = new Tables.AbstractCell() { @@ -133,7 +127,7 @@ public String getValue() { holder.string = "bar"; // Make sure it uses the original value. - assertEquals(ImmutableTable.of('K', 42, "foo"), builder.build()); + assertEquals(ImmutableTable.of('K', 42, "foo"), builder.buildOrThrow()); } public void testBuilder_noDuplicates() { @@ -141,34 +135,14 @@ public void testBuilder_noDuplicates() { new ImmutableTable.Builder() .put('a', 1, "foo") .put('a', 1, "bar"); - try { - builder.build(); - fail(); - } catch (IllegalArgumentException e) { - // success - } + assertThrows(IllegalArgumentException.class, () -> builder.buildOrThrow()); } public void testBuilder_noNulls() { ImmutableTable.Builder builder = new ImmutableTable.Builder<>(); - try { - builder.put(null, 1, "foo"); - fail(); - } catch (NullPointerException e) { - // success - } - try { - builder.put('a', null, "foo"); - fail(); - } catch (NullPointerException e) { - // success - } - try { - builder.put('a', 1, null); - fail(); - } catch (NullPointerException e) { - // success - } + assertThrows(NullPointerException.class, () -> builder.put(null, 1, "foo")); + assertThrows(NullPointerException.class, () -> builder.put('a', null, "foo")); + assertThrows(NullPointerException.class, () -> builder.put('a', 1, null)); } private static void validateTableCopies(Table original) { @@ -176,7 +150,7 @@ private static void validateTableCopies(Table original) { assertEquals(original, copy); validateViewOrdering(original, copy); - Table built = ImmutableTable.builder().putAll(original).build(); + Table built = ImmutableTable.builder().putAll(original).buildOrThrow(); assertEquals(original, built); validateViewOrdering(original, built); } @@ -239,7 +213,7 @@ public void testBuilder_orderRowsAndColumnsBy_putAll() { .orderRowsBy(Ordering.natural()) .orderColumnsBy(Ordering.natural()) .putAll(table) - .build(); + .buildOrThrow(); assertThat(copy.rowKeySet()).containsExactly('a', 'b').inOrder(); assertThat(copy.columnKeySet()).containsExactly(1, 2).inOrder(); assertThat(copy.values()).containsExactly("baz", "bar", "foo").inOrder(); @@ -259,7 +233,7 @@ public void testBuilder_orderRowsAndColumnsBy_sparse() { builder.put('e', 3, "tub"); builder.put('r', 4, "foo"); builder.put('x', 5, "bar"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table.rowKeySet()).containsExactly('b', 'c', 'e', 'r', 'x').inOrder(); assertThat(table.columnKeySet()).containsExactly(0, 1, 2, 3, 4, 5, 7).inOrder(); assertThat(table.values()) @@ -281,7 +255,7 @@ public void testBuilder_orderRowsAndColumnsBy_dense() { builder.put('a', 3, "foo"); builder.put('a', 2, "bar"); builder.put('a', 1, "baz"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table.rowKeySet()).containsExactly('a', 'b', 'c').inOrder(); assertThat(table.columnKeySet()).containsExactly(1, 2, 3).inOrder(); assertThat(table.values()) @@ -303,7 +277,7 @@ public void testBuilder_orderRowsBy_sparse() { builder.put('e', 3, "tub"); builder.put('r', 4, "foo"); builder.put('x', 5, "bar"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table.rowKeySet()).containsExactly('b', 'c', 'e', 'r', 'x').inOrder(); assertThat(table.column(5).keySet()).containsExactly('e', 'x').inOrder(); } @@ -319,7 +293,7 @@ public void testBuilder_orderRowsBy_dense() { builder.put('a', 3, "foo"); builder.put('a', 2, "bar"); builder.put('a', 1, "baz"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table.rowKeySet()).containsExactly('a', 'b', 'c').inOrder(); assertThat(table.column(1).keySet()).containsExactly('a', 'b', 'c').inOrder(); } @@ -336,7 +310,7 @@ public void testBuilder_orderColumnsBy_sparse() { builder.put('e', 3, "tub"); builder.put('r', 4, "foo"); builder.put('x', 5, "bar"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table.columnKeySet()).containsExactly(0, 1, 2, 3, 4, 5, 7).inOrder(); assertThat(table.row('c').keySet()).containsExactly(0, 3).inOrder(); } @@ -352,7 +326,7 @@ public void testBuilder_orderColumnsBy_dense() { builder.put('a', 3, "foo"); builder.put('a', 2, "bar"); builder.put('a', 1, "baz"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table.columnKeySet()).containsExactly(1, 2, 3).inOrder(); assertThat(table.row('c').keySet()).containsExactly(1, 2, 3).inOrder(); } @@ -370,7 +344,7 @@ public void testDenseSerialization_manualOrder() { builder.put('b', 2, "foo"); builder.put('b', 1, "bar"); builder.put('a', 2, "baz"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table).isInstanceOf(DenseImmutableTable.class); validateReserialization(table); } @@ -381,7 +355,7 @@ public void testDenseSerialization_rowOrder() { builder.put('b', 2, "foo"); builder.put('b', 1, "bar"); builder.put('a', 2, "baz"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table).isInstanceOf(DenseImmutableTable.class); validateReserialization(table); } @@ -392,7 +366,7 @@ public void testDenseSerialization_columnOrder() { builder.put('b', 2, "foo"); builder.put('b', 1, "bar"); builder.put('a', 2, "baz"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table).isInstanceOf(DenseImmutableTable.class); validateReserialization(table); } @@ -404,7 +378,7 @@ public void testDenseSerialization_bothOrders() { builder.put('b', 2, "foo"); builder.put('b', 1, "bar"); builder.put('a', 2, "baz"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table).isInstanceOf(DenseImmutableTable.class); validateReserialization(table); } @@ -416,7 +390,7 @@ public void testSparseSerialization_manualOrder() { builder.put('a', 2, "baz"); builder.put('c', 3, "cat"); builder.put('d', 4, "dog"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table).isInstanceOf(SparseImmutableTable.class); validateReserialization(table); } @@ -429,7 +403,7 @@ public void testSparseSerialization_rowOrder() { builder.put('a', 2, "baz"); builder.put('c', 3, "cat"); builder.put('d', 4, "dog"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table).isInstanceOf(SparseImmutableTable.class); validateReserialization(table); } @@ -442,7 +416,7 @@ public void testSparseSerialization_columnOrder() { builder.put('a', 2, "baz"); builder.put('c', 3, "cat"); builder.put('d', 4, "dog"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table).isInstanceOf(SparseImmutableTable.class); validateReserialization(table); } @@ -456,7 +430,7 @@ public void testSparseSerialization_bothOrders() { builder.put('a', 2, "baz"); builder.put('c', 3, "cat"); builder.put('d', 4, "dog"); - Table table = builder.build(); + Table table = builder.buildOrThrow(); assertThat(table).isInstanceOf(SparseImmutableTable.class); validateReserialization(table); } @@ -468,15 +442,16 @@ private static void validateReserialization(Table original) { assertThat(copy.columnKeySet()).containsExactlyElementsIn(original.columnKeySet()).inOrder(); } + @J2ktIncompatible @GwtIncompatible // Mind-bogglingly slow in GWT @AndroidIncompatible // slow public void testOverflowCondition() { - // See https://code.google.com/p/guava-libraries/issues/detail?id=1322 for details. + // See https://github.com/google/guava/issues/1322 for details. ImmutableTable.Builder builder = ImmutableTable.builder(); for (int i = 1; i < 0x10000; i++) { builder.put(i, 0, "foo"); builder.put(0, i, "bar"); } - assertTrue(builder.build() instanceof SparseImmutableTable); + assertTrue(builder.buildOrThrow() instanceof SparseImmutableTable); } } diff --git a/android/guava-tests/test/com/google/common/collect/InternersTest.java b/android/guava-tests/test/com/google/common/collect/InternersTest.java index cfa14b892244..bd28f8a85696 100644 --- a/android/guava-tests/test/com/google/common/collect/InternersTest.java +++ b/android/guava-tests/test/com/google/common/collect/InternersTest.java @@ -16,6 +16,8 @@ package com.google.common.collect; +import static org.junit.Assert.assertThrows; + import com.google.common.base.Function; import com.google.common.collect.Interners.InternerImpl; import com.google.common.collect.MapMakerInternalMap.Strength; @@ -23,12 +25,14 @@ import com.google.common.testing.NullPointerTester; import java.lang.ref.WeakReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Interners}. * * @author Kevin Bourrillion */ +@NullUnmarked public class InternersTest extends TestCase { public void testStrong_simplistic() { @@ -42,11 +46,7 @@ public void testStrong_simplistic() { public void testStrong_null() { Interner pool = Interners.newStrongInterner(); - try { - pool.intern(null); - fail(); - } catch (NullPointerException ok) { - } + assertThrows(NullPointerException.class, () -> pool.intern(null)); } public void testStrong_builder() { @@ -68,11 +68,7 @@ public void testWeak_simplistic() { public void testWeak_null() { Interner pool = Interners.newWeakInterner(); - try { - pool.intern(null); - fail(); - } catch (NullPointerException ok) { - } + assertThrows(NullPointerException.class, () -> pool.intern(null)); } public void testWeak_builder() { @@ -114,21 +110,13 @@ public void testNullPointerExceptions() { new NullPointerTester().testAllPublicStaticMethods(Interners.class); } - public void testConcurrencyLevel_Zero() { + public void testConcurrencyLevel_zero() { Interners.InternerBuilder builder = Interners.newBuilder(); - try { - builder.concurrencyLevel(0); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.concurrencyLevel(0)); } - public void testConcurrencyLevel_Negative() { + public void testConcurrencyLevel_negative() { Interners.InternerBuilder builder = Interners.newBuilder(); - try { - builder.concurrencyLevel(-42); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> builder.concurrencyLevel(-42)); } } diff --git a/android/guava-tests/test/com/google/common/collect/IterablesFilterArrayListTest.java b/android/guava-tests/test/com/google/common/collect/IterablesFilterArrayListTest.java new file mode 100644 index 000000000000..ff8706a22071 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/IterablesFilterArrayListTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Predicate; +import com.google.common.collect.FilteredCollectionsTestUtil.AbstractFilteredIterableTest; +import org.jspecify.annotations.NullUnmarked; + +@NullUnmarked +public final class IterablesFilterArrayListTest + extends AbstractFilteredIterableTest> { + @Override + Iterable createUnfiltered(Iterable contents) { + return Lists.newArrayList(contents); + } + + @Override + Iterable filter(Iterable elements, Predicate predicate) { + return Iterables.filter(elements, predicate); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/IterablesTest.java b/android/guava-tests/test/com/google/common/collect/IterablesTest.java index 41772100b8a4..4fcb4e6091fc 100644 --- a/android/guava-tests/test/com/google/common/collect/IterablesTest.java +++ b/android/guava-tests/test/com/google/common/collect/IterablesTest.java @@ -16,17 +16,34 @@ package com.google.common.collect; +import static com.google.common.base.Predicates.equalTo; +import static com.google.common.collect.Iterables.all; +import static com.google.common.collect.Iterables.any; +import static com.google.common.collect.Iterables.elementsEqual; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.find; +import static com.google.common.collect.Iterables.frequency; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Iterables.mergeSorted; +import static com.google.common.collect.Iterables.removeIf; import static com.google.common.collect.Iterables.skip; +import static com.google.common.collect.Iterables.tryFind; import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Sets.newLinkedHashSet; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.nCopies; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; @@ -37,8 +54,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.ConcurrentModificationException; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.Queue; @@ -47,6 +68,8 @@ import java.util.SortedSet; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code Iterables}. @@ -54,16 +77,17 @@ * @author Kevin Bourrillion * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class IterablesTest extends TestCase { public void testSize0() { - Iterable iterable = Collections.emptySet(); + Iterable iterable = emptySet(); assertEquals(0, Iterables.size(iterable)); } public void testSize1Collection() { - Iterable iterable = Collections.singleton("a"); + Iterable iterable = singleton("a"); assertEquals(1, Iterables.size(iterable)); } @@ -91,28 +115,28 @@ public Iterator iterator() { assertEquals(5, Iterables.size(collection)); } - private static Iterable iterable(String... elements) { - final List list = asList(elements); - return new Iterable() { + private static Iterable iterable(T... elements) { + List list = asList(elements); + return new Iterable() { @Override - public Iterator iterator() { + public Iterator iterator() { return list.iterator(); } }; } public void test_contains_null_set_yes() { - Iterable set = Sets.newHashSet("a", null, "b"); + Iterable<@Nullable String> set = newHashSet("a", null, "b"); assertTrue(Iterables.contains(set, null)); } public void test_contains_null_set_no() { - Iterable set = Sets.newHashSet("a", "b"); + Iterable set = newHashSet("a", "b"); assertFalse(Iterables.contains(set, null)); } public void test_contains_null_iterable_yes() { - Iterable set = iterable("a", null, "b"); + Iterable<@Nullable String> set = iterable("a", null, "b"); assertTrue(Iterables.contains(set, null)); } @@ -122,17 +146,17 @@ public void test_contains_null_iterable_no() { } public void test_contains_nonnull_set_yes() { - Iterable set = Sets.newHashSet("a", null, "b"); + Iterable<@Nullable String> set = newHashSet("a", null, "b"); assertTrue(Iterables.contains(set, "b")); } public void test_contains_nonnull_set_no() { - Iterable set = Sets.newHashSet("a", "b"); + Iterable set = newHashSet("a", "b"); assertFalse(Iterables.contains(set, "c")); } public void test_contains_nonnull_iterable_yes() { - Iterable set = iterable("a", null, "b"); + Iterable<@Nullable String> set = iterable("a", null, "b"); assertTrue(Iterables.contains(set, "b")); } @@ -142,64 +166,52 @@ public void test_contains_nonnull_iterable_no() { } public void testGetOnlyElement_noDefault_valid() { - Iterable iterable = Collections.singletonList("foo"); - assertEquals("foo", Iterables.getOnlyElement(iterable)); + Iterable iterable = singletonList("foo"); + assertEquals("foo", getOnlyElement(iterable)); } public void testGetOnlyElement_noDefault_empty() { - Iterable iterable = Collections.emptyList(); - try { - Iterables.getOnlyElement(iterable); - fail(); - } catch (NoSuchElementException expected) { - } + Iterable iterable = emptyList(); + assertThrows(NoSuchElementException.class, () -> getOnlyElement(iterable)); } public void testGetOnlyElement_noDefault_multiple() { Iterable iterable = asList("foo", "bar"); - try { - Iterables.getOnlyElement(iterable); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> getOnlyElement(iterable)); } public void testGetOnlyElement_withDefault_singleton() { - Iterable iterable = Collections.singletonList("foo"); - assertEquals("foo", Iterables.getOnlyElement(iterable, "bar")); + Iterable iterable = singletonList("foo"); + assertEquals("foo", getOnlyElement(iterable, "bar")); } public void testGetOnlyElement_withDefault_empty() { - Iterable iterable = Collections.emptyList(); - assertEquals("bar", Iterables.getOnlyElement(iterable, "bar")); + Iterable iterable = emptyList(); + assertEquals("bar", getOnlyElement(iterable, "bar")); } public void testGetOnlyElement_withDefault_empty_null() { - Iterable iterable = Collections.emptyList(); - assertNull(Iterables.getOnlyElement(iterable, null)); + Iterable iterable = emptyList(); + assertThat(Iterables.<@Nullable String>getOnlyElement(iterable, null)).isNull(); } public void testGetOnlyElement_withDefault_multiple() { Iterable iterable = asList("foo", "bar"); - try { - Iterables.getOnlyElement(iterable, "x"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> getOnlyElement(iterable, "x")); } @GwtIncompatible // Iterables.toArray(Iterable, Class) public void testToArrayEmpty() { - Iterable iterable = Collections.emptyList(); + Iterable iterable = emptyList(); String[] array = Iterables.toArray(iterable, String.class); - assertTrue(Arrays.equals(new String[0], array)); + assertThat(array).isEmpty(); } @GwtIncompatible // Iterables.toArray(Iterable, Class) public void testToArraySingleton() { - Iterable iterable = Collections.singletonList("a"); + Iterable iterable = singletonList("a"); String[] array = Iterables.toArray(iterable, String.class); - assertTrue(Arrays.equals(new String[] {"a"}, array)); + assertThat(array).isEqualTo(new String[] {"a"}); } @GwtIncompatible // Iterables.toArray(Iterable, Class) @@ -207,60 +219,56 @@ public void testToArray() { String[] sourceArray = new String[] {"a", "b", "c"}; Iterable iterable = asList(sourceArray); String[] newArray = Iterables.toArray(iterable, String.class); - assertTrue(Arrays.equals(sourceArray, newArray)); + assertThat(newArray).isEqualTo(sourceArray); } public void testAny() { - List list = newArrayList(); - Predicate predicate = Predicates.equalTo("pants"); + List list = new ArrayList<>(); + Predicate predicate = equalTo("pants"); - assertFalse(Iterables.any(list, predicate)); + assertFalse(any(list, predicate)); list.add("cool"); - assertFalse(Iterables.any(list, predicate)); + assertFalse(any(list, predicate)); list.add("pants"); - assertTrue(Iterables.any(list, predicate)); + assertTrue(any(list, predicate)); } public void testAll() { - List list = newArrayList(); - Predicate predicate = Predicates.equalTo("cool"); + List list = new ArrayList<>(); + Predicate predicate = equalTo("cool"); - assertTrue(Iterables.all(list, predicate)); + assertTrue(all(list, predicate)); list.add("cool"); - assertTrue(Iterables.all(list, predicate)); + assertTrue(all(list, predicate)); list.add("pants"); - assertFalse(Iterables.all(list, predicate)); + assertFalse(all(list, predicate)); } public void testFind() { Iterable list = newArrayList("cool", "pants"); - assertEquals("cool", Iterables.find(list, Predicates.equalTo("cool"))); - assertEquals("pants", Iterables.find(list, Predicates.equalTo("pants"))); - try { - Iterables.find(list, Predicates.alwaysFalse()); - fail(); - } catch (NoSuchElementException e) { - } - assertEquals("cool", Iterables.find(list, Predicates.alwaysTrue())); + assertEquals("cool", find(list, equalTo("cool"))); + assertEquals("pants", find(list, equalTo("pants"))); + assertThrows(NoSuchElementException.class, () -> find(list, Predicates.alwaysFalse())); + assertEquals("cool", find(list, Predicates.alwaysTrue())); assertCanIterateAgain(list); } public void testFind_withDefault() { Iterable list = Lists.newArrayList("cool", "pants"); - assertEquals("cool", Iterables.find(list, Predicates.equalTo("cool"), "woot")); - assertEquals("pants", Iterables.find(list, Predicates.equalTo("pants"), "woot")); - assertEquals("woot", Iterables.find(list, Predicates.alwaysFalse(), "woot")); - assertNull(Iterables.find(list, Predicates.alwaysFalse(), null)); - assertEquals("cool", Iterables.find(list, Predicates.alwaysTrue(), "woot")); + assertEquals("cool", find(list, equalTo("cool"), "woot")); + assertEquals("pants", find(list, equalTo("pants"), "woot")); + assertEquals("woot", find(list, Predicates.alwaysFalse(), "woot")); + assertThat(find(list, Predicates.alwaysFalse(), null)).isNull(); + assertEquals("cool", find(list, Predicates.alwaysTrue(), "woot")); assertCanIterateAgain(list); } public void testTryFind() { Iterable list = newArrayList("cool", "pants"); - assertThat(Iterables.tryFind(list, Predicates.equalTo("cool"))).hasValue("cool"); - assertThat(Iterables.tryFind(list, Predicates.equalTo("pants"))).hasValue("pants"); - assertThat(Iterables.tryFind(list, Predicates.alwaysTrue())).hasValue("cool"); - assertThat(Iterables.tryFind(list, Predicates.alwaysFalse())).isAbsent(); + assertThat(tryFind(list, equalTo("cool"))).hasValue("cool"); + assertThat(tryFind(list, equalTo("pants"))).hasValue("pants"); + assertThat(tryFind(list, Predicates.alwaysTrue())).hasValue("cool"); + assertThat(tryFind(list, Predicates.alwaysFalse())).isAbsent(); assertCanIterateAgain(list); } @@ -274,7 +282,7 @@ private static class HasBoth extends TypeA implements TypeB {} public void testFilterByType_iterator() throws Exception { HasBoth hasBoth = new HasBoth(); Iterable alist = Lists.newArrayList(new TypeA(), new TypeA(), hasBoth, new TypeA()); - Iterable blist = Iterables.filter(alist, TypeB.class); + Iterable blist = filter(alist, TypeB.class); assertThat(blist).containsExactly(hasBoth).inOrder(); } @@ -298,7 +306,7 @@ public Integer apply(String from) { } public void testPoorlyBehavedTransform() { - List input = asList("1", null, "3"); + List input = asList("1", "not a number", "3"); Iterable result = Iterables.transform( input, @@ -312,21 +320,17 @@ public Integer apply(String from) { Iterator resultIterator = result.iterator(); resultIterator.next(); - try { - resultIterator.next(); - fail("Expected NFE"); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> resultIterator.next()); } public void testNullFriendlyTransform() { - List input = asList(1, 2, null, 3); + List<@Nullable Integer> input = asList(1, 2, null, 3); Iterable result = Iterables.transform( input, - new Function() { + new Function<@Nullable Integer, String>() { @Override - public String apply(Integer from) { + public String apply(@Nullable Integer from) { return String.valueOf(from); } }); @@ -364,7 +368,6 @@ public void testConcatIterable() { List list1 = newArrayList(1); List list2 = newArrayList(4); - @SuppressWarnings("unchecked") List> input = newArrayList(list1, list2); Iterable result = Iterables.concat(input); @@ -386,7 +389,6 @@ public void testConcatVarargs() { List list3 = newArrayList(7, 8); List list4 = newArrayList(9); List list5 = newArrayList(10); - @SuppressWarnings("unchecked") Iterable result = Iterables.concat(list1, list2, list3, list4, list5); assertEquals(asList(1, 4, 7, 8, 9, 10), newArrayList(result)); assertEquals("[1, 4, 7, 8, 9, 10]", result.toString()); @@ -396,40 +398,32 @@ public void testConcatNullPointerException() { List list1 = newArrayList(1); List list2 = newArrayList(4); - try { - Iterables.concat(list1, null, list2); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Iterables.concat(list1, null, list2)); } public void testConcatPeformingFiniteCycle() { Iterable iterable = asList(1, 2, 3); int n = 4; - Iterable repeated = Iterables.concat(Collections.nCopies(n, iterable)); + Iterable repeated = Iterables.concat(nCopies(n, iterable)); assertThat(repeated).containsExactly(1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3).inOrder(); } public void testPartition_badSize() { - Iterable source = Collections.singleton(1); - try { - Iterables.partition(source, 0); - fail(); - } catch (IllegalArgumentException expected) { - } + Iterable source = singleton(1); + assertThrows(IllegalArgumentException.class, () -> Iterables.partition(source, 0)); } public void testPartition_empty() { - Iterable source = Collections.emptySet(); + Iterable source = emptySet(); Iterable> partitions = Iterables.partition(source, 1); assertTrue(Iterables.isEmpty(partitions)); } public void testPartition_singleton1() { - Iterable source = Collections.singleton(1); + Iterable source = singleton(1); Iterable> partitions = Iterables.partition(source, 1); assertEquals(1, Iterables.size(partitions)); - assertEquals(Collections.singletonList(1), partitions.iterator().next()); + assertEquals(singletonList(1), partitions.iterator().next()); } public void testPartition_view() { @@ -452,8 +446,8 @@ public void testPartition_view() { assertEquals(ImmutableList.of(3, 4), first); } - @GwtIncompatible // ? - // TODO: Figure out why this is failing in GWT. + @J2ktIncompatible // Arrays.asList(...).subList() doesn't implement RandomAccess in J2KT. + @GwtIncompatible // Arrays.asList(...).subList doesn't implement RandomAccess in GWT public void testPartitionRandomAccessInput() { Iterable source = asList(1, 2, 3); Iterable> partitions = Iterables.partition(source, 2); @@ -462,10 +456,10 @@ public void testPartitionRandomAccessInput() { assertTrue(iterator.next() instanceof RandomAccess); } - @GwtIncompatible // ? - // TODO: Figure out why this is failing in GWT. + @J2ktIncompatible // Arrays.asList(...).subList() doesn't implement RandomAccess in J2KT. + @GwtIncompatible // Arrays.asList(...).subList() doesn't implement RandomAccess in GWT public void testPartitionNonRandomAccessInput() { - Iterable source = Lists.newLinkedList(asList(1, 2, 3)); + Iterable source = new LinkedList<>(asList(1, 2, 3)); Iterable> partitions = Iterables.partition(source, 2); Iterator> iterator = partitions.iterator(); // Even though the input list doesn't implement RandomAccess, the output @@ -476,9 +470,9 @@ public void testPartitionNonRandomAccessInput() { public void testPaddedPartition_basic() { List list = asList(1, 2, 3, 4, 5); - Iterable> partitions = Iterables.paddedPartition(list, 2); + Iterable> partitions = Iterables.paddedPartition(list, 2); assertEquals(3, Iterables.size(partitions)); - assertEquals(asList(5, null), Iterables.getLast(partitions)); + assertEquals(Arrays.<@Nullable Integer>asList(5, null), Iterables.getLast(partitions)); } public void testPaddedPartitionRandomAccessInput() { @@ -490,7 +484,7 @@ public void testPaddedPartitionRandomAccessInput() { } public void testPaddedPartitionNonRandomAccessInput() { - Iterable source = Lists.newLinkedList(asList(1, 2, 3)); + Iterable source = new LinkedList<>(asList(1, 2, 3)); Iterable> partitions = Iterables.paddedPartition(source, 2); Iterator> iterator = partitions.iterator(); // Even though the input list doesn't implement RandomAccess, the output @@ -513,6 +507,7 @@ private static void assertCanIterateAgain(Iterable iterable) { for (@SuppressWarnings("unused") Object obj : iterable) {} } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -527,28 +522,28 @@ public void testElementsEqual() throws Exception { // A few elements. a = asList(4, 8, 15, 16, 23, 42); b = asList(4, 8, 15, 16, 23, 42); - assertTrue(Iterables.elementsEqual(a, b)); + assertTrue(elementsEqual(a, b)); // An element differs. a = asList(4, 8, 15, 12, 23, 42); b = asList(4, 8, 15, 16, 23, 42); - assertFalse(Iterables.elementsEqual(a, b)); + assertFalse(elementsEqual(a, b)); // null versus non-null. - a = asList(4, 8, 15, null, 23, 42); + a = Arrays.<@Nullable Integer>asList(4, 8, 15, null, 23, 42); b = asList(4, 8, 15, 16, 23, 42); - assertFalse(Iterables.elementsEqual(a, b)); - assertFalse(Iterables.elementsEqual(b, a)); + assertFalse(elementsEqual(a, b)); + assertFalse(elementsEqual(b, a)); // Different lengths. a = asList(4, 8, 15, 16, 23); b = asList(4, 8, 15, 16, 23, 42); - assertFalse(Iterables.elementsEqual(a, b)); - assertFalse(Iterables.elementsEqual(b, a)); + assertFalse(elementsEqual(a, b)); + assertFalse(elementsEqual(b, a)); } public void testToString() { - List list = Collections.emptyList(); + List list = emptyList(); assertEquals("[]", Iterables.toString(list)); list = newArrayList("yam", "bam", "jam", "ham"); @@ -568,18 +563,14 @@ public void testLimit() { public void testLimit_illegalArgument() { List list = newArrayList("a", "b", "c"); - try { - Iterables.limit(list, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Iterables.limit(list, -1)); } public void testIsEmpty() { - Iterable emptyList = Collections.emptyList(); + Iterable emptyList = emptyList(); assertTrue(Iterables.isEmpty(emptyList)); - Iterable singletonList = Collections.singletonList("foo"); + Iterable singletonList = singletonList("foo"); assertFalse(Iterables.isEmpty(singletonList)); } @@ -616,38 +607,26 @@ public void testSkip_skipNoneList() { } public void testSkip_removal() { - Collection set = Sets.newHashSet("a", "b"); + Collection set = newHashSet("a", "b"); Iterator iterator = skip(set, 2).iterator(); try { iterator.next(); } catch (NoSuchElementException suppressed) { // We want remove() to fail even after a failed call to next(). } - try { - iterator.remove(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> iterator.remove()); } public void testSkip_allOfMutableList_modifiable() { List list = newArrayList("a", "b"); Iterator iterator = skip(list, 2).iterator(); - try { - iterator.remove(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> iterator.remove()); } public void testSkip_allOfImmutableList_modifiable() { List list = ImmutableList.of("a", "b"); Iterator iterator = skip(list, 2).iterator(); - try { - iterator.remove(); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); } @GwtIncompatible // slow (~35s) @@ -656,7 +635,7 @@ public void testSkip_iterator() { 5, MODIFIABLE, newArrayList(2, 3), IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { - return skip(newLinkedHashSet(asList(1, 2, 3)), 1).iterator(); + return skip(new LinkedHashSet<>(asList(1, 2, 3)), 1).iterator(); } }.test(); } @@ -683,7 +662,7 @@ public void testSkip_nonStructurallyModifiedList() throws Exception { } public void testSkip_structurallyModifiedSkipSome() throws Exception { - Collection set = newLinkedHashSet(asList("a", "b", "c")); + Collection set = new LinkedHashSet<>(asList("a", "b", "c")); Iterable tail = skip(set, 1); set.remove("b"); set.addAll(newArrayList("A", "B", "C")); @@ -699,7 +678,7 @@ public void testSkip_structurallyModifiedSkipSomeList() throws Exception { } public void testSkip_structurallyModifiedSkipAll() throws Exception { - Collection set = newLinkedHashSet(asList("a", "b", "c")); + Collection set = new LinkedHashSet<>(asList("a", "b", "c")); Iterable tail = skip(set, 2); set.remove("a"); set.remove("b"); @@ -715,11 +694,7 @@ public void testSkip_structurallyModifiedSkipAllList() throws Exception { public void testSkip_illegalArgument() { List list = newArrayList("a", "b", "c"); - try { - skip(list, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> skip(list, -1)); } private void testGetOnAbc(Iterable iterable) { @@ -772,16 +747,12 @@ public void testGet_iterable() { } public void testGet_emptyIterable() { - testGetOnEmpty(Sets.newHashSet()); + testGetOnEmpty(new HashSet()); } public void testGet_withDefault_negativePosition() { - try { - Iterables.get(newArrayList("a", "b", "c"), -1, "d"); - fail(); - } catch (IndexOutOfBoundsException expected) { - // pass - } + assertThrows( + IndexOutOfBoundsException.class, () -> Iterables.get(newArrayList("a", "b", "c"), -1, "d")); } public void testGet_withDefault_simple() { @@ -811,18 +782,18 @@ public void testGet_withDefault_doesntIterate() { } public void testGetFirst_withDefault_singleton() { - Iterable iterable = Collections.singletonList("foo"); + Iterable iterable = singletonList("foo"); assertEquals("foo", Iterables.getFirst(iterable, "bar")); } public void testGetFirst_withDefault_empty() { - Iterable iterable = Collections.emptyList(); + Iterable iterable = emptyList(); assertEquals("bar", Iterables.getFirst(iterable, "bar")); } public void testGetFirst_withDefault_empty_null() { - Iterable iterable = Collections.emptyList(); - assertNull(Iterables.getFirst(iterable, null)); + Iterable iterable = emptyList(); + assertThat(Iterables.<@Nullable String>getFirst(iterable, null)).isNull(); } public void testGetFirst_withDefault_multiple() { @@ -836,12 +807,8 @@ public void testGetLast_list() { } public void testGetLast_emptyList() { - List list = Collections.emptyList(); - try { - Iterables.getLast(list); - fail(); - } catch (NoSuchElementException e) { - } + List list = emptyList(); + assertThrows(NoSuchElementException.class, () -> Iterables.getLast(list)); } public void testGetLast_sortedSet() { @@ -850,18 +817,18 @@ public void testGetLast_sortedSet() { } public void testGetLast_withDefault_singleton() { - Iterable iterable = Collections.singletonList("foo"); + Iterable iterable = singletonList("foo"); assertEquals("foo", Iterables.getLast(iterable, "bar")); } public void testGetLast_withDefault_empty() { - Iterable iterable = Collections.emptyList(); + Iterable iterable = emptyList(); assertEquals("bar", Iterables.getLast(iterable, "bar")); } public void testGetLast_withDefault_empty_null() { - Iterable iterable = Collections.emptyList(); - assertNull(Iterables.getLast(iterable, null)); + Iterable iterable = emptyList(); + assertThat(Iterables.<@Nullable String>getLast(iterable, null)).isNull(); } public void testGetLast_withDefault_multiple() { @@ -874,7 +841,9 @@ public void testGetLast_withDefault_multiple() { * need to prove that it isn't called. */ private static class DiesOnIteratorArrayList extends ArrayList { - /** @throws UnsupportedOperationException all the time */ + /** + * @throws UnsupportedOperationException all the time + */ @Override public Iterator iterator() { throw new UnsupportedOperationException(); @@ -891,11 +860,8 @@ public void testGetLast_withDefault_not_empty_list() { public void testGetLast_emptySortedSet() { SortedSet sortedSet = ImmutableSortedSet.of(); - try { - Iterables.getLast(sortedSet); - fail(); - } catch (NoSuchElementException e) { - } + assertThrows(NoSuchElementException.class, () -> Iterables.getLast(sortedSet)); + assertEquals("c", Iterables.getLast(sortedSet, "c")); } public void testGetLast_iterable() { @@ -904,12 +870,8 @@ public void testGetLast_iterable() { } public void testGetLast_emptyIterable() { - Set set = Sets.newHashSet(); - try { - Iterables.getLast(set); - fail(); - } catch (NoSuchElementException e) { - } + Set set = new HashSet<>(); + assertThrows(NoSuchElementException.class, () -> Iterables.getLast(set)); } public void testUnmodifiableIterable() { @@ -917,15 +879,11 @@ public void testUnmodifiableIterable() { Iterable iterable = Iterables.unmodifiableIterable(list); Iterator iterator = iterable.iterator(); iterator.next(); - try { - iterator.remove(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); assertEquals("[a, b, c]", iterable.toString()); } - @SuppressWarnings("deprecation") // test of deprecated method + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testUnmodifiableIterableShortCircuit() { List list = newArrayList("a", "b", "c"); Iterable iterable = Iterables.unmodifiableIterable(list); @@ -938,32 +896,32 @@ public void testUnmodifiableIterableShortCircuit() { public void testFrequency_multiset() { Multiset multiset = ImmutableMultiset.of("a", "b", "a", "c", "b", "a"); - assertEquals(3, Iterables.frequency(multiset, "a")); - assertEquals(2, Iterables.frequency(multiset, "b")); - assertEquals(1, Iterables.frequency(multiset, "c")); - assertEquals(0, Iterables.frequency(multiset, "d")); - assertEquals(0, Iterables.frequency(multiset, 4.2)); - assertEquals(0, Iterables.frequency(multiset, null)); + assertEquals(3, frequency(multiset, "a")); + assertEquals(2, frequency(multiset, "b")); + assertEquals(1, frequency(multiset, "c")); + assertEquals(0, frequency(multiset, "d")); + assertEquals(0, frequency(multiset, 4.2)); + assertEquals(0, frequency(multiset, null)); } public void testFrequency_set() { - Set set = Sets.newHashSet("a", "b", "c"); - assertEquals(1, Iterables.frequency(set, "a")); - assertEquals(1, Iterables.frequency(set, "b")); - assertEquals(1, Iterables.frequency(set, "c")); - assertEquals(0, Iterables.frequency(set, "d")); - assertEquals(0, Iterables.frequency(set, 4.2)); - assertEquals(0, Iterables.frequency(set, null)); + Set set = newHashSet("a", "b", "c"); + assertEquals(1, frequency(set, "a")); + assertEquals(1, frequency(set, "b")); + assertEquals(1, frequency(set, "c")); + assertEquals(0, frequency(set, "d")); + assertEquals(0, frequency(set, 4.2)); + assertEquals(0, frequency(set, null)); } public void testFrequency_list() { List list = newArrayList("a", "b", "a", "c", "b", "a"); - assertEquals(3, Iterables.frequency(list, "a")); - assertEquals(2, Iterables.frequency(list, "b")); - assertEquals(1, Iterables.frequency(list, "c")); - assertEquals(0, Iterables.frequency(list, "d")); - assertEquals(0, Iterables.frequency(list, 4.2)); - assertEquals(0, Iterables.frequency(list, null)); + assertEquals(3, frequency(list, "a")); + assertEquals(2, frequency(list, "b")); + assertEquals(1, frequency(list, "c")); + assertEquals(0, frequency(list, "d")); + assertEquals(0, frequency(list, 4.2)); + assertEquals(0, frequency(list, null)); } public void testRemoveAll_collection() { @@ -975,7 +933,7 @@ public void testRemoveAll_collection() { } public void testRemoveAll_iterable() { - final List list = newArrayList("a", "b", "c", "d", "e"); + List list = newArrayList("a", "b", "c", "d", "e"); Iterable iterable = new Iterable() { @Override @@ -998,7 +956,7 @@ public void testRetainAll_collection() { } public void testRetainAll_iterable() { - final List list = newArrayList("a", "b", "c", "d", "e"); + List list = newArrayList("a", "b", "c", "d", "e"); Iterable iterable = new Iterable() { @Override @@ -1015,7 +973,7 @@ public Iterator iterator() { public void testRemoveIf_randomAccess() { List list = newArrayList("a", "b", "c", "d", "e"); assertTrue( - Iterables.removeIf( + removeIf( list, new Predicate() { @Override @@ -1025,7 +983,7 @@ public boolean apply(String s) { })); assertEquals(newArrayList("a", "c", "e"), list); assertFalse( - Iterables.removeIf( + removeIf( list, new Predicate() { @Override @@ -1043,7 +1001,7 @@ public void testRemoveIf_randomAccess_notPermittingDuplicates() { assertTrue(uniqueList instanceof RandomAccess); assertTrue( - Iterables.removeIf( + removeIf( uniqueList, new Predicate() { @Override @@ -1053,7 +1011,7 @@ public boolean apply(String s) { })); assertEquals(newArrayList("a", "c", "e"), uniqueList); assertFalse( - Iterables.removeIf( + removeIf( uniqueList, new Predicate() { @Override @@ -1076,7 +1034,7 @@ public Integer apply(String s) { } }); assertTrue( - Iterables.removeIf( + removeIf( transformed, new Predicate() { @Override @@ -1086,7 +1044,7 @@ public boolean apply(Integer n) { })); assertEquals(newArrayList("1", "3", "5"), list); assertFalse( - Iterables.removeIf( + removeIf( transformed, new Predicate() { @Override @@ -1098,9 +1056,9 @@ public boolean apply(Integer n) { } public void testRemoveIf_noRandomAccess() { - List list = Lists.newLinkedList(asList("a", "b", "c", "d", "e")); + List list = new LinkedList<>(asList("a", "b", "c", "d", "e")); assertTrue( - Iterables.removeIf( + removeIf( list, new Predicate() { @Override @@ -1110,7 +1068,7 @@ public boolean apply(String s) { })); assertEquals(newArrayList("a", "c", "e"), list); assertFalse( - Iterables.removeIf( + removeIf( list, new Predicate() { @Override @@ -1122,7 +1080,7 @@ public boolean apply(String s) { } public void testRemoveIf_iterable() { - final List list = Lists.newLinkedList(asList("a", "b", "c", "d", "e")); + List list = new LinkedList<>(asList("a", "b", "c", "d", "e")); Iterable iterable = new Iterable() { @Override @@ -1131,7 +1089,7 @@ public Iterator iterator() { } }; assertTrue( - Iterables.removeIf( + removeIf( iterable, new Predicate() { @Override @@ -1141,7 +1099,7 @@ public boolean apply(String s) { })); assertEquals(newArrayList("a", "c", "e"), list); assertFalse( - Iterables.removeIf( + removeIf( iterable, new Predicate() { @Override @@ -1157,33 +1115,9 @@ public boolean apply(String s) { // Iterable. Those returned by Iterators.filter() and Iterables.filter() // are not tested because they are unmodifiable. - public void testIterableWithToString() { - assertEquals("[]", create().toString()); - assertEquals("[a]", create("a").toString()); - assertEquals("[a, b, c]", create("a", "b", "c").toString()); - assertEquals("[c, a, a]", create("c", "a", "a").toString()); - } - - public void testIterableWithToStringNull() { - assertEquals("[null]", create((String) null).toString()); - assertEquals("[null, null]", create(null, null).toString()); - assertEquals("[, null, a]", create("", null, "a").toString()); - } - - /** Returns a new iterable over the specified strings. */ - private static Iterable create(String... strings) { - final List list = asList(strings); - return new FluentIterable() { - @Override - public Iterator iterator() { - return list.iterator(); - } - }; - } - public void testConsumingIterable() { // Test data - List list = Lists.newArrayList(asList("a", "b")); + List list = new ArrayList<>(asList("a", "b")); // Test & Verify Iterable consumingIterable = Iterables.consumingIterable(list); @@ -1208,33 +1142,28 @@ public void testConsumingIterable() { // TODO: Figure out why this is failing in GWT. public void testConsumingIterable_duelingIterators() { // Test data - List list = Lists.newArrayList(asList("a", "b")); + List list = new ArrayList<>(asList("a", "b")); // Test & Verify Iterator i1 = Iterables.consumingIterable(list).iterator(); Iterator i2 = Iterables.consumingIterable(list).iterator(); i1.next(); - try { - i2.next(); - fail("Concurrent modification should throw an exception."); - } catch (ConcurrentModificationException cme) { - // Pass - } + assertThrows(ConcurrentModificationException.class, () -> i2.next()); } public void testConsumingIterable_queue_iterator() { - final List items = ImmutableList.of(4, 8, 15, 16, 23, 42); + List items = ImmutableList.of(4, 8, 15, 16, 23, 42); new IteratorTester(3, UNMODIFIABLE, items, IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { - return Iterables.consumingIterable(Lists.newLinkedList(items)).iterator(); + return Iterables.consumingIterable(new LinkedList<>(items)).iterator(); } }.test(); } public void testConsumingIterable_queue_removesFromQueue() { - Queue queue = Lists.newLinkedList(asList(5, 14)); + Queue queue = new LinkedList<>(asList(5, 14)); Iterator consumingIterator = Iterables.consumingIterable(queue).iterator(); @@ -1247,7 +1176,7 @@ public void testConsumingIterable_queue_removesFromQueue() { } public void testConsumingIterable_noIteratorCall() { - Queue queue = new UnIterableQueue<>(Lists.newLinkedList(asList(5, 14))); + Queue queue = new UnIterableQueue<>(new LinkedList<>(asList(5, 14))); Iterator consumingIterator = Iterables.consumingIterable(queue).iterator(); /* @@ -1277,28 +1206,28 @@ protected Queue delegate() { public void testIndexOf_empty() { List list = new ArrayList<>(); - assertEquals(-1, Iterables.indexOf(list, Predicates.equalTo(""))); + assertEquals(-1, Iterables.indexOf(list, equalTo(""))); } public void testIndexOf_oneElement() { List list = Lists.newArrayList("bob"); - assertEquals(0, Iterables.indexOf(list, Predicates.equalTo("bob"))); - assertEquals(-1, Iterables.indexOf(list, Predicates.equalTo("jack"))); + assertEquals(0, Iterables.indexOf(list, equalTo("bob"))); + assertEquals(-1, Iterables.indexOf(list, equalTo("jack"))); } public void testIndexOf_twoElements() { List list = Lists.newArrayList("mary", "bob"); - assertEquals(0, Iterables.indexOf(list, Predicates.equalTo("mary"))); - assertEquals(1, Iterables.indexOf(list, Predicates.equalTo("bob"))); - assertEquals(-1, Iterables.indexOf(list, Predicates.equalTo("jack"))); + assertEquals(0, Iterables.indexOf(list, equalTo("mary"))); + assertEquals(1, Iterables.indexOf(list, equalTo("bob"))); + assertEquals(-1, Iterables.indexOf(list, equalTo("jack"))); } public void testIndexOf_withDuplicates() { List list = Lists.newArrayList("mary", "bob", "bob", "bob", "sam"); - assertEquals(0, Iterables.indexOf(list, Predicates.equalTo("mary"))); - assertEquals(1, Iterables.indexOf(list, Predicates.equalTo("bob"))); - assertEquals(4, Iterables.indexOf(list, Predicates.equalTo("sam"))); - assertEquals(-1, Iterables.indexOf(list, Predicates.equalTo("jack"))); + assertEquals(0, Iterables.indexOf(list, equalTo("mary"))); + assertEquals(1, Iterables.indexOf(list, equalTo("bob"))); + assertEquals(4, Iterables.indexOf(list, equalTo("sam"))); + assertEquals(-1, Iterables.indexOf(list, equalTo("jack"))); } private static final Predicate STARTSWITH_A = @@ -1309,11 +1238,12 @@ public boolean apply(CharSequence input) { } }; + @SuppressWarnings("UnnecessaryStringBuilder") // false positive in a weird case public void testIndexOf_genericPredicate() { - List sequences = Lists.newArrayList(); + List sequences = new ArrayList<>(); sequences.add("bob"); sequences.add(new StringBuilder("charlie")); - sequences.add(new StringBuffer("henry")); + sequences.add(new StringBuilder("henry")); sequences.add(new StringBuilder("apple")); sequences.add("lemon"); @@ -1330,17 +1260,12 @@ public void testMergeSorted_empty() { Iterable> elements = ImmutableList.of(); // Test - Iterable iterable = Iterables.mergeSorted(elements, Ordering.natural()); + Iterable iterable = mergeSorted(elements, Ordering.natural()); // Verify Iterator iterator = iterable.iterator(); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail("next() on empty iterator should throw NoSuchElementException"); - } catch (NoSuchElementException e) { - // Huzzah! - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); } public void testMergeSorted_single_empty() { @@ -1362,17 +1287,17 @@ public void testMergeSorted_single() { } public void testMergeSorted_pyramid() { - List> iterables = Lists.newLinkedList(); - List allIntegers = Lists.newArrayList(); + List> iterables = new LinkedList<>(); + List allIntegers = new ArrayList<>(); // Creates iterators like: {{}, {0}, {0, 1}, {0, 1, 2}, ...} for (int i = 0; i < 10; i++) { - List list = Lists.newLinkedList(); + List list = new LinkedList<>(); for (int j = 0; j < i; j++) { list.add(j); allIntegers.add(j); } - iterables.add(Ordering.natural().sortedCopy(list)); + iterables.add(Ordering.natural().sortedCopy(list)); } verifyMergeSorted(iterables, allIntegers); @@ -1380,21 +1305,22 @@ public void testMergeSorted_pyramid() { // Like the pyramid, but creates more unique values, along with repeated ones. public void testMergeSorted_skipping_pyramid() { - List> iterables = Lists.newLinkedList(); - List allIntegers = Lists.newArrayList(); + List> iterables = new LinkedList<>(); + List allIntegers = new ArrayList<>(); for (int i = 0; i < 20; i++) { - List list = Lists.newLinkedList(); + List list = new LinkedList<>(); for (int j = 0; j < i; j++) { list.add(j * i); allIntegers.add(j * i); } - iterables.add(Ordering.natural().sortedCopy(list)); + iterables.add(Ordering.natural().sortedCopy(list)); } verifyMergeSorted(iterables, allIntegers); } + @J2ktIncompatible @GwtIncompatible // reflection public void testIterables_nullCheck() throws Exception { new ClassSanityTester() @@ -1405,10 +1331,20 @@ public void testIterables_nullCheck() throws Exception { private static void verifyMergeSorted( Iterable> iterables, Iterable unsortedExpected) { - Iterable expected = Ordering.natural().sortedCopy(unsortedExpected); + Iterable expected = Ordering.natural().sortedCopy(unsortedExpected); - Iterable mergedIterator = Iterables.mergeSorted(iterables, Ordering.natural()); + Iterable mergedIterator = mergeSorted(iterables, Ordering.natural()); assertEquals(Lists.newLinkedList(expected), Lists.newLinkedList(mergedIterator)); } + + public void testMergeSorted_stable_allEqual() { + ImmutableList first = ImmutableList.of(0, 2, 4, 6, 8); + ImmutableList second = ImmutableList.of(1, 3, 5, 7, 9); + + Comparator comparator = Ordering.allEqual(); + Iterable merged = Iterables.mergeSorted(ImmutableList.of(first, second), comparator); + + assertThat(merged).containsExactly(0, 2, 4, 6, 8, 1, 3, 5, 7, 9).inOrder(); + } } diff --git a/android/guava-tests/test/com/google/common/collect/IteratorsTest.java b/android/guava-tests/test/com/google/common/collect/IteratorsTest.java index 15f7ccc2040a..8aa9810c6874 100644 --- a/android/guava-tests/test/com/google/common/collect/IteratorsTest.java +++ b/android/guava-tests/test/com/google/common/collect/IteratorsTest.java @@ -16,19 +16,36 @@ package com.google.common.collect; +import static com.google.common.base.Predicates.equalTo; import static com.google.common.collect.CollectPreconditions.checkRemove; import static com.google.common.collect.Iterators.advance; +import static com.google.common.collect.Iterators.all; +import static com.google.common.collect.Iterators.any; +import static com.google.common.collect.Iterators.elementsEqual; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Iterators.filter; +import static com.google.common.collect.Iterators.find; +import static com.google.common.collect.Iterators.frequency; import static com.google.common.collect.Iterators.get; import static com.google.common.collect.Iterators.getLast; +import static com.google.common.collect.Iterators.getOnlyElement; +import static com.google.common.collect.Iterators.peekingIterator; +import static com.google.common.collect.Iterators.singletonIterator; +import static com.google.common.collect.Iterators.tryFind; import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; @@ -39,17 +56,21 @@ import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.ListFeature; +import com.google.common.primitives.Ints; import com.google.common.testing.NullPointerTester; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.Enumeration; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.RandomAccess; import java.util.Set; import java.util.Vector; @@ -57,16 +78,21 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code Iterators}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class IteratorsTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(IteratorsTest.class.getSimpleName()); suite.addTest(testsForRemoveAllAndRetainAll()); @@ -74,76 +100,42 @@ public static Test suite() { return suite; } + @SuppressWarnings("DoNotCall") public void testEmptyIterator() { - Iterator iterator = Iterators.emptyIterator(); + Iterator iterator = emptyIterator(); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail("no exception thrown"); - } catch (NoSuchElementException expected) { - } - try { - iterator.remove(); - fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); } + @SuppressWarnings("DoNotCall") public void testEmptyListIterator() { ListIterator iterator = Iterators.emptyListIterator(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasPrevious()); assertEquals(0, iterator.nextIndex()); assertEquals(-1, iterator.previousIndex()); - try { - iterator.next(); - fail("no exception thrown"); - } catch (NoSuchElementException expected) { - } - try { - iterator.previous(); - fail("no exception thrown"); - } catch (NoSuchElementException expected) { - } - try { - iterator.remove(); - fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - } - try { - iterator.set("a"); - fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - } - try { - iterator.add("a"); - fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); + assertThrows(NoSuchElementException.class, () -> iterator.previous()); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + assertThrows(UnsupportedOperationException.class, () -> iterator.set("a")); + assertThrows(UnsupportedOperationException.class, () -> iterator.add("a")); } public void testEmptyModifiableIterator() { Iterator iterator = Iterators.emptyModifiableIterator(); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail("Expected NoSuchElementException"); - } catch (NoSuchElementException expected) { - } - try { - iterator.remove(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); + assertThrows(IllegalStateException.class, () -> iterator.remove()); } public void testSize0() { - Iterator iterator = Iterators.emptyIterator(); + Iterator iterator = emptyIterator(); assertEquals(0, Iterators.size(iterator)); } public void testSize1() { - Iterator iterator = Collections.singleton(0).iterator(); + Iterator iterator = singleton(0).iterator(); assertEquals(1, Iterators.size(iterator)); } @@ -155,7 +147,7 @@ public void testSize_partiallyConsumed() { } public void test_contains_nonnull_yes() { - Iterator set = asList("a", null, "b").iterator(); + Iterator<@Nullable String> set = Arrays.<@Nullable String>asList("a", null, "b").iterator(); assertTrue(Iterators.contains(set, "b")); } @@ -165,7 +157,7 @@ public void test_contains_nonnull_no() { } public void test_contains_null_yes() { - Iterator set = asList("a", null, "b").iterator(); + Iterator<@Nullable String> set = Arrays.<@Nullable String>asList("a", null, "b").iterator(); assertTrue(Iterators.contains(set, null)); } @@ -175,90 +167,74 @@ public void test_contains_null_no() { } public void testGetOnlyElement_noDefault_valid() { - Iterator iterator = Collections.singletonList("foo").iterator(); - assertEquals("foo", Iterators.getOnlyElement(iterator)); + Iterator iterator = singletonList("foo").iterator(); + assertEquals("foo", getOnlyElement(iterator)); } public void testGetOnlyElement_noDefault_empty() { - Iterator iterator = Iterators.emptyIterator(); - try { - Iterators.getOnlyElement(iterator); - fail(); - } catch (NoSuchElementException expected) { - } + Iterator iterator = emptyIterator(); + assertThrows(NoSuchElementException.class, () -> getOnlyElement(iterator)); } public void testGetOnlyElement_noDefault_moreThanOneLessThanFiveElements() { Iterator iterator = asList("one", "two").iterator(); - try { - Iterators.getOnlyElement(iterator); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("expected one element but was: "); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> getOnlyElement(iterator)); + assertThat(expected).hasMessageThat().isEqualTo("expected one element but was: "); } public void testGetOnlyElement_noDefault_fiveElements() { Iterator iterator = asList("one", "two", "three", "four", "five").iterator(); - try { - Iterators.getOnlyElement(iterator); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("expected one element but was: "); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> getOnlyElement(iterator)); + assertThat(expected) + .hasMessageThat() + .isEqualTo("expected one element but was: "); } public void testGetOnlyElement_noDefault_moreThanFiveElements() { Iterator iterator = asList("one", "two", "three", "four", "five", "six").iterator(); - try { - Iterators.getOnlyElement(iterator); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("expected one element but was: "); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> getOnlyElement(iterator)); + assertThat(expected) + .hasMessageThat() + .isEqualTo("expected one element but was: "); } public void testGetOnlyElement_withDefault_singleton() { - Iterator iterator = Collections.singletonList("foo").iterator(); - assertEquals("foo", Iterators.getOnlyElement(iterator, "bar")); + Iterator iterator = singletonList("foo").iterator(); + assertEquals("foo", getOnlyElement(iterator, "bar")); } public void testGetOnlyElement_withDefault_empty() { - Iterator iterator = Iterators.emptyIterator(); - assertEquals("bar", Iterators.getOnlyElement(iterator, "bar")); + Iterator iterator = emptyIterator(); + assertEquals("bar", getOnlyElement(iterator, "bar")); } public void testGetOnlyElement_withDefault_empty_null() { - Iterator iterator = Iterators.emptyIterator(); - assertNull(Iterators.getOnlyElement(iterator, null)); + Iterator iterator = emptyIterator(); + assertThat(Iterators.<@Nullable String>getOnlyElement(iterator, null)).isNull(); } public void testGetOnlyElement_withDefault_two() { Iterator iterator = asList("foo", "bar").iterator(); - try { - Iterators.getOnlyElement(iterator, "x"); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("expected one element but was: "); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> getOnlyElement(iterator, "x")); + assertThat(expected).hasMessageThat().isEqualTo("expected one element but was: "); } @GwtIncompatible // Iterators.toArray(Iterator, Class) public void testToArrayEmpty() { Iterator iterator = Collections.emptyList().iterator(); String[] array = Iterators.toArray(iterator, String.class); - assertTrue(Arrays.equals(new String[0], array)); + assertThat(array).isEmpty(); } @GwtIncompatible // Iterators.toArray(Iterator, Class) public void testToArraySingleton() { - Iterator iterator = Collections.singletonList("a").iterator(); + Iterator iterator = singletonList("a").iterator(); String[] array = Iterators.toArray(iterator, String.class); - assertTrue(Arrays.equals(new String[] {"a"}, array)); + assertThat(array).isEqualTo(new String[] {"a"}); } @GwtIncompatible // Iterators.toArray(Iterator, Class) @@ -266,28 +242,28 @@ public void testToArray() { String[] sourceArray = new String[] {"a", "b", "c"}; Iterator iterator = asList(sourceArray).iterator(); String[] newArray = Iterators.toArray(iterator, String.class); - assertTrue(Arrays.equals(sourceArray, newArray)); + assertThat(newArray).isEqualTo(sourceArray); } public void testFilterSimple() { Iterator unfiltered = Lists.newArrayList("foo", "bar").iterator(); - Iterator filtered = Iterators.filter(unfiltered, Predicates.equalTo("foo")); - List expected = Collections.singletonList("foo"); + Iterator filtered = filter(unfiltered, equalTo("foo")); + List expected = singletonList("foo"); List actual = Lists.newArrayList(filtered); assertEquals(expected, actual); } public void testFilterNoMatch() { Iterator unfiltered = Lists.newArrayList("foo", "bar").iterator(); - Iterator filtered = Iterators.filter(unfiltered, Predicates.alwaysFalse()); - List expected = Collections.emptyList(); + Iterator filtered = filter(unfiltered, Predicates.alwaysFalse()); + List expected = emptyList(); List actual = Lists.newArrayList(filtered); assertEquals(expected, actual); } public void testFilterMatchAll() { Iterator unfiltered = Lists.newArrayList("foo", "bar").iterator(); - Iterator filtered = Iterators.filter(unfiltered, Predicates.alwaysTrue()); + Iterator filtered = filter(unfiltered, Predicates.alwaysTrue()); List expected = Lists.newArrayList("foo", "bar"); List actual = Lists.newArrayList(filtered); assertEquals(expected, actual); @@ -296,7 +272,7 @@ public void testFilterMatchAll() { public void testFilterNothing() { Iterator unfiltered = Collections.emptyList().iterator(); Iterator filtered = - Iterators.filter( + filter( unfiltered, new Predicate() { @Override @@ -305,15 +281,15 @@ public boolean apply(String s) { } }); - List expected = Collections.emptyList(); + List expected = emptyList(); List actual = Lists.newArrayList(filtered); assertEquals(expected, actual); } @GwtIncompatible // unreasonably slow public void testFilterUsingIteratorTester() { - final List list = asList(1, 2, 3, 4, 5); - final Predicate isEven = + List list = asList(1, 2, 3, 4, 5); + Predicate isEven = new Predicate() { @Override public boolean apply(Integer integer) { @@ -324,128 +300,124 @@ public boolean apply(Integer integer) { 5, UNMODIFIABLE, asList(2, 4), IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { - return Iterators.filter(list.iterator(), isEven); + return filter(list.iterator(), isEven); } }.test(); } public void testAny() { - List list = Lists.newArrayList(); - Predicate predicate = Predicates.equalTo("pants"); + List list = new ArrayList<>(); + Predicate predicate = equalTo("pants"); - assertFalse(Iterators.any(list.iterator(), predicate)); + assertFalse(any(list.iterator(), predicate)); list.add("cool"); - assertFalse(Iterators.any(list.iterator(), predicate)); + assertFalse(any(list.iterator(), predicate)); list.add("pants"); - assertTrue(Iterators.any(list.iterator(), predicate)); + assertTrue(any(list.iterator(), predicate)); } public void testAll() { - List list = Lists.newArrayList(); - Predicate predicate = Predicates.equalTo("cool"); + List list = new ArrayList<>(); + Predicate predicate = equalTo("cool"); - assertTrue(Iterators.all(list.iterator(), predicate)); + assertTrue(all(list.iterator(), predicate)); list.add("cool"); - assertTrue(Iterators.all(list.iterator(), predicate)); + assertTrue(all(list.iterator(), predicate)); list.add("pants"); - assertFalse(Iterators.all(list.iterator(), predicate)); + assertFalse(all(list.iterator(), predicate)); } public void testFind_firstElement() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertEquals("cool", Iterators.find(iterator, Predicates.equalTo("cool"))); + assertEquals("cool", find(iterator, equalTo("cool"))); assertEquals("pants", iterator.next()); } public void testFind_lastElement() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertEquals("pants", Iterators.find(iterator, Predicates.equalTo("pants"))); + assertEquals("pants", find(iterator, equalTo("pants"))); assertFalse(iterator.hasNext()); } public void testFind_notPresent() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - try { - Iterators.find(iterator, Predicates.alwaysFalse()); - fail(); - } catch (NoSuchElementException e) { - } + assertThrows(NoSuchElementException.class, () -> find(iterator, Predicates.alwaysFalse())); assertFalse(iterator.hasNext()); } public void testFind_matchAlways() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertEquals("cool", Iterators.find(iterator, Predicates.alwaysTrue())); + assertEquals("cool", find(iterator, Predicates.alwaysTrue())); } public void testFind_withDefault_first() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertEquals("cool", Iterators.find(iterator, Predicates.equalTo("cool"), "woot")); + assertEquals("cool", find(iterator, equalTo("cool"), "woot")); assertEquals("pants", iterator.next()); } public void testFind_withDefault_last() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertEquals("pants", Iterators.find(iterator, Predicates.equalTo("pants"), "woot")); + assertEquals("pants", find(iterator, equalTo("pants"), "woot")); assertFalse(iterator.hasNext()); } public void testFind_withDefault_notPresent() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertEquals("woot", Iterators.find(iterator, Predicates.alwaysFalse(), "woot")); + assertEquals("woot", find(iterator, Predicates.alwaysFalse(), "woot")); assertFalse(iterator.hasNext()); } public void testFind_withDefault_notPresent_nullReturn() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertNull(Iterators.find(iterator, Predicates.alwaysFalse(), null)); + assertThat(find(iterator, Predicates.alwaysFalse(), null)).isNull(); assertFalse(iterator.hasNext()); } public void testFind_withDefault_matchAlways() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertEquals("cool", Iterators.find(iterator, Predicates.alwaysTrue(), "woot")); + assertEquals("cool", find(iterator, Predicates.alwaysTrue(), "woot")); assertEquals("pants", iterator.next()); } public void testTryFind_firstElement() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertThat(Iterators.tryFind(iterator, Predicates.equalTo("cool"))).hasValue("cool"); + assertThat(tryFind(iterator, equalTo("cool"))).hasValue("cool"); } public void testTryFind_lastElement() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertThat(Iterators.tryFind(iterator, Predicates.equalTo("pants"))).hasValue("pants"); + assertThat(tryFind(iterator, equalTo("pants"))).hasValue("pants"); } public void testTryFind_alwaysTrue() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertThat(Iterators.tryFind(iterator, Predicates.alwaysTrue())).hasValue("cool"); + assertThat(tryFind(iterator, Predicates.alwaysTrue())).hasValue("cool"); } public void testTryFind_alwaysFalse_orDefault() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertEquals("woot", Iterators.tryFind(iterator, Predicates.alwaysFalse()).or("woot")); + assertEquals("woot", tryFind(iterator, Predicates.alwaysFalse()).or("woot")); assertFalse(iterator.hasNext()); } public void testTryFind_alwaysFalse_isPresent() { Iterable list = Lists.newArrayList("cool", "pants"); Iterator iterator = list.iterator(); - assertThat(Iterators.tryFind(iterator, Predicates.alwaysFalse())).isAbsent(); + assertThat(tryFind(iterator, Predicates.alwaysFalse())).isAbsent(); assertFalse(iterator.hasNext()); } @@ -486,7 +458,7 @@ public Integer apply(String from) { } public void testPoorlyBehavedTransform() { - Iterator input = asList("1", null, "3").iterator(); + Iterator input = asList("1", "not a number", "3").iterator(); Iterator result = Iterators.transform( input, @@ -498,21 +470,17 @@ public Integer apply(String from) { }); result.next(); - try { - result.next(); - fail("Expected NFE"); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> result.next()); } public void testNullFriendlyTransform() { - Iterator input = asList(1, 2, null, 3).iterator(); + Iterator<@Nullable Integer> input = Arrays.<@Nullable Integer>asList(1, 2, null, 3).iterator(); Iterator result = Iterators.transform( input, - new Function() { + new Function<@Nullable Integer, String>() { @Override - public String apply(Integer from) { + public String apply(@Nullable Integer from) { return String.valueOf(from); } }); @@ -542,7 +510,7 @@ public void testCycleOfOneWithRemove() { assertTrue(cycle.hasNext()); assertEquals("a", cycle.next()); cycle.remove(); - assertEquals(Collections.emptyList(), iterable); + assertEquals(emptyList(), iterable); assertFalse(cycle.hasNext()); } @@ -566,46 +534,34 @@ public void testCycleOfTwoWithRemove() { assertTrue(cycle.hasNext()); assertEquals("a", cycle.next()); cycle.remove(); - assertEquals(Collections.singletonList("b"), iterable); + assertEquals(singletonList("b"), iterable); assertTrue(cycle.hasNext()); assertEquals("b", cycle.next()); assertTrue(cycle.hasNext()); assertEquals("b", cycle.next()); cycle.remove(); - assertEquals(Collections.emptyList(), iterable); + assertEquals(emptyList(), iterable); assertFalse(cycle.hasNext()); } public void testCycleRemoveWithoutNext() { Iterator cycle = Iterators.cycle("a", "b"); assertTrue(cycle.hasNext()); - try { - cycle.remove(); - fail("no exception thrown"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> cycle.remove()); } public void testCycleRemoveSameElementTwice() { Iterator cycle = Iterators.cycle("a", "b"); cycle.next(); cycle.remove(); - try { - cycle.remove(); - fail("no exception thrown"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> cycle.remove()); } public void testCycleWhenRemoveIsNotSupported() { Iterable iterable = asList("a", "b"); Iterator cycle = Iterators.cycle(iterable); cycle.next(); - try { - cycle.remove(); - fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> cycle.remove()); } public void testCycleRemoveAfterHasNext() { @@ -615,7 +571,7 @@ public void testCycleRemoveAfterHasNext() { assertEquals("a", cycle.next()); assertTrue(cycle.hasNext()); cycle.remove(); - assertEquals(Collections.emptyList(), iterable); + assertEquals(emptyList(), iterable); assertFalse(cycle.hasNext()); } @@ -672,7 +628,7 @@ void checkConcurrentModification() { } public void testCycleRemoveAfterHasNextExtraPicky() { - PickyIterable iterable = new PickyIterable("a"); + PickyIterable iterable = new PickyIterable<>("a"); Iterator cycle = Iterators.cycle(iterable); assertTrue(cycle.hasNext()); assertEquals("a", cycle.next()); @@ -689,11 +645,7 @@ public void testCycleNoSuchElementException() { assertEquals("a", cycle.next()); cycle.remove(); assertFalse(cycle.hasNext()); - try { - cycle.next(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> cycle.next()); } @GwtIncompatible // unreasonably slow @@ -713,7 +665,6 @@ protected Iterator newTargetIterator() { @GwtIncompatible // slow (~5s) public void testConcatNoIteratorsYieldsEmpty() { new EmptyIteratorTester() { - @SuppressWarnings("unchecked") @Override protected Iterator newTargetIterator() { return Iterators.concat(); @@ -724,7 +675,6 @@ protected Iterator newTargetIterator() { @GwtIncompatible // slow (~5s) public void testConcatOneEmptyIteratorYieldsEmpty() { new EmptyIteratorTester() { - @SuppressWarnings("unchecked") @Override protected Iterator newTargetIterator() { return Iterators.concat(iterateOver()); @@ -745,7 +695,6 @@ protected Iterator newTargetIterator() { @GwtIncompatible // slow (~3s) public void testConcatSingletonYieldsSingleton() { new SingletonIteratorTester() { - @SuppressWarnings("unchecked") @Override protected Iterator newTargetIterator() { return Iterators.concat(iterateOver(1)); @@ -796,56 +745,47 @@ protected Iterator newTargetIterator() { } public void testConcatPartiallyAdvancedSecond() { - Iterator itr1 = - Iterators.concat(Iterators.singletonIterator("a"), Iterators.forArray("b", "c")); + Iterator itr1 = Iterators.concat(singletonIterator("a"), Iterators.forArray("b", "c")); assertEquals("a", itr1.next()); assertEquals("b", itr1.next()); - Iterator itr2 = Iterators.concat(Iterators.singletonIterator("d"), itr1); + Iterator itr2 = Iterators.concat(singletonIterator("d"), itr1); assertEquals("d", itr2.next()); assertEquals("c", itr2.next()); } public void testConcatPartiallyAdvancedFirst() { - Iterator itr1 = - Iterators.concat(Iterators.singletonIterator("a"), Iterators.forArray("b", "c")); + Iterator itr1 = Iterators.concat(singletonIterator("a"), Iterators.forArray("b", "c")); assertEquals("a", itr1.next()); assertEquals("b", itr1.next()); - Iterator itr2 = Iterators.concat(itr1, Iterators.singletonIterator("d")); + Iterator itr2 = Iterators.concat(itr1, singletonIterator("d")); assertEquals("c", itr2.next()); assertEquals("d", itr2.next()); } /** Illustrates the somewhat bizarre behavior when a null is passed in. */ public void testConcatContainingNull() { - @SuppressWarnings("unchecked") - Iterator> input = asList(iterateOver(1, 2), null, iterateOver(3)).iterator(); + Iterator> input = + (Iterator>) + Arrays.<@Nullable Iterator>asList(iterateOver(1, 2), null, iterateOver(3)) + .iterator(); Iterator result = Iterators.concat(input); assertEquals(1, (int) result.next()); assertEquals(2, (int) result.next()); - try { - result.hasNext(); - fail("no exception thrown"); - } catch (NullPointerException e) { - } - try { - result.next(); - fail("no exception thrown"); - } catch (NullPointerException e) { - } + assertThrows(NullPointerException.class, () -> result.hasNext()); + assertThrows(NullPointerException.class, () -> result.next()); // There is no way to get "through" to the 3. Buh-bye } - @SuppressWarnings("unchecked") public void testConcatVarArgsContainingNull() { - try { - Iterators.concat(iterateOver(1, 2), null, iterateOver(3), iterateOver(4), iterateOver(5)); - fail("no exception thrown"); - } catch (NullPointerException e) { - } + assertThrows( + NullPointerException.class, + () -> + Iterators.concat( + iterateOver(1, 2), null, iterateOver(3), iterateOver(4), iterateOver(5))); } public void testConcatNested_appendToEnd() { - final int nestingDepth = 128; + int nestingDepth = 128; Iterator iterator = iterateOver(); for (int i = 0; i < nestingDepth; i++) { iterator = Iterators.concat(iterator, iterateOver(1)); @@ -854,7 +794,7 @@ public void testConcatNested_appendToEnd() { } public void testConcatNested_appendToBeginning() { - final int nestingDepth = 128; + int nestingDepth = 128; Iterator iterator = iterateOver(); for (int i = 0; i < nestingDepth; i++) { iterator = Iterators.concat(iterateOver(1), iterator); @@ -881,7 +821,7 @@ public void testAddAllToList() { } public void testAddAllToSet() { - Set alreadyThere = Sets.newLinkedHashSet(asList("already", "there")); + Set alreadyThere = new LinkedHashSet<>(asList("already", "there")); List oneMore = Lists.newArrayList("there"); boolean changed = Iterators.addAll(alreadyThere, oneMore.iterator()); @@ -889,6 +829,7 @@ public void testAddAllToSet() { assertFalse(changed); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -897,27 +838,28 @@ public void testNullPointerExceptions() { @GwtIncompatible // Only used by @GwtIncompatible code private abstract static class EmptyIteratorTester extends IteratorTester { - protected EmptyIteratorTester() { + EmptyIteratorTester() { super(3, MODIFIABLE, Collections.emptySet(), IteratorTester.KnownOrder.KNOWN_ORDER); } } @GwtIncompatible // Only used by @GwtIncompatible code private abstract static class SingletonIteratorTester extends IteratorTester { - protected SingletonIteratorTester() { + SingletonIteratorTester() { super(3, MODIFIABLE, singleton(1), IteratorTester.KnownOrder.KNOWN_ORDER); } } @GwtIncompatible // Only used by @GwtIncompatible code private abstract static class DoubletonIteratorTester extends IteratorTester { - protected DoubletonIteratorTester() { + DoubletonIteratorTester() { super(5, MODIFIABLE, newArrayList(1, 2), IteratorTester.KnownOrder.KNOWN_ORDER); } } - private static Iterator iterateOver(final Integer... values) { - return newArrayList(values).iterator(); + private static Iterator iterateOver(int... values) { + // Note: Ints.asList's iterator does not support remove which we need for testing. + return new ArrayList<>(Ints.asList(values)).iterator(); } public void testElementsEqual() { @@ -925,66 +867,62 @@ public void testElementsEqual() { Iterable b; // Base case. - a = Lists.newArrayList(); - b = Collections.emptySet(); - assertTrue(Iterators.elementsEqual(a.iterator(), b.iterator())); + a = new ArrayList<>(); + b = emptySet(); + assertTrue(elementsEqual(a.iterator(), b.iterator())); // A few elements. a = asList(4, 8, 15, 16, 23, 42); b = asList(4, 8, 15, 16, 23, 42); - assertTrue(Iterators.elementsEqual(a.iterator(), b.iterator())); + assertTrue(elementsEqual(a.iterator(), b.iterator())); // The same, but with nulls. - a = asList(4, 8, null, 16, 23, 42); - b = asList(4, 8, null, 16, 23, 42); - assertTrue(Iterators.elementsEqual(a.iterator(), b.iterator())); + a = Arrays.<@Nullable Integer>asList(4, 8, null, 16, 23, 42); + b = Arrays.<@Nullable Integer>asList(4, 8, null, 16, 23, 42); + assertTrue(elementsEqual(a.iterator(), b.iterator())); // Different Iterable types (still equal elements, though). a = ImmutableList.of(4, 8, 15, 16, 23, 42); b = asList(4, 8, 15, 16, 23, 42); - assertTrue(Iterators.elementsEqual(a.iterator(), b.iterator())); + assertTrue(elementsEqual(a.iterator(), b.iterator())); // An element differs. a = asList(4, 8, 15, 12, 23, 42); b = asList(4, 8, 15, 16, 23, 42); - assertFalse(Iterators.elementsEqual(a.iterator(), b.iterator())); + assertFalse(elementsEqual(a.iterator(), b.iterator())); // null versus non-null. - a = asList(4, 8, 15, null, 23, 42); + a = Arrays.<@Nullable Integer>asList(4, 8, 15, null, 23, 42); b = asList(4, 8, 15, 16, 23, 42); - assertFalse(Iterators.elementsEqual(a.iterator(), b.iterator())); - assertFalse(Iterators.elementsEqual(b.iterator(), a.iterator())); + assertFalse(elementsEqual(a.iterator(), b.iterator())); + assertFalse(elementsEqual(b.iterator(), a.iterator())); // Different lengths. a = asList(4, 8, 15, 16, 23); b = asList(4, 8, 15, 16, 23, 42); - assertFalse(Iterators.elementsEqual(a.iterator(), b.iterator())); - assertFalse(Iterators.elementsEqual(b.iterator(), a.iterator())); + assertFalse(elementsEqual(a.iterator(), b.iterator())); + assertFalse(elementsEqual(b.iterator(), a.iterator())); // Different lengths, one is empty. - a = Collections.emptySet(); + a = emptySet(); b = asList(4, 8, 15, 16, 23, 42); - assertFalse(Iterators.elementsEqual(a.iterator(), b.iterator())); - assertFalse(Iterators.elementsEqual(b.iterator(), a.iterator())); + assertFalse(elementsEqual(a.iterator(), b.iterator())); + assertFalse(elementsEqual(b.iterator(), a.iterator())); } public void testPartition_badSize() { - Iterator source = Iterators.singletonIterator(1); - try { - Iterators.partition(source, 0); - fail(); - } catch (IllegalArgumentException expected) { - } + Iterator source = singletonIterator(1); + assertThrows(IllegalArgumentException.class, () -> Iterators.partition(source, 0)); } public void testPartition_empty() { - Iterator source = Iterators.emptyIterator(); + Iterator source = emptyIterator(); Iterator> partitions = Iterators.partition(source, 1); assertFalse(partitions.hasNext()); } public void testPartition_singleton1() { - Iterator source = Iterators.singletonIterator(1); + Iterator source = singletonIterator(1); Iterator> partitions = Iterators.partition(source, 1); assertTrue(partitions.hasNext()); assertTrue(partitions.hasNext()); @@ -993,7 +931,7 @@ public void testPartition_singleton1() { } public void testPartition_singleton2() { - Iterator source = Iterators.singletonIterator(1); + Iterator source = singletonIterator(1); Iterator> partitions = Iterators.partition(source, 2); assertTrue(partitions.hasNext()); assertTrue(partitions.hasNext()); @@ -1030,8 +968,8 @@ public void testPartition_view() { assertEquals(ImmutableList.of(3), first); } - @GwtIncompatible // ? - // TODO: Figure out why this is failing in GWT. + @J2ktIncompatible // Arrays.asList(...).subList() doesn't implement RandomAccess in J2KT. + @GwtIncompatible // Arrays.asList(...).subList() doesn't implement RandomAccess in GWT public void testPartitionRandomAccess() { Iterator source = asList(1, 2, 3).iterator(); Iterator> partitions = Iterators.partition(source, 2); @@ -1040,22 +978,18 @@ public void testPartitionRandomAccess() { } public void testPaddedPartition_badSize() { - Iterator source = Iterators.singletonIterator(1); - try { - Iterators.paddedPartition(source, 0); - fail(); - } catch (IllegalArgumentException expected) { - } + Iterator source = singletonIterator(1); + assertThrows(IllegalArgumentException.class, () -> Iterators.paddedPartition(source, 0)); } public void testPaddedPartition_empty() { - Iterator source = Iterators.emptyIterator(); + Iterator source = emptyIterator(); Iterator> partitions = Iterators.paddedPartition(source, 1); assertFalse(partitions.hasNext()); } public void testPaddedPartition_singleton1() { - Iterator source = Iterators.singletonIterator(1); + Iterator source = singletonIterator(1); Iterator> partitions = Iterators.paddedPartition(source, 1); assertTrue(partitions.hasNext()); assertTrue(partitions.hasNext()); @@ -1064,21 +998,21 @@ public void testPaddedPartition_singleton1() { } public void testPaddedPartition_singleton2() { - Iterator source = Iterators.singletonIterator(1); + Iterator source = singletonIterator(1); Iterator> partitions = Iterators.paddedPartition(source, 2); assertTrue(partitions.hasNext()); assertTrue(partitions.hasNext()); - assertEquals(asList(1, null), partitions.next()); + assertEquals(Arrays.<@Nullable Integer>asList(1, null), partitions.next()); assertFalse(partitions.hasNext()); } @GwtIncompatible // fairly slow (~50s) public void testPaddedPartition_general() { + ImmutableList> expectedElements = + ImmutableList.of( + asList(1, 2, 3), asList(4, 5, 6), Arrays.<@Nullable Integer>asList(7, null, null)); new IteratorTester>( - 5, - IteratorFeature.UNMODIFIABLE, - ImmutableList.of(asList(1, 2, 3), asList(4, 5, 6), asList(7, null, null)), - IteratorTester.KnownOrder.KNOWN_ORDER) { + 5, IteratorFeature.UNMODIFIABLE, expectedElements, IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator> newTargetIterator() { Iterator source = Iterators.forArray(1, 2, 3, 4, 5, 6, 7); @@ -1112,63 +1046,38 @@ public void testForArrayEmpty() { String[] array = new String[0]; Iterator iterator = Iterators.forArray(array); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); + assertThrows(IndexOutOfBoundsException.class, () -> Iterators.forArrayWithPosition(array, 1)); } + @SuppressWarnings("DoNotCall") public void testForArrayTypical() { String[] array = {"foo", "bar"}; Iterator iterator = Iterators.forArray(array); assertTrue(iterator.hasNext()); assertEquals("foo", iterator.next()); assertTrue(iterator.hasNext()); - try { - iterator.remove(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); assertEquals("bar", iterator.next()); assertFalse(iterator.hasNext()); - try { - iterator.next(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> iterator.next()); } - public void testForArrayOffset() { - String[] array = {"foo", "bar", "cat", "dog"}; - Iterator iterator = Iterators.forArray(array, 1, 2, 0); + public void testForArrayWithPosition() { + String[] array = {"foo", "bar", "cat"}; + Iterator iterator = Iterators.forArrayWithPosition(array, 1); assertTrue(iterator.hasNext()); assertEquals("bar", iterator.next()); assertTrue(iterator.hasNext()); assertEquals("cat", iterator.next()); assertFalse(iterator.hasNext()); - try { - Iterators.forArray(array, 2, 3, 0); - fail(); - } catch (IndexOutOfBoundsException expected) { - } } - public void testForArrayLength0() { + public void testForArrayLengthWithPositionBoundaryCases() { String[] array = {"foo", "bar"}; - assertFalse(Iterators.forArray(array, 0, 0, 0).hasNext()); - assertFalse(Iterators.forArray(array, 1, 0, 0).hasNext()); - assertFalse(Iterators.forArray(array, 2, 0, 0).hasNext()); - try { - Iterators.forArray(array, -1, 0, 0); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - Iterators.forArray(array, 3, 0, 0); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertFalse(Iterators.forArrayWithPosition(array, 2).hasNext()); + assertThrows(IndexOutOfBoundsException.class, () -> Iterators.forArrayWithPosition(array, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> Iterators.forArrayWithPosition(array, 3)); } @GwtIncompatible // unreasonably slow @@ -1182,29 +1091,20 @@ protected Iterator newTargetIterator() { }.test(); } - @GwtIncompatible // unreasonably slow - public void testForArrayWithOffsetUsingTester() { - new IteratorTester( - 6, UNMODIFIABLE, asList(1, 2, 3), IteratorTester.KnownOrder.KNOWN_ORDER) { - @Override - protected Iterator newTargetIterator() { - return Iterators.forArray(new Integer[] {0, 1, 2, 3, 4}, 1, 3, 0); - } - }.test(); - } + /* + * TODO(cpovirk): Test forArray with ListIteratorTester (not just IteratorTester), including with + * a start position other than 0. + */ public void testForEnumerationEmpty() { Enumeration enumer = enumerate(); Iterator iter = Iterators.forEnumeration(enumer); assertFalse(iter.hasNext()); - try { - iter.next(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> iter.next()); } + @SuppressWarnings("DoNotCall") public void testForEnumerationSingleton() { Enumeration enumer = enumerate(1); Iterator iter = Iterators.forEnumeration(enumer); @@ -1212,17 +1112,9 @@ public void testForEnumerationSingleton() { assertTrue(iter.hasNext()); assertTrue(iter.hasNext()); assertEquals(1, (int) iter.next()); - try { - iter.remove(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iter.remove()); assertFalse(iter.hasNext()); - try { - iter.next(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> iter.next()); } public void testForEnumerationTypical() { @@ -1239,15 +1131,11 @@ public void testForEnumerationTypical() { } public void testAsEnumerationEmpty() { - Iterator iter = Iterators.emptyIterator(); + Iterator iter = emptyIterator(); Enumeration enumer = Iterators.asEnumeration(iter); assertFalse(enumer.hasMoreElements()); - try { - enumer.nextElement(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> enumer.nextElement()); } public void testAsEnumerationSingleton() { @@ -1258,11 +1146,7 @@ public void testAsEnumerationSingleton() { assertTrue(enumer.hasMoreElements()); assertEquals(1, (int) enumer.nextElement()); assertFalse(enumer.hasMoreElements()); - try { - enumer.nextElement(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> enumer.nextElement()); } public void testAsEnumerationTypical() { @@ -1278,8 +1162,10 @@ public void testAsEnumerationTypical() { assertFalse(enumer.hasMoreElements()); } - private static Enumeration enumerate(Integer... ints) { - Vector vector = new Vector<>(asList(ints)); + // We're testing our asEnumeration method against a known-good implementation. + @SuppressWarnings("JdkObsolete") + private static Enumeration enumerate(int... ints) { + Vector vector = new Vector<>(Ints.asList(ints)); return vector.elements(); } @@ -1289,7 +1175,8 @@ public void testToString() { } public void testToStringWithNull() { - Iterator iterator = Lists.newArrayList("hello", null, "world").iterator(); + Iterator<@Nullable String> iterator = + Lists.<@Nullable String>newArrayList("hello", null, "world").iterator(); assertEquals("[hello, null, world]", Iterators.toString(iterator)); } @@ -1298,13 +1185,10 @@ public void testToStringEmptyIterator() { assertEquals("[]", Iterators.toString(iterator)); } + @SuppressWarnings("JUnitIncompatibleType") // Fails with j2kt. public void testLimit() { - List list = newArrayList(); - try { - Iterators.limit(list.iterator(), -1); - fail("expected exception"); - } catch (IllegalArgumentException expected) { - } + List list = new ArrayList<>(); + assertThrows(IllegalArgumentException.class, () -> Iterators.limit(list.iterator(), -1)); assertFalse(Iterators.limit(list.iterator(), 0).hasNext()); assertFalse(Iterators.limit(list.iterator(), 1).hasNext()); @@ -1322,7 +1206,7 @@ public void testLimit() { } public void testLimitRemove() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("cool"); list.add("pants"); Iterator iterator = Iterators.limit(list.iterator(), 1); @@ -1335,29 +1219,29 @@ public void testLimitRemove() { @GwtIncompatible // fairly slow (~30s) public void testLimitUsingIteratorTester() { - final List list = Lists.newArrayList(1, 2, 3, 4, 5); + List list = Lists.newArrayList(1, 2, 3, 4, 5); new IteratorTester( 5, MODIFIABLE, newArrayList(1, 2, 3), IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { - return Iterators.limit(Lists.newArrayList(list).iterator(), 3); + return Iterators.limit(new ArrayList<>(list).iterator(), 3); } }.test(); } public void testGetNext_withDefault_singleton() { - Iterator iterator = Collections.singletonList("foo").iterator(); + Iterator iterator = singletonList("foo").iterator(); assertEquals("foo", Iterators.getNext(iterator, "bar")); } public void testGetNext_withDefault_empty() { - Iterator iterator = Iterators.emptyIterator(); + Iterator iterator = emptyIterator(); assertEquals("bar", Iterators.getNext(iterator, "bar")); } public void testGetNext_withDefault_empty_null() { - Iterator iterator = Iterators.emptyIterator(); - assertNull(Iterators.getNext(iterator, null)); + Iterator iterator = emptyIterator(); + assertThat(Iterators.<@Nullable String>getNext(iterator, null)).isNull(); } public void testGetNext_withDefault_two() { @@ -1366,34 +1250,30 @@ public void testGetNext_withDefault_two() { } public void testGetLast_basic() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); assertEquals("b", getLast(list.iterator())); } public void testGetLast_exception() { - List list = newArrayList(); - try { - getLast(list.iterator()); - fail(); - } catch (NoSuchElementException expected) { - } + List list = new ArrayList<>(); + assertThrows(NoSuchElementException.class, () -> getLast(list.iterator())); } public void testGetLast_withDefault_singleton() { - Iterator iterator = Collections.singletonList("foo").iterator(); + Iterator iterator = singletonList("foo").iterator(); assertEquals("foo", Iterators.getLast(iterator, "bar")); } public void testGetLast_withDefault_empty() { - Iterator iterator = Iterators.emptyIterator(); + Iterator iterator = emptyIterator(); assertEquals("bar", Iterators.getLast(iterator, "bar")); } public void testGetLast_withDefault_empty_null() { - Iterator iterator = Iterators.emptyIterator(); - assertNull(Iterators.getLast(iterator, null)); + Iterator iterator = emptyIterator(); + assertThat(Iterators.<@Nullable String>getLast(iterator, null)).isNull(); } public void testGetLast_withDefault_two() { @@ -1402,7 +1282,7 @@ public void testGetLast_withDefault_two() { } public void testGet_basic() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); @@ -1411,54 +1291,38 @@ public void testGet_basic() { } public void testGet_atSize() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); - try { - get(iterator, 2); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> get(iterator, 2)); assertFalse(iterator.hasNext()); } public void testGet_pastEnd() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); - try { - get(iterator, 5); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> get(iterator, 5)); assertFalse(iterator.hasNext()); } public void testGet_empty() { - List list = newArrayList(); + List list = new ArrayList<>(); Iterator iterator = list.iterator(); - try { - get(iterator, 0); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> get(iterator, 0)); assertFalse(iterator.hasNext()); } public void testGet_negativeIndex() { List list = newArrayList("a", "b", "c"); Iterator iterator = list.iterator(); - try { - get(iterator, -1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> get(iterator, -1)); } public void testGet_withDefault_basic() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); @@ -1467,7 +1331,7 @@ public void testGet_withDefault_basic() { } public void testGet_withDefault_atSize() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); @@ -1476,7 +1340,7 @@ public void testGet_withDefault_atSize() { } public void testGet_withDefault_pastEnd() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); @@ -1485,21 +1349,16 @@ public void testGet_withDefault_pastEnd() { } public void testGet_withDefault_negativeIndex() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); - try { - get(iterator, -1, "c"); - fail(); - } catch (IndexOutOfBoundsException expected) { - // pass - } + assertThrows(IndexOutOfBoundsException.class, () -> get(iterator, -1, "c")); assertTrue(iterator.hasNext()); } public void testAdvance_basic() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); @@ -1508,7 +1367,7 @@ public void testAdvance_basic() { } public void testAdvance_pastEnd() { - List list = newArrayList(); + List list = new ArrayList<>(); list.add("a"); list.add("b"); Iterator iterator = list.iterator(); @@ -1519,20 +1378,16 @@ public void testAdvance_pastEnd() { public void testAdvance_illegalArgument() { List list = newArrayList("a", "b", "c"); Iterator iterator = list.iterator(); - try { - advance(iterator, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> advance(iterator, -1)); } public void testFrequency() { - List list = newArrayList("a", null, "b", null, "a", null); - assertEquals(2, Iterators.frequency(list.iterator(), "a")); - assertEquals(1, Iterators.frequency(list.iterator(), "b")); - assertEquals(0, Iterators.frequency(list.iterator(), "c")); - assertEquals(0, Iterators.frequency(list.iterator(), 4.2)); - assertEquals(3, Iterators.frequency(list.iterator(), null)); + List<@Nullable String> list = newArrayList("a", null, "b", null, "a", null); + assertEquals(2, frequency(list.iterator(), "a")); + assertEquals(1, frequency(list.iterator(), "b")); + assertEquals(0, frequency(list.iterator(), "c")); + assertEquals(0, frequency(list.iterator(), 4.2)); + assertEquals(3, frequency(list.iterator(), null)); } @GwtIncompatible // slow (~4s) @@ -1541,7 +1396,7 @@ public void testSingletonIterator() { 3, UNMODIFIABLE, singleton(1), IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { - return Iterators.singletonIterator(1); + return singletonIterator(1); } }.test(); } @@ -1586,13 +1441,15 @@ public void testRetainAll() { assertEquals(newArrayList("b", "d"), list); } + @J2ktIncompatible @GwtIncompatible // ListTestSuiteBuilder + @AndroidIncompatible // test-suite builders private static Test testsForRemoveAllAndRetainAll() { return ListTestSuiteBuilder.using( new TestStringListGenerator() { @Override - public List create(final String[] elements) { - final List delegate = newArrayList(elements); + public List create(String[] elements) { + List delegate = newArrayList(elements); return new ForwardingList() { @Override protected List delegate() { @@ -1651,24 +1508,19 @@ public void testConsumingIterator_duelingIterators() { Iterator i2 = Iterators.consumingIterator(list.iterator()); i1.next(); - try { - i2.next(); - fail("Concurrent modification should throw an exception."); - } catch (ConcurrentModificationException cme) { - // Pass - } + assertThrows(ConcurrentModificationException.class, () -> i2.next()); } public void testIndexOf_consumedData() { Iterator iterator = Lists.newArrayList("manny", "mo", "jack").iterator(); - assertEquals(1, Iterators.indexOf(iterator, Predicates.equalTo("mo"))); + assertEquals(1, Iterators.indexOf(iterator, equalTo("mo"))); assertEquals("jack", iterator.next()); assertFalse(iterator.hasNext()); } public void testIndexOf_consumedDataWithDuplicates() { Iterator iterator = Lists.newArrayList("manny", "mo", "mo", "jack").iterator(); - assertEquals(1, Iterators.indexOf(iterator, Predicates.equalTo("mo"))); + assertEquals(1, Iterators.indexOf(iterator, equalTo("mo"))); assertEquals("mo", iterator.next()); assertEquals("jack", iterator.next()); assertFalse(iterator.hasNext()); @@ -1676,11 +1528,11 @@ public void testIndexOf_consumedDataWithDuplicates() { public void testIndexOf_consumedDataNoMatch() { Iterator iterator = Lists.newArrayList("manny", "mo", "mo", "jack").iterator(); - assertEquals(-1, Iterators.indexOf(iterator, Predicates.equalTo("bob"))); + assertEquals(-1, Iterators.indexOf(iterator, equalTo("bob"))); assertFalse(iterator.hasNext()); } - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testUnmodifiableIteratorShortCircuit() { Iterator mod = Lists.newArrayList("a", "b", "c").iterator(); UnmodifiableIterator unmod = Iterators.unmodifiableIterator(mod); @@ -1689,12 +1541,84 @@ public void testUnmodifiableIteratorShortCircuit() { assertSame(unmod, Iterators.unmodifiableIterator((Iterator) unmod)); } - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testPeekingIteratorShortCircuit() { Iterator nonpeek = Lists.newArrayList("a", "b", "c").iterator(); - PeekingIterator peek = Iterators.peekingIterator(nonpeek); + PeekingIterator peek = peekingIterator(nonpeek); assertNotSame(peek, nonpeek); - assertSame(peek, Iterators.peekingIterator(peek)); - assertSame(peek, Iterators.peekingIterator((Iterator) peek)); + assertSame(peek, peekingIterator(peek)); + assertSame(peek, peekingIterator((Iterator) peek)); + } + + public void testMergeSorted_stable_issue5773Example() { + ImmutableList left = ImmutableList.of(new TestDatum("B", 1), new TestDatum("C", 1)); + ImmutableList right = ImmutableList.of(new TestDatum("A", 2), new TestDatum("C", 2)); + + Comparator comparator = Comparator.comparing(d -> d.letter); + + // When elements compare as equal (both C's have same letter), our merge should always return C1 + // before C2, since C1 is from the first iterator. + + Iterator merged = + Iterators.mergeSorted(ImmutableList.of(left.iterator(), right.iterator()), comparator); + + ImmutableList result = ImmutableList.copyOf(merged); + + assertThat(result) + .containsExactly( + new TestDatum("A", 2), + new TestDatum("B", 1), + new TestDatum("C", 1), + new TestDatum("C", 2)) + .inOrder(); + } + + public void testMergeSorted_stable_allEqual() { + ImmutableList first = ImmutableList.of(new TestDatum("A", 1), new TestDatum("A", 2)); + ImmutableList second = + ImmutableList.of(new TestDatum("A", 3), new TestDatum("A", 4)); + + Comparator comparator = Comparator.comparing(d -> d.letter); + Iterator merged = + Iterators.mergeSorted(ImmutableList.of(first.iterator(), second.iterator()), comparator); + + ImmutableList result = ImmutableList.copyOf(merged); + + assertThat(result) + .containsExactly( + new TestDatum("A", 1), + new TestDatum("A", 2), + new TestDatum("A", 3), + new TestDatum("A", 4)) + .inOrder(); + } + + private static final class TestDatum { + final String letter; + final int number; + + TestDatum(String letter, int number) { + this.letter = letter; + this.number = number; + } + + @Override + public String toString() { + return letter + number; + } + + @Override + public boolean equals(@Nullable Object o) { + if (!(o instanceof TestDatum)) { + return false; + } + TestDatum other = (TestDatum) o; + return letter.equals(other.letter) && number == other.number; + } + + @Override + public int hashCode() { + return Objects.hash(letter, number); + } } } diff --git a/android/guava-tests/test/com/google/common/collect/LegacyComparable.java b/android/guava-tests/test/com/google/common/collect/LegacyComparable.java index 8b0c9620cd68..941471b7a2f3 100644 --- a/android/guava-tests/test/com/google/common/collect/LegacyComparable.java +++ b/android/guava-tests/test/com/google/common/collect/LegacyComparable.java @@ -16,9 +16,14 @@ package com.google.common.collect; +import static java.util.Arrays.asList; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; -import java.util.Arrays; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A class that implements {@code Comparable} without generics, such as those found in libraries @@ -27,15 +32,16 @@ * * @author Kevin Bourrillion */ -@SuppressWarnings("ComparableType") +@SuppressWarnings({"ComparableType", "rawtypes"}) // https://github.com/google/guava/issues/989 @GwtCompatible +@NullMarked class LegacyComparable implements Comparable, Serializable { static final LegacyComparable X = new LegacyComparable("x"); static final LegacyComparable Y = new LegacyComparable("y"); static final LegacyComparable Z = new LegacyComparable("z"); - static final Iterable VALUES_FORWARD = Arrays.asList(X, Y, Z); - static final Iterable VALUES_BACKWARD = Arrays.asList(Z, Y, X); + static final Iterable VALUES_FORWARD = asList(X, Y, Z); + static final Iterable VALUES_BACKWARD = asList(Z, Y, X); private final String value; @@ -51,7 +57,7 @@ public int compareTo(Object object) { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof LegacyComparable) { LegacyComparable that = (LegacyComparable) object; return this.value.equals(that.value); @@ -64,5 +70,5 @@ public int hashCode() { return value.hashCode(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava-tests/test/com/google/common/collect/LenientSerializableTester.java b/android/guava-tests/test/com/google/common/collect/LenientSerializableTester.java index ce3ec9d9f1fa..1862a315db93 100644 --- a/android/guava-tests/test/com/google/common/collect/LenientSerializableTester.java +++ b/android/guava-tests/test/com/google/common/collect/LenientSerializableTester.java @@ -16,6 +16,7 @@ package com.google.common.collect; +import static com.google.common.collect.Iterables.elementsEqual; import static com.google.common.testing.SerializableTester.reserialize; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; @@ -26,6 +27,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; /** * Variant of {@link SerializableTester} that does not require the reserialized object's class to be @@ -37,7 +39,8 @@ * The whole thing is really @GwtIncompatible, but GwtJUnitConvertedTestModule doesn't have a * parameter for non-GWT, non-test files, and it didn't seem worth adding one for this unusual case. */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked final class LenientSerializableTester { /* * TODO(cpovirk): move this to c.g.c.testing if we allow for c.g.c.annotations dependencies so @@ -65,7 +68,7 @@ static Multiset reserializeAndAssertLenient(Multiset original) { @GwtIncompatible // SerializableTester static Collection reserializeAndAssertElementsEqual(Collection original) { Collection copy = reserialize(original); - assertTrue(Iterables.elementsEqual(original, copy)); + assertTrue(elementsEqual(original, copy)); assertTrue(copy instanceof ImmutableCollection); return copy; } diff --git a/android/guava-tests/test/com/google/common/collect/LinkedHashMultimapTest.java b/android/guava-tests/test/com/google/common/collect/LinkedHashMultimapTest.java index 92d8e052a8d0..2b8fd4336a3f 100644 --- a/android/guava-tests/test/com/google/common/collect/LinkedHashMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/LinkedHashMultimapTest.java @@ -17,8 +17,10 @@ package com.google.common.collect; import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Multimaps.synchronizedMultimap; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.Sets.newHashSet; -import static com.google.common.collect.Sets.newLinkedHashSet; import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.truth.Truth.assertThat; @@ -26,6 +28,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.IteratorTester; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -34,25 +37,32 @@ import com.google.common.collect.testing.google.TestStringSetMultimapGenerator; import com.google.common.testing.EqualsTester; import com.google.common.testing.SerializableTester; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit tests for {@code LinkedHashMultimap}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class LinkedHashMultimapTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -100,8 +110,8 @@ public void testValueSetHashTableExpansion() { } } - private Multimap initializeMultimap5() { - Multimap multimap = LinkedHashMultimap.create(); + private SetMultimap initializeMultimap5() { + SetMultimap multimap = LinkedHashMultimap.create(); multimap.put("foo", 5); multimap.put("bar", 4); multimap.put("foo", 3); @@ -111,40 +121,43 @@ private Multimap initializeMultimap5() { } public void testToString() { - Multimap multimap = LinkedHashMultimap.create(); + SetMultimap multimap = LinkedHashMultimap.create(); multimap.put("foo", 3); multimap.put("bar", 1); - multimap.putAll("foo", Arrays.asList(-1, 2, 4)); - multimap.putAll("bar", Arrays.asList(2, 3)); + multimap.putAll("foo", asList(-1, 2, 4)); + multimap.putAll("bar", asList(2, 3)); multimap.put("foo", 1); assertEquals("{foo=[3, -1, 2, 4, 1], bar=[1, 2, 3]}", multimap.toString()); } public void testOrderingReadOnly() { - Multimap multimap = initializeMultimap5(); + SetMultimap multimap = initializeMultimap5(); assertOrderingReadOnly(multimap); } public void testOrderingUnmodifiable() { - Multimap multimap = initializeMultimap5(); + SetMultimap multimap = initializeMultimap5(); assertOrderingReadOnly(Multimaps.unmodifiableMultimap(multimap)); } + @J2ktIncompatible // Synchronized public void testOrderingSynchronized() { - Multimap multimap = initializeMultimap5(); - assertOrderingReadOnly(Multimaps.synchronizedMultimap(multimap)); + SetMultimap multimap = initializeMultimap5(); + assertOrderingReadOnly(synchronizedMultimap(multimap)); } + @J2ktIncompatible @GwtIncompatible // SeriazableTester public void testSerializationOrdering() { - Multimap multimap = initializeMultimap5(); - Multimap copy = SerializableTester.reserializeAndAssert(multimap); + SetMultimap multimap = initializeMultimap5(); + SetMultimap copy = SerializableTester.reserializeAndAssert(multimap); assertOrderingReadOnly(copy); } + @J2ktIncompatible @GwtIncompatible // SeriazableTester public void testSerializationOrderingKeysAndEntries() { - Multimap multimap = LinkedHashMultimap.create(); + SetMultimap multimap = LinkedHashMultimap.create(); multimap.put("a", 1); multimap.put("b", 2); multimap.put("a", 3); @@ -167,11 +180,11 @@ private void assertOrderingReadOnly(Multimap multimap) { assertThat(multimap.values()).containsExactly(5, 4, 3, 2, 1).inOrder(); Iterator> entryIterator = multimap.entries().iterator(); - assertEquals(Maps.immutableEntry("foo", 5), entryIterator.next()); - assertEquals(Maps.immutableEntry("bar", 4), entryIterator.next()); - assertEquals(Maps.immutableEntry("foo", 3), entryIterator.next()); - assertEquals(Maps.immutableEntry("cow", 2), entryIterator.next()); - assertEquals(Maps.immutableEntry("bar", 1), entryIterator.next()); + assertEquals(immutableEntry("foo", 5), entryIterator.next()); + assertEquals(immutableEntry("bar", 4), entryIterator.next()); + assertEquals(immutableEntry("foo", 3), entryIterator.next()); + assertEquals(immutableEntry("cow", 2), entryIterator.next()); + assertEquals(immutableEntry("bar", 1), entryIterator.next()); Iterator>> collectionIterator = multimap.asMap().entrySet().iterator(); @@ -187,7 +200,7 @@ private void assertOrderingReadOnly(Multimap multimap) { } public void testOrderingUpdates() { - Multimap multimap = initializeMultimap5(); + SetMultimap multimap = initializeMultimap5(); assertThat(multimap.replaceValues("foo", asList(6, 7))).containsExactly(5, 3).inOrder(); assertThat(multimap.keySet()).containsExactly("foo", "bar", "cow").inOrder(); @@ -202,7 +215,7 @@ public void testOrderingUpdates() { } public void testToStringNullExact() { - Multimap multimap = LinkedHashMultimap.create(); + SetMultimap<@Nullable String, @Nullable Integer> multimap = LinkedHashMultimap.create(); multimap.put("foo", 3); multimap.put("foo", -1); @@ -225,13 +238,13 @@ public void testToStringNullExact() { } public void testPutMultimapOrdered() { - Multimap multimap = LinkedHashMultimap.create(); + SetMultimap multimap = LinkedHashMultimap.create(); multimap.putAll(initializeMultimap5()); assertOrderingReadOnly(multimap); } public void testKeysToString_ordering() { - Multimap multimap = initializeMultimap5(); + SetMultimap multimap = initializeMultimap5(); assertEquals("[foo x 2, bar x 2, cow]", multimap.keys().toString()); } @@ -244,7 +257,7 @@ public void testCreate() { } public void testCreateFromMultimap() { - Multimap multimap = LinkedHashMultimap.create(); + SetMultimap multimap = LinkedHashMultimap.create(); multimap.put("a", 1); multimap.put("b", 2); multimap.put("a", 3); @@ -262,17 +275,9 @@ public void testCreateFromSizes() { } public void testCreateFromIllegalSizes() { - try { - LinkedHashMultimap.create(-20, 15); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LinkedHashMultimap.create(-20, 15)); - try { - LinkedHashMultimap.create(20, -15); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LinkedHashMultimap.create(20, -15)); } @GwtIncompatible // unreasonably slow @@ -280,9 +285,9 @@ public void testGetIteration() { new IteratorTester( 6, MODIFIABLE, - newLinkedHashSet(asList(2, 3, 4, 7, 8)), + new LinkedHashSet<>(asList(2, 3, 4, 7, 8)), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable SetMultimap multimap; @Override protected Iterator newTargetIterator() { @@ -295,26 +300,25 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(newHashSet(elements), multimap.get("foo")); + assertEquals(new HashSet<>(elements), multimap.get("foo")); } }.test(); } @GwtIncompatible // unreasonably slow public void testEntriesIteration() { - @SuppressWarnings("unchecked") Set> set = - Sets.newLinkedHashSet( + new LinkedHashSet<>( asList( - Maps.immutableEntry("foo", 2), - Maps.immutableEntry("foo", 3), - Maps.immutableEntry("bar", 4), - Maps.immutableEntry("bar", 5), - Maps.immutableEntry("foo", 6))); + immutableEntry("foo", 2), + immutableEntry("foo", 3), + immutableEntry("bar", 4), + immutableEntry("bar", 5), + immutableEntry("foo", 6))); new IteratorTester>( 6, MODIFIABLE, set, IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable SetMultimap multimap; @Override protected Iterator> newTargetIterator() { @@ -327,7 +331,7 @@ protected Iterator> newTargetIterator() { @Override protected void verify(List> elements) { - assertEquals(newHashSet(elements), multimap.entries()); + assertEquals(new HashSet<>(elements), multimap.entries()); } }.test(); } @@ -339,7 +343,7 @@ public void testKeysIteration() { MODIFIABLE, newArrayList("foo", "foo", "bar", "bar", "foo"), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable SetMultimap multimap; @Override protected Iterator newTargetIterator() { @@ -352,7 +356,7 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(elements, Lists.newArrayList(multimap.keys())); + assertEquals(elements, new ArrayList<>(multimap.keys())); } }.test(); } @@ -361,7 +365,7 @@ protected void verify(List elements) { public void testValuesIteration() { new IteratorTester( 6, MODIFIABLE, newArrayList(2, 3, 4, 5, 6), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable SetMultimap multimap; @Override protected Iterator newTargetIterator() { @@ -374,7 +378,7 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(elements, Lists.newArrayList(multimap.values())); + assertEquals(elements, new ArrayList<>(multimap.values())); } }.test(); } @@ -384,9 +388,9 @@ public void testKeySetIteration() { new IteratorTester( 6, MODIFIABLE, - newLinkedHashSet(asList("foo", "bar", "baz", "dog", "cat")), + new LinkedHashSet<>(asList("foo", "bar", "baz", "dog", "cat")), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable SetMultimap multimap; @Override protected Iterator newTargetIterator() { @@ -403,25 +407,24 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(newHashSet(elements), multimap.keySet()); + assertEquals(new HashSet<>(elements), multimap.keySet()); } }.test(); } @GwtIncompatible // unreasonably slow public void testAsSetIteration() { - @SuppressWarnings("unchecked") Set>> set = - newLinkedHashSet( + new LinkedHashSet<>( asList( - Maps.immutableEntry("foo", (Collection) Sets.newHashSet(2, 3, 6)), - Maps.immutableEntry("bar", (Collection) Sets.newHashSet(4, 5, 10, 11)), - Maps.immutableEntry("baz", (Collection) Sets.newHashSet(7, 8)), - Maps.immutableEntry("dog", (Collection) Sets.newHashSet(9)), - Maps.immutableEntry("cat", (Collection) Sets.newHashSet(12, 13, 14)))); + immutableEntry("foo", (Collection) newHashSet(2, 3, 6)), + immutableEntry("bar", (Collection) newHashSet(4, 5, 10, 11)), + immutableEntry("baz", (Collection) newHashSet(7, 8)), + immutableEntry("dog", (Collection) newHashSet(9)), + immutableEntry("cat", (Collection) newHashSet(12, 13, 14)))); new IteratorTester>>( 6, MODIFIABLE, set, IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable SetMultimap multimap; @Override protected Iterator>> newTargetIterator() { @@ -438,7 +441,7 @@ protected Iterator>> newTargetIterator() { @Override protected void verify(List>> elements) { - assertEquals(newHashSet(elements), multimap.asMap().entrySet()); + assertEquals(new HashSet<>(elements), multimap.asMap().entrySet()); } }.test(); } diff --git a/android/guava-tests/test/com/google/common/collect/LinkedHashMultisetTest.java b/android/guava-tests/test/com/google/common/collect/LinkedHashMultisetTest.java index da7e86867d10..cb1595b59963 100644 --- a/android/guava-tests/test/com/google/common/collect/LinkedHashMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/LinkedHashMultisetTest.java @@ -21,26 +21,31 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.google.MultisetFeature; import com.google.common.collect.testing.google.MultisetTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringMultisetGenerator; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link LinkedHashMultiset}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class LinkedHashMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -59,6 +64,8 @@ public static Test suite() { return suite; } + @J2ktIncompatible + @AndroidIncompatible // test-suite builders private static TestStringMultisetGenerator linkedHashMultisetGenerator() { return new TestStringMultisetGenerator() { @Override @@ -68,7 +75,7 @@ protected Multiset create(String[] elements) { @Override public List order(List insertionOrder) { - List order = Lists.newArrayList(); + List order = new ArrayList<>(); for (String s : insertionOrder) { int index = order.indexOf(s); if (index == -1) { @@ -101,7 +108,7 @@ public void testCreateWithSize() { } public void testCreateFromIterable() { - Multiset multiset = LinkedHashMultiset.create(Arrays.asList("foo", "bar", "foo")); + Multiset multiset = LinkedHashMultiset.create(asList("foo", "bar", "foo")); assertEquals(3, multiset.size()); assertEquals(2, multiset.count("foo")); assertEquals("[foo x 2, bar]", multiset.toString()); diff --git a/android/guava-tests/test/com/google/common/collect/LinkedListMultimapTest.java b/android/guava-tests/test/com/google/common/collect/LinkedListMultimapTest.java index d57d5c044c87..e8451e410a5b 100644 --- a/android/guava-tests/test/com/google/common/collect/LinkedListMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/LinkedListMultimapTest.java @@ -17,16 +17,18 @@ package com.google.common.collect; import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Sets.newHashSet; -import static com.google.common.collect.Sets.newLinkedHashSet; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.collect.testing.IteratorFeature.SUPPORTS_REMOVE; import static com.google.common.collect.testing.IteratorFeature.SUPPORTS_SET; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.IteratorTester; import com.google.common.collect.testing.ListIteratorTester; import com.google.common.collect.testing.features.CollectionFeature; @@ -35,10 +37,13 @@ import com.google.common.collect.testing.google.ListMultimapTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringListMultimapGenerator; import com.google.common.testing.EqualsTester; +import com.google.common.testing.NullPointerTester; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map.Entry; @@ -47,16 +52,21 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code LinkedListMultimap}. * * @author Mike Bostock */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class LinkedListMultimapTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -119,8 +129,8 @@ public void testReplaceValuesRandomAccess() { Multimap multimap = create(); multimap.put("foo", 1); multimap.put("foo", 3); - assertTrue(multimap.replaceValues("foo", Arrays.asList(2, 4)) instanceof RandomAccess); - assertTrue(multimap.replaceValues("bar", Arrays.asList(2, 4)) instanceof RandomAccess); + assertTrue(multimap.replaceValues("foo", asList(2, 4)) instanceof RandomAccess); + assertTrue(multimap.replaceValues("bar", asList(2, 4)) instanceof RandomAccess); } public void testCreateFromMultimap() { @@ -142,11 +152,7 @@ public void testCreateFromSize() { } public void testCreateFromIllegalSize() { - try { - LinkedListMultimap.create(-20); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LinkedListMultimap.create(-20)); } public void testLinkedGetAdd() { @@ -224,7 +230,7 @@ public void testLinkedClear() { assertEquals(asList(1, 2), foos); assertThat(values).containsExactly(1, 2, 3).inOrder(); map.clear(); - assertEquals(Collections.emptyList(), foos); + assertEquals(emptyList(), foos); assertThat(values).isEmpty(); assertEquals("[]", map.entries().toString()); assertEquals("{}", map.toString()); @@ -291,18 +297,15 @@ public void testLinkedAsMapEntries() { map.put("foo", 2); map.put("bar", 3); Iterator>> entries = map.asMap().entrySet().iterator(); - Entry> entry = entries.next(); - assertEquals("bar", entry.getKey()); - assertThat(entry.getValue()).containsExactly(1, 3).inOrder(); - try { - entry.setValue(Arrays.asList()); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + Entry> barEntry = entries.next(); + assertEquals("bar", barEntry.getKey()); + assertThat(barEntry.getValue()).containsExactly(1, 3).inOrder(); + assertThrows( + UnsupportedOperationException.class, () -> barEntry.setValue(Arrays.asList())); entries.remove(); // clear - entry = entries.next(); - assertEquals("foo", entry.getKey()); - assertThat(entry.getValue()).contains(2); + Entry> fooEntry = entries.next(); + assertEquals("foo", fooEntry.getKey()); + assertThat(fooEntry.getValue()).contains(2); assertFalse(entries.hasNext()); assertEquals("{foo=[2]}", map.toString()); } @@ -331,26 +334,23 @@ public void testEntriesAfterMultimapUpdate() { assertEquals(3, (int) entryb.getValue()); } - @SuppressWarnings("unchecked") @GwtIncompatible // unreasonably slow public void testEntriesIteration() { List> addItems = ImmutableList.of( - Maps.immutableEntry("foo", 99), - Maps.immutableEntry("foo", 88), - Maps.immutableEntry("bar", 77)); + immutableEntry("foo", 99), immutableEntry("foo", 88), immutableEntry("bar", 77)); - for (final int startIndex : new int[] {0, 3, 5}) { + for (int startIndex : new int[] {0, 3, 5}) { List> list = Lists.newArrayList( - Maps.immutableEntry("foo", 2), - Maps.immutableEntry("foo", 3), - Maps.immutableEntry("bar", 4), - Maps.immutableEntry("bar", 5), - Maps.immutableEntry("foo", 6)); + immutableEntry("foo", 2), + immutableEntry("foo", 3), + immutableEntry("bar", 4), + immutableEntry("bar", 5), + immutableEntry("foo", 6)); new ListIteratorTester>( 3, addItems, ImmutableList.of(SUPPORTS_REMOVE), list, startIndex) { - private LinkedListMultimap multimap; + private @Nullable LinkedListMultimap multimap; @Override protected ListIterator> newTargetIterator() { @@ -376,7 +376,7 @@ public void testKeysIteration() { MODIFIABLE, newArrayList("foo", "foo", "bar", "bar", "foo"), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable Multimap multimap; @Override protected Iterator newTargetIterator() { @@ -389,7 +389,7 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(elements, Lists.newArrayList(multimap.keys())); + assertEquals(elements, new ArrayList<>(multimap.keys())); } }.test(); } @@ -398,20 +398,20 @@ protected void verify(List elements) { public void testValuesIteration() { List addItems = ImmutableList.of(99, 88, 77); - for (final int startIndex : new int[] {0, 3, 5}) { + for (int startIndex : new int[] {0, 3, 5}) { new ListIteratorTester( 3, addItems, ImmutableList.of(SUPPORTS_REMOVE, SUPPORTS_SET), Lists.newArrayList(2, 3, 4, 5, 6), startIndex) { - private LinkedListMultimap multimap; + private @Nullable LinkedListMultimap multimap; @Override protected ListIterator newTargetIterator() { multimap = create(); multimap.put("bar", 2); - multimap.putAll("foo", Arrays.asList(3, 4)); + multimap.putAll("foo", asList(3, 4)); multimap.put("bar", 5); multimap.put("foo", 6); return multimap.values().listIterator(startIndex); @@ -430,9 +430,9 @@ public void testKeySetIteration() { new IteratorTester( 6, MODIFIABLE, - newLinkedHashSet(asList("foo", "bar", "baz", "dog", "cat")), + new LinkedHashSet<>(asList("foo", "bar", "baz", "dog", "cat")), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable Multimap multimap; @Override protected Iterator newTargetIterator() { @@ -449,26 +449,25 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(newHashSet(elements), multimap.keySet()); + assertEquals(new HashSet<>(elements), multimap.keySet()); } }.test(); } - @SuppressWarnings("unchecked") @GwtIncompatible // unreasonably slow public void testAsSetIteration() { Set>> set = - Sets.newLinkedHashSet( + new LinkedHashSet<>( asList( - Maps.immutableEntry("foo", (Collection) asList(2, 3, 6)), - Maps.immutableEntry("bar", (Collection) asList(4, 5, 10, 11)), - Maps.immutableEntry("baz", (Collection) asList(7, 8)), - Maps.immutableEntry("dog", (Collection) asList(9)), - Maps.immutableEntry("cat", (Collection) asList(12, 13, 14)))); + immutableEntry("foo", (Collection) asList(2, 3, 6)), + immutableEntry("bar", (Collection) asList(4, 5, 10, 11)), + immutableEntry("baz", (Collection) asList(7, 8)), + immutableEntry("dog", (Collection) asList(9)), + immutableEntry("cat", (Collection) asList(12, 13, 14)))); new IteratorTester>>( 6, MODIFIABLE, set, IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable Multimap multimap; @Override protected Iterator>> newTargetIterator() { @@ -485,7 +484,7 @@ protected Iterator>> newTargetIterator() { @Override protected void verify(List>> elements) { - assertEquals(newHashSet(elements), multimap.asMap().entrySet()); + assertEquals(new HashSet<>(elements), multimap.asMap().entrySet()); } }.test(); } @@ -496,4 +495,13 @@ public void testEquals() { LinkedListMultimap.create(), LinkedListMultimap.create(), LinkedListMultimap.create(1)) .testEquals(); } + + @GwtIncompatible // reflection + public void testNulls() throws Exception { + NullPointerTester tester = new NullPointerTester(); + tester.testAllPublicStaticMethods(LinkedListMultimap.class); + tester.ignore(LinkedListMultimap.class.getMethod("get", Object.class)); + tester.ignore(LinkedListMultimap.class.getMethod("removeAll", Object.class)); + tester.testAllPublicInstanceMethods(LinkedListMultimap.create()); + } } diff --git a/android/guava-tests/test/com/google/common/collect/ListsImplTest.java b/android/guava-tests/test/com/google/common/collect/ListsImplTest.java index 2d18f5a72e56..dc96954f0257 100644 --- a/android/guava-tests/test/com/google/common/collect/ListsImplTest.java +++ b/android/guava-tests/test/com/google/common/collect/ListsImplTest.java @@ -18,13 +18,15 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; @@ -32,9 +34,12 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** Tests the package level *impl methods directly using various types of lists. */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ListsImplTest extends TestCase { /** Describes how a list is modifiable */ @@ -64,12 +69,13 @@ public String getName() { /** Creates a new list with the given contents. */ public abstract List createList(Class listType, Collection contents); - /** The modifiablity of this list example. */ + /** The modifiability of this list example. */ public Modifiability modifiability() { return modifiability; } } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { TestSuite suite = new TestSuite(); @@ -82,6 +88,7 @@ public static Test suite() { return suite; } + @J2ktIncompatible @GwtIncompatible // suite sub call private static TestSuite createExampleSuite(ListExample example) { TestSuite resultSuite = new TestSuite(ListsImplTest.class); @@ -92,18 +99,22 @@ private static TestSuite createExampleSuite(ListExample example) { return resultSuite; } - private ListExample example; + private @Nullable ListExample example; private ListExample getExample() { // because sometimes one version with a null example is created. return example == null ? new ImmutableListExample("test") : example; } + @J2ktIncompatible + @GwtIncompatible // not used under GWT, and super.getName() is not available under J2CL @Override public String getName() { return example == null ? super.getName() : buildTestName(); } + @J2ktIncompatible + @GwtIncompatible // not used under GWT, and super.getName() is not available under J2CL private String buildTestName() { return super.getName() + ":" + example.getName(); } @@ -136,8 +147,8 @@ public void testEqualsImpl() { assertThat(Lists.equalsImpl(base, copy)).isTrue(); assertThat(Lists.equalsImpl(base, otherType)).isTrue(); - List unEqualItems = - Arrays.asList(outOfOrder, diffValue, diffLength, empty, null, new Object()); + List<@Nullable Object> unEqualItems = + asList(outOfOrder, diffValue, diffLength, empty, null, new Object()); for (Object other : unEqualItems) { assertWithMessage("%s", other).that(Lists.equalsImpl(base, other)).isFalse(); } @@ -151,14 +162,14 @@ public void testAddAllImpl() { List> toAdd = ImmutableList.of( - (Iterable) Collections.singleton("A"), - Collections.emptyList(), + singleton("A"), + emptyList(), ImmutableList.of("A", "B", "C"), ImmutableList.of("D", "E")); List indexes = ImmutableList.of(0, 0, 1, 3); List> expected = ImmutableList.of( - Collections.singletonList("A"), + ImmutableList.of("A"), ImmutableList.of("A"), ImmutableList.of("A", "A", "B", "C"), ImmutableList.of("A", "A", "D", "E", "B", "C")); @@ -232,14 +243,13 @@ private void checkLastIndexOf(List toTest, int[] expected) { } @SafeVarargs - @SuppressWarnings("varargs") private final List createList(Class listType, T... contents) { - return getExample().createList(listType, Arrays.asList(contents)); + return getExample().createList(listType, asList(contents)); } private static final class ArrayListExample extends ListExample { - protected ArrayListExample(String name) { + ArrayListExample(String name) { super(name, Modifiability.ALL); } @@ -251,11 +261,13 @@ public List createList(Class listType, Collection content private static final class LinkedListExample extends ListExample { - protected LinkedListExample(String name) { + LinkedListExample(String name) { super(name, Modifiability.ALL); } @Override + // We are testing our utilities on LinkedList. + @SuppressWarnings("JdkObsolete") public List createList(Class listType, Collection contents) { return new LinkedList<>(contents); } @@ -264,21 +276,20 @@ public List createList(Class listType, Collection content @GwtIncompatible // Iterables.toArray private static final class ArraysAsListExample extends ListExample { - protected ArraysAsListExample(String name) { + ArraysAsListExample(String name) { super(name, Modifiability.BY_ELEMENT); } @Override public List createList(Class listType, Collection contents) { - @SuppressWarnings("unchecked") // safe by contract T[] array = Iterables.toArray(contents, listType); - return Arrays.asList(array); + return asList(array); } } private static final class ImmutableListExample extends ListExample { - protected ImmutableListExample(String name) { + ImmutableListExample(String name) { super(name, Modifiability.NONE); } @@ -288,10 +299,11 @@ public List createList(Class listType, Collection content } } + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArrayList private static final class CopyOnWriteListExample extends ListExample { - protected CopyOnWriteListExample(String name) { + CopyOnWriteListExample(String name) { super(name, Modifiability.DIRECT_ONLY); } diff --git a/android/guava-tests/test/com/google/common/collect/ListsTest.java b/android/guava-tests/test/com/google/common/collect/ListsTest.java index ef90e5493f96..fe80cafc6f30 100644 --- a/android/guava-tests/test/com/google/common/collect/ListsTest.java +++ b/android/guava-tests/test/com/google/common/collect/ListsTest.java @@ -17,13 +17,25 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.elementsEqual; +import static com.google.common.collect.Lists.cartesianProduct; +import static com.google.common.collect.Lists.charactersOf; +import static com.google.common.collect.Lists.computeArrayListCapacity; +import static com.google.common.collect.Lists.newArrayListWithExpectedSize; +import static com.google.common.collect.Lists.partition; +import static com.google.common.collect.Lists.transform; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; import static com.google.common.truth.Truth.assertThat; +import static java.lang.System.arraycopy; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.nCopies; import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.testing.IteratorTester; @@ -39,7 +51,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -50,6 +61,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@code Lists}. @@ -58,7 +70,8 @@ * @author Mike Bostock * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ListsTest extends TestCase { private static final Collection SOME_COLLECTION = asList(0, 1, 1); @@ -78,12 +91,12 @@ public Iterator iterator() { return SOME_COLLECTION.iterator(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } private static final List SOME_LIST = Lists.newArrayList(1, 2, 3, 4); - private static final List SOME_SEQUENTIAL_LIST = Lists.newLinkedList(asList(1, 2, 3, 4)); + private static final List SOME_SEQUENTIAL_LIST = new LinkedList<>(asList(1, 2, 3, 4)); private static final List SOME_STRING_LIST = asList("1", "2", "3", "4"); @@ -95,10 +108,12 @@ public String apply(Number n) { return String.valueOf(n); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(ListsTest.class); @@ -109,7 +124,7 @@ public static Test suite() { @Override protected List create(String[] elements) { String[] rest = new String[elements.length - 1]; - System.arraycopy(elements, 1, rest, 0, elements.length - 1); + arraycopy(elements, 1, rest, 0, elements.length - 1); return Lists.asList(elements[0], rest); } }) @@ -127,7 +142,7 @@ protected List create(String[] elements) { @Override protected List create(String[] elements) { String[] rest = new String[elements.length - 2]; - System.arraycopy(elements, 2, rest, 0, elements.length - 2); + arraycopy(elements, 2, rest, 0, elements.length - 2); return Lists.asList(elements[0], elements[1], rest); } }) @@ -138,18 +153,18 @@ protected List create(String[] elements) { CollectionFeature.ALLOWS_NULL_VALUES) .createTestSuite()); - final Function removeFirst = new RemoveFirstFunction(); + Function removeFirst = new RemoveFirstFunction(); suite.addTest( ListTestSuiteBuilder.using( new TestStringListGenerator() { @Override protected List create(String[] elements) { - List fromList = Lists.newArrayList(); + List fromList = new ArrayList<>(); for (String element : elements) { fromList.add("q" + checkNotNull(element)); } - return Lists.transform(fromList, removeFirst); + return transform(fromList, removeFirst); } }) .named("Lists.transform, random access, no nulls") @@ -165,11 +180,11 @@ protected List create(String[] elements) { new TestStringListGenerator() { @Override protected List create(String[] elements) { - List fromList = Lists.newLinkedList(); + List fromList = new LinkedList<>(); for (String element : elements) { fromList.add("q" + checkNotNull(element)); } - return Lists.transform(fromList, removeFirst); + return transform(fromList, removeFirst); } }) .named("Lists.transform, sequential access, no nulls") @@ -186,7 +201,7 @@ protected List create(String[] elements) { @Override protected List create(String[] elements) { List fromList = Lists.newArrayList(elements); - return Lists.transform(fromList, Functions.identity()); + return transform(fromList, Functions.identity()); } }) .named("Lists.transform, random access, nulls") @@ -202,8 +217,8 @@ protected List create(String[] elements) { new TestStringListGenerator() { @Override protected List create(String[] elements) { - List fromList = Lists.newLinkedList(asList(elements)); - return Lists.transform(fromList, Functions.identity()); + List fromList = new LinkedList<>(asList(elements)); + return transform(fromList, Functions.identity()); } }) .named("Lists.transform, sequential access, nulls") @@ -219,7 +234,7 @@ protected List create(String[] elements) { new TestStringListGenerator() { @Override protected List create(String[] elements) { - List list = Lists.newArrayList(); + List list = new ArrayList<>(); for (int i = elements.length - 1; i >= 0; i--) { list.add(elements[i]); } @@ -255,7 +270,7 @@ protected List create(String[] elements) { new TestStringListGenerator() { @Override protected List create(String[] elements) { - List list = Lists.newLinkedList(); + List list = new LinkedList<>(); for (int i = elements.length - 1; i >= 0; i--) { list.add(elements[i]); } @@ -305,7 +320,7 @@ protected List create(String[] elements) { public void testCharactersOfIsView() { StringBuilder builder = new StringBuilder("abc"); - List chars = Lists.charactersOf(builder); + List chars = charactersOf(builder); assertEquals(asList('a', 'b', 'c'), chars); builder.append("def"); assertEquals(asList('a', 'b', 'c', 'd', 'e', 'f'), chars); @@ -314,40 +329,33 @@ public void testCharactersOfIsView() { } public void testNewArrayListEmpty() { + @SuppressWarnings("UseCollectionConstructor") // test of factory method ArrayList list = Lists.newArrayList(); - assertEquals(Collections.emptyList(), list); + assertEquals(emptyList(), list); } public void testNewArrayListWithCapacity() { ArrayList list = Lists.newArrayListWithCapacity(0); - assertEquals(Collections.emptyList(), list); + assertEquals(emptyList(), list); ArrayList bigger = Lists.newArrayListWithCapacity(256); - assertEquals(Collections.emptyList(), bigger); + assertEquals(emptyList(), bigger); } public void testNewArrayListWithCapacity_negative() { - try { - Lists.newArrayListWithCapacity(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Lists.newArrayListWithCapacity(-1)); } public void testNewArrayListWithExpectedSize() { - ArrayList list = Lists.newArrayListWithExpectedSize(0); - assertEquals(Collections.emptyList(), list); + ArrayList list = newArrayListWithExpectedSize(0); + assertEquals(emptyList(), list); - ArrayList bigger = Lists.newArrayListWithExpectedSize(256); - assertEquals(Collections.emptyList(), bigger); + ArrayList bigger = newArrayListWithExpectedSize(256); + assertEquals(emptyList(), bigger); } public void testNewArrayListWithExpectedSize_negative() { - try { - Lists.newArrayListWithExpectedSize(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> newArrayListWithExpectedSize(-1)); } public void testNewArrayListVarArgs() { @@ -356,14 +364,15 @@ public void testNewArrayListVarArgs() { } public void testComputeArrayListCapacity() { - assertEquals(5, Lists.computeArrayListCapacity(0)); - assertEquals(13, Lists.computeArrayListCapacity(8)); - assertEquals(89, Lists.computeArrayListCapacity(77)); - assertEquals(22000005, Lists.computeArrayListCapacity(20000000)); - assertEquals(Integer.MAX_VALUE, Lists.computeArrayListCapacity(Integer.MAX_VALUE - 1000)); + assertEquals(5, computeArrayListCapacity(0)); + assertEquals(13, computeArrayListCapacity(8)); + assertEquals(89, computeArrayListCapacity(77)); + assertEquals(22000005, computeArrayListCapacity(20000000)); + assertEquals(Integer.MAX_VALUE, computeArrayListCapacity(Integer.MAX_VALUE - 1000)); } public void testNewArrayListFromCollection() { + @SuppressWarnings("UseCollectionConstructor") // test of factory method ArrayList list = Lists.newArrayList(SOME_COLLECTION); assertEquals(SOME_COLLECTION, list); } @@ -379,11 +388,13 @@ public void testNewArrayListFromIterator() { } public void testNewLinkedListEmpty() { + @SuppressWarnings("UseCollectionConstructor") // test of factory method LinkedList list = Lists.newLinkedList(); - assertEquals(Collections.emptyList(), list); + assertEquals(emptyList(), list); } public void testNewLinkedListFromCollection() { + @SuppressWarnings("UseCollectionConstructor") // test of factory method LinkedList list = Lists.newLinkedList(SOME_COLLECTION); assertEquals(SOME_COLLECTION, list); } @@ -393,18 +404,21 @@ public void testNewLinkedListFromIterable() { assertEquals(SOME_COLLECTION, list); } + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArrayList public void testNewCOWALEmpty() { CopyOnWriteArrayList list = Lists.newCopyOnWriteArrayList(); - assertEquals(Collections.emptyList(), list); + assertEquals(emptyList(), list); } + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArrayList public void testNewCOWALFromIterable() { CopyOnWriteArrayList list = Lists.newCopyOnWriteArrayList(SOME_ITERABLE); assertEquals(SOME_COLLECTION, list); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -427,20 +441,13 @@ public void testArraysAsList() { assertEquals("FOO", otherWay.get(0)); // But it can't grow - try { - otherWay.add("nope"); - fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> otherWay.add("nope")); // And it can't shrink - try { - otherWay.remove(2); - fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> otherWay.remove(2)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testAsList1() { List list = Lists.asList("foo", new String[] {"bar", "baz"}); @@ -499,6 +506,7 @@ protected Iterator newTargetIterator() { }.test(); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testAsList2Small() { List list = Lists.asList("foo", "bar", new String[0]); @@ -529,13 +537,13 @@ private static void assertIndexIsOutOfBounds(List list, int index) { } public void testReverseViewRandomAccess() { - List fromList = Lists.newArrayList(SOME_LIST); + List fromList = new ArrayList<>(SOME_LIST); List toList = Lists.reverse(fromList); assertReverseView(fromList, toList); } public void testReverseViewSequential() { - List fromList = Lists.newLinkedList(SOME_SEQUENTIAL_LIST); + List fromList = new LinkedList<>(SOME_SEQUENTIAL_LIST); List toList = Lists.reverse(fromList); assertReverseView(fromList, toList); } @@ -565,7 +573,7 @@ private static void assertReverseView(List fromList, List toLi toList.set(1, 8); assertEquals(asList(5, 7, 8, 3), fromList); toList.clear(); - assertEquals(Collections.emptyList(), fromList); + assertEquals(emptyList(), fromList); } @SafeVarargs @@ -573,28 +581,24 @@ private static List list(E... elements) { return ImmutableList.copyOf(elements); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary1x1() { - assertThat(Lists.cartesianProduct(list(1), list(2))).contains(list(1, 2)); + assertThat(cartesianProduct(list(1), list(2))).contains(list(1, 2)); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary1x2() { - assertThat(Lists.cartesianProduct(list(1), list(2, 3))) + assertThat(cartesianProduct(list(1), list(2, 3))) .containsExactly(list(1, 2), list(1, 3)) .inOrder(); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary2x2() { - assertThat(Lists.cartesianProduct(list(1, 2), list(3, 4))) + assertThat(cartesianProduct(list(1, 2), list(3, 4))) .containsExactly(list(1, 3), list(1, 4), list(2, 3), list(2, 4)) .inOrder(); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_2x2x2() { - assertThat(Lists.cartesianProduct(list(0, 1), list(0, 1), list(0, 1))) + assertThat(cartesianProduct(list(0, 1), list(0, 1), list(0, 1))) .containsExactly( list(0, 0, 0), list(0, 0, 1), @@ -607,9 +611,8 @@ public void testCartesianProduct_2x2x2() { .inOrder(); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_contains() { - List> actual = Lists.cartesianProduct(list(1, 2), list(3, 4)); + List> actual = cartesianProduct(list(1, 2), list(3, 4)); assertTrue(actual.contains(list(1, 3))); assertTrue(actual.contains(list(1, 4))); assertTrue(actual.contains(list(2, 3))); @@ -618,19 +621,19 @@ public void testCartesianProduct_contains() { } public void testCartesianProduct_indexOf() { - List> actual = Lists.cartesianProduct(list(1, 2), list(3, 4)); - assertEquals(actual.indexOf(list(1, 3)), 0); - assertEquals(actual.indexOf(list(1, 4)), 1); - assertEquals(actual.indexOf(list(2, 3)), 2); - assertEquals(actual.indexOf(list(2, 4)), 3); - assertEquals(actual.indexOf(list(3, 1)), -1); + List> actual = cartesianProduct(list(1, 2), list(3, 4)); + assertEquals(0, actual.indexOf(list(1, 3))); + assertEquals(1, actual.indexOf(list(1, 4))); + assertEquals(2, actual.indexOf(list(2, 3))); + assertEquals(3, actual.indexOf(list(2, 4))); + assertEquals(-1, actual.indexOf(list(3, 1))); - assertEquals(actual.indexOf(list(1)), -1); - assertEquals(actual.indexOf(list(1, 1, 1)), -1); + assertEquals(-1, actual.indexOf(list(1))); + assertEquals(-1, actual.indexOf(list(1, 1, 1))); } public void testCartesianProduct_lastIndexOf() { - List> actual = Lists.cartesianProduct(list(1, 1), list(2, 3)); + List> actual = cartesianProduct(list(1, 1), list(2, 3)); assertThat(actual.lastIndexOf(list(1, 2))).isEqualTo(2); assertThat(actual.lastIndexOf(list(1, 3))).isEqualTo(3); assertThat(actual.lastIndexOf(list(1, 1))).isEqualTo(-1); @@ -639,7 +642,6 @@ public void testCartesianProduct_lastIndexOf() { assertThat(actual.lastIndexOf(list(1, 1, 1))).isEqualTo(-1); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_unrelatedTypes() { List x = list(1, 2); List y = list("3", "4"); @@ -654,35 +656,31 @@ public void testCartesianProduct_unrelatedTypes() { .inOrder(); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProductTooBig() { - List list = Collections.nCopies(10000, "foo"); - try { - Lists.cartesianProduct(list, list, list, list, list); - fail("Expected IAE"); - } catch (IllegalArgumentException expected) { - } + List list = nCopies(10000, "foo"); + assertThrows( + IllegalArgumentException.class, () -> cartesianProduct(list, list, list, list, list)); } public void testTransformHashCodeRandomAccess() { - List list = Lists.transform(SOME_LIST, SOME_FUNCTION); + List list = transform(SOME_LIST, SOME_FUNCTION); assertEquals(SOME_STRING_LIST.hashCode(), list.hashCode()); } public void testTransformHashCodeSequential() { - List list = Lists.transform(SOME_SEQUENTIAL_LIST, SOME_FUNCTION); + List list = transform(SOME_SEQUENTIAL_LIST, SOME_FUNCTION); assertEquals(SOME_STRING_LIST.hashCode(), list.hashCode()); } public void testTransformModifiableRandomAccess() { - List fromList = Lists.newArrayList(SOME_LIST); - List list = Lists.transform(fromList, SOME_FUNCTION); + List fromList = new ArrayList<>(SOME_LIST); + List list = transform(fromList, SOME_FUNCTION); assertTransformModifiable(list); } public void testTransformModifiableSequential() { - List fromList = Lists.newLinkedList(SOME_SEQUENTIAL_LIST); - List list = Lists.transform(fromList, SOME_FUNCTION); + List fromList = new LinkedList<>(SOME_SEQUENTIAL_LIST); + List list = transform(fromList, SOME_FUNCTION); assertTransformModifiable(list); } @@ -702,18 +700,18 @@ private static void assertTransformModifiable(List list) { } catch (UnsupportedOperationException expected) { } list.clear(); - assertEquals(Collections.emptyList(), list); + assertEquals(emptyList(), list); } public void testTransformViewRandomAccess() { - List fromList = Lists.newArrayList(SOME_LIST); - List toList = Lists.transform(fromList, SOME_FUNCTION); + List fromList = new ArrayList<>(SOME_LIST); + List toList = transform(fromList, SOME_FUNCTION); assertTransformView(fromList, toList); } public void testTransformViewSequential() { - List fromList = Lists.newLinkedList(SOME_SEQUENTIAL_LIST); - List toList = Lists.transform(fromList, SOME_FUNCTION); + List fromList = new LinkedList<>(SOME_SEQUENTIAL_LIST); + List toList = transform(fromList, SOME_FUNCTION); assertTransformView(fromList, toList); } @@ -734,46 +732,54 @@ private static void assertTransformView(List fromList, List toL toList.remove("5"); assertEquals(asList(3), fromList); toList.clear(); - assertEquals(Collections.emptyList(), fromList); + assertEquals(emptyList(), fromList); } public void testTransformRandomAccess() { - List list = Lists.transform(SOME_LIST, SOME_FUNCTION); + List list = transform(SOME_LIST, SOME_FUNCTION); assertTrue(list instanceof RandomAccess); } public void testTransformSequential() { - List list = Lists.transform(SOME_SEQUENTIAL_LIST, SOME_FUNCTION); + List list = transform(SOME_SEQUENTIAL_LIST, SOME_FUNCTION); assertFalse(list instanceof RandomAccess); } + public void testTransformRandomAccessIsNotEmpty() { + List transformedList = transform(SOME_LIST, SOME_FUNCTION); + assertFalse(transformedList.isEmpty()); + } + + public void testTransformSequentialIsNotEmpty() { + List transformedList = transform(SOME_SEQUENTIAL_LIST, SOME_FUNCTION); + assertFalse(transformedList.isEmpty()); + } + public void testTransformListIteratorRandomAccess() { - List fromList = Lists.newArrayList(SOME_LIST); - List list = Lists.transform(fromList, SOME_FUNCTION); + List fromList = new ArrayList<>(SOME_LIST); + List list = transform(fromList, SOME_FUNCTION); assertTransformListIterator(list); } public void testTransformListIteratorSequential() { - List fromList = Lists.newLinkedList(SOME_SEQUENTIAL_LIST); - List list = Lists.transform(fromList, SOME_FUNCTION); + List fromList = new LinkedList<>(SOME_SEQUENTIAL_LIST); + List list = transform(fromList, SOME_FUNCTION); assertTransformListIterator(list); } public void testTransformPreservesIOOBEsThrownByFunction() { - try { - Lists.transform( - ImmutableList.of("foo", "bar"), - new Function() { - @Override - public String apply(String input) { - throw new IndexOutOfBoundsException(); - } - }) - .toArray(); - fail(); - } catch (IndexOutOfBoundsException expected) { - // success - } + assertThrows( + IndexOutOfBoundsException.class, + () -> + transform( + ImmutableList.of("foo", "bar"), + new Function() { + @Override + public String apply(String input) { + throw new IndexOutOfBoundsException(); + } + }) + .toArray()); } private static void assertTransformListIterator(List list) { @@ -809,26 +815,24 @@ private static void assertTransformListIterator(List list) { try { iterator.add("1"); fail("transformed list iterator is addable"); - } catch (UnsupportedOperationException expected) { - } catch (IllegalStateException expected) { + } catch (UnsupportedOperationException | IllegalStateException expected) { } try { iterator.set("1"); fail("transformed list iterator is settable"); - } catch (UnsupportedOperationException expected) { - } catch (IllegalStateException expected) { + } catch (UnsupportedOperationException | IllegalStateException expected) { } } public void testTransformIteratorRandomAccess() { - List fromList = Lists.newArrayList(SOME_LIST); - List list = Lists.transform(fromList, SOME_FUNCTION); + List fromList = new ArrayList<>(SOME_LIST); + List list = transform(fromList, SOME_FUNCTION); assertTransformIterator(list); } public void testTransformIteratorSequential() { - List fromList = Lists.newLinkedList(SOME_SEQUENTIAL_LIST); - List list = Lists.transform(fromList, SOME_FUNCTION); + List fromList = new LinkedList<>(SOME_SEQUENTIAL_LIST); + List list = transform(fromList, SOME_FUNCTION); assertTransformIterator(list); } @@ -838,11 +842,10 @@ public void testTransformIteratorSequential() { * behavior is clearly documented so it's not expected to change. */ public void testTransformedSequentialIterationUsesBackingListIterationOnly() { - List randomAccessList = Lists.newArrayList(SOME_SEQUENTIAL_LIST); + List randomAccessList = new ArrayList<>(SOME_SEQUENTIAL_LIST); List listIteratorOnlyList = new ListIterationOnlyList<>(randomAccessList); - List transform = Lists.transform(listIteratorOnlyList, SOME_FUNCTION); - assertTrue( - Iterables.elementsEqual(transform, Lists.transform(randomAccessList, SOME_FUNCTION))); + List transform = transform(listIteratorOnlyList, SOME_FUNCTION); + assertTrue(elementsEqual(transform, transform(randomAccessList, SOME_FUNCTION))); } private static class ListIterationOnlyList extends ForwardingList { @@ -890,55 +893,52 @@ private static void assertTransformIterator(List list) { } public void testPartition_badSize() { - List source = Collections.singletonList(1); - try { - Lists.partition(source, 0); - fail(); - } catch (IllegalArgumentException expected) { - } + List source = singletonList(1); + assertThrows(IllegalArgumentException.class, () -> partition(source, 0)); } public void testPartition_empty() { - List source = Collections.emptyList(); - List> partitions = Lists.partition(source, 1); + List source = emptyList(); + List> partitions = partition(source, 1); assertTrue(partitions.isEmpty()); assertEquals(0, partitions.size()); } public void testPartition_1_1() { - List source = Collections.singletonList(1); - List> partitions = Lists.partition(source, 1); + List source = singletonList(1); + List> partitions = partition(source, 1); assertEquals(1, partitions.size()); - assertEquals(Collections.singletonList(1), partitions.get(0)); + assertEquals(singletonList(1), partitions.get(0)); } public void testPartition_1_2() { - List source = Collections.singletonList(1); - List> partitions = Lists.partition(source, 2); + List source = singletonList(1); + List> partitions = partition(source, 2); assertEquals(1, partitions.size()); - assertEquals(Collections.singletonList(1), partitions.get(0)); + assertEquals(singletonList(1), partitions.get(0)); } public void testPartition_2_1() { List source = asList(1, 2); - List> partitions = Lists.partition(source, 1); + List> partitions = partition(source, 1); assertEquals(2, partitions.size()); - assertEquals(Collections.singletonList(1), partitions.get(0)); - assertEquals(Collections.singletonList(2), partitions.get(1)); + assertEquals(singletonList(1), partitions.get(0)); + assertEquals(singletonList(2), partitions.get(1)); } public void testPartition_3_2() { List source = asList(1, 2, 3); - List> partitions = Lists.partition(source, 2); + List> partitions = partition(source, 2); assertEquals(2, partitions.size()); assertEquals(asList(1, 2), partitions.get(0)); assertEquals(asList(3), partitions.get(1)); } + @J2ktIncompatible // Arrays.asList(...).subList() doesn't implement RandomAccess in J2KT. @GwtIncompatible // ArrayList.subList doesn't implement RandomAccess in GWT. public void testPartitionRandomAccessTrue() { List source = asList(1, 2, 3); - List> partitions = Lists.partition(source, 2); + List> partitions = partition(source, 2); assertTrue( "partition should be RandomAccess, but not: " + partitions.getClass(), @@ -954,8 +954,8 @@ public void testPartitionRandomAccessTrue() { } public void testPartitionRandomAccessFalse() { - List source = Lists.newLinkedList(asList(1, 2, 3)); - List> partitions = Lists.partition(source, 2); + List source = new LinkedList<>(asList(1, 2, 3)); + List> partitions = partition(source, 2); assertFalse(partitions instanceof RandomAccess); assertFalse(partitions.get(0) instanceof RandomAccess); assertFalse(partitions.get(1) instanceof RandomAccess); @@ -965,7 +965,7 @@ public void testPartitionRandomAccessFalse() { public void testPartition_view() { List list = asList(1, 2, 3); - List> partitions = Lists.partition(list, 3); + List> partitions = partition(list, 3); // Changes before the partition is retrieved are reflected list.set(0, 3); @@ -989,12 +989,13 @@ public void testPartition_view() { public void testPartitionSize_1() { List list = asList(1, 2, 3); - assertEquals(1, Lists.partition(list, Integer.MAX_VALUE).size()); - assertEquals(1, Lists.partition(list, Integer.MAX_VALUE - 1).size()); + assertEquals(1, partition(list, Integer.MAX_VALUE).size()); + assertEquals(1, partition(list, Integer.MAX_VALUE - 1).size()); } @GwtIncompatible // cannot do such a big explicit copy + @J2ktIncompatible // too slow public void testPartitionSize_2() { - assertEquals(2, Lists.partition(Collections.nCopies(0x40000001, 1), 0x40000000).size()); + assertEquals(2, partition(nCopies(0x40000001, 1), 0x40000000).size()); } } diff --git a/android/guava-tests/test/com/google/common/collect/LockHeldAssertingSet.java b/android/guava-tests/test/com/google/common/collect/LockHeldAssertingSet.java new file mode 100644 index 000000000000..90ce704a6661 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/LockHeldAssertingSet.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static junit.framework.Assert.assertTrue; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Set; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; + +/** + * {@link Set} implementation that asserts that a given lock is held whenever one of its methods is + * called. + */ +@NullUnmarked +class LockHeldAssertingSet extends ForwardingSet implements Serializable { + final Set delegate; + final Object mutex; + + LockHeldAssertingSet(Set delegate, Object mutex) { + checkNotNull(mutex); + this.delegate = delegate; + this.mutex = mutex; + } + + @Override + protected Set delegate() { + return delegate; + } + + @Override + public String toString() { + assertTrue(Thread.holdsLock(mutex)); + return super.toString(); + } + + @Override + public boolean equals(@Nullable Object o) { + assertTrue(Thread.holdsLock(mutex)); + return super.equals(o); + } + + @Override + public int hashCode() { + assertTrue(Thread.holdsLock(mutex)); + return super.hashCode(); + } + + @Override + public boolean add(@Nullable E o) { + assertTrue(Thread.holdsLock(mutex)); + return super.add(o); + } + + @Override + public boolean addAll(Collection c) { + assertTrue(Thread.holdsLock(mutex)); + return super.addAll(c); + } + + @Override + public void clear() { + assertTrue(Thread.holdsLock(mutex)); + super.clear(); + } + + @Override + public boolean contains(@Nullable Object o) { + assertTrue(Thread.holdsLock(mutex)); + return super.contains(o); + } + + @Override + public boolean containsAll(Collection c) { + assertTrue(Thread.holdsLock(mutex)); + return super.containsAll(c); + } + + @Override + public boolean isEmpty() { + assertTrue(Thread.holdsLock(mutex)); + return super.isEmpty(); + } + + /* + * We don't assert that the lock is held during calls to iterator(), stream(), and spliterator: + * `Synchronized` doesn't guarantee that it will hold the mutex for those calls because callers + * are responsible for taking the mutex themselves: + * https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/util/Collections.html#synchronizedCollection(java.util.Collection) + * + * Similarly, we avoid having those methods *implemented* in terms of *other* TestSet methods + * that will perform holdsLock assertions: + * + * - For iterator(), we can accomplish that by not overriding iterator() at all. That way, we + * inherit an implementation that forwards to the delegate collection, which performs no + * holdsLock assertions. + * + * - For stream() and spliterator(), we have to forward to the delegate ourselves because + * ForwardingSet does not forward `default` methods, as discussed in its Javadoc. + */ + + // Currently, we don't include stream() and spliterator() for our classes in the Android flavor. + + @Override + public boolean remove(@Nullable Object o) { + assertTrue(Thread.holdsLock(mutex)); + return super.remove(o); + } + + @Override + public boolean removeAll(Collection c) { + assertTrue(Thread.holdsLock(mutex)); + return super.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + assertTrue(Thread.holdsLock(mutex)); + return super.retainAll(c); + } + + @Override + public int size() { + assertTrue(Thread.holdsLock(mutex)); + return super.size(); + } + + @Override + public Object[] toArray() { + assertTrue(Thread.holdsLock(mutex)); + return super.toArray(); + } + + @Override + public T[] toArray(T[] a) { + assertTrue(Thread.holdsLock(mutex)); + return super.toArray(a); + } + + private static final long serialVersionUID = 0; +} diff --git a/android/guava-tests/test/com/google/common/collect/MapMakerInternalMapTest.java b/android/guava-tests/test/com/google/common/collect/MapMakerInternalMapTest.java index 47fc74c82e5e..e2f3683fc61c 100644 --- a/android/guava-tests/test/com/google/common/collect/MapMakerInternalMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapMakerInternalMapTest.java @@ -29,9 +29,13 @@ import java.lang.ref.Reference; import java.util.concurrent.atomic.AtomicReferenceArray; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author Charles Fry */ +/** + * @author Charles Fry + */ @SuppressWarnings("deprecation") // many tests of deprecated methods +@NullUnmarked public class MapMakerInternalMapTest extends TestCase { static final int SMALL_MAX_SIZE = DRAIN_THRESHOLD * 5; @@ -90,7 +94,7 @@ protected int doHash(Object t) { } public void testSetConcurrencyLevel() { - // round up to nearest power of two + // round up to the nearest power of two checkConcurrencyLevel(1, 1); checkConcurrencyLevel(2, 2); @@ -109,7 +113,7 @@ private static void checkConcurrencyLevel(int concurrencyLevel, int segmentCount } public void testSetInitialCapacity() { - // share capacity over each segment, then round up to nearest power of two + // share capacity over each segment, then round up to the nearest power of two checkInitialCapacity(1, 0, 1); checkInitialCapacity(1, 1, 1); @@ -152,42 +156,6 @@ private static void checkInitialCapacity( } } - public void testSetMaximumSize() { - // vary maximumSize wrt concurrencyLevel - - for (int maxSize = 1; maxSize < 8; maxSize++) { - checkMaximumSize(1, 8, maxSize); - checkMaximumSize(2, 8, maxSize); - checkMaximumSize(4, 8, maxSize); - checkMaximumSize(8, 8, maxSize); - } - - checkMaximumSize(1, 8, Integer.MAX_VALUE); - checkMaximumSize(2, 8, Integer.MAX_VALUE); - checkMaximumSize(4, 8, Integer.MAX_VALUE); - checkMaximumSize(8, 8, Integer.MAX_VALUE); - - // vary initial capacity wrt maximumSize - - for (int capacity = 0; capacity < 8; capacity++) { - checkMaximumSize(1, capacity, 4); - checkMaximumSize(2, capacity, 4); - checkMaximumSize(4, capacity, 4); - checkMaximumSize(8, capacity, 4); - } - } - - private static void checkMaximumSize(int concurrencyLevel, int initialCapacity, int maxSize) { - MapMakerInternalMap map = - makeMap( - createMapMaker().concurrencyLevel(concurrencyLevel).initialCapacity(initialCapacity)); - int totalCapacity = 0; - for (int i = 0; i < map.segments.length; i++) { - totalCapacity += map.segments[i].maxSegmentSize; - } - assertTrue("totalCapcity=" + totalCapacity + ", maxSize=" + maxSize, totalCapacity <= maxSize); - } - public void testSetWeakKeys() { MapMakerInternalMap map = makeMap(createMapMaker().weakKeys()); checkStrength(map, Strength.WEAK, Strength.STRONG); @@ -228,7 +196,7 @@ public void testNewEntry() { assertSame(keyOne, entryOne.getKey()); assertEquals(hashOne, entryOne.getHash()); - assertNull(entryOne.getNext()); + assertThat(entryOne.getNext()).isNull(); assertSame(valueRefOne, segment.getWeakValueReferenceForTesting(entryOne)); Object keyTwo = new Object(); @@ -269,7 +237,7 @@ public void testCopyEntry() { InternalEntry copyOne = segment.copyForTesting(entryOne, null); assertSame(keyOne, entryOne.getKey()); assertEquals(hashOne, entryOne.getHash()); - assertNull(entryOne.getNext()); + assertThat(entryOne.getNext()).isNull(); assertSame(valueOne, copyOne.getValue()); InternalEntry copyTwo = segment.copyForTesting(entryTwo, copyOne); @@ -295,11 +263,11 @@ public void testSegmentGetAndContains() { InternalEntry entry = segment.newEntryForTesting(key, hash, null); segment.setValueForTesting(entry, value); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); // count == 0 segment.setTableEntryForTesting(index, entry); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); assertFalse(segment.containsKey(key, hash)); assertFalse(segment.containsValue(value)); @@ -309,7 +277,7 @@ public void testSegmentGetAndContains() { assertTrue(segment.containsKey(key, hash)); assertTrue(segment.containsValue(value)); // don't see absent values now that count > 0 - assertNull(segment.get(new Object(), hash)); + assertThat(segment.get(new Object(), hash)).isNull(); // null key InternalEntry nullEntry = segment.newEntryForTesting(null, hash, entry); @@ -391,7 +359,7 @@ public void testSegmentReplaceValue() { oldValueRef.clear(); assertFalse(segment.replace(key, hash, oldValue, newValue)); assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); } public void testSegmentReplace() { @@ -413,7 +381,7 @@ public void testSegmentReplace() { segment.setWeakValueReferenceForTesting(entry, oldValueRef); // no entry - assertNull(segment.replace(key, hash, newValue)); + assertThat(segment.replace(key, hash, newValue)).isNull(); assertEquals(0, segment.count); // same key @@ -428,9 +396,9 @@ public void testSegmentReplace() { // cleared segment.setWeakValueReferenceForTesting(entry, oldValueRef); oldValueRef.clear(); - assertNull(segment.replace(key, hash, newValue)); + assertThat(segment.replace(key, hash, newValue)).isNull(); assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); } public void testSegmentPut() { @@ -446,7 +414,7 @@ public void testSegmentPut() { // no entry assertEquals(0, segment.count); - assertNull(segment.put(key, hash, oldValue, false)); + assertThat(segment.put(key, hash, oldValue, false)).isNull(); assertEquals(1, segment.count); // same key @@ -461,7 +429,7 @@ public void testSegmentPut() { segment.setWeakValueReferenceForTesting(entry, oldValueRef); assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); - assertNull(segment.put(key, hash, newValue, false)); + assertThat(segment.put(key, hash, newValue, false)).isNull(); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); } @@ -479,7 +447,7 @@ public void testSegmentPutIfAbsent() { // no entry assertEquals(0, segment.count); - assertNull(segment.put(key, hash, oldValue, true)); + assertThat(segment.put(key, hash, oldValue, true)).isNull(); assertEquals(1, segment.count); // same key @@ -494,7 +462,7 @@ public void testSegmentPutIfAbsent() { segment.setWeakValueReferenceForTesting(entry, oldValueRef); assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); - assertNull(segment.put(key, hash, newValue, true)); + assertThat(segment.put(key, hash, newValue, true)).isNull(); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); } @@ -510,8 +478,8 @@ public void testSegmentPut_expand() { Object key = new Object(); Object value = new Object(); int hash = map.hash(key); - assertNull(segment.put(key, hash, value, false)); - assertTrue(segment.table.length() > i); + assertThat(segment.put(key, hash, value, false)).isNull(); + assertThat(segment.table.length()).isGreaterThan(i); } } @@ -533,7 +501,7 @@ public void testSegmentRemove() { // no entry assertEquals(0, segment.count); - assertNull(segment.remove(key, hash)); + assertThat(segment.remove(key, hash)).isNull(); assertEquals(0, segment.count); // same key @@ -543,7 +511,7 @@ public void testSegmentRemove() { assertSame(oldValue, segment.get(key, hash)); assertSame(oldValue, segment.remove(key, hash)); assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); // cleared segment.setTableEntryForTesting(index, entry); @@ -551,9 +519,9 @@ public void testSegmentRemove() { assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); - assertNull(segment.remove(key, hash)); + assertThat(segment.remove(key, hash)).isNull(); assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); } public void testSegmentRemoveValue() { @@ -575,7 +543,7 @@ public void testSegmentRemoveValue() { // no entry assertEquals(0, segment.count); - assertNull(segment.remove(key, hash)); + assertThat(segment.remove(key, hash)).isNull(); assertEquals(0, segment.count); // same value @@ -585,7 +553,7 @@ public void testSegmentRemoveValue() { assertSame(oldValue, segment.get(key, hash)); assertTrue(segment.remove(key, hash, oldValue)); assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); // different value segment.setTableEntryForTesting(index, entry); @@ -601,7 +569,7 @@ public void testSegmentRemoveValue() { oldValueRef.clear(); assertFalse(segment.remove(key, hash, oldValue)); assertEquals(0, segment.count); - assertNull(segment.get(key, hash)); + assertThat(segment.get(key, hash)).isNull(); } @SuppressWarnings("GuardedBy") @@ -665,7 +633,7 @@ public void testRemoveFromChain() { segment.setValueForTesting(entryThree, valueThree); // alone - assertNull(segment.removeFromChainForTesting(entryOne, entryOne)); + assertThat(segment.removeFromChainForTesting(entryOne, entryOne)).isNull(); // head assertSame(entryOne, segment.removeFromChainForTesting(entryTwo, entryTwo)); @@ -687,7 +655,7 @@ public void testRemoveFromChain() { assertSame(keyThree, newFirst.getKey()); assertSame(valueThree, newFirst.getValue()); assertEquals(hashThree, newFirst.getHash()); - assertNull(newFirst.getNext()); + assertThat(newFirst.getNext()).isNull(); } @SuppressWarnings("GuardedBy") @@ -730,8 +698,8 @@ public void testExpand_cleanup() { assertEquals(i, segment.table.length()); assertEquals(liveCount, countLiveEntries(map)); // expansion cleanup is sloppy, with a goal of avoiding unnecessary copies - assertTrue(segment.count >= liveCount); - assertTrue(segment.count <= originalCount); + assertThat(segment.count).isAtLeast(liveCount); + assertThat(segment.count).isAtMost(originalCount); assertEquals(originalMap, ImmutableMap.copyOf(map)); } } @@ -771,7 +739,7 @@ public void testClear() { assertSame(entry, table.get(0)); segment.clear(); - assertNull(table.get(0)); + assertThat(table.get(0)).isNull(); assertEquals(0, segment.readCount.get()); assertEquals(0, segment.count); } @@ -796,7 +764,7 @@ public void testRemoveEntry() { segment.count = 1; assertTrue(segment.removeTableEntryForTesting(entry)); assertEquals(0, segment.count); - assertNull(table.get(0)); + assertThat(table.get(0)).isNull(); } public void testClearValue() { @@ -821,7 +789,7 @@ public void testClearValue() { assertTrue(segment.clearValueForTesting(key, hash, valueRef)); // no notification sent with clearValue assertEquals(0, segment.count); - assertNull(table.get(0)); + assertThat(table.get(0)).isNull(); // clear wrong value reference segment.setTableEntryForTesting(0, entry); @@ -857,9 +825,9 @@ public void testDrainKeyReferenceQueueOnWrite() { map.put(keyTwo, valueTwo); assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); - assertNull(map.get(keyOne)); + assertThat(map.get(keyOne)).isNull(); assertEquals(1, map.size()); - assertNull(segment.getKeyReferenceQueueForTesting().poll()); + assertThat(segment.getKeyReferenceQueueForTesting().poll()).isNull(); } } } @@ -877,7 +845,6 @@ public void testDrainValueReferenceQueueOnWrite() { Object valueTwo = new Object(); map.put(keyOne, valueOne); - @SuppressWarnings("unchecked") WeakValueEntry entry = (WeakValueEntry) segment.getEntry(keyOne, hashOne); WeakValueReference valueReference = entry.getValueReference(); @@ -889,9 +856,9 @@ public void testDrainValueReferenceQueueOnWrite() { map.put(keyTwo, valueTwo); assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); - assertNull(map.get(keyOne)); + assertThat(map.get(keyOne)).isNull(); assertEquals(1, map.size()); - assertNull(segment.getValueReferenceQueueForTesting().poll()); + assertThat(segment.getValueReferenceQueueForTesting().poll()).isNull(); } } } @@ -919,9 +886,9 @@ public void testDrainKeyReferenceQueueOnRead() { } assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); - assertNull(map.get(keyOne)); + assertThat(map.get(keyOne)).isNull(); assertEquals(0, map.size()); - assertNull(segment.getKeyReferenceQueueForTesting().poll()); + assertThat(segment.getKeyReferenceQueueForTesting().poll()).isNull(); } } } @@ -938,7 +905,6 @@ public void testDrainValueReferenceQueueOnRead() { Object keyTwo = new Object(); map.put(keyOne, valueOne); - @SuppressWarnings("unchecked") WeakValueEntry entry = (WeakValueEntry) segment.getEntry(keyOne, hashOne); WeakValueReference valueReference = entry.getValueReference(); @@ -952,9 +918,9 @@ public void testDrainValueReferenceQueueOnRead() { } assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); - assertNull(map.get(keyOne)); + assertThat(map.get(keyOne)).isNull(); assertEquals(0, map.size()); - assertNull(segment.getValueReferenceQueueForTesting().poll()); + assertThat(segment.getValueReferenceQueueForTesting().poll()).isNull(); } } } diff --git a/android/guava-tests/test/com/google/common/collect/MapMakerTest.java b/android/guava-tests/test/com/google/common/collect/MapMakerTest.java index 4b54f0dd5350..3b49f95b7b43 100644 --- a/android/guava-tests/test/com/google/common/collect/MapMakerTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapMakerTest.java @@ -16,21 +16,28 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.testing.NullPointerTester; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author Charles Fry */ -@GwtCompatible(emulated = true) +/** + * @author Charles Fry + */ +@GwtCompatible +@J2ktIncompatible // MapMaker +@NullUnmarked public class MapMakerTest extends TestCase { - @GwtIncompatible // NullPointerTester public void testNullParameters() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -45,6 +52,7 @@ static final class DelayingIdentityLoader implements Function { this.delayLatch = delayLatch; } + @CanIgnoreReturnValue // Sure, why not? @Override public T apply(T key) { awaitUninterruptibly(delayLatch); @@ -57,31 +65,24 @@ public T apply(T key) { * anywhere else */ - /** Tests for the builder. */ - public static class MakerTest extends TestCase { - public void testInitialCapacity_negative() { - MapMaker maker = new MapMaker(); - try { - maker.initialCapacity(-1); - fail(); - } catch (IllegalArgumentException expected) { - } - } + public void testInitialCapacity_negative() { + MapMaker maker = new MapMaker(); + assertThrows(IllegalArgumentException.class, () -> maker.initialCapacity(-1)); + } - // TODO(cpovirk): enable when ready - public void xtestInitialCapacity_setTwice() { - MapMaker maker = new MapMaker().initialCapacity(16); - try { - // even to the same value is not allowed - maker.initialCapacity(16); - fail(); - } catch (IllegalArgumentException expected) { - } + // TODO(cpovirk): enable when ready (apparently after a change to our GWT emulation) + public void xtestInitialCapacity_setTwice() { + MapMaker maker = new MapMaker().initialCapacity(16); + try { + // even to the same value is not allowed + maker.initialCapacity(16); + fail(); + } catch (IllegalStateException expected) { } + } - public void testReturnsPlainConcurrentHashMapWhenPossible() { - Map map = new MapMaker().initialCapacity(5).makeMap(); - assertTrue(map instanceof ConcurrentHashMap); - } + public void testReturnsPlainConcurrentHashMapWhenPossible() { + Map map = new MapMaker().initialCapacity(5).makeMap(); + assertTrue(map instanceof ConcurrentHashMap); } } diff --git a/android/guava-tests/test/com/google/common/collect/MapsCollectionTest.java b/android/guava-tests/test/com/google/common/collect/MapsCollectionTest.java index 54aa1aa2e65e..2bd475175ebb 100644 --- a/android/guava-tests/test/com/google/common/collect/MapsCollectionTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapsCollectionTest.java @@ -18,13 +18,14 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Maps.transformValues; import static com.google.common.collect.testing.Helpers.mapEntry; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.sort; -import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Maps.EntryTransformer; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.NavigableMapTestSuiteBuilder; import com.google.common.collect.testing.SafeTreeMap; @@ -39,26 +40,32 @@ import com.google.common.collect.testing.google.BiMapTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringBiMapGenerator; import com.google.common.io.BaseEncoding; -import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NavigableMap; import java.util.NavigableSet; +import java.util.Objects; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Test suites for wrappers in {@code Maps}. * * @author Louis Wasserman */ +@NullUnmarked +@AndroidIncompatible // test-suite builders public class MapsCollectionTest extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); @@ -114,7 +121,7 @@ public SampleElements> samples() { @Override public Map create(Object... elements) { - Set set = Sets.newLinkedHashSet(); + Set set = new LinkedHashSet<>(); for (Object e : elements) { Entry entry = (Entry) e; checkNotNull(entry.getValue()); @@ -133,7 +140,7 @@ public Integer apply(String input) { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -202,13 +209,13 @@ public Integer apply(String input) { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override public Iterable> order( List> insertionOrder) { - Collections.sort( + sort( insertionOrder, new Comparator>() { @Override @@ -269,13 +276,13 @@ public Integer apply(String input) { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override public Iterable> order( List> insertionOrder) { - Collections.sort( + sort( insertionOrder, new Comparator>() { @Override @@ -313,7 +320,7 @@ static TestSuite filterMapSuite() { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); putEntries(map, entries); map.putAll(ENTRIES_TO_FILTER); return Maps.filterKeys(map, FILTER_KEYS); @@ -332,7 +339,7 @@ protected Map create(Entry[] entries) { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); putEntries(map, entries); map.putAll(ENTRIES_TO_FILTER); return Maps.filterValues(map, FILTER_VALUES); @@ -351,7 +358,7 @@ protected Map create(Entry[] entries) { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); putEntries(map, entries); map.putAll(ENTRIES_TO_FILTER); return Maps.filterEntries(map, FILTER_ENTRIES); @@ -370,7 +377,7 @@ protected Map create(Entry[] entries) { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); putEntries(map, entries); map.putAll(ENTRIES_TO_FILTER); map = Maps.filterEntries(map, FILTER_ENTRIES_1); @@ -561,16 +568,16 @@ static void putEntries(Map map, Entry[] entries) static final Predicate FILTER_KEYS = new Predicate() { @Override - public boolean apply(@NullableDecl String string) { - return !"banana".equals(string) && !"eggplant".equals(string); + public boolean apply(@Nullable String string) { + return !Objects.equals(string, "banana") && !Objects.equals(string, "eggplant"); } }; static final Predicate FILTER_VALUES = new Predicate() { @Override - public boolean apply(@NullableDecl String string) { - return !"toast".equals(string) && !"spam".equals(string); + public boolean apply(@Nullable String string) { + return !Objects.equals(string, "toast") && !Objects.equals(string, "spam"); } }; @@ -578,8 +585,8 @@ public boolean apply(@NullableDecl String string) { new Predicate>() { @Override public boolean apply(Entry entry) { - return !Helpers.mapEntry("banana", "toast").equals(entry) - && !Helpers.mapEntry("eggplant", "spam").equals(entry); + return !mapEntry("banana", "toast").equals(entry) + && !mapEntry("eggplant", "spam").equals(entry); } }; @@ -587,7 +594,7 @@ public boolean apply(Entry entry) { new Predicate>() { @Override public boolean apply(Entry entry) { - return !Helpers.mapEntry("banana", "toast").equals(entry); + return !mapEntry("banana", "toast").equals(entry); } }; @@ -595,7 +602,7 @@ public boolean apply(Entry entry) { new Predicate>() { @Override public boolean apply(Entry entry) { - return !Helpers.mapEntry("eggplant", "spam").equals(entry); + return !mapEntry("eggplant", "spam").equals(entry); } }; @@ -631,14 +638,14 @@ protected SortedMap delegate() { } private static String encode(String str) { - return BaseEncoding.base64().encode(str.getBytes(Charsets.UTF_8)); + return BaseEncoding.base64().encode(str.getBytes(UTF_8)); } private static final Function DECODE_FUNCTION = new Function() { @Override public String apply(String input) { - return new String(BaseEncoding.base64().decode(input), Charsets.UTF_8); + return new String(BaseEncoding.base64().decode(input), UTF_8); } }; @@ -665,11 +672,11 @@ static TestSuite transformMapSuite() { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - Map map = Maps.newLinkedHashMap(); + Map map = new LinkedHashMap<>(); for (Entry entry : entries) { map.put(entry.getKey(), encode(entry.getValue())); } - return Maps.transformValues(map, DECODE_FUNCTION); + return transformValues(map, DECODE_FUNCTION); } }) .named("Maps.transformValues[Map, Function]") @@ -686,7 +693,7 @@ protected Map create(Entry[] entries) { new TestStringMapGenerator() { @Override protected Map create(Entry[] entries) { - Map map = Maps.newLinkedHashMap(); + Map map = new LinkedHashMap<>(); for (Entry entry : entries) { map.put(entry.getKey(), encode(entry.getValue())); } @@ -716,7 +723,7 @@ protected SortedMap create(Entry[] entries) { for (Entry entry : entries) { map.put(entry.getKey(), encode(entry.getValue())); } - return Maps.transformValues(map, DECODE_FUNCTION); + return transformValues(map, DECODE_FUNCTION); } }) .named("Maps.transformValues[SortedMap, Function]") @@ -759,7 +766,7 @@ protected NavigableMap create(Entry[] entries) { for (Entry entry : entries) { map.put(entry.getKey(), encode(entry.getValue())); } - return Maps.transformValues(map, DECODE_FUNCTION); + return transformValues(map, DECODE_FUNCTION); } }) .named("Maps.transformValues[NavigableMap, Function]") diff --git a/android/guava-tests/test/com/google/common/collect/MapsSortedTransformValuesTest.java b/android/guava-tests/test/com/google/common/collect/MapsSortedTransformValuesTest.java index 98bbde501aa4..b6580f68c66a 100644 --- a/android/guava-tests/test/com/google/common/collect/MapsSortedTransformValuesTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapsSortedTransformValuesTest.java @@ -16,10 +16,13 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.transformValues; + import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import com.google.common.base.Functions; import java.util.SortedMap; +import org.jspecify.annotations.NullMarked; /** * Tests for {@link Maps#transformValues(SortedMap, Function)}. @@ -27,11 +30,11 @@ * @author Louis Wasserman */ @GwtCompatible -public class MapsSortedTransformValuesTest extends MapsTransformValuesTest { - +@NullMarked +public class MapsSortedTransformValuesTest extends AbstractMapsTransformValuesTest { @Override protected SortedMap makeEmptyMap() { - return Maps.transformValues(Maps.newTreeMap(), Functions.identity()); + return transformValues(Maps.newTreeMap(), Functions.identity()); } @Override @@ -40,6 +43,6 @@ protected SortedMap makePopulatedMap() { underlying.put("a", 1); underlying.put("b", 2); underlying.put("c", 3); - return Maps.transformValues(underlying, Functions.toStringFunction()); + return transformValues(underlying, Functions.toStringFunction()); } } diff --git a/android/guava-tests/test/com/google/common/collect/MapsTest.java b/android/guava-tests/test/com/google/common/collect/MapsTest.java index 76394c883498..031eca52c85d 100644 --- a/android/guava-tests/test/com/google/common/collect/MapsTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapsTest.java @@ -16,25 +16,28 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.collect.Maps.transformEntries; import static com.google.common.collect.Maps.transformValues; import static com.google.common.collect.Maps.unmodifiableNavigableMap; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; import com.google.common.base.Equivalence; import com.google.common.base.Function; import com.google.common.base.Functions; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; import com.google.common.collect.Maps.EntryTransformer; import com.google.common.collect.Maps.ValueDifferenceImpl; -import com.google.common.collect.SetsTest.Derived; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; @@ -51,6 +54,7 @@ import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -63,6 +67,8 @@ import java.util.TreeMap; import java.util.concurrent.ConcurrentMap; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code Maps}. @@ -71,14 +77,17 @@ * @author Mike Bostock * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked +@SuppressWarnings("JUnitIncompatibleType") // Many intentional violations here. public class MapsTest extends TestCase { private static final Comparator SOME_COMPARATOR = Collections.reverseOrder(); public void testHashMap() { + @SuppressWarnings("UseCollectionConstructor") // test of factory method HashMap map = Maps.newHashMap(); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); } public void testHashMapWithInitialMap() { @@ -86,6 +95,7 @@ public void testHashMapWithInitialMap() { original.put("a", 1); original.put("b", 2); original.put("c", 3); + @SuppressWarnings("UseCollectionConstructor") // test of factory method HashMap map = Maps.newHashMap(original); assertEquals(original, map); } @@ -95,17 +105,13 @@ public void testHashMapGeneralizesTypes() { original.put("a", 1); original.put("b", 2); original.put("c", 3); - HashMap map = - Maps.newHashMap((Map) original); + @SuppressWarnings("UseCollectionConstructor") // test of factory method + HashMap map = Maps.newHashMap(original); assertEquals(original, map); } public void testCapacityForNegativeSizeFails() { - try { - Maps.capacity(-1); - fail("Negative expected size must result in IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Maps.capacity(-1)); } /** @@ -117,36 +123,47 @@ public void testCapacityForNegativeSizeFails() { * *

    This test may fail miserably on non-OpenJDK environments... */ + @J2ktIncompatible @GwtIncompatible // reflection @AndroidIncompatible // relies on assumptions about OpenJDK public void testNewHashMapWithExpectedSize_wontGrow() throws Exception { // before jdk7u40: creates one-bucket table // after jdk7u40: creates empty table - assertTrue(bucketsOf(Maps.newHashMapWithExpectedSize(0)) <= 1); + assertThat(bucketsOf(Maps.newHashMapWithExpectedSize(0))).isAtMost(1); for (int size = 1; size < 200; size++) { assertWontGrow( - size, Maps.newHashMapWithExpectedSize(size), Maps.newHashMapWithExpectedSize(size)); + size, + new HashMap<>(), + Maps.newHashMapWithExpectedSize(size), + Maps.newHashMapWithExpectedSize(size)); } } /** Same test as above but for newLinkedHashMapWithExpectedSize */ + @J2ktIncompatible @GwtIncompatible // reflection @AndroidIncompatible // relies on assumptions about OpenJDK public void testNewLinkedHashMapWithExpectedSize_wontGrow() throws Exception { - assertTrue(bucketsOf(Maps.newLinkedHashMapWithExpectedSize(0)) <= 1); + assertThat(bucketsOf(Maps.newLinkedHashMapWithExpectedSize(0))).isAtMost(1); for (int size = 1; size < 200; size++) { assertWontGrow( size, + new LinkedHashMap<>(), Maps.newLinkedHashMapWithExpectedSize(size), Maps.newLinkedHashMapWithExpectedSize(size)); } } + @J2ktIncompatible @GwtIncompatible // reflection private static void assertWontGrow( - int size, HashMap map1, HashMap map2) throws Exception { + int size, + HashMap referenceMap, + HashMap map1, + HashMap map2) + throws Exception { // Only start measuring table size after the first element inserted, to // deal with empty-map optimization. map1.put(0, null); @@ -168,8 +185,19 @@ private static void assertWontGrow( assertWithMessage("table size after adding " + size + " elements") .that(bucketsOf(map1)) .isEqualTo(initialBuckets); + + // Ensure that referenceMap, which doesn't use WithExpectedSize, ends up with the same table + // size as the other two maps. If it ended up with a smaller size that would imply that we + // computed the wrong initial capacity. + for (int i = 0; i < size; i++) { + referenceMap.put(i, null); + } + assertWithMessage("table size after adding " + size + " elements") + .that(initialBuckets) + .isAtMost(bucketsOf(referenceMap)); } + @J2ktIncompatible @GwtIncompatible // reflection private static int bucketsOf(HashMap hashMap) throws Exception { Field tableField = HashMap.class.getDeclaredField("table"); @@ -197,11 +225,11 @@ public void testCapacityForLargeSizes() { } public void testLinkedHashMap() { + @SuppressWarnings("UseCollectionConstructor") // test of factory method LinkedHashMap map = Maps.newLinkedHashMap(); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); } - @SuppressWarnings("serial") public void testLinkedHashMapWithInitialMap() { Map map = new LinkedHashMap( @@ -211,6 +239,7 @@ public void testLinkedHashMapWithInitialMap() { "polygene", "lubricants", "alpha", "betical")); + @SuppressWarnings("UseCollectionConstructor") // test of factory method LinkedHashMap copy = Maps.newLinkedHashMap(map); Iterator> iter = copy.entrySet().iterator(); @@ -241,6 +270,7 @@ public void testLinkedHashMapGeneralizesTypes() { original.put("a", 1); original.put("b", 2); original.put("c", 3); + @SuppressWarnings("UseCollectionConstructor") // test of factory method HashMap map = Maps.newLinkedHashMap(original); assertEquals(original, map); } @@ -249,45 +279,45 @@ public void testLinkedHashMapGeneralizesTypes() { @SuppressWarnings("IdentityHashMapBoxing") public void testIdentityHashMap() { IdentityHashMap map = Maps.newIdentityHashMap(); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); } public void testConcurrentMap() { ConcurrentMap map = Maps.newConcurrentMap(); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); } public void testTreeMap() { TreeMap map = Maps.newTreeMap(); - assertEquals(Collections.emptyMap(), map); - assertNull(map.comparator()); + assertEquals(emptyMap(), map); + assertThat(map.comparator()).isNull(); } public void testTreeMapDerived() { TreeMap map = Maps.newTreeMap(); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); map.put(new Derived("foo"), 1); map.put(new Derived("bar"), 2); assertThat(map.keySet()).containsExactly(new Derived("bar"), new Derived("foo")).inOrder(); assertThat(map.values()).containsExactly(2, 1).inOrder(); - assertNull(map.comparator()); + assertThat(map.comparator()).isNull(); } public void testTreeMapNonGeneric() { TreeMap map = Maps.newTreeMap(); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); map.put(new LegacyComparable("foo"), 1); map.put(new LegacyComparable("bar"), 2); assertThat(map.keySet()) .containsExactly(new LegacyComparable("bar"), new LegacyComparable("foo")) .inOrder(); assertThat(map.values()).containsExactly(2, 1).inOrder(); - assertNull(map.comparator()); + assertThat(map.comparator()).isNull(); } public void testTreeMapWithComparator() { TreeMap map = Maps.newTreeMap(SOME_COMPARATOR); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); assertSame(SOME_COMPARATOR, map.comparator()); } @@ -307,17 +337,15 @@ public enum SomeEnum { public void testEnumMap() { EnumMap map = Maps.newEnumMap(SomeEnum.class); - assertEquals(Collections.emptyMap(), map); + assertEquals(emptyMap(), map); map.put(SomeEnum.SOME_INSTANCE, 0); - assertEquals(Collections.singletonMap(SomeEnum.SOME_INSTANCE, 0), map); + assertEquals(singletonMap(SomeEnum.SOME_INSTANCE, 0), map); } public void testEnumMapNullClass() { - try { - Maps.newEnumMap((Class) null); - fail("no exception thrown"); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> Maps.newEnumMap((Class) null)); } public void testEnumMapWithInitialEnumMap() { @@ -335,23 +363,19 @@ public void testEnumMapWithInitialEmptyEnumMap() { } public void testEnumMapWithInitialMap() { - HashMap original = Maps.newHashMap(); + HashMap original = new HashMap<>(); original.put(SomeEnum.SOME_INSTANCE, 0); EnumMap copy = Maps.newEnumMap(original); assertEquals(original, copy); } public void testEnumMapWithInitialEmptyMap() { - Map original = Maps.newHashMap(); - try { - Maps.newEnumMap(original); - fail("Empty map must result in an IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + Map original = new HashMap<>(); + assertThrows(IllegalArgumentException.class, () -> Maps.newEnumMap(original)); } public void testToStringImplWithNullKeys() throws Exception { - Map hashmap = Maps.newHashMap(); + Map<@Nullable String, String> hashmap = new HashMap<>(); hashmap.put("foo", "bar"); hashmap.put(null, "baz"); @@ -359,20 +383,21 @@ public void testToStringImplWithNullKeys() throws Exception { } public void testToStringImplWithNullValues() throws Exception { - Map hashmap = Maps.newHashMap(); + Map hashmap = new HashMap<>(); hashmap.put("foo", "bar"); hashmap.put("baz", null); assertEquals(hashmap.toString(), Maps.toStringImpl(hashmap)); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { new NullPointerTester().testAllPublicStaticMethods(Maps.class); } - private static final Map EMPTY = Collections.emptyMap(); - private static final Map SINGLETON = Collections.singletonMap(1, 2); + private static final Map EMPTY = emptyMap(); + private static final Map SINGLETON = singletonMap(1, 2); public void testMapDifferenceEmptyEmpty() { MapDifference diff = Maps.difference(EMPTY, EMPTY); @@ -553,14 +578,14 @@ public void testSortedMapDifferenceTypical() { SortedMapDifference diff1 = Maps.difference(left, right); assertFalse(diff1.areEqual()); assertThat(diff1.entriesOnlyOnLeft().entrySet()) - .containsExactly(Maps.immutableEntry(4, "d"), Maps.immutableEntry(2, "b")) + .containsExactly(immutableEntry(4, "d"), immutableEntry(2, "b")) .inOrder(); - assertThat(diff1.entriesOnlyOnRight().entrySet()).contains(Maps.immutableEntry(6, "z")); - assertThat(diff1.entriesInCommon().entrySet()).contains(Maps.immutableEntry(1, "a")); + assertThat(diff1.entriesOnlyOnRight().entrySet()).contains(immutableEntry(6, "z")); + assertThat(diff1.entriesInCommon().entrySet()).contains(immutableEntry(1, "a")); assertThat(diff1.entriesDiffering().entrySet()) .containsExactly( - Maps.immutableEntry(5, ValueDifferenceImpl.create("e", "g")), - Maps.immutableEntry(3, ValueDifferenceImpl.create("c", "f"))) + immutableEntry(5, ValueDifferenceImpl.create("e", "g")), + immutableEntry(3, ValueDifferenceImpl.create("c", "f"))) .inOrder(); assertEquals( "not equal: only on left={4=d, 2=b}: only on right={6=z}: " @@ -569,11 +594,11 @@ public void testSortedMapDifferenceTypical() { SortedMapDifference diff2 = Maps.difference(right, left); assertFalse(diff2.areEqual()); - assertThat(diff2.entriesOnlyOnLeft().entrySet()).contains(Maps.immutableEntry(6, "z")); + assertThat(diff2.entriesOnlyOnLeft().entrySet()).contains(immutableEntry(6, "z")); assertThat(diff2.entriesOnlyOnRight().entrySet()) - .containsExactly(Maps.immutableEntry(2, "b"), Maps.immutableEntry(4, "d")) + .containsExactly(immutableEntry(2, "b"), immutableEntry(4, "d")) .inOrder(); - assertThat(diff1.entriesInCommon().entrySet()).contains(Maps.immutableEntry(1, "a")); + assertThat(diff1.entriesInCommon().entrySet()).contains(immutableEntry(1, "a")); assertEquals( ImmutableMap.of( 3, ValueDifferenceImpl.create("f", "c"), @@ -595,30 +620,18 @@ public void testSortedMapDifferenceImmutable() { left.put(6, "z"); assertFalse(diff1.areEqual()); assertThat(diff1.entriesOnlyOnLeft().entrySet()) - .containsExactly(Maps.immutableEntry(2, "b"), Maps.immutableEntry(4, "d")) + .containsExactly(immutableEntry(2, "b"), immutableEntry(4, "d")) .inOrder(); - assertThat(diff1.entriesOnlyOnRight().entrySet()).contains(Maps.immutableEntry(6, "z")); - assertThat(diff1.entriesInCommon().entrySet()).contains(Maps.immutableEntry(1, "a")); + assertThat(diff1.entriesOnlyOnRight().entrySet()).contains(immutableEntry(6, "z")); + assertThat(diff1.entriesInCommon().entrySet()).contains(immutableEntry(1, "a")); assertThat(diff1.entriesDiffering().entrySet()) .containsExactly( - Maps.immutableEntry(3, ValueDifferenceImpl.create("c", "f")), - Maps.immutableEntry(5, ValueDifferenceImpl.create("e", "g"))) + immutableEntry(3, ValueDifferenceImpl.create("c", "f")), + immutableEntry(5, ValueDifferenceImpl.create("e", "g"))) .inOrder(); - try { - diff1.entriesInCommon().put(7, "x"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - diff1.entriesOnlyOnLeft().put(7, "x"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - diff1.entriesOnlyOnRight().put(7, "x"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> diff1.entriesInCommon().put(7, "x")); + assertThrows(UnsupportedOperationException.class, () -> diff1.entriesOnlyOnLeft().put(7, "x")); + assertThrows(UnsupportedOperationException.class, () -> diff1.entriesOnlyOnRight().put(7, "x")); } public void testSortedMapDifferenceEquals() { @@ -650,25 +663,25 @@ public void testAsMap() { Map map = Maps.asMap(strings, LENGTH_FUNCTION); assertEquals(ImmutableMap.of("one", 3, "two", 3, "three", 5), map); assertEquals(Integer.valueOf(5), map.get("three")); - assertNull(map.get("five")); + assertThat(map.get("five")).isNull(); assertThat(map.entrySet()) .containsExactly(mapEntry("one", 3), mapEntry("two", 3), mapEntry("three", 5)) .inOrder(); } public void testAsMapReadsThrough() { - Set strings = Sets.newLinkedHashSet(); + Set strings = new LinkedHashSet<>(); Collections.addAll(strings, "one", "two", "three"); Map map = Maps.asMap(strings, LENGTH_FUNCTION); assertEquals(ImmutableMap.of("one", 3, "two", 3, "three", 5), map); - assertNull(map.get("four")); + assertThat(map.get("four")).isNull(); strings.add("four"); assertEquals(ImmutableMap.of("one", 3, "two", 3, "three", 5, "four", 4), map); assertEquals(Integer.valueOf(4), map.get("four")); } public void testAsMapWritesThrough() { - Set strings = Sets.newLinkedHashSet(); + Set strings = new LinkedHashSet<>(); Collections.addAll(strings, "one", "two", "three"); Map map = Maps.asMap(strings, LENGTH_FUNCTION); assertEquals(ImmutableMap.of("one", 3, "two", 3, "three", 5), map); @@ -681,7 +694,7 @@ public void testAsMapEmpty() { Map map = Maps.asMap(strings, LENGTH_FUNCTION); assertThat(map.entrySet()).isEmpty(); assertTrue(map.isEmpty()); - assertNull(map.get("five")); + assertThat(map.get("five")).isNull(); } private static class NonNavigableSortedSet extends ForwardingSortedSet { @@ -699,7 +712,7 @@ public void testAsMapSorted() { SortedMap map = Maps.asMap(strings, LENGTH_FUNCTION); assertEquals(ImmutableMap.of("one", 3, "two", 3, "three", 5), map); assertEquals(Integer.valueOf(5), map.get("three")); - assertNull(map.get("five")); + assertThat(map.get("five")).isNull(); assertThat(map.entrySet()) .containsExactly(mapEntry("one", 3), mapEntry("three", 5), mapEntry("two", 3)) .inOrder(); @@ -715,9 +728,9 @@ public void testAsMapSortedReadsThrough() { SortedSet strings = new NonNavigableSortedSet(); Collections.addAll(strings, "one", "two", "three"); SortedMap map = Maps.asMap(strings, LENGTH_FUNCTION); - assertNull(map.comparator()); + assertThat(map.comparator()).isNull(); assertEquals(ImmutableSortedMap.of("one", 3, "two", 3, "three", 5), map); - assertNull(map.get("four")); + assertThat(map.get("four")).isNull(); strings.add("four"); assertEquals(ImmutableSortedMap.of("one", 3, "two", 3, "three", 5, "four", 4), map); assertEquals(Integer.valueOf(4), map.get("four")); @@ -743,26 +756,11 @@ public void testAsMapSortedWritesThrough() { public void testAsMapSortedSubViewKeySetsDoNotSupportAdd() { SortedMap map = Maps.asMap(new NonNavigableSortedSet(), LENGTH_FUNCTION); - try { - map.subMap("a", "z").keySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - map.tailMap("a").keySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - map.headMap("r").keySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - map.headMap("r").tailMap("m").keySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.subMap("a", "z").keySet().add("a")); + assertThrows(UnsupportedOperationException.class, () -> map.tailMap("a").keySet().add("a")); + assertThrows(UnsupportedOperationException.class, () -> map.headMap("r").keySet().add("a")); + assertThrows( + UnsupportedOperationException.class, () -> map.headMap("r").tailMap("m").keySet().add("a")); } public void testAsMapSortedEmpty() { @@ -770,7 +768,7 @@ public void testAsMapSortedEmpty() { SortedMap map = Maps.asMap(strings, LENGTH_FUNCTION); assertThat(map.entrySet()).isEmpty(); assertTrue(map.isEmpty()); - assertNull(map.get("five")); + assertThat(map.get("five")).isNull(); } @GwtIncompatible // NavigableMap @@ -779,7 +777,7 @@ public void testAsMapNavigable() { NavigableMap map = Maps.asMap(strings, LENGTH_FUNCTION); assertEquals(ImmutableMap.of("one", 3, "two", 3, "three", 5), map); assertEquals(Integer.valueOf(5), map.get("three")); - assertNull(map.get("five")); + assertThat(map.get("five")).isNull(); assertThat(map.entrySet()) .containsExactly(mapEntry("one", 3), mapEntry("three", 5), mapEntry("two", 3)) .inOrder(); @@ -810,7 +808,7 @@ public void testAsMapNavigable() { .inOrder(); assertEquals(map.headMap("three", true), map.descendingMap().tailMap("three", true)); assertThat(map.tailMap("three", false).entrySet()).contains(mapEntry("two", 3)); - assertNull(map.tailMap("three", true).lowerEntry("three")); + assertThat(map.tailMap("three", true).lowerEntry("three")).isNull(); assertThat(map.headMap("two", false).values()).containsExactly(3, 5).inOrder(); assertThat(map.headMap("two", false).descendingMap().values()).containsExactly(5, 3).inOrder(); assertThat(map.descendingKeySet()).containsExactly("two", "three", "one").inOrder(); @@ -825,9 +823,9 @@ public void testAsMapNavigableReadsThrough() { NavigableSet strings = Sets.newTreeSet(); Collections.addAll(strings, "one", "two", "three"); NavigableMap map = Maps.asMap(strings, LENGTH_FUNCTION); - assertNull(map.comparator()); + assertThat(map.comparator()).isNull(); assertEquals(ImmutableSortedMap.of("one", 3, "two", 3, "three", 5), map); - assertNull(map.get("four")); + assertThat(map.get("four")).isNull(); strings.add("four"); assertEquals(ImmutableSortedMap.of("one", 3, "two", 3, "three", 5, "four", 4), map); assertEquals(Integer.valueOf(4), map.get("four")); @@ -869,31 +867,17 @@ public void testAsMapNavigableWritesThrough() { @GwtIncompatible // NavigableMap public void testAsMapNavigableSubViewKeySetsDoNotSupportAdd() { NavigableMap map = Maps.asMap(Sets.newTreeSet(), LENGTH_FUNCTION); - try { - map.descendingKeySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - map.subMap("a", true, "z", false).keySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - map.tailMap("a", true).keySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - map.headMap("r", true).keySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } - try { - map.headMap("r", false).tailMap("m", true).keySet().add("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.descendingKeySet().add("a")); + assertThrows( + UnsupportedOperationException.class, + () -> map.subMap("a", true, "z", false).keySet().add("a")); + assertThrows( + UnsupportedOperationException.class, () -> map.tailMap("a", true).keySet().add("a")); + assertThrows( + UnsupportedOperationException.class, () -> map.headMap("r", true).keySet().add("a")); + assertThrows( + UnsupportedOperationException.class, + () -> map.headMap("r", false).tailMap("m", true).keySet().add("a")); } @GwtIncompatible // NavigableMap @@ -902,7 +886,7 @@ public void testAsMapNavigableEmpty() { NavigableMap map = Maps.asMap(strings, LENGTH_FUNCTION); assertThat(map.entrySet()).isEmpty(); assertTrue(map.isEmpty()); - assertNull(map.get("five")); + assertThat(map.get("five")).isNull(); } public void testToMap() { @@ -933,21 +917,15 @@ public void testToMapWithDuplicateKeys() { } public void testToMapWithNullKeys() { - Iterable strings = Arrays.asList("one", null, "three"); - try { - Maps.toMap(strings, Functions.constant("foo")); - fail(); - } catch (NullPointerException expected) { - } + Iterable<@Nullable String> strings = asList("one", null, "three"); + assertThrows( + NullPointerException.class, + () -> Maps.toMap((Iterable) strings, Functions.constant("foo"))); } public void testToMapWithNullValues() { Iterable strings = ImmutableList.of("one", "two", "three"); - try { - Maps.toMap(strings, Functions.constant(null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Maps.toMap(strings, Functions.constant(null))); } private static final ImmutableBiMap INT_TO_STRING_MAP = @@ -985,37 +963,31 @@ public void testUniqueIndexIterator() { /** Can't create the map if more than one value maps to the same key. */ public void testUniqueIndexDuplicates() { - try { - Map unused = - Maps.uniqueIndex(ImmutableSet.of("one", "uno"), Functions.constant(1)); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()).contains("Multimaps.index"); - } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> Maps.uniqueIndex(ImmutableSet.of("one", "uno"), Functions.constant(1))); + assertThat(expected).hasMessageThat().contains("Multimaps.index"); } /** Null values are not allowed. */ public void testUniqueIndexNullValue() { - List listWithNull = Lists.newArrayList((String) null); - try { - Maps.uniqueIndex(listWithNull, Functions.constant(1)); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable String> listWithNull = Lists.newArrayList((String) null); + assertThrows( + NullPointerException.class, + () -> Maps.uniqueIndex((List) listWithNull, Functions.constant(1))); } /** Null keys aren't allowed either. */ public void testUniqueIndexNullKey() { List oneStringList = Lists.newArrayList("foo"); - try { - Maps.uniqueIndex(oneStringList, Functions.constant(null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> Maps.uniqueIndex(oneStringList, Functions.constant(null))); } + @J2ktIncompatible @GwtIncompatible // Maps.fromProperties - @SuppressWarnings("deprecation") // StringBufferInputStream public void testFromProperties() throws IOException { Properties testProp = new Properties(); @@ -1057,12 +1029,13 @@ public void testFromProperties() throws IOException { testProp.load(new StringReader(override)); result = Maps.fromProperties(testProp); - assertTrue(result.size() > 2); + assertThat(result.size()).isGreaterThan(2); assertEquals("", result.get("test")); assertEquals("hidden", result.get("java.version")); assertNotSame(System.getProperty("java.version"), result.get("java.version")); } + @J2ktIncompatible @GwtIncompatible // Maps.fromProperties @SuppressWarnings("serial") // never serialized public void testFromPropertiesNullKey() { @@ -1070,19 +1043,16 @@ public void testFromPropertiesNullKey() { new Properties() { @Override public Enumeration propertyNames() { - return Iterators.asEnumeration(Arrays.asList(null, "first", "second").iterator()); + return Iterators.asEnumeration(asList(null, "first", "second").iterator()); } }; properties.setProperty("first", "true"); properties.setProperty("second", "null"); - try { - Maps.fromProperties(properties); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Maps.fromProperties(properties)); } + @J2ktIncompatible @GwtIncompatible // Maps.fromProperties @SuppressWarnings("serial") // never serialized public void testFromPropertiesNonStringKeys() { @@ -1095,11 +1065,7 @@ public Enumeration propertyNames() { } }; - try { - Maps.fromProperties(properties); - fail(); - } catch (ClassCastException expected) { - } + assertThrows(ClassCastException.class, () -> Maps.fromProperties(properties)); } public void testAsConverter_nominal() throws Exception { @@ -1130,11 +1096,7 @@ public void testAsConverter_noMapping() throws Exception { "one", 1, "two", 2); Converter converter = Maps.asConverter(biMap); - try { - converter.convert("three"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> converter.convert("three")); } public void testAsConverter_nullConversions() throws Exception { @@ -1143,8 +1105,8 @@ public void testAsConverter_nullConversions() throws Exception { "one", 1, "two", 2); Converter converter = Maps.asConverter(biMap); - assertNull(converter.convert(null)); - assertNull(converter.reverse().convert(null)); + assertThat(converter.convert(null)).isNull(); + assertThat(converter.reverse().convert(null)).isNull(); } public void testAsConverter_isAView() throws Exception { @@ -1153,31 +1115,25 @@ public void testAsConverter_isAView() throws Exception { biMap.put("two", 2); Converter converter = Maps.asConverter(biMap); - assertSame(1, converter.convert("one")); - assertSame(2, converter.convert("two")); - try { - converter.convert("three"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertEquals((Integer) 1, converter.convert("one")); + assertEquals((Integer) 2, converter.convert("two")); + assertThrows(IllegalArgumentException.class, () -> converter.convert("three")); biMap.put("three", 3); - assertSame(1, converter.convert("one")); - assertSame(2, converter.convert("two")); - assertSame(3, converter.convert("three")); + assertEquals((Integer) 1, converter.convert("one")); + assertEquals((Integer) 2, converter.convert("two")); + assertEquals((Integer) 3, converter.convert("three")); } public void testAsConverter_withNullMapping() throws Exception { - BiMap biMap = HashBiMap.create(); + BiMap biMap = HashBiMap.create(); biMap.put("one", 1); biMap.put("two", 2); biMap.put("three", null); - try { - Maps.asConverter(biMap).convert("three"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Maps.asConverter((BiMap) biMap).convert("three")); } public void testAsConverter_toString() { @@ -1216,88 +1172,46 @@ public void testUnmodifiableBiMap() { assertEquals(true, unmod.inverse().get("four").equals(4)); /* UnsupportedOperationException on direct modifications. */ - try { - unmod.put(4, "four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - unmod.forcePut(4, "four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - unmod.putAll(Collections.singletonMap(4, "four")); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> unmod.put(4, "four")); + assertThrows(UnsupportedOperationException.class, () -> unmod.forcePut(4, "four")); + assertThrows(UnsupportedOperationException.class, () -> unmod.putAll(singletonMap(4, "four"))); /* UnsupportedOperationException on indirect modifications. */ BiMap inverse = unmod.inverse(); - try { - inverse.put("four", 4); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - inverse.forcePut("four", 4); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - inverse.putAll(Collections.singletonMap("four", 4)); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> inverse.put("four", 4)); + assertThrows(UnsupportedOperationException.class, () -> inverse.forcePut("four", 4)); + assertThrows( + UnsupportedOperationException.class, () -> inverse.putAll(singletonMap("four", 4))); Set values = unmod.values(); - try { - values.remove("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> values.remove("four")); Set> entries = unmod.entrySet(); Entry entry = entries.iterator().next(); - try { - entry.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); @SuppressWarnings("unchecked") Entry entry2 = (Entry) entries.toArray()[0]; - try { - entry2.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> entry2.setValue("four")); } public void testImmutableEntry() { - Entry e = Maps.immutableEntry("foo", 1); + Entry e = immutableEntry("foo", 1); assertEquals("foo", e.getKey()); assertEquals(1, (int) e.getValue()); - try { - e.setValue(2); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> e.setValue(2)); assertEquals("foo=1", e.toString()); assertEquals(101575, e.hashCode()); } public void testImmutableEntryNull() { - Entry e = Maps.immutableEntry((String) null, (Integer) null); - assertNull(e.getKey()); - assertNull(e.getValue()); - try { - e.setValue(null); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + Entry<@Nullable String, @Nullable Integer> e = immutableEntry((String) null, (Integer) null); + assertThat(e.getKey()).isNull(); + assertThat(e.getValue()).isNull(); + assertThrows(UnsupportedOperationException.class, () -> e.setValue(null)); assertEquals("null=null", e.toString()); assertEquals(0, e.hashCode()); } /** See {@link SynchronizedBiMapTest} for more tests. */ + @J2ktIncompatible // Synchronized public void testSynchronizedBiMap() { BiMap bimap = HashBiMap.create(); bimap.put("one", 1); @@ -1308,264 +1222,7 @@ public void testSynchronizedBiMap() { assertEquals(ImmutableSet.of(1, 2, 3), sync.inverse().keySet()); } - private static final Predicate NOT_LENGTH_3 = - new Predicate() { - @Override - public boolean apply(String input) { - return input == null || input.length() != 3; - } - }; - - private static final Predicate EVEN = - new Predicate() { - @Override - public boolean apply(Integer input) { - return input == null || input % 2 == 0; - } - }; - - private static final Predicate> CORRECT_LENGTH = - new Predicate>() { - @Override - public boolean apply(Entry input) { - return input.getKey().length() == input.getValue(); - } - }; - - private static final Function SQRT_FUNCTION = - new Function() { - @Override - public Double apply(Integer in) { - return Math.sqrt(in); - } - }; - - public static class FilteredMapTest extends TestCase { - Map createUnfiltered() { - return Maps.newHashMap(); - } - - public void testFilteredKeysIllegalPut() { - Map unfiltered = createUnfiltered(); - Map filtered = Maps.filterKeys(unfiltered, NOT_LENGTH_3); - filtered.put("a", 1); - filtered.put("b", 2); - assertEquals(ImmutableMap.of("a", 1, "b", 2), filtered); - - try { - filtered.put("yyy", 3); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - public void testFilteredKeysIllegalPutAll() { - Map unfiltered = createUnfiltered(); - Map filtered = Maps.filterKeys(unfiltered, NOT_LENGTH_3); - filtered.put("a", 1); - filtered.put("b", 2); - assertEquals(ImmutableMap.of("a", 1, "b", 2), filtered); - - try { - filtered.putAll(ImmutableMap.of("c", 3, "zzz", 4, "b", 5)); - fail(); - } catch (IllegalArgumentException expected) { - } - - assertEquals(ImmutableMap.of("a", 1, "b", 2), filtered); - } - - public void testFilteredKeysFilteredReflectsBackingChanges() { - Map unfiltered = createUnfiltered(); - Map filtered = Maps.filterKeys(unfiltered, NOT_LENGTH_3); - unfiltered.put("two", 2); - unfiltered.put("three", 3); - unfiltered.put("four", 4); - assertEquals(ImmutableMap.of("two", 2, "three", 3, "four", 4), unfiltered); - assertEquals(ImmutableMap.of("three", 3, "four", 4), filtered); - - unfiltered.remove("three"); - assertEquals(ImmutableMap.of("two", 2, "four", 4), unfiltered); - assertEquals(ImmutableMap.of("four", 4), filtered); - - unfiltered.clear(); - assertEquals(ImmutableMap.of(), unfiltered); - assertEquals(ImmutableMap.of(), filtered); - } - - public void testFilteredValuesIllegalPut() { - Map unfiltered = createUnfiltered(); - Map filtered = Maps.filterValues(unfiltered, EVEN); - filtered.put("a", 2); - unfiltered.put("b", 4); - unfiltered.put("c", 5); - assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); - - try { - filtered.put("yyy", 3); - fail(); - } catch (IllegalArgumentException expected) { - } - assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); - } - - public void testFilteredValuesIllegalPutAll() { - Map unfiltered = createUnfiltered(); - Map filtered = Maps.filterValues(unfiltered, EVEN); - filtered.put("a", 2); - unfiltered.put("b", 4); - unfiltered.put("c", 5); - assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); - - try { - filtered.putAll(ImmutableMap.of("c", 4, "zzz", 5, "b", 6)); - fail(); - } catch (IllegalArgumentException expected) { - } - assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); - } - - public void testFilteredValuesIllegalSetValue() { - Map unfiltered = createUnfiltered(); - Map filtered = Maps.filterValues(unfiltered, EVEN); - filtered.put("a", 2); - filtered.put("b", 4); - assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); - - Entry entry = filtered.entrySet().iterator().next(); - try { - entry.setValue(5); - fail(); - } catch (IllegalArgumentException expected) { - } - - assertEquals(ImmutableMap.of("a", 2, "b", 4), filtered); - } - - public void testFilteredValuesClear() { - Map unfiltered = createUnfiltered(); - unfiltered.put("one", 1); - unfiltered.put("two", 2); - unfiltered.put("three", 3); - unfiltered.put("four", 4); - Map filtered = Maps.filterValues(unfiltered, EVEN); - assertEquals(ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4), unfiltered); - assertEquals(ImmutableMap.of("two", 2, "four", 4), filtered); - - filtered.clear(); - assertEquals(ImmutableMap.of("one", 1, "three", 3), unfiltered); - assertTrue(filtered.isEmpty()); - } - - public void testFilteredEntriesIllegalPut() { - Map unfiltered = createUnfiltered(); - unfiltered.put("cat", 3); - unfiltered.put("dog", 2); - unfiltered.put("horse", 5); - Map filtered = Maps.filterEntries(unfiltered, CORRECT_LENGTH); - assertEquals(ImmutableMap.of("cat", 3, "horse", 5), filtered); - - filtered.put("chicken", 7); - assertEquals(ImmutableMap.of("cat", 3, "horse", 5, "chicken", 7), filtered); - - try { - filtered.put("cow", 7); - fail(); - } catch (IllegalArgumentException expected) { - } - assertEquals(ImmutableMap.of("cat", 3, "horse", 5, "chicken", 7), filtered); - } - - public void testFilteredEntriesIllegalPutAll() { - Map unfiltered = createUnfiltered(); - unfiltered.put("cat", 3); - unfiltered.put("dog", 2); - unfiltered.put("horse", 5); - Map filtered = Maps.filterEntries(unfiltered, CORRECT_LENGTH); - assertEquals(ImmutableMap.of("cat", 3, "horse", 5), filtered); - - filtered.put("chicken", 7); - assertEquals(ImmutableMap.of("cat", 3, "horse", 5, "chicken", 7), filtered); - - try { - filtered.putAll(ImmutableMap.of("sheep", 5, "cow", 7)); - fail(); - } catch (IllegalArgumentException expected) { - } - assertEquals(ImmutableMap.of("cat", 3, "horse", 5, "chicken", 7), filtered); - } - - public void testFilteredEntriesObjectPredicate() { - Map unfiltered = createUnfiltered(); - unfiltered.put("cat", 3); - unfiltered.put("dog", 2); - unfiltered.put("horse", 5); - Predicate predicate = Predicates.alwaysFalse(); - Map filtered = Maps.filterEntries(unfiltered, predicate); - assertTrue(filtered.isEmpty()); - } - - public void testFilteredEntriesWildCardEntryPredicate() { - Map unfiltered = createUnfiltered(); - unfiltered.put("cat", 3); - unfiltered.put("dog", 2); - unfiltered.put("horse", 5); - Predicate> predicate = - new Predicate>() { - @Override - public boolean apply(Entry input) { - return "cat".equals(input.getKey()) || Integer.valueOf(2) == input.getValue(); - } - }; - Map filtered = Maps.filterEntries(unfiltered, predicate); - assertEquals(ImmutableMap.of("cat", 3, "dog", 2), filtered); - } - } - - public static class FilteredSortedMapTest extends FilteredMapTest { - @Override - SortedMap createUnfiltered() { - return Maps.newTreeMap(); - } - - public void testFirstAndLastKeyFilteredMap() { - SortedMap unfiltered = createUnfiltered(); - unfiltered.put("apple", 2); - unfiltered.put("banana", 6); - unfiltered.put("cat", 3); - unfiltered.put("dog", 5); - - SortedMap filtered = Maps.filterEntries(unfiltered, CORRECT_LENGTH); - assertEquals("banana", filtered.firstKey()); - assertEquals("cat", filtered.lastKey()); - } - - public void testHeadSubTailMap_FilteredMap() { - SortedMap unfiltered = createUnfiltered(); - unfiltered.put("apple", 2); - unfiltered.put("banana", 6); - unfiltered.put("cat", 4); - unfiltered.put("dog", 3); - SortedMap filtered = Maps.filterEntries(unfiltered, CORRECT_LENGTH); - - assertEquals(ImmutableMap.of("banana", 6), filtered.headMap("dog")); - assertEquals(ImmutableMap.of(), filtered.headMap("banana")); - assertEquals(ImmutableMap.of("banana", 6, "dog", 3), filtered.headMap("emu")); - - assertEquals(ImmutableMap.of("banana", 6), filtered.subMap("banana", "dog")); - assertEquals(ImmutableMap.of("dog", 3), filtered.subMap("cat", "emu")); - - assertEquals(ImmutableMap.of("dog", 3), filtered.tailMap("cat")); - assertEquals(ImmutableMap.of("banana", 6, "dog", 3), filtered.tailMap("banana")); - } - } - - public static class FilteredBiMapTest extends FilteredMapTest { - @Override - BiMap createUnfiltered() { - return HashBiMap.create(); - } - } + private static final Function SQRT_FUNCTION = in -> Math.sqrt(in); public void testTransformValues() { Map map = ImmutableMap.of("a", 4, "b", 9); @@ -1659,7 +1316,7 @@ public String transformEntry(String key, Boolean value) { } // Logically this would accept a NavigableMap, but that won't work under GWT. - private static SortedMap sortedNotNavigable(final SortedMap map) { + private static SortedMap sortedNotNavigable(SortedMap map) { return new ForwardingSortedMap() { @Override protected SortedMap delegate() { @@ -1741,92 +1398,62 @@ public void testUnmodifiableNavigableMap() { ensureNotDirectlyModifiable(unmod.tailMap(2, true)); Collection values = unmod.values(); - try { - values.add("4"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - values.remove("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - values.removeAll(Collections.singleton("four")); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - values.retainAll(Collections.singleton("four")); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - Iterator iterator = values.iterator(); - iterator.next(); - iterator.remove(); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> values.add("4")); + assertThrows(UnsupportedOperationException.class, () -> values.remove("four")); + assertThrows(UnsupportedOperationException.class, () -> values.removeAll(singleton("four"))); + assertThrows(UnsupportedOperationException.class, () -> values.retainAll(singleton("four"))); + assertThrows( + UnsupportedOperationException.class, + () -> { + Iterator iterator = values.iterator(); + iterator.next(); + iterator.remove(); + }); Set> entries = unmod.entrySet(); - try { - Iterator> iterator = entries.iterator(); - iterator.next(); - iterator.remove(); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + assertThrows( + UnsupportedOperationException.class, + () -> { + Iterator> iterator = entries.iterator(); + iterator.next(); + iterator.remove(); + }); + { + Entry entry = entries.iterator().next(); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); } - Entry entry = entries.iterator().next(); - try { - entry.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + { + Entry entry = unmod.lowerEntry(1); + assertThat(entry).isNull(); } - entry = unmod.lowerEntry(1); - assertNull(entry); - entry = unmod.floorEntry(2); - try { - entry.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + { + Entry entry = unmod.floorEntry(2); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); } - entry = unmod.ceilingEntry(2); - try { - entry.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + { + Entry entry = unmod.ceilingEntry(2); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); } - entry = unmod.lowerEntry(2); - try { - entry.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + { + Entry entry = unmod.lowerEntry(2); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); } - entry = unmod.higherEntry(2); - try { - entry.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + { + Entry entry = unmod.higherEntry(2); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); } - entry = unmod.firstEntry(); - try { - entry.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + { + Entry entry = unmod.firstEntry(); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); } - entry = unmod.lastEntry(); - try { - entry.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + { + Entry entry = unmod.lastEntry(); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); } - @SuppressWarnings("unchecked") - Entry entry2 = (Entry) entries.toArray()[0]; - try { - entry2.setValue("four"); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { + { + @SuppressWarnings("unchecked") + Entry entry = (Entry) entries.toArray()[0]; + assertThrows(UnsupportedOperationException.class, () -> entry.setValue("four")); } } @@ -1838,7 +1465,7 @@ void ensureNotDirectlyModifiable(NavigableMap unmod) { } catch (UnsupportedOperationException expected) { } try { - unmod.putAll(Collections.singletonMap(4, "four")); + unmod.putAll(singletonMap(4, "four")); fail("UnsupportedOperationException expected"); } catch (UnsupportedOperationException expected) { } @@ -1934,11 +1561,7 @@ public void testSubMap_unnaturalOrdering() { .put(10, 0) .build(); - try { - Maps.subMap(map, Range.closed(4, 8)); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Maps.subMap(map, Range.closed(4, 8))); // These results are all incorrect, but there's no way (short of iterating over the result) // to verify that with an arbitrary ordering or comparator. diff --git a/android/guava-tests/test/com/google/common/collect/MapsTransformValuesTest.java b/android/guava-tests/test/com/google/common/collect/MapsTransformValuesTest.java index d81f546c55cb..e7c2376b3dfa 100644 --- a/android/guava-tests/test/com/google/common/collect/MapsTransformValuesTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapsTransformValuesTest.java @@ -16,293 +16,34 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.transformValues; + import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import com.google.common.base.Functions; -import com.google.common.collect.testing.MapInterfaceTest; -import java.util.Collection; -import java.util.Iterator; +import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; /** - * Tests for {@link Maps#transformValues}. + * Tests for {@link Maps#transformValues(Map, Function)}. * * @author Isaac Shum */ @GwtCompatible -public class MapsTransformValuesTest extends MapInterfaceTest { - - /** - * Constructor that assigns {@code supportsIteratorRemove} the same value as {@code - * supportsRemove}. - */ - protected MapsTransformValuesTest( - boolean allowsNullKeys, - boolean allowsNullValues, - boolean supportsPut, - boolean supportsRemove, - boolean supportsClear) { - super( - allowsNullKeys, - allowsNullValues, - supportsPut, - supportsRemove, - supportsClear, - supportsRemove); - } - - public MapsTransformValuesTest() { - super(false, true, false, true, true); - } - +@NullMarked +public class MapsTransformValuesTest extends AbstractMapsTransformValuesTest { + @Override protected Map makeEmptyMap() { - return Maps.transformValues(Maps.newHashMap(), Functions.identity()); + return transformValues(new HashMap(), Functions.identity()); } @Override protected Map makePopulatedMap() { - Map underlying = Maps.newHashMap(); + Map underlying = new HashMap<>(); underlying.put("a", 1); underlying.put("b", 2); underlying.put("c", 3); - return Maps.transformValues(underlying, Functions.toStringFunction()); - } - - @Override - protected String getKeyNotInPopulatedMap() throws UnsupportedOperationException { - return "z"; - } - - @Override - protected String getValueNotInPopulatedMap() throws UnsupportedOperationException { - return "26"; - } - - /** Helper assertion comparing two maps */ - private void assertMapsEqual(Map expected, Map map) { - assertEquals(expected, map); - assertEquals(expected.hashCode(), map.hashCode()); - assertEquals(expected.entrySet(), map.entrySet()); - - // Assert that expectedValues > mapValues and that - // mapValues > expectedValues; i.e. that expectedValues == mapValues. - Collection expectedValues = expected.values(); - Collection mapValues = map.values(); - assertEquals(expectedValues.size(), mapValues.size()); - assertTrue(expectedValues.containsAll(mapValues)); - assertTrue(mapValues.containsAll(expectedValues)); - } - - public void testTransformEmptyMapEquality() { - Map map = - Maps.transformValues(ImmutableMap.of(), Functions.toStringFunction()); - assertMapsEqual(Maps.newHashMap(), map); - } - - public void testTransformSingletonMapEquality() { - Map map = - Maps.transformValues(ImmutableMap.of("a", 1), Functions.toStringFunction()); - Map expected = ImmutableMap.of("a", "1"); - assertMapsEqual(expected, map); - assertEquals(expected.get("a"), map.get("a")); - } - - public void testTransformIdentityFunctionEquality() { - Map underlying = ImmutableMap.of("a", 1); - Map map = Maps.transformValues(underlying, Functions.identity()); - assertMapsEqual(underlying, map); - } - - public void testTransformPutEntryIsUnsupported() { - Map map = - Maps.transformValues(ImmutableMap.of("a", 1), Functions.toStringFunction()); - try { - map.put("b", "2"); - fail(); - } catch (UnsupportedOperationException expected) { - } - - try { - map.putAll(ImmutableMap.of("b", "2")); - fail(); - } catch (UnsupportedOperationException expected) { - } - - try { - map.entrySet().iterator().next().setValue("one"); - fail(); - } catch (UnsupportedOperationException expected) { - } - } - - public void testTransformRemoveEntry() { - Map underlying = Maps.newHashMap(); - underlying.put("a", 1); - Map map = Maps.transformValues(underlying, Functions.toStringFunction()); - assertEquals("1", map.remove("a")); - assertNull(map.remove("b")); - } - - public void testTransformEqualityOfMapsWithNullValues() { - Map underlying = Maps.newHashMap(); - underlying.put("a", null); - underlying.put("b", ""); - - Map map = - Maps.transformValues( - underlying, - new Function() { - @Override - public Boolean apply(@NullableDecl String from) { - return from == null; - } - }); - Map expected = ImmutableMap.of("a", true, "b", false); - assertMapsEqual(expected, map); - assertEquals(expected.get("a"), map.get("a")); - assertEquals(expected.containsKey("a"), map.containsKey("a")); - assertEquals(expected.get("b"), map.get("b")); - assertEquals(expected.containsKey("b"), map.containsKey("b")); - assertEquals(expected.get("c"), map.get("c")); - assertEquals(expected.containsKey("c"), map.containsKey("c")); - } - - public void testTransformReflectsUnderlyingMap() { - Map underlying = Maps.newHashMap(); - underlying.put("a", 1); - underlying.put("b", 2); - underlying.put("c", 3); - Map map = Maps.transformValues(underlying, Functions.toStringFunction()); - assertEquals(underlying.size(), map.size()); - - underlying.put("d", 4); - assertEquals(underlying.size(), map.size()); - assertEquals("4", map.get("d")); - - underlying.remove("c"); - assertEquals(underlying.size(), map.size()); - assertFalse(map.containsKey("c")); - - underlying.clear(); - assertEquals(underlying.size(), map.size()); - } - - public void testTransformChangesAreReflectedInUnderlyingMap() { - Map underlying = Maps.newLinkedHashMap(); - underlying.put("a", 1); - underlying.put("b", 2); - underlying.put("c", 3); - underlying.put("d", 4); - underlying.put("e", 5); - underlying.put("f", 6); - underlying.put("g", 7); - Map map = Maps.transformValues(underlying, Functions.toStringFunction()); - - map.remove("a"); - assertFalse(underlying.containsKey("a")); - - Set keys = map.keySet(); - keys.remove("b"); - assertFalse(underlying.containsKey("b")); - - Iterator keyIterator = keys.iterator(); - keyIterator.next(); - keyIterator.remove(); - assertFalse(underlying.containsKey("c")); - - Collection values = map.values(); - values.remove("4"); - assertFalse(underlying.containsKey("d")); - - Iterator valueIterator = values.iterator(); - valueIterator.next(); - valueIterator.remove(); - assertFalse(underlying.containsKey("e")); - - Set> entries = map.entrySet(); - Entry firstEntry = entries.iterator().next(); - entries.remove(firstEntry); - assertFalse(underlying.containsKey("f")); - - Iterator> entryIterator = entries.iterator(); - entryIterator.next(); - entryIterator.remove(); - assertFalse(underlying.containsKey("g")); - - assertTrue(underlying.isEmpty()); - assertTrue(map.isEmpty()); - assertTrue(keys.isEmpty()); - assertTrue(values.isEmpty()); - assertTrue(entries.isEmpty()); - } - - public void testTransformEquals() { - Map underlying = ImmutableMap.of("a", 0, "b", 1, "c", 2); - Map expected = Maps.transformValues(underlying, Functions.identity()); - - assertMapsEqual(expected, expected); - - Map equalToUnderlying = Maps.newTreeMap(); - equalToUnderlying.putAll(underlying); - Map map = - Maps.transformValues(equalToUnderlying, Functions.identity()); - assertMapsEqual(expected, map); - - map = - Maps.transformValues( - ImmutableMap.of("a", 1, "b", 2, "c", 3), - new Function() { - @Override - public Integer apply(Integer from) { - return from - 1; - } - }); - assertMapsEqual(expected, map); - } - - public void testTransformEntrySetContains() { - Map underlying = Maps.newHashMap(); - underlying.put("a", null); - underlying.put("b", true); - underlying.put(null, true); - - Map map = - Maps.transformValues( - underlying, - new Function() { - @Override - public Boolean apply(@NullableDecl Boolean from) { - return (from == null) ? true : null; - } - }); - - Set> entries = map.entrySet(); - assertTrue(entries.contains(Maps.immutableEntry("a", true))); - assertTrue(entries.contains(Maps.immutableEntry("b", (Boolean) null))); - assertTrue(entries.contains(Maps.immutableEntry((String) null, (Boolean) null))); - - assertFalse(entries.contains(Maps.immutableEntry("c", (Boolean) null))); - assertFalse(entries.contains(Maps.immutableEntry((String) null, true))); - } - - @Override - public void testKeySetRemoveAllNullFromEmpty() { - try { - super.testKeySetRemoveAllNullFromEmpty(); - } catch (RuntimeException tolerated) { - // GWT's HashMap.keySet().removeAll(null) doesn't throws NPE. - } - } - - @Override - public void testEntrySetRemoveAllNullFromEmpty() { - try { - super.testEntrySetRemoveAllNullFromEmpty(); - } catch (RuntimeException tolerated) { - // GWT's HashMap.entrySet().removeAll(null) doesn't throws NPE. - } + return transformValues(underlying, Functions.toStringFunction()); } } diff --git a/android/guava-tests/test/com/google/common/collect/MapsTransformValuesUnmodifiableIteratorTest.java b/android/guava-tests/test/com/google/common/collect/MapsTransformValuesUnmodifiableIteratorTest.java index 81a2d935bdbf..b07f50886101 100644 --- a/android/guava-tests/test/com/google/common/collect/MapsTransformValuesUnmodifiableIteratorTest.java +++ b/android/guava-tests/test/com/google/common/collect/MapsTransformValuesUnmodifiableIteratorTest.java @@ -16,16 +16,24 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Maps.transformValues; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.testing.MapInterfaceTest; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Maps#transformValues} when the backing map's views have iterators that don't @@ -34,6 +42,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class MapsTransformValuesUnmodifiableIteratorTest extends MapInterfaceTest { // TODO(jlevy): Move shared code of this class and MapsTransformValuesTest // to a superclass. @@ -132,18 +141,18 @@ public boolean retainAll(Collection c) { @Override protected Map makeEmptyMap() { - Map underlying = Maps.newHashMap(); - return Maps.transformValues( + Map underlying = new HashMap<>(); + return transformValues( new UnmodifiableIteratorMap(underlying), Functions.toStringFunction()); } @Override protected Map makePopulatedMap() { - Map underlying = Maps.newHashMap(); + Map underlying = new HashMap<>(); underlying.put("a", 1); underlying.put("b", 2); underlying.put("c", 3); - return Maps.transformValues( + return transformValues( new UnmodifiableIteratorMap(underlying), Functions.toStringFunction()); } @@ -174,13 +183,13 @@ private void assertMapsEqual(Map expected, Map map) { public void testTransformEmptyMapEquality() { Map map = - Maps.transformValues(ImmutableMap.of(), Functions.toStringFunction()); - assertMapsEqual(Maps.newHashMap(), map); + transformValues(ImmutableMap.of(), Functions.toStringFunction()); + assertMapsEqual(new HashMap<>(), map); } public void testTransformSingletonMapEquality() { Map map = - Maps.transformValues(ImmutableMap.of("a", 1), Functions.toStringFunction()); + transformValues(ImmutableMap.of("a", 1), Functions.toStringFunction()); Map expected = ImmutableMap.of("a", "1"); assertMapsEqual(expected, map); assertEquals(expected.get("a"), map.get("a")); @@ -188,51 +197,41 @@ public void testTransformSingletonMapEquality() { public void testTransformIdentityFunctionEquality() { Map underlying = ImmutableMap.of("a", 1); - Map map = Maps.transformValues(underlying, Functions.identity()); + Map map = transformValues(underlying, Functions.identity()); assertMapsEqual(underlying, map); } public void testTransformPutEntryIsUnsupported() { Map map = - Maps.transformValues(ImmutableMap.of("a", 1), Functions.toStringFunction()); - try { - map.put("b", "2"); - fail(); - } catch (UnsupportedOperationException expected) { - } + transformValues(ImmutableMap.of("a", 1), Functions.toStringFunction()); + assertThrows(UnsupportedOperationException.class, () -> map.put("b", "2")); - try { - map.putAll(ImmutableMap.of("b", "2")); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.putAll(ImmutableMap.of("b", "2"))); - try { - map.entrySet().iterator().next().setValue("one"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, + () -> map.entrySet().iterator().next().setValue("one")); } public void testTransformRemoveEntry() { - Map underlying = Maps.newHashMap(); + Map underlying = new HashMap<>(); underlying.put("a", 1); - Map map = Maps.transformValues(underlying, Functions.toStringFunction()); + Map map = transformValues(underlying, Functions.toStringFunction()); assertEquals("1", map.remove("a")); - assertNull(map.remove("b")); + assertThat(map.remove("b")).isNull(); } public void testTransformEqualityOfMapsWithNullValues() { - Map underlying = Maps.newHashMap(); + Map underlying = new HashMap<>(); underlying.put("a", null); underlying.put("b", ""); - Map map = - Maps.transformValues( + Map<@Nullable String, Boolean> map = + transformValues( underlying, - new Function() { + new Function<@Nullable String, Boolean>() { @Override - public Boolean apply(@NullableDecl String from) { + public Boolean apply(@Nullable String from) { return from == null; } }); @@ -247,11 +246,11 @@ public Boolean apply(@NullableDecl String from) { } public void testTransformReflectsUnderlyingMap() { - Map underlying = Maps.newHashMap(); + Map underlying = new HashMap<>(); underlying.put("a", 1); underlying.put("b", 2); underlying.put("c", 3); - Map map = Maps.transformValues(underlying, Functions.toStringFunction()); + Map map = transformValues(underlying, Functions.toStringFunction()); assertEquals(underlying.size(), map.size()); underlying.put("d", 4); @@ -267,7 +266,7 @@ public void testTransformReflectsUnderlyingMap() { } public void testTransformChangesAreReflectedInUnderlyingMap() { - Map underlying = Maps.newLinkedHashMap(); + Map underlying = new LinkedHashMap<>(); underlying.put("a", 1); underlying.put("b", 2); underlying.put("c", 3); @@ -275,7 +274,7 @@ public void testTransformChangesAreReflectedInUnderlyingMap() { underlying.put("e", 5); underlying.put("f", 6); underlying.put("g", 7); - Map map = Maps.transformValues(underlying, Functions.toStringFunction()); + Map map = transformValues(underlying, Functions.toStringFunction()); map.remove("a"); assertFalse(underlying.containsKey("a")); @@ -317,18 +316,17 @@ public void testTransformChangesAreReflectedInUnderlyingMap() { public void testTransformEquals() { Map underlying = ImmutableMap.of("a", 0, "b", 1, "c", 2); - Map expected = Maps.transformValues(underlying, Functions.identity()); + Map expected = transformValues(underlying, Functions.identity()); assertMapsEqual(expected, expected); Map equalToUnderlying = Maps.newTreeMap(); equalToUnderlying.putAll(underlying); - Map map = - Maps.transformValues(equalToUnderlying, Functions.identity()); + Map map = transformValues(equalToUnderlying, Functions.identity()); assertMapsEqual(expected, map); map = - Maps.transformValues( + transformValues( ImmutableMap.of("a", 1, "b", 2, "c", 3), new Function() { @Override @@ -340,28 +338,29 @@ public Integer apply(Integer from) { } public void testTransformEntrySetContains() { - Map underlying = Maps.newHashMap(); + Map<@Nullable String, @Nullable Boolean> underlying = new HashMap<>(); underlying.put("a", null); underlying.put("b", true); underlying.put(null, true); - Map map = - Maps.transformValues( + Map<@Nullable String, @Nullable Boolean> map = + transformValues( underlying, - new Function() { + new Function<@Nullable Boolean, @Nullable Boolean>() { @Override - public Boolean apply(@NullableDecl Boolean from) { + public @Nullable Boolean apply(@Nullable Boolean from) { return (from == null) ? true : null; } }); - Set> entries = map.entrySet(); - assertTrue(entries.contains(Maps.immutableEntry("a", true))); - assertTrue(entries.contains(Maps.immutableEntry("b", (Boolean) null))); - assertTrue(entries.contains(Maps.immutableEntry((String) null, (Boolean) null))); + Set> entries = map.entrySet(); + assertTrue(entries.contains(immutableEntry("a", true))); + assertTrue(entries.contains(Maps.immutableEntry("b", null))); + assertTrue( + entries.contains(Maps.<@Nullable String, @Nullable Boolean>immutableEntry(null, null))); - assertFalse(entries.contains(Maps.immutableEntry("c", (Boolean) null))); - assertFalse(entries.contains(Maps.immutableEntry((String) null, true))); + assertFalse(entries.contains(Maps.immutableEntry("c", null))); + assertFalse(entries.contains(Maps.<@Nullable String, Boolean>immutableEntry(null, true))); } @Override diff --git a/android/guava-tests/test/com/google/common/collect/MinMaxPriorityQueueTest.java b/android/guava-tests/test/com/google/common/collect/MinMaxPriorityQueueTest.java index 5471e3609d04..51acb256953d 100644 --- a/android/guava-tests/test/com/google/common/collect/MinMaxPriorityQueueTest.java +++ b/android/guava-tests/test/com/google/common/collect/MinMaxPriorityQueueTest.java @@ -16,13 +16,17 @@ package com.google.common.collect; -import static com.google.common.base.Objects.equal; import static com.google.common.collect.Platform.reduceExponentIfGwt; import static com.google.common.collect.Platform.reduceIterationsIfGwt; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Sets.newHashSet; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; +import static java.util.Collections.shuffle; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.IteratorFeature; import com.google.common.collect.testing.IteratorTester; import com.google.common.collect.testing.QueueTestSuiteBuilder; @@ -35,10 +39,13 @@ import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.PriorityQueue; import java.util.Queue; import java.util.Random; @@ -47,6 +54,8 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link MinMaxPriorityQueue}. @@ -54,11 +63,14 @@ * @author Alexei Stolboushkin * @author Sverre Sundsdal */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class MinMaxPriorityQueueTest extends TestCase { - private static final Ordering SOME_COMPARATOR = Ordering.natural().reverse(); + private static final Ordering SOME_COMPARATOR = Ordering.natural().reverse(); + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(MinMaxPriorityQueueTest.class); @@ -67,7 +79,7 @@ public static Test suite() { new TestStringQueueGenerator() { @Override protected Queue create(String[] elements) { - return MinMaxPriorityQueue.create(Arrays.asList(elements)); + return MinMaxPriorityQueue.create(asList(elements)); } }) .named("MinMaxPriorityQueue") @@ -92,6 +104,9 @@ public void testCreation_comparator() { assertSame(SOME_COMPARATOR, queue.comparator()); } + // We use the rawtypeToWildcard "cast" to make the test work with J2KT in other tests. Leaving one + // test without that cast to verify that using the raw Comparable works outside J2KT. + @J2ktIncompatible // J2KT's translation of raw Comparable is not a supertype of Int translation public void testCreation_expectedSize() { MinMaxPriorityQueue queue = MinMaxPriorityQueue.expectedSize(8).create(); assertEquals(8, queue.capacity()); @@ -108,7 +123,8 @@ public void testCreation_expectedSize_comparator() { } public void testCreation_maximumSize() { - MinMaxPriorityQueue queue = MinMaxPriorityQueue.maximumSize(42).create(); + MinMaxPriorityQueue queue = + rawtypeToWildcard(MinMaxPriorityQueue.maximumSize(42)).create(); assertEquals(11, queue.capacity()); assertEquals(42, queue.maximumSize); checkNatural(queue); @@ -124,7 +140,7 @@ public void testCreation_comparator_maximumSize() { public void testCreation_expectedSize_maximumSize() { MinMaxPriorityQueue queue = - MinMaxPriorityQueue.expectedSize(8).maximumSize(42).create(); + rawtypeToWildcard(MinMaxPriorityQueue.expectedSize(8)).maximumSize(42).create(); assertEquals(8, queue.capacity()); assertEquals(42, queue.maximumSize); checkNatural(queue); @@ -150,7 +166,8 @@ public void testCreation_comparator_withContents() { } public void testCreation_expectedSize_withContents() { - MinMaxPriorityQueue queue = MinMaxPriorityQueue.expectedSize(8).create(NUMBERS); + MinMaxPriorityQueue queue = + rawtypeToWildcard(MinMaxPriorityQueue.expectedSize(8)).create(NUMBERS); assertEquals(6, queue.size()); assertEquals(8, queue.capacity()); checkUnbounded(queue); @@ -158,7 +175,8 @@ public void testCreation_expectedSize_withContents() { } public void testCreation_maximumSize_withContents() { - MinMaxPriorityQueue queue = MinMaxPriorityQueue.maximumSize(42).create(NUMBERS); + MinMaxPriorityQueue queue = + rawtypeToWildcard(MinMaxPriorityQueue.maximumSize(42)).create(NUMBERS); assertEquals(6, queue.size()); assertEquals(11, queue.capacity()); assertEquals(42, queue.maximumSize); @@ -194,7 +212,8 @@ public void testHeapIntact() { Random random = new Random(0); int heapSize = 99; int numberOfModifications = 100; - MinMaxPriorityQueue mmHeap = MinMaxPriorityQueue.expectedSize(heapSize).create(); + MinMaxPriorityQueue mmHeap = + rawtypeToWildcard(MinMaxPriorityQueue.expectedSize(heapSize)).create(); /* * this map would contain the same exact elements as the MinMaxHeap; the * value in the map is the number of occurrences of the key. @@ -250,9 +269,9 @@ public void testSmall() { assertEquals(1, (int) mmHeap.peek()); assertEquals(1, (int) mmHeap.peekLast()); assertEquals(1, (int) mmHeap.pollLast()); - assertNull(mmHeap.peek()); - assertNull(mmHeap.peekLast()); - assertNull(mmHeap.pollLast()); + assertThat(mmHeap.peek()).isNull(); + assertThat(mmHeap.peekLast()).isNull(); + assertThat(mmHeap.pollLast()).isNull(); } public void testSmallMinHeap() { @@ -268,15 +287,15 @@ public void testSmallMinHeap() { assertEquals(3, (int) mmHeap.peekLast()); assertEquals(3, (int) mmHeap.peek()); assertEquals(3, (int) mmHeap.poll()); - assertNull(mmHeap.peekLast()); - assertNull(mmHeap.peek()); - assertNull(mmHeap.poll()); + assertThat(mmHeap.peekLast()).isNull(); + assertThat(mmHeap.peek()).isNull(); + assertThat(mmHeap.poll()).isNull(); } public void testRemove() { MinMaxPriorityQueue mmHeap = MinMaxPriorityQueue.create(); mmHeap.addAll(Lists.newArrayList(1, 2, 3, 4, 47, 1, 5, 3, 0)); - assertTrue("Heap is not intact initally", mmHeap.isIntact()); + assertTrue("Heap is not intact initially", mmHeap.isIntact()); assertEquals(9, mmHeap.size()); mmHeap.remove(5); assertEquals(8, mmHeap.size()); @@ -315,11 +334,7 @@ public void testIteratorPastEndException() { assertTrue("Iterator has reached end prematurely", it.hasNext()); it.next(); it.next(); - try { - it.next(); - fail("No exception thrown when iterating past end of heap"); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> it.next()); } public void testIteratorConcurrentModification() { @@ -330,16 +345,12 @@ public void testIteratorConcurrentModification() { it.next(); it.next(); mmHeap.remove(4); - try { - it.next(); - fail("No exception thrown when iterating a modified heap"); - } catch (ConcurrentModificationException expected) { - } + assertThrows(ConcurrentModificationException.class, () -> it.next()); } /** Tests a failure caused by fix to childless uncle issue. */ public void testIteratorRegressionChildlessUncle() { - final ArrayList initial = Lists.newArrayList(1, 15, 13, 8, 9, 10, 11, 14); + ArrayList initial = Lists.newArrayList(1, 15, 13, 8, 9, 10, 11, 14); MinMaxPriorityQueue q = MinMaxPriorityQueue.create(initial); assertIntact(q); q.remove(9); @@ -466,7 +477,8 @@ public void testIteratorInvalidatingIteratorRemove2() { } public void testRemoveFromStringHeap() { - MinMaxPriorityQueue mmHeap = MinMaxPriorityQueue.expectedSize(5).create(); + MinMaxPriorityQueue mmHeap = + rawtypeToWildcard(MinMaxPriorityQueue.expectedSize(5)).create(); Collections.addAll(mmHeap, "foo", "bar", "foobar", "barfoo", "larry", "sergey", "eric"); assertTrue("Heap is not intact initially", mmHeap.isIntact()); assertEquals("bar", mmHeap.peek()); @@ -483,7 +495,7 @@ public void testRemoveFromStringHeap() { public void testCreateWithOrdering() { MinMaxPriorityQueue mmHeap = - MinMaxPriorityQueue.orderedBy(Ordering.natural().reverse()).create(); + MinMaxPriorityQueue.orderedBy(Ordering.natural().reverse()).create(); Collections.addAll(mmHeap, "foo", "bar", "foobar", "barfoo", "larry", "sergey", "eric"); assertTrue("Heap is not intact initially", mmHeap.isIntact()); assertEquals("sergey", mmHeap.peek()); @@ -492,22 +504,23 @@ public void testCreateWithOrdering() { public void testCreateWithCapacityAndOrdering() { MinMaxPriorityQueue mmHeap = - MinMaxPriorityQueue.orderedBy(Ordering.natural().reverse()).expectedSize(5).create(); + MinMaxPriorityQueue.orderedBy(Ordering.natural().reverse()) + .expectedSize(5) + .create(); Collections.addAll(mmHeap, 1, 7, 2, 56, 2, 5, 23, 68, 0, 3); assertTrue("Heap is not intact initially", mmHeap.isIntact()); assertEquals(68, (int) mmHeap.peek()); assertEquals(0, (int) mmHeap.peekLast()); } - private > void runIterator(final List values, int steps) - throws Exception { + private > void runIterator(List values, int steps) throws Exception { IteratorTester tester = new IteratorTester( steps, IteratorFeature.MODIFIABLE, - Lists.newLinkedList(values), + new LinkedList<>(values), IteratorTester.KnownOrder.UNKNOWN_ORDER) { - private MinMaxPriorityQueue mmHeap; + private @Nullable MinMaxPriorityQueue mmHeap; @Override protected Iterator newTargetIterator() { @@ -517,7 +530,7 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(Sets.newHashSet(elements), Sets.newHashSet(mmHeap.iterator())); + assertEquals(new HashSet<>(elements), newHashSet(mmHeap.iterator())); assertIntact(mmHeap); } }; @@ -526,7 +539,7 @@ protected void verify(List elements) { public void testIteratorTester() throws Exception { Random random = new Random(0); - List list = Lists.newArrayList(); + List list = new ArrayList<>(); for (int i = 0; i < 3; i++) { list.add(random.nextInt()); } @@ -542,7 +555,8 @@ public void testRemoveAt() { Random random = new Random(seed); int heapSize = 999; int numberOfModifications = reduceIterationsIfGwt(500); - MinMaxPriorityQueue mmHeap = MinMaxPriorityQueue.expectedSize(heapSize).create(); + MinMaxPriorityQueue mmHeap = + rawtypeToWildcard(MinMaxPriorityQueue.expectedSize(heapSize)).create(); for (int i = 0; i < heapSize; i++) { mmHeap.add(random.nextInt()); } @@ -702,7 +716,7 @@ public void testRegression_dataCorruption() { int size = 8; List expected = createOrderedList(size); MinMaxPriorityQueue q = MinMaxPriorityQueue.create(expected); - List contents = Lists.newArrayList(expected); + List contents = new ArrayList<>(expected); List elements = Lists.newArrayListWithCapacity(size); while (!q.isEmpty()) { assertThat(q).containsExactlyElementsIn(contents); @@ -741,9 +755,9 @@ public void testRandomRemoves() { Random random = new Random(0); for (int attempts = 0; attempts < reduceIterationsIfGwt(1000); attempts++) { ArrayList elements = createOrderedList(10); - Collections.shuffle(elements, random); + shuffle(elements, random); MinMaxPriorityQueue queue = MinMaxPriorityQueue.create(elements); - Collections.shuffle(elements, random); + shuffle(elements, random); for (Integer element : elements) { assertThat(queue.remove(element)).isTrue(); assertIntact(queue); @@ -864,28 +878,15 @@ public void testIsEvenLevel() { // since isEvenLevel adds 1, we need to do - 2. assertTrue(MinMaxPriorityQueue.isEvenLevel((1 << 31) - 2)); assertTrue(MinMaxPriorityQueue.isEvenLevel(Integer.MAX_VALUE - 1)); - try { - MinMaxPriorityQueue.isEvenLevel((1 << 31) - 1); - fail("Should overflow"); - } catch (IllegalStateException expected) { - } - try { - MinMaxPriorityQueue.isEvenLevel(Integer.MAX_VALUE); - fail("Should overflow"); - } catch (IllegalStateException expected) { - } - try { - MinMaxPriorityQueue.isEvenLevel(1 << 31); - fail("Should be negative"); - } catch (IllegalStateException expected) { - } - try { - MinMaxPriorityQueue.isEvenLevel(Integer.MIN_VALUE); - fail("Should be negative"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> MinMaxPriorityQueue.isEvenLevel((1 << 31) - 1)); + assertThrows( + IllegalStateException.class, () -> MinMaxPriorityQueue.isEvenLevel(Integer.MAX_VALUE)); + assertThrows(IllegalStateException.class, () -> MinMaxPriorityQueue.isEvenLevel(1 << 31)); + assertThrows( + IllegalStateException.class, () -> MinMaxPriorityQueue.isEvenLevel(Integer.MIN_VALUE)); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -942,18 +943,27 @@ private static void assertIntactUsingStartedWith( } } - private static void assertEqualsUsingSeed(long seed, Object expected, Object actual) { - if (!equal(actual, expected)) { + private static void assertEqualsUsingSeed( + long seed, @Nullable Object expected, @Nullable Object actual) { + if (!Objects.equals(actual, expected)) { // fail(), but with the JUnit-supplied message. assertEquals("Using seed " + seed, expected, actual); } } private static void assertEqualsUsingStartedWith( - Collection startedWith, Object expected, Object actual) { - if (!equal(actual, expected)) { + Collection startedWith, @Nullable Object expected, @Nullable Object actual) { + if (!Objects.equals(actual, expected)) { // fail(), but with the JUnit-supplied message. assertEquals("Started with " + startedWith, expected, actual); } } + + // J2kt cannot translate the Comparable rawtype in a usable way (it becomes Comparable + // but types are typically only Comparable to themselves). + @SuppressWarnings({"rawtypes", "unchecked"}) + private static MinMaxPriorityQueue.Builder> rawtypeToWildcard( + MinMaxPriorityQueue.Builder builder) { + return (MinMaxPriorityQueue.Builder) builder; + } } diff --git a/android/guava-tests/test/com/google/common/collect/MoreCollectorsTest.java b/android/guava-tests/test/com/google/common/collect/MoreCollectorsTest.java new file mode 100644 index 000000000000..d0e7af517d8f --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/MoreCollectorsTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.MoreCollectors.onlyElement; +import static com.google.common.collect.MoreCollectors.toOptional; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.annotations.GwtCompatible; +import java.util.NoSuchElementException; +import java.util.stream.Stream; +import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Tests for {@code MoreCollectors}. + * + * @author Louis Wasserman + */ +@GwtCompatible +@NullMarked +public class MoreCollectorsTest extends TestCase { + public void testToOptionalEmpty() { + assertThat(Stream.empty().collect(toOptional())).isEmpty(); + } + + public void testToOptionalSingleton() { + assertThat(Stream.of(1).collect(toOptional())).hasValue(1); + } + + public void testToOptionalNull() { + Stream<@Nullable Object> stream = Stream.of((Object) null); + assertThrows(NullPointerException.class, () -> stream.collect(toOptional())); + } + + public void testToOptionalMultiple() { + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> Stream.of(1, 2).collect(toOptional())); + assertThat(expected).hasMessageThat().contains("1, 2"); + } + + public void testToOptionalMultipleWithNull() { + assertThrows(NullPointerException.class, () -> Stream.of(1, null).collect(toOptional())); + } + + public void testToOptionalMany() { + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> Stream.of(1, 2, 3, 4, 5, 6).collect(toOptional())); + assertThat(expected).hasMessageThat().contains("1, 2, 3, 4, 5, ..."); + } + + public void testOnlyElement() { + assertThrows(NoSuchElementException.class, () -> Stream.empty().collect(onlyElement())); + } + + public void testOnlyElementSingleton() { + assertThat(Stream.of(1).collect(onlyElement())).isEqualTo(1); + } + + public void testOnlyElementNull() { + assertThat(Stream.<@Nullable Object>of((Object) null).collect(onlyElement())).isNull(); + } + + public void testOnlyElementMultiple() { + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> Stream.of(1, 2).collect(onlyElement())); + assertThat(expected).hasMessageThat().contains("1, 2"); + } + + public void testOnlyElementMany() { + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> Stream.of(1, 2, 3, 4, 5, 6).collect(onlyElement())); + assertThat(expected).hasMessageThat().contains("1, 2, 3, 4, 5, ..."); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/MultimapBuilderTest.java b/android/guava-tests/test/com/google/common/collect/MultimapBuilderTest.java index 5528aba94c67..8806abdd10a1 100644 --- a/android/guava-tests/test/com/google/common/collect/MultimapBuilderTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultimapBuilderTest.java @@ -18,7 +18,9 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.MultimapBuilder.MultimapBuilderWithKeys; +import com.google.common.collect.MultimapBuilder.SortedSetMultimapBuilder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; @@ -27,34 +29,41 @@ import java.util.SortedMap; import java.util.SortedSet; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link MultimapBuilder}. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class MultimapBuilderTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // doesn't build without explicit type parameters on build() methods public void testGenerics() { - ListMultimap a = MultimapBuilder.hashKeys().arrayListValues().build(); - SortedSetMultimap b = MultimapBuilder.linkedHashKeys().treeSetValues().build(); - SetMultimap c = + ListMultimap unusedA = MultimapBuilder.hashKeys().arrayListValues().build(); + SortedSetMultimap unusedB = + MultimapBuilder.linkedHashKeys().treeSetValues().build(); + SetMultimap unusedC = MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER).hashSetValues().build(); } public void testGenerics_gwtCompatible() { - ListMultimap a = + ListMultimap unusedA = MultimapBuilder.hashKeys().arrayListValues().build(); - SortedSetMultimap b = - MultimapBuilder.linkedHashKeys().treeSetValues().build(); - SetMultimap c = + SortedSetMultimap unusedB = + rawtypeToWildcard(MultimapBuilder.linkedHashKeys().treeSetValues()) + .build(); + SetMultimap unusedC = MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER) .hashSetValues() .build(); } + @J2ktIncompatible @GwtIncompatible // doesn't build without explicit type parameters on build() methods public void testTreeKeys() { ListMultimap multimap = MultimapBuilder.treeKeys().arrayListValues().build(); @@ -64,13 +73,29 @@ public void testTreeKeys() { public void testTreeKeys_gwtCompatible() { ListMultimap multimap = - MultimapBuilder.treeKeys().arrayListValues().build(); + rawtypeToWildcard(MultimapBuilder.treeKeys()).arrayListValues().build(); assertTrue(multimap.keySet() instanceof SortedSet); assertTrue(multimap.asMap() instanceof SortedMap); } - @GwtIncompatible // serialization - public void testSerialization() throws Exception { + // J2kt cannot translate the Comparable rawtype in a usable way (it becomes Comparable + // but types are typically only Comparable to themselves). + @SuppressWarnings({"rawtypes", "unchecked"}) + private static MultimapBuilderWithKeys> rawtypeToWildcard( + MultimapBuilderWithKeys treeKeys) { + return (MultimapBuilderWithKeys) treeKeys; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static + SortedSetMultimapBuilder> rawtypeToWildcard( + SortedSetMultimapBuilder setMultimapBuilder) { + return (SortedSetMultimapBuilder) setMultimapBuilder; + } + + @GwtIncompatible + @J2ktIncompatible + public void testSerialization() throws Exception { for (MultimapBuilderWithKeys builderWithKeys : ImmutableList.of( MultimapBuilder.hashKeys(), @@ -93,15 +118,17 @@ public void testSerialization() throws Exception { } } - @GwtIncompatible // serialization - private static void reserializeAndAssert(Object object) throws Exception { + @GwtIncompatible + @J2ktIncompatible + private static void reserializeAndAssert(Object object) throws Exception { Object copy = reserialize(object); assertEquals(object, copy); assertEquals(object.getClass(), copy.getClass()); } - @GwtIncompatible // serialization - private static Object reserialize(Object object) throws Exception { + @GwtIncompatible + @J2ktIncompatible + private static Object reserialize(Object object) throws Exception { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); new ObjectOutputStream(bytes).writeObject(object); return new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())).readObject(); diff --git a/android/guava-tests/test/com/google/common/collect/MultimapsCollectionTest.java b/android/guava-tests/test/com/google/common/collect/MultimapsCollectionTest.java index e684c3b1f095..882abdd8e131 100644 --- a/android/guava-tests/test/com/google/common/collect/MultimapsCollectionTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultimapsCollectionTest.java @@ -16,7 +16,12 @@ package com.google.common.collect; -import static com.google.common.collect.Maps.newHashMap; +import static com.google.common.base.Predicates.equalTo; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Multimaps.filterKeys; +import static com.google.common.collect.Multimaps.filterValues; +import static com.google.common.collect.Multimaps.synchronizedListMultimap; import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES; import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE; @@ -25,12 +30,11 @@ import static com.google.common.collect.testing.google.MultisetElementSetTester.getElementSetDuplicateInitializingMethods; import static com.google.common.collect.testing.google.MultisetIteratorTester.getIteratorDuplicateInitializingMethods; import static com.google.common.collect.testing.google.MultisetRemoveTester.getRemoveDuplicateInitializingMethods; -import static java.lang.reflect.Proxy.newProxyInstance; +import static com.google.common.reflect.Reflection.newProxy; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Ascii; import com.google.common.base.Function; -import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.collect.Maps.EntryTransformer; @@ -53,9 +57,8 @@ import com.google.common.collect.testing.google.TestSetMultimapGenerator; import com.google.common.collect.testing.google.TestStringListMultimapGenerator; import com.google.common.collect.testing.google.TestStringMultisetGenerator; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -64,6 +67,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Run collection tests on wrappers from {@link Multimaps}. @@ -71,6 +75,8 @@ * @author Jared Levy */ @GwtIncompatible // suite // TODO(cpovirk): set up collect/gwt/suites version +@NullUnmarked +@AndroidIncompatible // test-suite builders public class MultimapsCollectionTest extends TestCase { private static final Feature[] FOR_MAP_FEATURES_ONE = { @@ -139,18 +145,13 @@ static PopulatableMapAsMultimap create() { @SuppressWarnings("unchecked") // all methods throw immediately PopulatableMapAsMultimap() { - this.map = newHashMap(); + this.map = new HashMap<>(); this.unusableDelegate = (SetMultimap) - newProxyInstance( - SetMultimap.class.getClassLoader(), - new Class[] {SetMultimap.class}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable { - throw new UnsupportedOperationException(); - } + newProxy( + SetMultimap.class, + (proxy, method, args) -> { + throw new UnsupportedOperationException(); }); } @@ -175,11 +176,11 @@ abstract static class TestEntriesGenerator @Override public SampleElements> samples() { return new SampleElements<>( - Maps.immutableEntry("bar", 1), - Maps.immutableEntry("bar", 2), - Maps.immutableEntry("foo", 3), - Maps.immutableEntry("bar", 3), - Maps.immutableEntry("cat", 2)); + immutableEntry("bar", 1), + immutableEntry("bar", 2), + immutableEntry("foo", 3), + immutableEntry("bar", 3), + immutableEntry("cat", 2)); } @Override @@ -215,22 +216,6 @@ public List> create(Object... elements) { } } - private static final Predicate> FILTER_GET_PREDICATE = - new Predicate>() { - @Override - public boolean apply(Entry entry) { - return !"badvalue".equals(entry.getValue()) && 55556 != entry.getKey(); - } - }; - - private static final Predicate> FILTER_KEYSET_PREDICATE = - new Predicate>() { - @Override - public boolean apply(Entry entry) { - return !"badkey".equals(entry.getKey()) && 55556 != entry.getValue(); - } - }; - public static Test suite() { TestSuite suite = new TestSuite(); @@ -243,8 +228,7 @@ public static Test suite() { @Override protected ListMultimap create(Entry[] entries) { ListMultimap multimap = - Multimaps.synchronizedListMultimap( - ArrayListMultimap.create()); + synchronizedListMultimap(ArrayListMultimap.create()); for (Entry entry : entries) { multimap.put(entry.getKey(), entry.getValue()); } @@ -373,7 +357,7 @@ public M create(Object... elements) { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -492,7 +476,7 @@ public SampleElements> samples() { @SuppressWarnings("unchecked") @Override public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override @@ -577,8 +561,7 @@ SetMultimap filter(SetMultimap multimap) { multimap.put("foo", 17); multimap.put("bar", 32); multimap.put("foo", 16); - return Multimaps.filterKeys( - multimap, Predicates.not(Predicates.in(ImmutableSet.of("foo", "bar")))); + return filterKeys(multimap, not(Predicates.in(ImmutableSet.of("foo", "bar")))); } }) .named("Multimaps.filterKeys[SetMultimap, Predicate]") @@ -599,8 +582,7 @@ ListMultimap filter(ListMultimap multimap) { multimap.put("foo", 17); multimap.put("bar", 32); multimap.put("foo", 16); - return Multimaps.filterKeys( - multimap, Predicates.not(Predicates.in(ImmutableSet.of("foo", "bar")))); + return filterKeys(multimap, not(Predicates.in(ImmutableSet.of("foo", "bar")))); } }) .named("Multimaps.filterKeys[ListMultimap, Predicate]") @@ -620,10 +602,8 @@ ListMultimap filter(ListMultimap multimap) { multimap.put("foo", 17); multimap.put("bar", 32); multimap.put("foo", 16); - multimap = - Multimaps.filterKeys(multimap, Predicates.not(Predicates.equalTo("foo"))); - return Multimaps.filterKeys( - multimap, Predicates.not(Predicates.equalTo("bar"))); + multimap = filterKeys(multimap, not(equalTo("foo"))); + return filterKeys(multimap, not(equalTo("bar"))); } }) .named("Multimaps.filterKeys[Multimaps.filterKeys[ListMultimap], Predicate]") @@ -643,8 +623,8 @@ SetMultimap filter(SetMultimap multimap) { multimap.put("one", 314); multimap.put("two", 159); multimap.put("one", 265); - return Multimaps.filterValues( - multimap, Predicates.not(Predicates.in(ImmutableSet.of(314, 159, 265)))); + return filterValues( + multimap, not(Predicates.in(ImmutableSet.of(314, 159, 265)))); } }) .named("Multimaps.filterValues[SetMultimap, Predicate]") @@ -664,7 +644,7 @@ SetMultimap filter(SetMultimap multimap) { ImmutableSetMultimap.of("foo", 314, "one", 159, "two", 265, "bar", 358); multimap.putAll(badEntries); return Multimaps.filterEntries( - multimap, Predicates.not(Predicates.in(badEntries.entries()))); + multimap, not(Predicates.in(badEntries.entries()))); } }) .named("Multimaps.filterEntries[SetMultimap, Predicate]") @@ -684,10 +664,9 @@ SetMultimap filter(SetMultimap multimap) { ImmutableSetMultimap.of("foo", 314, "one", 159, "two", 265, "bar", 358); multimap.putAll(badEntries); multimap = - Multimaps.filterKeys( - multimap, Predicates.not(Predicates.in(ImmutableSet.of("foo", "bar")))); + filterKeys(multimap, not(Predicates.in(ImmutableSet.of("foo", "bar")))); return Multimaps.filterEntries( - multimap, Predicates.not(Predicates.in(badEntries.entries()))); + multimap, not(Predicates.in(badEntries.entries()))); } }) .named("Multimaps.filterEntries[Multimaps.filterKeys[SetMultimap]]") @@ -709,10 +688,8 @@ SetMultimap filter(SetMultimap multimap) { multimap = Multimaps.filterEntries( multimap, - Predicates.not( - Predicates.in(ImmutableMap.of("one", 159, "two", 265).entrySet()))); - return Multimaps.filterKeys( - multimap, Predicates.not(Predicates.in(ImmutableSet.of("foo", "bar")))); + not(Predicates.in(ImmutableMap.of("one", 159, "two", 265).entrySet()))); + return filterKeys(multimap, not(Predicates.in(ImmutableSet.of("foo", "bar")))); } }) .named("Multimaps.filterKeys[Multimaps.filterEntries[SetMultimap]]") @@ -731,10 +708,8 @@ SetMultimap filter(SetMultimap multimap) { ImmutableSetMultimap badEntries = ImmutableSetMultimap.of("foo", 314, "bar", 358); multimap.putAll(badEntries); - multimap = - Multimaps.filterKeys(multimap, Predicates.not(Predicates.equalTo("foo"))); - multimap = - Multimaps.filterKeys(multimap, Predicates.not(Predicates.equalTo("bar"))); + multimap = filterKeys(multimap, not(equalTo("foo"))); + multimap = filterKeys(multimap, not(equalTo("bar"))); return multimap; } }) diff --git a/android/guava-tests/test/com/google/common/collect/MultimapsFilterEntriesAsMapTest.java b/android/guava-tests/test/com/google/common/collect/MultimapsFilterEntriesAsMapTest.java index 314a12774e8d..528fe4724557 100644 --- a/android/guava-tests/test/com/google/common/collect/MultimapsFilterEntriesAsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultimapsFilterEntriesAsMapTest.java @@ -21,6 +21,8 @@ import java.util.Collection; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import org.jspecify.annotations.NullUnmarked; /** * Tests for Multimaps.filterEntries().asMap(). @@ -28,12 +30,13 @@ * @author Jared Levy */ @GwtIncompatible(value = "untested") +@NullUnmarked public class MultimapsFilterEntriesAsMapTest extends AbstractMultimapAsMapImplementsMapTest { private static final Predicate> PREDICATE = new Predicate>() { @Override public boolean apply(Entry entry) { - return !"badkey".equals(entry.getKey()) && 55556 != entry.getValue(); + return !Objects.equals(entry.getKey(), "badkey") && entry.getValue() != 55556; } }; diff --git a/android/guava-tests/test/com/google/common/collect/MultimapsTest.java b/android/guava-tests/test/com/google/common/collect/MultimapsTest.java index 1a8f84e5ab9e..3f61eb21a094 100644 --- a/android/guava-tests/test/com/google/common/collect/MultimapsTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultimapsTest.java @@ -18,14 +18,24 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Multimaps.filterKeys; +import static com.google.common.collect.Multimaps.synchronizedListMultimap; +import static com.google.common.collect.Multimaps.synchronizedMultimap; +import static com.google.common.collect.Multimaps.synchronizedSetMultimap; +import static com.google.common.collect.Multimaps.synchronizedSortedSetMultimap; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.testing.Helpers.nefariousMapEntry; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Predicates; @@ -44,6 +54,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -56,20 +67,22 @@ import java.util.SortedSet; import java.util.TreeSet; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code Multimaps}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class MultimapsTest extends TestCase { private static final Comparator INT_COMPARATOR = Ordering.natural().reverse().nullsFirst(); - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testUnmodifiableListMultimapShortCircuit() { ListMultimap mod = ArrayListMultimap.create(); ListMultimap unmod = Multimaps.unmodifiableListMultimap(mod); @@ -82,7 +95,7 @@ public void testUnmodifiableListMultimapShortCircuit() { immutable, Multimaps.unmodifiableListMultimap((ListMultimap) immutable)); } - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testUnmodifiableSetMultimapShortCircuit() { SetMultimap mod = HashMultimap.create(); SetMultimap unmod = Multimaps.unmodifiableSetMultimap(mod); @@ -95,7 +108,7 @@ public void testUnmodifiableSetMultimapShortCircuit() { immutable, Multimaps.unmodifiableSetMultimap((SetMultimap) immutable)); } - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testUnmodifiableMultimapShortCircuit() { Multimap mod = HashMultimap.create(); Multimap unmod = Multimaps.unmodifiableMultimap(mod); @@ -108,9 +121,11 @@ public void testUnmodifiableMultimapShortCircuit() { @GwtIncompatible // slow (~10s) public void testUnmodifiableArrayListMultimap() { - checkUnmodifiableMultimap(ArrayListMultimap.create(), true); + checkUnmodifiableMultimap( + ArrayListMultimap.<@Nullable String, @Nullable Integer>create(), true); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializingUnmodifiableArrayListMultimap() { Multimap unmodifiable = @@ -138,9 +153,10 @@ public void testUnmodifiableLinkedListMultimapRandomAccess() { @GwtIncompatible // slow (~10s) public void testUnmodifiableHashMultimap() { - checkUnmodifiableMultimap(HashMultimap.create(), false); + checkUnmodifiableMultimap(HashMultimap.<@Nullable String, @Nullable Integer>create(), false); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializingUnmodifiableHashMultimap() { Multimap unmodifiable = @@ -153,6 +169,7 @@ public void testUnmodifiableTreeMultimap() { checkUnmodifiableMultimap(TreeMultimap.create(), false, "null", 42); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializingUnmodifiableTreeMultimap() { Multimap unmodifiable = @@ -161,16 +178,19 @@ public void testSerializingUnmodifiableTreeMultimap() { } @GwtIncompatible // slow (~10s) + @J2ktIncompatible // Synchronized public void testUnmodifiableSynchronizedArrayListMultimap() { checkUnmodifiableMultimap( - Multimaps.synchronizedListMultimap(ArrayListMultimap.create()), true); + synchronizedListMultimap(ArrayListMultimap.<@Nullable String, @Nullable Integer>create()), + true); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializingUnmodifiableSynchronizedArrayListMultimap() { Multimap unmodifiable = prepareUnmodifiableTests( - Multimaps.synchronizedListMultimap(ArrayListMultimap.create()), + synchronizedListMultimap(ArrayListMultimap.create()), true, null, null); @@ -178,36 +198,37 @@ public void testSerializingUnmodifiableSynchronizedArrayListMultimap() { } @GwtIncompatible // slow (~10s) + @J2ktIncompatible // Synchronized public void testUnmodifiableSynchronizedHashMultimap() { checkUnmodifiableMultimap( - Multimaps.synchronizedSetMultimap(HashMultimap.create()), false); + synchronizedSetMultimap(HashMultimap.<@Nullable String, @Nullable Integer>create()), false); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializingUnmodifiableSynchronizedHashMultimap() { Multimap unmodifiable = prepareUnmodifiableTests( - Multimaps.synchronizedSetMultimap(HashMultimap.create()), - false, - null, - null); + synchronizedSetMultimap(HashMultimap.create()), false, null, null); SerializableTester.reserializeAndAssert(unmodifiable); } @GwtIncompatible // slow (~10s) + @J2ktIncompatible // Synchronized public void testUnmodifiableSynchronizedTreeMultimap() { TreeMultimap delegate = TreeMultimap.create(Ordering.natural(), INT_COMPARATOR); - SortedSetMultimap multimap = Multimaps.synchronizedSortedSetMultimap(delegate); + SortedSetMultimap multimap = synchronizedSortedSetMultimap(delegate); checkUnmodifiableMultimap(multimap, false, "null", 42); assertSame(INT_COMPARATOR, multimap.valueComparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerializingUnmodifiableSynchronizedTreeMultimap() { TreeMultimap delegate = TreeMultimap.create(Ordering.natural(), INT_COMPARATOR); - SortedSetMultimap multimap = Multimaps.synchronizedSortedSetMultimap(delegate); + SortedSetMultimap multimap = synchronizedSortedSetMultimap(delegate); Multimap unmodifiable = prepareUnmodifiableTests(multimap, false, "null", 42); SerializableTester.reserializeAndAssert(unmodifiable); assertSame(INT_COMPARATOR, multimap.valueComparator()); @@ -227,25 +248,13 @@ public void testUnmodifiableMultimapEntries() { Multimap mod = HashMultimap.create(); Multimap unmod = Multimaps.unmodifiableMultimap(mod); mod.put("foo", 1); - Entry entry = unmod.entries().iterator().next(); - try { - entry.setValue(2); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - entry = (Entry) unmod.entries().toArray()[0]; - try { - entry.setValue(2); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + Entry fromIterator = unmod.entries().iterator().next(); + assertThrows(UnsupportedOperationException.class, () -> fromIterator.setValue(2)); + Entry fromToArray = (Entry) unmod.entries().toArray()[0]; + assertThrows(UnsupportedOperationException.class, () -> fromToArray.setValue(2)); Entry[] array = (Entry[]) new Entry[2]; assertSame(array, unmod.entries().toArray(array)); - try { - array[0].setValue(2); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> array[0].setValue(2)); assertFalse(unmod.entries().contains(nefariousMapEntry("pwnd", 2))); assertFalse(unmod.keys().contains("pwnd")); } @@ -255,7 +264,7 @@ public void testUnmodifiableMultimapEntries() { * multimap must support null keys and values. */ private static void checkUnmodifiableMultimap( - Multimap multimap, boolean permitsDuplicates) { + Multimap<@Nullable String, @Nullable Integer> multimap, boolean permitsDuplicates) { checkUnmodifiableMultimap(multimap, permitsDuplicates, null, null); } @@ -265,10 +274,10 @@ private static void checkUnmodifiableMultimap( * involving nulls. */ private static void checkUnmodifiableMultimap( - Multimap multimap, + Multimap<@Nullable String, @Nullable Integer> multimap, boolean permitsDuplicates, - @NullableDecl String nullKey, - @NullableDecl Integer nullValue) { + @Nullable String nullKey, + @Nullable Integer nullValue) { Multimap unmodifiable = prepareUnmodifiableTests(multimap, permitsDuplicates, nullKey, nullValue); @@ -288,17 +297,17 @@ private static void checkUnmodifiableMultimap( assertEquals(multimap, unmodifiable); assertThat(unmodifiable.asMap().get("bar")).containsExactly(5, -1); - assertNull(unmodifiable.asMap().get("missing")); + assertThat(unmodifiable.asMap().get("missing")).isNull(); assertFalse(unmodifiable.entries() instanceof Serializable); } /** Prepares the multimap for unmodifiable tests, returning an unmodifiable view of the map. */ - private static Multimap prepareUnmodifiableTests( - Multimap multimap, + private static Multimap<@Nullable String, @Nullable Integer> prepareUnmodifiableTests( + Multimap<@Nullable String, @Nullable Integer> multimap, boolean permitsDuplicates, - @NullableDecl String nullKey, - @NullableDecl Integer nullValue) { + @Nullable String nullKey, + @Nullable Integer nullValue) { multimap.clear(); multimap.put("foo", 1); multimap.put("foo", 2); @@ -316,21 +325,26 @@ private static Multimap prepareUnmodifiableTests( assertEquals(8, multimap.size()); } - Multimap unmodifiable; + Multimap<@Nullable String, @Nullable Integer> unmodifiable; if (multimap instanceof SortedSetMultimap) { unmodifiable = - Multimaps.unmodifiableSortedSetMultimap((SortedSetMultimap) multimap); + Multimaps.unmodifiableSortedSetMultimap( + (SortedSetMultimap<@Nullable String, @Nullable Integer>) multimap); } else if (multimap instanceof SetMultimap) { - unmodifiable = Multimaps.unmodifiableSetMultimap((SetMultimap) multimap); + unmodifiable = + Multimaps.unmodifiableSetMultimap( + (SetMultimap<@Nullable String, @Nullable Integer>) multimap); } else if (multimap instanceof ListMultimap) { - unmodifiable = Multimaps.unmodifiableListMultimap((ListMultimap) multimap); + unmodifiable = + Multimaps.unmodifiableListMultimap( + (ListMultimap<@Nullable String, @Nullable Integer>) multimap); } else { unmodifiable = Multimaps.unmodifiableMultimap(multimap); } return unmodifiable; } - private static void assertUnmodifiableIterableInTandem( + private static void assertUnmodifiableIterableInTandem( Iterable unmodifiable, Iterable modifiable) { UnmodifiableCollectionTests.assertIteratorIsUnmodifiable(unmodifiable.iterator()); UnmodifiableCollectionTests.assertIteratorsInOrder( @@ -383,7 +397,7 @@ public void testAsMap_sortedSetMultimap() { } public void testForMap() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("foo", 1); map.put("bar", 2); Multimap multimap = HashMultimap.create(); @@ -404,28 +418,15 @@ public void testForMap() { assertTrue(multimapView.containsKey("foo")); assertTrue(multimapView.containsValue(1)); assertTrue(multimapView.containsEntry("bar", 2)); - assertEquals(Collections.singleton(1), multimapView.get("foo")); - assertEquals(Collections.singleton(2), multimapView.get("bar")); - try { - multimapView.put("baz", 3); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - multimapView.putAll("baz", Collections.singleton(3)); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - multimapView.putAll(multimap); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - multimapView.replaceValues("foo", Collections.emptySet()); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertEquals(singleton(1), multimapView.get("foo")); + assertEquals(singleton(2), multimapView.get("bar")); + assertThrows(UnsupportedOperationException.class, () -> multimapView.put("baz", 3)); + assertThrows( + UnsupportedOperationException.class, () -> multimapView.putAll("baz", singleton(3))); + assertThrows(UnsupportedOperationException.class, () -> multimapView.putAll(multimap)); + assertThrows( + UnsupportedOperationException.class, + () -> multimapView.replaceValues("foo", Collections.emptySet())); multimapView.remove("bar", 2); assertFalse(multimapView.containsKey("bar")); assertFalse(map.containsKey("bar")); @@ -435,7 +436,7 @@ public void testForMap() { assertThat(multimapView.values()).contains(1); assertThat(multimapView.entries()).contains(Maps.immutableEntry("foo", 1)); assertThat(multimapView.asMap().entrySet()) - .contains(Maps.immutableEntry("foo", (Collection) Collections.singleton(1))); + .contains(Maps.immutableEntry("foo", (Collection) singleton(1))); multimapView.clear(); assertFalse(multimapView.containsKey("foo")); assertFalse(map.containsKey("foo")); @@ -448,9 +449,10 @@ public void testForMap() { assertEquals(multimapView, ArrayListMultimap.create()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testForMapSerialization() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("foo", 1); map.put("bar", 2); Multimap multimapView = Multimaps.forMap(map); @@ -458,42 +460,42 @@ public void testForMapSerialization() { } public void testForMapRemoveAll() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("foo", 1); map.put("bar", 2); map.put("cow", 3); Multimap multimap = Multimaps.forMap(map); assertEquals(3, multimap.size()); - assertEquals(Collections.emptySet(), multimap.removeAll("dog")); + assertEquals(emptySet(), multimap.removeAll("dog")); assertEquals(3, multimap.size()); assertTrue(multimap.containsKey("bar")); - assertEquals(Collections.singleton(2), multimap.removeAll("bar")); + assertEquals(singleton(2), multimap.removeAll("bar")); assertEquals(2, multimap.size()); assertFalse(multimap.containsKey("bar")); } public void testForMapAsMap() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("foo", 1); map.put("bar", 2); Map> asMap = Multimaps.forMap(map).asMap(); - assertEquals(Collections.singleton(1), asMap.get("foo")); - assertNull(asMap.get("cow")); + assertEquals(singleton(1), asMap.get("foo")); + assertThat(asMap.get("cow")).isNull(); assertTrue(asMap.containsKey("foo")); assertFalse(asMap.containsKey("cow")); Set>> entries = asMap.entrySet(); assertFalse(entries.contains((Object) 4.5)); assertFalse(entries.remove((Object) 4.5)); - assertFalse(entries.contains(Maps.immutableEntry("foo", Collections.singletonList(1)))); - assertFalse(entries.remove(Maps.immutableEntry("foo", Collections.singletonList(1)))); - assertFalse(entries.contains(Maps.immutableEntry("foo", Sets.newLinkedHashSet(asList(1, 2))))); - assertFalse(entries.remove(Maps.immutableEntry("foo", Sets.newLinkedHashSet(asList(1, 2))))); - assertFalse(entries.contains(Maps.immutableEntry("foo", Collections.singleton(2)))); - assertFalse(entries.remove(Maps.immutableEntry("foo", Collections.singleton(2)))); + assertFalse(entries.contains(Maps.immutableEntry("foo", singletonList(1)))); + assertFalse(entries.remove(Maps.immutableEntry("foo", singletonList(1)))); + assertFalse(entries.contains(Maps.immutableEntry("foo", new LinkedHashSet<>(asList(1, 2))))); + assertFalse(entries.remove(Maps.immutableEntry("foo", new LinkedHashSet<>(asList(1, 2))))); + assertFalse(entries.contains(Maps.immutableEntry("foo", singleton(2)))); + assertFalse(entries.remove(Maps.immutableEntry("foo", singleton(2)))); assertTrue(map.containsKey("foo")); - assertTrue(entries.contains(Maps.immutableEntry("foo", Collections.singleton(1)))); - assertTrue(entries.remove(Maps.immutableEntry("foo", Collections.singleton(1)))); + assertTrue(entries.contains(Maps.immutableEntry("foo", singleton(1)))); + assertTrue(entries.remove(Maps.immutableEntry("foo", singleton(1)))); assertFalse(map.containsKey("foo")); } @@ -501,11 +503,11 @@ public void testForMapGetIteration() { IteratorTester tester = new IteratorTester( 4, MODIFIABLE, newHashSet(1), IteratorTester.KnownOrder.KNOWN_ORDER) { - private Multimap multimap; + private @Nullable Multimap multimap; @Override protected Iterator newTargetIterator() { - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); map.put("foo", 1); map.put("bar", 2); multimap = Multimaps.forMap(map); @@ -514,7 +516,7 @@ protected Iterator newTargetIterator() { @Override protected void verify(List elements) { - assertEquals(newHashSet(elements), multimap.get("foo")); + assertEquals(new HashSet<>(elements), multimap.get("foo")); } }; @@ -542,11 +544,16 @@ public E get() { private static class QueueSupplier extends CountingSupplier> { @Override + /* + * We need a Queue that implements equals() for the equality tests we perform after + * reserializing the multimap. + */ + @SuppressWarnings("JdkObsolete") public Queue getImpl() { return new LinkedList<>(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } public void testNewMultimapWithCollectionRejectingNegativeElements() { @@ -554,7 +561,7 @@ public void testNewMultimapWithCollectionRejectingNegativeElements() { new SetSupplier() { @Override public Set getImpl() { - final Set backing = super.getImpl(); + Set backing = super.getImpl(); return new ForwardingSet() { @Override protected Set delegate() { @@ -577,24 +584,16 @@ public boolean addAll(Collection collection) { Map> map = Maps.newEnumMap(Color.class); Multimap multimap = Multimaps.newMultimap(map, factory); - try { - multimap.put(Color.BLUE, -1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> multimap.put(Color.BLUE, -1)); multimap.put(Color.RED, 1); multimap.put(Color.BLUE, 2); - try { - multimap.put(Color.GREEN, -1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> multimap.put(Color.GREEN, -1)); assertThat(multimap.entries()) .containsExactly(Maps.immutableEntry(Color.RED, 1), Maps.immutableEntry(Color.BLUE, 2)); } public void testNewMultimap() { - // The ubiquitous EnumArrayBlockingQueueMultimap + // The ubiquitous EnumLinkedListMultimap CountingSupplier> factory = new QueueSupplier(); Map> map = Maps.newEnumMap(Color.class); @@ -610,7 +609,8 @@ public void testNewMultimap() { assertEquals("[3, 1, 4]", ummodifiable.get(Color.BLUE).toString()); Collection collection = multimap.get(Color.BLUE); - assertEquals(collection, collection); + // Explicitly call `equals`; `assertEquals` might return fast + assertTrue(collection.equals(collection)); assertFalse(multimap.keySet() instanceof SortedSet); assertFalse(multimap.asMap() instanceof SortedMap); @@ -630,6 +630,7 @@ public void testNewMultimapValueCollectionMatchesList() { assertTrue(multimap.get(Color.BLUE) instanceof List); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewMultimapSerialization() { CountingSupplier> factory = new QueueSupplier(); @@ -646,7 +647,7 @@ public LinkedList getImpl() { return new LinkedList<>(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } public void testNewListMultimap() { @@ -665,6 +666,7 @@ public void testNewListMultimap() { assertTrue(multimap.asMap() instanceof SortedMap); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewListMultimapSerialization() { CountingSupplier> factory = new ListSupplier(); @@ -681,12 +683,12 @@ public Set getImpl() { return new HashSet<>(4); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } public void testNewSetMultimap() { CountingSupplier> factory = new SetSupplier(); - Map> map = Maps.newHashMap(); + Map> map = new HashMap<>(); SetMultimap multimap = Multimaps.newSetMultimap(map, factory); assertEquals(0, factory.count); multimap.putAll(Color.BLUE, asList(3, 1, 4)); @@ -696,10 +698,11 @@ public void testNewSetMultimap() { assertEquals(Sets.newHashSet(4, 3, 1), multimap.get(Color.BLUE)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewSetMultimapSerialization() { CountingSupplier> factory = new SetSupplier(); - Map> map = Maps.newHashMap(); + Map> map = new HashMap<>(); SetMultimap multimap = Multimaps.newSetMultimap(map, factory); multimap.putAll(Color.BLUE, asList(3, 1, 4)); multimap.putAll(Color.RED, asList(2, 7, 1, 8)); @@ -712,7 +715,7 @@ public TreeSet getImpl() { return Sets.newTreeSet(INT_COMPARATOR); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } public void testNewSortedSetMultimap() { @@ -729,6 +732,7 @@ public void testNewSortedSetMultimap() { assertEquals(INT_COMPARATOR, multimap.valueComparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewSortedSetMultimapSerialization() { CountingSupplier> factory = new SortedSetSupplier(); @@ -741,7 +745,7 @@ public void testNewSortedSetMultimapSerialization() { } public void testIndex() { - final Multimap stringToObject = + Multimap stringToObject = new ImmutableMultimap.Builder() .put("1", 1) .put("1", 1L) @@ -756,7 +760,7 @@ public void testIndex() { } public void testIndexIterator() { - final Multimap stringToObject = + Multimap stringToObject = new ImmutableMultimap.Builder() .put("1", 1) .put("1", 1L) @@ -771,7 +775,7 @@ public void testIndexIterator() { } public void testIndex_ordering() { - final Multimap expectedIndex = + Multimap expectedIndex = new ImmutableListMultimap.Builder() .put(4, "Inky") .put(6, "Blinky") @@ -780,8 +784,8 @@ public void testIndex_ordering() { .put(5, "Clyde") .build(); - final List badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde"); - final Function stringLengthFunction = + List badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde"); + Function stringLengthFunction = new Function() { @Override public Integer apply(String input) { @@ -795,21 +799,16 @@ public Integer apply(String input) { } public void testIndex_nullValue() { - List values = Arrays.asList(1, null); - try { - Multimaps.index(values, Functions.identity()); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Integer> values = Arrays.asList(1, null); + assertThrows( + NullPointerException.class, + () -> Multimaps.index((List) values, Functions.identity())); } public void testIndex_nullKey() { List values = Arrays.asList(1, 2); - try { - Multimaps.index(values, Functions.constant(null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> Multimaps.index(values, Functions.constant(null))); } @GwtIncompatible(value = "untested") @@ -908,10 +907,18 @@ public String transformEntry(String key, Integer value) { assertEquals("{a=[a1, a4, a4], b=[b6]}", transformed.toString()); } - public void testSynchronizedMultimapSampleCodeCompilation() { + @J2ktIncompatible // Synchronized + public void testSynchronizedMultimapSampleCodeCompilation() { + // Extra indirection for J2KT, to avoid error: not enough information to infer type variable K + this.<@Nullable Object, @Nullable Object>genericTestSynchronizedMultimapSampleCodeCompilation(); + } + + @J2ktIncompatible // Synchronized + private + void genericTestSynchronizedMultimapSampleCodeCompilation() { K key = null; - Multimap multimap = Multimaps.synchronizedMultimap(HashMultimap.create()); + Multimap multimap = synchronizedMultimap(HashMultimap.create()); Collection values = multimap.get(key); // Needn't be in synchronized block synchronized (multimap) { // Synchronizing on multimap, not values! Iterator i = values.iterator(); // Must be in synchronized block @@ -921,7 +928,7 @@ public void testSynchronizedMultimapSampleCodeCompilation() { } } - private static void foo(Object o) {} + private static void foo(Object unused) {} public void testFilteredKeysSetMultimapReplaceValues() { SetMultimap multimap = LinkedHashMultimap.create(); @@ -931,15 +938,12 @@ public void testFilteredKeysSetMultimapReplaceValues() { multimap.put("bar", 4); SetMultimap filtered = - Multimaps.filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar"))); + filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar"))); assertEquals(ImmutableSet.of(), filtered.replaceValues("baz", ImmutableSet.of())); - try { - filtered.replaceValues("baz", ImmutableSet.of(5)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> filtered.replaceValues("baz", ImmutableSet.of(5))); } public void testFilteredKeysSetMultimapGetBadValue() { @@ -950,19 +954,11 @@ public void testFilteredKeysSetMultimapGetBadValue() { multimap.put("bar", 4); SetMultimap filtered = - Multimaps.filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar"))); + filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar"))); Set bazSet = filtered.get("baz"); assertThat(bazSet).isEmpty(); - try { - bazSet.add(5); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - bazSet.addAll(ImmutableSet.of(6, 7)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> bazSet.add(5)); + assertThrows(IllegalArgumentException.class, () -> bazSet.addAll(ImmutableSet.of(6, 7))); } public void testFilteredKeysListMultimapGetBadValue() { @@ -973,31 +969,16 @@ public void testFilteredKeysListMultimapGetBadValue() { multimap.put("bar", 4); ListMultimap filtered = - Multimaps.filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar"))); + filterKeys(multimap, Predicates.in(ImmutableSet.of("foo", "bar"))); List bazList = filtered.get("baz"); assertThat(bazList).isEmpty(); - try { - bazList.add(5); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - bazList.add(0, 6); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - bazList.addAll(ImmutableList.of(7, 8)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - bazList.addAll(0, ImmutableList.of(9, 10)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> bazList.add(5)); + assertThrows(IllegalArgumentException.class, () -> bazList.add(0, 6)); + assertThrows(IllegalArgumentException.class, () -> bazList.addAll(ImmutableList.of(7, 8))); + assertThrows(IllegalArgumentException.class, () -> bazList.addAll(0, ImmutableList.of(9, 10))); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { new NullPointerTester().testAllPublicStaticMethods(Multimaps.class); diff --git a/android/guava-tests/test/com/google/common/collect/MultimapsTransformValuesAsMapTest.java b/android/guava-tests/test/com/google/common/collect/MultimapsTransformValuesAsMapTest.java index 491a632f9e6e..d68c9829ec44 100644 --- a/android/guava-tests/test/com/google/common/collect/MultimapsTransformValuesAsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultimapsTransformValuesAsMapTest.java @@ -20,6 +20,7 @@ import com.google.common.base.Functions; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.NullMarked; /** * Tests for Multimaps.transformValues().asMap(). @@ -27,6 +28,7 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public class MultimapsTransformValuesAsMapTest extends AbstractMultimapAsMapImplementsMapTest { public MultimapsTransformValuesAsMapTest() { diff --git a/android/guava-tests/test/com/google/common/collect/MultisetsCollectionTest.java b/android/guava-tests/test/com/google/common/collect/MultisetsCollectionTest.java index f143ebd10831..b627bde97f61 100644 --- a/android/guava-tests/test/com/google/common/collect/MultisetsCollectionTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultisetsCollectionTest.java @@ -16,10 +16,16 @@ package com.google.common.collect; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Multisets.difference; +import static com.google.common.collect.Multisets.intersection; +import static com.google.common.collect.Multisets.sum; +import static com.google.common.collect.Multisets.union; +import static com.google.common.collect.Multisets.unmodifiableMultiset; import static java.util.Arrays.asList; +import static java.util.Collections.sort; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.testing.features.CollectionFeature; @@ -30,9 +36,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Collection tests on wrappers from {@link Multisets}. @@ -40,6 +48,8 @@ * @author Jared Levy */ @GwtIncompatible // suite // TODO(cpovirk): set up collect/gwt/suites version +@NullUnmarked +@AndroidIncompatible // test-suite builders public class MultisetsCollectionTest extends TestCase { public static Test suite() { TestSuite suite = new TestSuite(); @@ -50,7 +60,7 @@ public static Test suite() { CollectionSize.ANY, CollectionFeature.KNOWN_ORDER, CollectionFeature.SERIALIZABLE, - CollectionFeature.ALLOWS_NULL_QUERIES) + CollectionFeature.ALLOWS_NULL_VALUES) .named("Multisets.unmodifiableMultiset[LinkedHashMultiset]") .createTestSuite()); @@ -111,7 +121,7 @@ private static TestStringMultisetGenerator unmodifiableMultisetGenerator() { return new TestStringMultisetGenerator() { @Override protected Multiset create(String[] elements) { - return Multisets.unmodifiableMultiset(LinkedHashMultiset.create(asList(elements))); + return unmodifiableMultiset(LinkedHashMultiset.create(asList(elements))); } @Override @@ -139,7 +149,7 @@ protected Multiset create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }; @@ -163,7 +173,7 @@ protected Multiset create(String[] elements) { multiset2.add(elements[i]); } } - return Multisets.union(multiset1, multiset2); + return union(multiset1, multiset2); } }; } @@ -189,11 +199,11 @@ protected Multiset create(String[] elements) { * "add an extra item 0 to A and an extra item 1 to B" really means * "add an extra item 0 to A and B," which isn't what we want. */ - if (!Objects.equal(elements[0], elements[1])) { + if (!Objects.equals(elements[0], elements[1])) { multiset2.add(elements[1], 2); } } - return Multisets.intersection(multiset1, multiset2); + return intersection(multiset1, multiset2); } }; } @@ -212,7 +222,7 @@ protected Multiset create(String[] elements) { multiset2.add(elements[i]); } } - return Multisets.sum(multiset1, multiset2); + return sum(multiset1, multiset2); } }; } @@ -233,7 +243,7 @@ protected Multiset create(String[] elements) { multiset1.add(elements[i], i + 2); multiset2.add(elements[i], i + 1); } - return Multisets.difference(multiset1, multiset2); + return difference(multiset1, multiset2); } }; } @@ -241,8 +251,7 @@ protected Multiset create(String[] elements) { private static final ImmutableMultiset ELEMENTS_TO_FILTER_OUT = ImmutableMultiset.of("foobar", "bazfoo", "foobar", "foobar"); - private static final Predicate PREDICATE = - Predicates.not(Predicates.in(ELEMENTS_TO_FILTER_OUT)); + private static final Predicate PREDICATE = not(Predicates.in(ELEMENTS_TO_FILTER_OUT)); private static TestStringMultisetGenerator filteredGenerator() { return new TestStringMultisetGenerator() { @@ -256,7 +265,7 @@ protected Multiset create(String[] elements) { @Override public List order(List insertionOrder) { - return Lists.newArrayList(LinkedHashMultiset.create(insertionOrder)); + return new ArrayList<>(LinkedHashMultiset.create(insertionOrder)); } }; } diff --git a/android/guava-tests/test/com/google/common/collect/MultisetsImmutableEntryTest.java b/android/guava-tests/test/com/google/common/collect/MultisetsImmutableEntryTest.java index bef27b9b20e5..05b90b3db86a 100644 --- a/android/guava-tests/test/com/google/common/collect/MultisetsImmutableEntryTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultisetsImmutableEntryTest.java @@ -16,10 +16,14 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static java.util.Collections.nCopies; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Multiset.Entry; -import java.util.Collections; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Multisets#immutableEntry}. @@ -27,15 +31,16 @@ * @author Mike Bostock */ @GwtCompatible +@NullMarked public class MultisetsImmutableEntryTest extends TestCase { - private static final String NE = null; + private static final @Nullable String NE = null; - private static Entry entry(final E element, final int count) { + private static Entry entry(E element, int count) { return Multisets.immutableEntry(element, count); } - private static Entry control(E element, int count) { - return HashMultiset.create(Collections.nCopies(count, element)).entrySet().iterator().next(); + private static Entry control(E element, int count) { + return HashMultiset.create(nCopies(count, element)).entrySet().iterator().next(); } public void testToString() { @@ -75,10 +80,6 @@ public void testHashCodeNull() { } public void testNegativeCount() { - try { - entry("foo", -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> entry("foo", -1)); } } diff --git a/android/guava-tests/test/com/google/common/collect/MultisetsTest.java b/android/guava-tests/test/com/google/common/collect/MultisetsTest.java index 32e4408f99c9..2acd68edec89 100644 --- a/android/guava-tests/test/com/google/common/collect/MultisetsTest.java +++ b/android/guava-tests/test/com/google/common/collect/MultisetsTest.java @@ -16,16 +16,23 @@ package com.google.common.collect; +import static com.google.common.collect.Multisets.difference; +import static com.google.common.collect.Multisets.intersection; +import static com.google.common.collect.Multisets.sum; +import static com.google.common.collect.Multisets.union; +import static com.google.common.collect.Multisets.unmodifiableMultiset; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.DerivedComparable; import com.google.common.testing.NullPointerTester; -import java.util.Arrays; import java.util.Collections; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for {@link Multisets}. @@ -34,7 +41,8 @@ * @author Jared Levy * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class MultisetsTest extends TestCase { /* See MultisetsImmutableEntryTest for immutableEntry() tests. */ @@ -78,95 +86,95 @@ public void testNewTreeMultisetComparator() { public void testRetainOccurrencesEmpty() { Multiset multiset = HashMultiset.create(); - Multiset toRetain = HashMultiset.create(Arrays.asList("a", "b", "a")); + Multiset toRetain = HashMultiset.create(asList("a", "b", "a")); assertFalse(Multisets.retainOccurrences(multiset, toRetain)); assertThat(multiset).isEmpty(); } public void testRemoveOccurrencesIterableEmpty() { Multiset multiset = HashMultiset.create(); - Iterable toRemove = Arrays.asList("a", "b", "a"); + Iterable toRemove = asList("a", "b", "a"); assertFalse(Multisets.removeOccurrences(multiset, toRemove)); assertTrue(multiset.isEmpty()); } public void testRemoveOccurrencesMultisetEmpty() { Multiset multiset = HashMultiset.create(); - Multiset toRemove = HashMultiset.create(Arrays.asList("a", "b", "a")); + Multiset toRemove = HashMultiset.create(asList("a", "b", "a")); assertFalse(Multisets.removeOccurrences(multiset, toRemove)); assertTrue(multiset.isEmpty()); } public void testUnion() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); - Multiset ms2 = HashMultiset.create(Arrays.asList("a", "b", "b", "c")); - assertThat(Multisets.union(ms1, ms2)).containsExactly("a", "a", "b", "b", "c"); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); + Multiset ms2 = HashMultiset.create(asList("a", "b", "b", "c")); + assertThat(union(ms1, ms2)).containsExactly("a", "a", "b", "b", "c"); } public void testUnionEqualMultisets() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); - Multiset ms2 = HashMultiset.create(Arrays.asList("a", "b", "a")); - assertEquals(ms1, Multisets.union(ms1, ms2)); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); + Multiset ms2 = HashMultiset.create(asList("a", "b", "a")); + assertEquals(ms1, union(ms1, ms2)); } public void testUnionEmptyNonempty() { Multiset ms1 = HashMultiset.create(); - Multiset ms2 = HashMultiset.create(Arrays.asList("a", "b", "a")); - assertEquals(ms2, Multisets.union(ms1, ms2)); + Multiset ms2 = HashMultiset.create(asList("a", "b", "a")); + assertEquals(ms2, union(ms1, ms2)); } public void testUnionNonemptyEmpty() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); Multiset ms2 = HashMultiset.create(); - assertEquals(ms1, Multisets.union(ms1, ms2)); + assertEquals(ms1, union(ms1, ms2)); } public void testIntersectEmptyNonempty() { Multiset ms1 = HashMultiset.create(); - Multiset ms2 = HashMultiset.create(Arrays.asList("a", "b", "a")); - assertThat(Multisets.intersection(ms1, ms2)).isEmpty(); + Multiset ms2 = HashMultiset.create(asList("a", "b", "a")); + assertThat(intersection(ms1, ms2)).isEmpty(); } public void testIntersectNonemptyEmpty() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); Multiset ms2 = HashMultiset.create(); - assertThat(Multisets.intersection(ms1, ms2)).isEmpty(); + assertThat(intersection(ms1, ms2)).isEmpty(); } public void testSum() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); - Multiset ms2 = HashMultiset.create(Arrays.asList("b", "c")); - assertThat(Multisets.sum(ms1, ms2)).containsExactly("a", "a", "b", "b", "c"); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); + Multiset ms2 = HashMultiset.create(asList("b", "c")); + assertThat(sum(ms1, ms2)).containsExactly("a", "a", "b", "b", "c"); } public void testSumEmptyNonempty() { Multiset ms1 = HashMultiset.create(); - Multiset ms2 = HashMultiset.create(Arrays.asList("a", "b", "a")); - assertThat(Multisets.sum(ms1, ms2)).containsExactly("a", "b", "a"); + Multiset ms2 = HashMultiset.create(asList("a", "b", "a")); + assertThat(sum(ms1, ms2)).containsExactly("a", "b", "a"); } public void testSumNonemptyEmpty() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); Multiset ms2 = HashMultiset.create(); - assertThat(Multisets.sum(ms1, ms2)).containsExactly("a", "b", "a"); + assertThat(sum(ms1, ms2)).containsExactly("a", "b", "a"); } public void testDifferenceWithNoRemovedElements() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); - Multiset ms2 = HashMultiset.create(Arrays.asList("a")); - assertThat(Multisets.difference(ms1, ms2)).containsExactly("a", "b"); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); + Multiset ms2 = HashMultiset.create(asList("a")); + assertThat(difference(ms1, ms2)).containsExactly("a", "b"); } public void testDifferenceWithRemovedElement() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); - Multiset ms2 = HashMultiset.create(Arrays.asList("b")); - assertThat(Multisets.difference(ms1, ms2)).containsExactly("a", "a"); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); + Multiset ms2 = HashMultiset.create(asList("b")); + assertThat(difference(ms1, ms2)).containsExactly("a", "a"); } public void testDifferenceWithMoreElementsInSecondMultiset() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); - Multiset ms2 = HashMultiset.create(Arrays.asList("a", "b", "b", "b")); - Multiset diff = Multisets.difference(ms1, ms2); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); + Multiset ms2 = HashMultiset.create(asList("a", "b", "b", "b")); + Multiset diff = difference(ms1, ms2); assertThat(diff).contains("a"); assertEquals(0, diff.count("b")); assertEquals(1, diff.count("a")); @@ -176,88 +184,88 @@ public void testDifferenceWithMoreElementsInSecondMultiset() { public void testDifferenceEmptyNonempty() { Multiset ms1 = HashMultiset.create(); - Multiset ms2 = HashMultiset.create(Arrays.asList("a", "b", "a")); - assertEquals(ms1, Multisets.difference(ms1, ms2)); + Multiset ms2 = HashMultiset.create(asList("a", "b", "a")); + assertEquals(ms1, difference(ms1, ms2)); } public void testDifferenceNonemptyEmpty() { - Multiset ms1 = HashMultiset.create(Arrays.asList("a", "b", "a")); + Multiset ms1 = HashMultiset.create(asList("a", "b", "a")); Multiset ms2 = HashMultiset.create(); - assertEquals(ms1, Multisets.difference(ms1, ms2)); + assertEquals(ms1, difference(ms1, ms2)); } public void testContainsOccurrencesEmpty() { - Multiset superMultiset = HashMultiset.create(Arrays.asList("a", "b", "a")); + Multiset superMultiset = HashMultiset.create(asList("a", "b", "a")); Multiset subMultiset = HashMultiset.create(); assertTrue(Multisets.containsOccurrences(superMultiset, subMultiset)); assertFalse(Multisets.containsOccurrences(subMultiset, superMultiset)); } public void testContainsOccurrences() { - Multiset superMultiset = HashMultiset.create(Arrays.asList("a", "b", "a")); - Multiset subMultiset = HashMultiset.create(Arrays.asList("a", "b")); + Multiset superMultiset = HashMultiset.create(asList("a", "b", "a")); + Multiset subMultiset = HashMultiset.create(asList("a", "b")); assertTrue(Multisets.containsOccurrences(superMultiset, subMultiset)); assertFalse(Multisets.containsOccurrences(subMultiset, superMultiset)); - Multiset diffMultiset = HashMultiset.create(Arrays.asList("a", "b", "c")); + Multiset diffMultiset = HashMultiset.create(asList("a", "b", "c")); assertFalse(Multisets.containsOccurrences(superMultiset, diffMultiset)); assertTrue(Multisets.containsOccurrences(diffMultiset, subMultiset)); } public void testRetainEmptyOccurrences() { - Multiset multiset = HashMultiset.create(Arrays.asList("a", "b", "a")); + Multiset multiset = HashMultiset.create(asList("a", "b", "a")); Multiset toRetain = HashMultiset.create(); assertTrue(Multisets.retainOccurrences(multiset, toRetain)); assertTrue(multiset.isEmpty()); } public void testRetainOccurrences() { - Multiset multiset = TreeMultiset.create(Arrays.asList("a", "b", "a", "c")); - Multiset toRetain = HashMultiset.create(Arrays.asList("a", "b", "b")); + Multiset multiset = TreeMultiset.create(asList("a", "b", "a", "c")); + Multiset toRetain = HashMultiset.create(asList("a", "b", "b")); assertTrue(Multisets.retainOccurrences(multiset, toRetain)); assertThat(multiset).containsExactly("a", "b").inOrder(); } public void testRemoveEmptyOccurrencesMultiset() { - Multiset multiset = TreeMultiset.create(Arrays.asList("a", "b", "a")); + Multiset multiset = TreeMultiset.create(asList("a", "b", "a")); Multiset toRemove = HashMultiset.create(); assertFalse(Multisets.removeOccurrences(multiset, toRemove)); assertThat(multiset).containsExactly("a", "a", "b").inOrder(); } public void testRemoveOccurrencesMultiset() { - Multiset multiset = TreeMultiset.create(Arrays.asList("a", "b", "a", "c")); - Multiset toRemove = HashMultiset.create(Arrays.asList("a", "b", "b")); + Multiset multiset = TreeMultiset.create(asList("a", "b", "a", "c")); + Multiset toRemove = HashMultiset.create(asList("a", "b", "b")); assertTrue(Multisets.removeOccurrences(multiset, toRemove)); assertThat(multiset).containsExactly("a", "c").inOrder(); } public void testRemoveEmptyOccurrencesIterable() { - Multiset multiset = TreeMultiset.create(Arrays.asList("a", "b", "a")); + Multiset multiset = TreeMultiset.create(asList("a", "b", "a")); Iterable toRemove = ImmutableList.of(); assertFalse(Multisets.removeOccurrences(multiset, toRemove)); assertThat(multiset).containsExactly("a", "a", "b").inOrder(); } public void testRemoveOccurrencesMultisetIterable() { - Multiset multiset = TreeMultiset.create(Arrays.asList("a", "b", "a", "c")); - List toRemove = Arrays.asList("a", "b", "b"); + Multiset multiset = TreeMultiset.create(asList("a", "b", "a", "c")); + List toRemove = asList("a", "b", "b"); assertTrue(Multisets.removeOccurrences(multiset, toRemove)); assertThat(multiset).containsExactly("a", "c").inOrder(); } - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testUnmodifiableMultisetShortCircuit() { Multiset mod = HashMultiset.create(); - Multiset unmod = Multisets.unmodifiableMultiset(mod); + Multiset unmod = unmodifiableMultiset(mod); assertNotSame(mod, unmod); - assertSame(unmod, Multisets.unmodifiableMultiset(unmod)); + assertSame(unmod, unmodifiableMultiset(unmod)); ImmutableMultiset immutable = ImmutableMultiset.of("a", "a", "b", "a"); - assertSame(immutable, Multisets.unmodifiableMultiset(immutable)); - assertSame(immutable, Multisets.unmodifiableMultiset((Multiset) immutable)); + assertSame(immutable, unmodifiableMultiset(immutable)); + assertSame(immutable, unmodifiableMultiset((Multiset) immutable)); } public void testHighestCountFirst() { - Multiset multiset = HashMultiset.create(Arrays.asList("a", "a", "a", "b", "c", "c")); + Multiset multiset = HashMultiset.create(asList("a", "a", "a", "b", "c", "c")); ImmutableMultiset sortedMultiset = Multisets.copyHighestCountFirst(multiset); assertThat(sortedMultiset.entrySet()) @@ -272,6 +280,7 @@ public void testHighestCountFirst() { assertThat(Multisets.copyHighestCountFirst(ImmutableMultiset.of())).isEmpty(); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { new NullPointerTester().testAllPublicStaticMethods(Multisets.class); diff --git a/android/guava-tests/test/com/google/common/collect/MutableClassToInstanceMapTest.java b/android/guava-tests/test/com/google/common/collect/MutableClassToInstanceMapTest.java index 156b62d9875c..da1d15565099 100644 --- a/android/guava-tests/test/com/google/common/collect/MutableClassToInstanceMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/MutableClassToInstanceMapTest.java @@ -16,6 +16,9 @@ package com.google.common.collect; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + import com.google.common.collect.ImmutableClassToInstanceMapTest.Impl; import com.google.common.collect.ImmutableClassToInstanceMapTest.TestClassToInstanceMapGenerator; import com.google.common.collect.testing.MapTestSuiteBuilder; @@ -27,13 +30,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Unit test of {@link MutableClassToInstanceMap}. * * @author Kevin Bourrillion */ +@NullUnmarked public class MutableClassToInstanceMapTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(MutableClassToInstanceMapTest.class); @@ -44,7 +50,7 @@ public static Test suite() { // Other tests will verify what real, warning-free usage looks like // but here we have to do some serious fudging @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) public Map create(Object... elements) { MutableClassToInstanceMap map = MutableClassToInstanceMap.create(); for (Object object : elements) { @@ -76,23 +82,18 @@ protected void setUp() throws Exception { } public void testConstraint() { - - /** + /* * We'll give ourselves a pass on testing all the possible ways of breaking the constraint, * because we know that newClassMap() is implemented using ConstrainedMap which is itself * well-tested. A purist would object to this, but what can I say, we're dirty cheaters. */ map.put(Integer.class, new Integer(5)); - try { - map.put(Double.class, new Long(42)); - fail(); - } catch (ClassCastException expected) { - } + assertThrows(ClassCastException.class, () -> map.put(Double.class, new Long(42))); // Won't compile: map.put(String.class, "x"); } public void testPutAndGetInstance() { - assertNull(map.putInstance(Integer.class, new Integer(5))); + assertThat(map.putInstance(Integer.class, new Integer(5))).isNull(); Integer oldValue = map.putInstance(Integer.class, new Integer(7)); assertEquals(5, (int) oldValue); @@ -104,26 +105,22 @@ public void testPutAndGetInstance() { } public void testNull() { - try { - map.put(null, new Integer(1)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> map.put(null, new Integer(1))); map.putInstance(Integer.class, null); - assertNull(map.get(Integer.class)); - assertNull(map.getInstance(Integer.class)); + assertThat(map.get(Integer.class)).isNull(); + assertThat(map.getInstance(Integer.class)).isNull(); map.put(Long.class, null); - assertNull(map.get(Long.class)); - assertNull(map.getInstance(Long.class)); + assertThat(map.get(Long.class)).isNull(); + assertThat(map.getInstance(Long.class)).isNull(); } public void testPrimitiveAndWrapper() { - assertNull(map.getInstance(int.class)); - assertNull(map.getInstance(Integer.class)); + assertThat(map.getInstance(int.class)).isNull(); + assertThat(map.getInstance(Integer.class)).isNull(); - assertNull(map.putInstance(int.class, 0)); - assertNull(map.putInstance(Integer.class, 1)); + assertThat(map.putInstance(int.class, 0)).isNull(); + assertThat(map.putInstance(Integer.class, 1)).isNull(); assertEquals(2, map.size()); assertEquals(0, (int) map.getInstance(int.class)); @@ -132,8 +129,8 @@ public void testPrimitiveAndWrapper() { assertEquals(0, (int) map.putInstance(int.class, null)); assertEquals(1, (int) map.putInstance(Integer.class, null)); - assertNull(map.getInstance(int.class)); - assertNull(map.getInstance(Integer.class)); + assertThat(map.getInstance(int.class)).isNull(); + assertThat(map.getInstance(Integer.class)).isNull(); assertEquals(2, map.size()); } } diff --git a/android/guava-tests/test/com/google/common/collect/NewCustomTableTest.java b/android/guava-tests/test/com/google/common/collect/NewCustomTableTest.java index 6ad3aa212a1f..dc444152b1e6 100644 --- a/android/guava-tests/test/com/google/common/collect/NewCustomTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/NewCustomTableTest.java @@ -16,12 +16,16 @@ package com.google.common.collect; +import static com.google.common.collect.Tables.newCustomTable; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Supplier; +import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link Tables#newCustomTable}. @@ -29,10 +33,11 @@ * @author Jared Levy */ @GwtCompatible -public class NewCustomTableTest extends AbstractTableTest { +@NullMarked +public class NewCustomTableTest extends AbstractTableTest { @Override - protected Table create(Object... data) { + protected Table create(@Nullable Object... data) { Supplier> factory = new Supplier>() { @Override @@ -40,8 +45,8 @@ public TreeMap get() { return Maps.newTreeMap(); } }; - Map> backingMap = Maps.newLinkedHashMap(); - Table table = Tables.newCustomTable(backingMap, factory); + Map> backingMap = new LinkedHashMap<>(); + Table table = newCustomTable(backingMap, factory); populate(table, data); return table; } diff --git a/android/guava-tests/test/com/google/common/collect/ObjectArraysTest.java b/android/guava-tests/test/com/google/common/collect/ObjectArraysTest.java index 32d043f74543..2f6d757101e4 100644 --- a/android/guava-tests/test/com/google/common/collect/ObjectArraysTest.java +++ b/android/guava-tests/test/com/google/common/collect/ObjectArraysTest.java @@ -20,20 +20,25 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; -import java.io.Serializable; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code ObjectArrays}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class ObjectArraysTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -41,46 +46,47 @@ public void testNullPointerExceptions() { } @GwtIncompatible // ObjectArrays.newArray(Class, int) - public void testNewArray_fromClass_Empty() { + public void testNewArray_fromClass_empty() { String[] empty = ObjectArrays.newArray(String.class, 0); assertEquals(String[].class, empty.getClass()); assertThat(empty).isEmpty(); } @GwtIncompatible // ObjectArrays.newArray(Class, int) - public void testNewArray_fromClass_Nonempty() { + public void testNewArray_fromClass_nonempty() { String[] array = ObjectArrays.newArray(String.class, 2); assertEquals(String[].class, array.getClass()); assertThat(array).hasLength(2); - assertNull(array[0]); + assertThat(array[0]).isNull(); } + @J2ktIncompatible // Array::class literal not available in Kotlin KMP @GwtIncompatible // ObjectArrays.newArray(Class, int) - public void testNewArray_fromClass_OfArray() { + public void testNewArray_fromClass_ofArray() { String[][] array = ObjectArrays.newArray(String[].class, 1); assertEquals(String[][].class, array.getClass()); assertThat(array).hasLength(1); - assertNull(array[0]); + assertThat(array[0]).isNull(); } - public void testNewArray_fromArray_Empty() { + public void testNewArray_fromArray_empty() { String[] in = new String[0]; String[] empty = ObjectArrays.newArray(in, 0); assertThat(empty).isEmpty(); } - public void testNewArray_fromArray_Nonempty() { + public void testNewArray_fromArray_nonempty() { String[] array = ObjectArrays.newArray(new String[0], 2); assertEquals(String[].class, array.getClass()); assertThat(array).hasLength(2); - assertNull(array[0]); + assertThat(array[0]).isNull(); } - public void testNewArray_fromArray_OfArray() { + public void testNewArray_fromArray_ofArray() { String[][] array = ObjectArrays.newArray(new String[0][0], 1); assertEquals(String[][].class, array.getClass()); assertThat(array).hasLength(1); - assertNull(array[0]); + assertThat(array[0]).isNull(); } @GwtIncompatible // ObjectArrays.concat(Object[], Object[], Class) @@ -114,34 +120,34 @@ public void testConcatBasic() { @GwtIncompatible // ObjectArrays.concat(Object[], Object[], Class) public void testConcatWithMoreGeneralType() { - Serializable[] result = ObjectArrays.concat(new String[0], new String[0], Serializable.class); - assertEquals(Serializable[].class, result.getClass()); + CharSequence[] result = ObjectArrays.concat(new String[0], new String[0], CharSequence.class); + assertEquals(CharSequence[].class, result.getClass()); } public void testToArrayImpl1() { - doTestToArrayImpl1(Lists.newArrayList()); + doTestToArrayImpl1(new ArrayList()); doTestToArrayImpl1(Lists.newArrayList(1)); - doTestToArrayImpl1(Lists.newArrayList(1, null, 3)); + doTestToArrayImpl1(Lists.<@Nullable Integer>newArrayList(1, null, 3)); } private void doTestToArrayImpl1(List list) { Object[] reference = list.toArray(); Object[] target = ObjectArrays.toArrayImpl(list); assertEquals(reference.getClass(), target.getClass()); - assertTrue(Arrays.equals(reference, target)); + assertThat(target).isEqualTo(reference); } public void testToArrayImpl2() { - doTestToArrayImpl2(Lists.newArrayList(), new Integer[0], false); - doTestToArrayImpl2(Lists.newArrayList(), new Integer[1], true); + doTestToArrayImpl2(new ArrayList(), new Integer[0], false); + doTestToArrayImpl2(new ArrayList(), new Integer[1], true); doTestToArrayImpl2(Lists.newArrayList(1), new Integer[0], false); doTestToArrayImpl2(Lists.newArrayList(1), new Integer[1], true); doTestToArrayImpl2(Lists.newArrayList(1), new Integer[] {2, 3}, true); - doTestToArrayImpl2(Lists.newArrayList(1, null, 3), new Integer[0], false); - doTestToArrayImpl2(Lists.newArrayList(1, null, 3), new Integer[2], false); - doTestToArrayImpl2(Lists.newArrayList(1, null, 3), new Integer[3], true); + doTestToArrayImpl2(Lists.<@Nullable Integer>newArrayList(1, null, 3), new Integer[0], false); + doTestToArrayImpl2(Lists.<@Nullable Integer>newArrayList(1, null, 3), new Integer[2], false); + doTestToArrayImpl2(Lists.<@Nullable Integer>newArrayList(1, null, 3), new Integer[3], true); } private void doTestToArrayImpl2(List list, Integer[] array1, boolean expectModify) { @@ -152,13 +158,13 @@ private void doTestToArrayImpl2(List list, Integer[] array1, boolean ex Object[] target = ObjectArrays.toArrayImpl(list, array2); assertEquals(reference.getClass(), target.getClass()); - assertTrue(Arrays.equals(reference, target)); - assertTrue(Arrays.equals(reference, target)); + assertThat(target).isEqualTo(reference); + assertThat(target).isEqualTo(reference); Object[] expectedArray1 = expectModify ? reference : starting; Object[] expectedArray2 = expectModify ? target : starting; - assertTrue(Arrays.equals(expectedArray1, array1)); - assertTrue(Arrays.equals(expectedArray2, array2)); + assertThat(array1).isEqualTo(expectedArray1); + assertThat(array2).isEqualTo(expectedArray2); } public void testPrependZeroElements() { diff --git a/android/guava-tests/test/com/google/common/collect/OrderingTest.java b/android/guava-tests/test/com/google/common/collect/OrderingTest.java index b449a29ee34c..367c64aa9955 100644 --- a/android/guava-tests/test/com/google/common/collect/OrderingTest.java +++ b/android/guava-tests/test/com/google/common/collect/OrderingTest.java @@ -17,22 +17,30 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Iterators.singletonIterator; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.testing.Helpers.testComparator; import static com.google.common.testing.SerializableTester.reserialize; import static com.google.common.testing.SerializableTester.reserializeAndAssert; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static java.util.Arrays.sort; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static java.util.Collections.sort; +import static java.util.Collections.unmodifiableList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.Ordering.ArbitraryOrdering; import com.google.common.collect.Ordering.IncomparableValueException; import com.google.common.collect.testing.Helpers; -import com.google.common.primitives.Ints; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -41,21 +49,24 @@ import java.util.Random; import java.util.RandomAccess; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit tests for {@code Ordering}. * * @author Jesse Wilson */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class OrderingTest extends TestCase { // TODO(cpovirk): some of these are inexplicably slow (20-30s) under GWT private final Ordering numberOrdering = new NumberOrdering(); public void testAllEqual() { - Ordering comparator = Ordering.allEqual(); + Ordering<@Nullable Object> comparator = Ordering.allEqual(); assertSame(comparator, comparator.reverse()); assertEquals(0, comparator.compare(null, null)); @@ -72,53 +83,46 @@ public void testAllEqual() { // From https://github.com/google/guava/issues/1342 public void testComplicatedOrderingExample() { Integer nullInt = (Integer) null; - Ordering> example = - Ordering.natural().nullsFirst().reverse().lexicographical().reverse().nullsLast(); - List list1 = Lists.newArrayList(); - List list2 = Lists.newArrayList(1); - List list3 = Lists.newArrayList(1, 1); - List list4 = Lists.newArrayList(1, 2); - List list5 = Lists.newArrayList(1, null, 2); - List list6 = Lists.newArrayList(2); - List list7 = Lists.newArrayList(nullInt); - List list8 = Lists.newArrayList(nullInt, nullInt); - List> list = + Ordering<@Nullable Iterable<@Nullable Integer>> example = + Ordering.natural() + .nullsFirst() + .<@Nullable Integer>reverse() + .<@Nullable Integer>lexicographical() + .reverse() + .>nullsLast(); + List<@Nullable Integer> list1 = new ArrayList<>(); + List<@Nullable Integer> list2 = Lists.newArrayList(1); + List<@Nullable Integer> list3 = Lists.newArrayList(1, 1); + List<@Nullable Integer> list4 = Lists.newArrayList(1, 2); + List<@Nullable Integer> list5 = Lists.newArrayList(1, null, 2); + List<@Nullable Integer> list6 = Lists.newArrayList(2); + List<@Nullable Integer> list7 = Lists.newArrayList(nullInt); + List<@Nullable Integer> list8 = Lists.newArrayList(nullInt, nullInt); + List<@Nullable List<@Nullable Integer>> list = Lists.newArrayList(list1, list2, list3, list4, list5, list6, list7, list8, null); - List> sorted = example.sortedCopy(list); + List<@Nullable List<@Nullable Integer>> sorted = example.sortedCopy(list); // [[null, null], [null], [1, null, 2], [1, 1], [1, 2], [1], [2], [], null] assertThat(sorted) .containsExactly( - Lists.newArrayList(nullInt, nullInt), - Lists.newArrayList(nullInt), - Lists.newArrayList(1, null, 2), + Lists.<@Nullable Integer>newArrayList(nullInt, nullInt), + Lists.<@Nullable Integer>newArrayList(nullInt), + Lists.<@Nullable Integer>newArrayList(1, null, 2), Lists.newArrayList(1, 1), Lists.newArrayList(1, 2), Lists.newArrayList(1), Lists.newArrayList(2), - Lists.newArrayList(), + new ArrayList<>(), null) .inOrder(); } public void testNatural() { Ordering comparator = Ordering.natural(); - Helpers.testComparator(comparator, Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE); - try { - comparator.compare(1, null); - fail(); - } catch (NullPointerException expected) { - } - try { - comparator.compare(null, 2); - fail(); - } catch (NullPointerException expected) { - } - try { - comparator.compare(null, null); - fail(); - } catch (NullPointerException expected) { - } + testComparator(comparator, Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE); + assertThrows(NullPointerException.class, () -> comparator.compare(1, null)); + assertThrows(NullPointerException.class, () -> comparator.compare(null, 2)); + assertThrows(NullPointerException.class, () -> comparator.compare(null, null)); assertSame(comparator, reserialize(comparator)); assertEquals("Ordering.natural()", comparator.toString()); } @@ -129,7 +133,7 @@ public void testFrom() { assertTrue(caseInsensitiveOrdering.compare("a", "B") < 0); assertTrue(caseInsensitiveOrdering.compare("B", "a") > 0); - @SuppressWarnings("deprecation") // test of deprecated method + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method Ordering orderingFromOrdering = Ordering.from(Ordering.natural()); new EqualsTester() .addEqualityGroup(caseInsensitiveOrdering, Ordering.from(String.CASE_INSENSITIVE_ORDER)) @@ -139,39 +143,40 @@ public void testFrom() { public void testExplicit_none() { Comparator c = Ordering.explicit(Collections.emptyList()); - try { - c.compare(0, 0); - fail(); - } catch (IncomparableValueException expected) { - assertEquals(0, expected.value); - } + IncomparableValueException expected = + assertThrows(IncomparableValueException.class, () -> c.compare(0, 0)); + assertEquals(0, expected.value); reserializeAndAssert(c); } public void testExplicit_one() { Comparator c = Ordering.explicit(0); assertEquals(0, c.compare(0, 0)); - try { - c.compare(0, 1); - fail(); - } catch (IncomparableValueException expected) { - assertEquals(1, expected.value); - } + IncomparableValueException expected = + assertThrows(IncomparableValueException.class, () -> c.compare(0, 1)); + assertEquals(1, expected.value); reserializeAndAssert(c); assertEquals("Ordering.explicit([0])", c.toString()); } + public void testExplicitMax_b297601553() { + Ordering c = Ordering.explicit(1, 2, 3); + + // TODO(b/297601553): this should probably throw CCE since 0 isn't explicitly listed + assertEquals(0, (int) c.max(asList(0))); + IncomparableValueException expected = + assertThrows(IncomparableValueException.class, () -> c.max(asList(0, 1))); + assertEquals(0, expected.value); + } + public void testExplicit_two() { Comparator c = Ordering.explicit(42, 5); assertEquals(0, c.compare(5, 5)); assertTrue(c.compare(5, 42) > 0); assertTrue(c.compare(42, 5) < 0); - try { - c.compare(5, 666); - fail(); - } catch (IncomparableValueException expected) { - assertEquals(666, expected.value); - } + IncomparableValueException expected = + assertThrows(IncomparableValueException.class, () -> c.compare(5, 666)); + assertEquals(666, expected.value); new EqualsTester() .addEqualityGroup(c, Ordering.explicit(42, 5)) .addEqualityGroup(Ordering.explicit(5, 42)) @@ -182,40 +187,39 @@ public void testExplicit_two() { public void testExplicit_sortingExample() { Comparator c = Ordering.explicit(2, 8, 6, 1, 7, 5, 3, 4, 0, 9); - List list = Arrays.asList(0, 3, 5, 6, 7, 8, 9); - Collections.sort(list, c); + List list = asList(0, 3, 5, 6, 7, 8, 9); + sort(list, c); assertThat(list).containsExactly(8, 6, 7, 5, 3, 0, 9).inOrder(); reserializeAndAssert(c); } + @SuppressWarnings("DistinctVarargsChecker") // test of buggy call public void testExplicit_withDuplicates() { - try { - Ordering.explicit(1, 2, 3, 4, 2); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Ordering.explicit(1, 2, 3, 4, 2)); } // A more limited test than the one that follows, but this one uses the // actual public API. + @J2ktIncompatible // Ordering.arbitrary public void testArbitrary_withoutCollisions() { - List list = Lists.newArrayList(); + List list = new ArrayList<>(); for (int i = 0; i < 50; i++) { list.add(new Object()); } Ordering arbitrary = Ordering.arbitrary(); - Collections.sort(list, arbitrary); + sort(list, arbitrary); // Now we don't care what order it's put the list in, only that // comparing any pair of elements gives the answer we expect. - Helpers.testComparator(arbitrary, list); + testComparator(arbitrary, list); assertEquals("Ordering.arbitrary()", arbitrary.toString()); } + @J2ktIncompatible // ArbitraryOrdering public void testArbitrary_withCollisions() { - List list = Lists.newArrayList(); + List list = new ArrayList<>(); for (int i = 0; i < 50; i++) { list.add(i); } @@ -231,16 +235,16 @@ int identityHashCode(Object object) { // Don't let the elements be in such a predictable order list = shuffledCopy(list, new Random(1)); - Collections.sort(list, arbitrary); + sort(list, arbitrary); // Now we don't care what order it's put the list in, only that // comparing any pair of elements gives the answer we expect. - Helpers.testComparator(arbitrary, list); + testComparator(arbitrary, list); } public void testUsingToString() { Ordering ordering = Ordering.usingToString(); - Helpers.testComparator(ordering, 1, 12, 124, 2); + testComparator(ordering, 1, 12, 124, 2); assertEquals("Ordering.usingToString()", ordering.toString()); assertSame(ordering, reserialize(ordering)); } @@ -268,7 +272,7 @@ public Character apply(String string) { } private static Ordering byCharAt(int index) { - return Ordering.natural().onResultOf(CharAtFunction.values()[index]); + return Ordering.natural().onResultOf(CharAtFunction.values()[index]); } public void testCompound_static() { @@ -276,7 +280,7 @@ public void testCompound_static() { Ordering.compound( ImmutableList.of( byCharAt(0), byCharAt(1), byCharAt(2), byCharAt(3), byCharAt(4), byCharAt(5))); - Helpers.testComparator( + testComparator( comparator, ImmutableList.of( "applesauce", @@ -292,7 +296,7 @@ public void testCompound_static() { public void testCompound_instance() { Comparator comparator = byCharAt(1).compound(byCharAt(0)); - Helpers.testComparator( + testComparator( comparator, ImmutableList.of("red", "yellow", "violet", "blue", "indigo", "green", "orange")); } @@ -303,42 +307,42 @@ public void testCompound_instance_generics() { Ordering integers = Ordering.explicit(1); // Like by like equals like - Ordering a = numbers.compound(numbers); + Ordering unusedA = numbers.compound(numbers); // The compound takes the more specific type of the two, regardless of order - Ordering b = numbers.compound(objects); - Ordering c = objects.compound(numbers); + Ordering unusedB = numbers.compound(objects); + Ordering unusedC = objects.compound(numbers); - Ordering d = numbers.compound(integers); - Ordering e = integers.compound(numbers); + Ordering unusedD = numbers.compound(integers); + Ordering unusedE = integers.compound(numbers); // This works with three levels too (IDEA falsely reports errors as noted // below. Both javac and eclipse handle these cases correctly.) - Ordering f = numbers.compound(objects).compound(objects); // bad IDEA - Ordering g = objects.compound(numbers).compound(objects); - Ordering h = objects.compound(objects).compound(numbers); + Ordering unusedF = numbers.compound(objects).compound(objects); // bad IDEA + Ordering unusedG = objects.compound(numbers).compound(objects); + Ordering unusedH = objects.compound(objects).compound(numbers); - Ordering i = numbers.compound(objects.compound(objects)); - Ordering j = objects.compound(numbers.compound(objects)); // bad IDEA - Ordering k = objects.compound(objects.compound(numbers)); + Ordering unusedI = numbers.compound(objects.compound(objects)); + Ordering unusedJ = objects.compound(numbers.compound(objects)); // bad IDEA + Ordering unusedK = objects.compound(objects.compound(numbers)); // You can also arbitrarily assign a more restricted type - not an intended // feature, exactly, but unavoidable (I think) and harmless - Ordering l = objects.compound(numbers); + Ordering unusedL = objects.compound(numbers); // This correctly doesn't work: - // Ordering m = numbers.compound(objects); + // Ordering unusedM = numbers.compound(objects); // Sadly, the following works in javac 1.6, but at least it fails for // eclipse, and is *correctly* highlighted red in IDEA. - // Ordering n = objects.compound(numbers); + // Ordering unusedN = objects.compound(numbers); } public void testReverse() { Ordering reverseOrder = numberOrdering.reverse(); - Helpers.testComparator(reverseOrder, Integer.MAX_VALUE, 1, 0, -1, Integer.MIN_VALUE); + testComparator(reverseOrder, Integer.MAX_VALUE, 1, 0, -1, Integer.MIN_VALUE); new EqualsTester() .addEqualityGroup(reverseOrder, numberOrdering.reverse()) @@ -354,7 +358,7 @@ public void testReverseOfReverseSameAsForward() { } private enum StringLengthFunction implements Function { - StringLength; + STRING_LENGTH; @Override public Integer apply(String string) { @@ -362,42 +366,41 @@ public Integer apply(String string) { } } - private static final Ordering DECREASING_INTEGER = Ordering.natural().reverse(); + private static final Ordering DECREASING_INTEGER = Ordering.natural().reverse(); public void testOnResultOf_natural() { Comparator comparator = - Ordering.natural().onResultOf(StringLengthFunction.StringLength); + Ordering.natural().onResultOf(StringLengthFunction.STRING_LENGTH); assertTrue(comparator.compare("to", "be") == 0); assertTrue(comparator.compare("or", "not") < 0); assertTrue(comparator.compare("that", "to") > 0); new EqualsTester() .addEqualityGroup( - comparator, Ordering.natural().onResultOf(StringLengthFunction.StringLength)) + comparator, Ordering.natural().onResultOf(StringLengthFunction.STRING_LENGTH)) .addEqualityGroup(DECREASING_INTEGER) .testEquals(); reserializeAndAssert(comparator); - assertEquals("Ordering.natural().onResultOf(StringLength)", comparator.toString()); + assertEquals("Ordering.natural().onResultOf(STRING_LENGTH)", comparator.toString()); } public void testOnResultOf_chained() { Comparator comparator = - DECREASING_INTEGER.onResultOf(StringLengthFunction.StringLength); + DECREASING_INTEGER.onResultOf(StringLengthFunction.STRING_LENGTH); assertTrue(comparator.compare("to", "be") == 0); assertTrue(comparator.compare("not", "or") < 0); assertTrue(comparator.compare("to", "that") > 0); new EqualsTester() .addEqualityGroup( - comparator, DECREASING_INTEGER.onResultOf(StringLengthFunction.StringLength)) + comparator, DECREASING_INTEGER.onResultOf(StringLengthFunction.STRING_LENGTH)) .addEqualityGroup(DECREASING_INTEGER.onResultOf(Functions.constant(1))) .addEqualityGroup(Ordering.natural()) .testEquals(); reserializeAndAssert(comparator); - assertEquals("Ordering.natural().reverse().onResultOf(StringLength)", comparator.toString()); + assertEquals("Ordering.natural().reverse().onResultOf(STRING_LENGTH)", comparator.toString()); } - @SuppressWarnings("unchecked") // dang varargs public void testLexicographical() { Ordering ordering = Ordering.natural(); Ordering> lexy = ordering.lexicographical(); @@ -408,7 +411,7 @@ public void testLexicographical() { ImmutableList ab = ImmutableList.of("a", "b"); ImmutableList b = ImmutableList.of("b"); - Helpers.testComparator(lexy, empty, a, aa, ab, b); + testComparator(lexy, empty, a, aa, ab, b); new EqualsTester() .addEqualityGroup(lexy, ordering.lexicographical()) @@ -418,8 +421,8 @@ public void testLexicographical() { } public void testNullsFirst() { - Ordering ordering = Ordering.natural().nullsFirst(); - Helpers.testComparator(ordering, null, Integer.MIN_VALUE, 0, 1); + Ordering<@Nullable Integer> ordering = Ordering.natural().nullsFirst(); + Helpers.<@Nullable Integer>testComparator(ordering, null, Integer.MIN_VALUE, 0, 1); new EqualsTester() .addEqualityGroup(ordering, Ordering.natural().nullsFirst()) @@ -429,8 +432,8 @@ public void testNullsFirst() { } public void testNullsLast() { - Ordering ordering = Ordering.natural().nullsLast(); - Helpers.testComparator(ordering, 0, 1, Integer.MAX_VALUE, null); + Ordering<@Nullable Integer> ordering = Ordering.natural().nullsLast(); + Helpers.<@Nullable Integer>testComparator(ordering, 0, 1, Integer.MAX_VALUE, null); new EqualsTester() .addEqualityGroup(ordering, Ordering.natural().nullsLast()) @@ -439,35 +442,37 @@ public void testNullsLast() { .testEquals(); } + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testBinarySearch() { List ints = Lists.newArrayList(0, 2, 3, 5, 7, 9); assertEquals(4, numberOrdering.binarySearch(ints, 7)); } public void testSortedCopy() { - List unsortedInts = Collections.unmodifiableList(Arrays.asList(5, 0, 3, null, 0, 9)); - List sortedInts = numberOrdering.nullsLast().sortedCopy(unsortedInts); - assertEquals(Arrays.asList(0, 0, 3, 5, 9, null), sortedInts); + List<@Nullable Integer> unsortedInts = + unmodifiableList(Arrays.<@Nullable Integer>asList(5, 0, 3, null, 0, 9)); + List<@Nullable Integer> sortedInts = numberOrdering.nullsLast().sortedCopy(unsortedInts); + assertEquals(Arrays.<@Nullable Integer>asList(0, 0, 3, 5, 9, null), sortedInts); - assertEquals( - Collections.emptyList(), numberOrdering.sortedCopy(Collections.emptyList())); + assertEquals(emptyList(), numberOrdering.sortedCopy(Collections.emptyList())); } public void testImmutableSortedCopy() { ImmutableList unsortedInts = ImmutableList.of(5, 3, 0, 9, 3); ImmutableList sortedInts = numberOrdering.immutableSortedCopy(unsortedInts); - assertEquals(Arrays.asList(0, 3, 3, 5, 9), sortedInts); + assertEquals(asList(0, 3, 3, 5, 9), sortedInts); assertEquals( Collections.emptyList(), numberOrdering.immutableSortedCopy(Collections.emptyList())); - List listWithNull = Arrays.asList(5, 3, null, 9); - try { - Ordering.natural().nullsFirst().immutableSortedCopy(listWithNull); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Integer> listWithNull = asList(5, 3, null, 9); + assertThrows( + NullPointerException.class, + () -> + Ordering.natural() + .nullsFirst() + .immutableSortedCopy((List) listWithNull)); } public void testIsOrdered() { @@ -476,7 +481,7 @@ public void testIsOrdered() { assertTrue(numberOrdering.isOrdered(asList(0, 3, 5, 9))); assertTrue(numberOrdering.isOrdered(asList(0, 0, 3, 3))); assertTrue(numberOrdering.isOrdered(asList(0, 3))); - assertTrue(numberOrdering.isOrdered(Collections.singleton(1))); + assertTrue(numberOrdering.isOrdered(singleton(1))); assertTrue(numberOrdering.isOrdered(Collections.emptyList())); } @@ -486,7 +491,7 @@ public void testIsStrictlyOrdered() { assertTrue(numberOrdering.isStrictlyOrdered(asList(0, 3, 5, 9))); assertFalse(numberOrdering.isStrictlyOrdered(asList(0, 0, 3, 3))); assertTrue(numberOrdering.isStrictlyOrdered(asList(0, 3))); - assertTrue(numberOrdering.isStrictlyOrdered(Collections.singleton(1))); + assertTrue(numberOrdering.isStrictlyOrdered(singleton(1))); assertTrue(numberOrdering.isStrictlyOrdered(Collections.emptyList())); } @@ -519,37 +524,32 @@ public void testLeastOfIterator_empty_1() { } public void testLeastOfIterable_simple_negativeOne() { - try { - numberOrdering.leastOf(Arrays.asList(3, 4, 5, -1), -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> numberOrdering.leastOf(asList(3, 4, 5, -1), -1)); } public void testLeastOfIterator_simple_negativeOne() { - try { - numberOrdering.leastOf(Iterators.forArray(3, 4, 5, -1), -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> numberOrdering.leastOf(Iterators.forArray(3, 4, 5, -1), -1)); } public void testLeastOfIterable_singleton_0() { - List result = numberOrdering.leastOf(Arrays.asList(3), 0); + List result = numberOrdering.leastOf(asList(3), 0); assertTrue(result instanceof RandomAccess); assertListImmutable(result); assertEquals(ImmutableList.of(), result); } public void testLeastOfIterator_singleton_0() { - List result = numberOrdering.leastOf(Iterators.singletonIterator(3), 0); + List result = numberOrdering.leastOf(singletonIterator(3), 0); assertTrue(result instanceof RandomAccess); assertListImmutable(result); assertEquals(ImmutableList.of(), result); } public void testLeastOfIterable_simple_0() { - List result = numberOrdering.leastOf(Arrays.asList(3, 4, 5, -1), 0); + List result = numberOrdering.leastOf(asList(3, 4, 5, -1), 0); assertTrue(result instanceof RandomAccess); assertListImmutable(result); assertEquals(ImmutableList.of(), result); @@ -563,7 +563,7 @@ public void testLeastOfIterator_simple_0() { } public void testLeastOfIterable_simple_1() { - List result = numberOrdering.leastOf(Arrays.asList(3, 4, 5, -1), 1); + List result = numberOrdering.leastOf(asList(3, 4, 5, -1), 1); assertTrue(result instanceof RandomAccess); assertListImmutable(result); assertEquals(ImmutableList.of(-1), result); @@ -577,23 +577,24 @@ public void testLeastOfIterator_simple_1() { } public void testLeastOfIterable_simple_nMinusOne_withNullElement() { - List list = Arrays.asList(3, null, 5, -1); - List result = Ordering.natural().nullsLast().leastOf(list, list.size() - 1); + List<@Nullable Integer> list = asList(3, null, 5, -1); + List<@Nullable Integer> result = + Ordering.natural().nullsLast().leastOf(list, list.size() - 1); assertTrue(result instanceof RandomAccess); assertListImmutable(result); assertEquals(ImmutableList.of(-1, 3, 5), result); } public void testLeastOfIterator_simple_nMinusOne_withNullElement() { - Iterator itr = Iterators.forArray(3, null, 5, -1); - List result = Ordering.natural().nullsLast().leastOf(itr, 3); + Iterator<@Nullable Integer> itr = Iterators.forArray(3, null, 5, -1); + List<@Nullable Integer> result = Ordering.natural().nullsLast().leastOf(itr, 3); assertTrue(result instanceof RandomAccess); assertListImmutable(result); assertEquals(ImmutableList.of(-1, 3, 5), result); } public void testLeastOfIterable_simple_nMinusOne() { - List list = Arrays.asList(3, 4, 5, -1); + List list = asList(3, 4, 5, -1); List result = numberOrdering.leastOf(list, list.size() - 1); assertTrue(result instanceof RandomAccess); assertListImmutable(result); @@ -601,7 +602,7 @@ public void testLeastOfIterable_simple_nMinusOne() { } public void testLeastOfIterator_simple_nMinusOne() { - List list = Arrays.asList(3, 4, 5, -1); + List list = asList(3, 4, 5, -1); List result = numberOrdering.leastOf(list.iterator(), list.size() - 1); assertTrue(result instanceof RandomAccess); assertListImmutable(result); @@ -609,7 +610,7 @@ public void testLeastOfIterator_simple_nMinusOne() { } public void testLeastOfIterable_simple_n() { - List list = Arrays.asList(3, 4, 5, -1); + List list = asList(3, 4, 5, -1); List result = numberOrdering.leastOf(list, list.size()); assertTrue(result instanceof RandomAccess); assertListImmutable(result); @@ -617,7 +618,7 @@ public void testLeastOfIterable_simple_n() { } public void testLeastOfIterator_simple_n() { - List list = Arrays.asList(3, 4, 5, -1); + List list = asList(3, 4, 5, -1); List result = numberOrdering.leastOf(list.iterator(), list.size()); assertTrue(result instanceof RandomAccess); assertListImmutable(result); @@ -625,23 +626,25 @@ public void testLeastOfIterator_simple_n() { } public void testLeastOfIterable_simple_n_withNullElement() { - List list = Arrays.asList(3, 4, 5, null, -1); - List result = Ordering.natural().nullsLast().leastOf(list, list.size()); + List<@Nullable Integer> list = asList(3, 4, 5, null, -1); + List<@Nullable Integer> result = + Ordering.natural().nullsLast().leastOf(list, list.size()); assertTrue(result instanceof RandomAccess); assertListImmutable(result); - assertEquals(Arrays.asList(-1, 3, 4, 5, null), result); + assertEquals(Arrays.<@Nullable Integer>asList(-1, 3, 4, 5, null), result); } public void testLeastOfIterator_simple_n_withNullElement() { - List list = Arrays.asList(3, 4, 5, null, -1); - List result = Ordering.natural().nullsLast().leastOf(list.iterator(), list.size()); + List<@Nullable Integer> list = asList(3, 4, 5, null, -1); + List<@Nullable Integer> result = + Ordering.natural().nullsLast().leastOf(list.iterator(), list.size()); assertTrue(result instanceof RandomAccess); assertListImmutable(result); - assertEquals(Arrays.asList(-1, 3, 4, 5, null), result); + assertEquals(Arrays.<@Nullable Integer>asList(-1, 3, 4, 5, null), result); } public void testLeastOfIterable_simple_nPlusOne() { - List list = Arrays.asList(3, 4, 5, -1); + List list = asList(3, 4, 5, -1); List result = numberOrdering.leastOf(list, list.size() + 1); assertTrue(result instanceof RandomAccess); assertListImmutable(result); @@ -649,7 +652,7 @@ public void testLeastOfIterable_simple_nPlusOne() { } public void testLeastOfIterator_simple_nPlusOne() { - List list = Arrays.asList(3, 4, 5, -1); + List list = asList(3, 4, 5, -1); List result = numberOrdering.leastOf(list.iterator(), list.size() + 1); assertTrue(result instanceof RandomAccess); assertListImmutable(result); @@ -663,7 +666,7 @@ public void testLeastOfIterable_ties() { assertNotSame(foo, bar); assertEquals(foo, bar); - List list = Arrays.asList(3, foo, bar, -1); + List list = asList(3, foo, bar, -1); List result = numberOrdering.leastOf(list, list.size()); assertEquals(ImmutableList.of(-1, 3, foo, bar), result); } @@ -675,7 +678,7 @@ public void testLeastOfIterator_ties() { assertNotSame(foo, bar); assertEquals(foo, bar); - List list = Arrays.asList(3, foo, bar, -1); + List list = asList(3, foo, bar, -1); List result = numberOrdering.leastOf(list.iterator(), list.size()); assertEquals(ImmutableList.of(-1, 3, foo, bar), result); } @@ -694,7 +697,7 @@ private static void runLeastOfComparison(int iterations, int elements, int seeds Ordering ordering = Ordering.natural(); for (int i = 0; i < iterations; i++) { - List list = Lists.newArrayList(); + List list = new ArrayList<>(); for (int j = 0; j < elements; j++) { list.add(random.nextInt(10 * i + j + 1)); } @@ -707,15 +710,16 @@ private static void runLeastOfComparison(int iterations, int elements, int seeds } public void testLeastOfIterableLargeK() { - List list = Arrays.asList(4, 2, 3, 5, 1); - assertEquals(Arrays.asList(1, 2, 3, 4, 5), Ordering.natural().leastOf(list, Integer.MAX_VALUE)); + List list = asList(4, 2, 3, 5, 1); + assertEquals( + asList(1, 2, 3, 4, 5), Ordering.natural().leastOf(list, Integer.MAX_VALUE)); } public void testLeastOfIteratorLargeK() { - List list = Arrays.asList(4, 2, 3, 5, 1); + List list = asList(4, 2, 3, 5, 1); assertEquals( - Arrays.asList(1, 2, 3, 4, 5), - Ordering.natural().leastOf(list.iterator(), Integer.MAX_VALUE)); + asList(1, 2, 3, 4, 5), + Ordering.natural().leastOf(list.iterator(), Integer.MAX_VALUE)); } public void testGreatestOfIterable_simple() { @@ -724,8 +728,8 @@ public void testGreatestOfIterable_simple() { * test would be enough. It doesn't... but we'll cheat and act like it does * anyway. There's a comment there to remind us to fix this if we change it. */ - List list = Arrays.asList(3, 1, 3, 2, 4, 2, 4, 3); - assertEquals(Arrays.asList(4, 4, 3, 3), numberOrdering.greatestOf(list, 4)); + List list = asList(3, 1, 3, 2, 4, 2, 4, 3); + assertEquals(asList(4, 4, 3, 3), numberOrdering.greatestOf(list, 4)); } public void testGreatestOfIterator_simple() { @@ -734,8 +738,8 @@ public void testGreatestOfIterator_simple() { * test would be enough. It doesn't... but we'll cheat and act like it does * anyway. There's a comment there to remind us to fix this if we change it. */ - List list = Arrays.asList(3, 1, 3, 2, 4, 2, 4, 3); - assertEquals(Arrays.asList(4, 4, 3, 3), numberOrdering.greatestOf(list.iterator(), 4)); + List list = asList(3, 1, 3, 2, 4, 2, 4, 3); + assertEquals(asList(4, 4, 3, 3), numberOrdering.greatestOf(list.iterator(), 4)); } private static void assertListImmutable(List result) { @@ -833,11 +837,11 @@ public int hashCode() { } @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { return other instanceof NumberOrdering; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /* @@ -868,13 +872,14 @@ public void testCombinationsExhaustively_startingFromFromComparator() { testExhaustively(Ordering.from(String.CASE_INSENSITIVE_ORDER), "A", "b", "C", "d"); } + @J2ktIncompatible // Ordering.arbitrary @GwtIncompatible // too slow public void testCombinationsExhaustively_startingFromArbitrary() { Ordering arbitrary = Ordering.arbitrary(); Object[] array = {1, "foo", new Object()}; // There's no way to tell what the order should be except empirically - Arrays.sort(array, arbitrary); + sort(array, arbitrary); testExhaustively(arbitrary, array); } @@ -886,19 +891,19 @@ private static void testExhaustively( Ordering ordering, T... strictlyOrderedElements) { checkArgument( strictlyOrderedElements.length >= 3, - "strictlyOrderedElements " + "requires at least 3 elements"); - List list = Arrays.asList(strictlyOrderedElements); + "strictlyOrderedElements requires at least 3 elements"); + List list = asList(strictlyOrderedElements); // for use calling Collection.toArray later T[] emptyArray = Platform.newArray(strictlyOrderedElements, 0); // shoot me, but I didn't want to deal with wildcards through the whole test @SuppressWarnings("unchecked") - Scenario starter = new Scenario<>((Ordering) ordering, list, emptyArray); + Scenario starter = new Scenario<>((Ordering) ordering, list, emptyArray); verifyScenario(starter, 0); } - private static void verifyScenario(Scenario scenario, int level) { + private static void verifyScenario(Scenario scenario, int level) { scenario.testCompareTo(); scenario.testIsOrdered(); scenario.testMinAndMax(); @@ -916,7 +921,7 @@ private static void verifyScenario(Scenario scenario, int level) { * An aggregation of an ordering with a list (of size > 1) that should prove to be in strictly * increasing order according to that ordering. */ - private static class Scenario { + private static class Scenario { final Ordering ordering; final List strictlyOrderedList; final T[] emptyArray; @@ -928,7 +933,7 @@ private static class Scenario { } void testCompareTo() { - Helpers.testComparator(ordering, strictlyOrderedList); + testComparator(ordering, strictlyOrderedList); } void testIsOrdered() { @@ -936,9 +941,9 @@ void testIsOrdered() { assertTrue(ordering.isStrictlyOrdered(strictlyOrderedList)); } - @SuppressWarnings("unchecked") // generic arrays and unchecked cast + // generic arrays and unchecked cast void testMinAndMax() { - List shuffledList = Lists.newArrayList(strictlyOrderedList); + List shuffledList = new ArrayList<>(strictlyOrderedList); shuffledList = shuffledCopy(shuffledList, new Random(5)); T min = strictlyOrderedList.get(0); @@ -962,23 +967,25 @@ void testMinAndMax() { assertEquals(max, ordering.max(max, min)); } + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method void testBinarySearch() { for (int i = 0; i < strictlyOrderedList.size(); i++) { assertEquals(i, ordering.binarySearch(strictlyOrderedList, strictlyOrderedList.get(i))); } - List newList = Lists.newArrayList(strictlyOrderedList); + List newList = new ArrayList<>(strictlyOrderedList); T valueNotInList = newList.remove(1); assertEquals(-2, ordering.binarySearch(newList, valueNotInList)); } void testSortedCopy() { - List shuffledList = Lists.newArrayList(strictlyOrderedList); + List shuffledList = new ArrayList<>(strictlyOrderedList); shuffledList = shuffledCopy(shuffledList, new Random(5)); assertEquals(strictlyOrderedList, ordering.sortedCopy(shuffledList)); if (!strictlyOrderedList.contains(null)) { - assertEquals(strictlyOrderedList, ordering.immutableSortedCopy(shuffledList)); + List<@NonNull T> nonNullShuffledList = (List<@NonNull T>) shuffledList; + assertEquals(strictlyOrderedList, ordering.immutableSortedCopy(nonNullShuffledList)); } } } @@ -991,16 +998,15 @@ void testSortedCopy() { private enum OrderingMutation { REVERSE { @Override - Scenario mutate(Scenario scenario) { - List newList = Lists.newArrayList(scenario.strictlyOrderedList); + Scenario mutate(Scenario scenario) { + List newList = new ArrayList<>(scenario.strictlyOrderedList); Collections.reverse(newList); return new Scenario(scenario.ordering.reverse(), newList, scenario.emptyArray); } }, NULLS_FIRST { @Override - Scenario mutate(Scenario scenario) { - @SuppressWarnings("unchecked") + Scenario mutate(Scenario scenario) { List newList = Lists.newArrayList((T) null); for (T t : scenario.strictlyOrderedList) { if (t != null) { @@ -1012,8 +1018,8 @@ Scenario mutate(Scenario scenario) { }, NULLS_LAST { @Override - Scenario mutate(Scenario scenario) { - List newList = Lists.newArrayList(); + Scenario mutate(Scenario scenario) { + List newList = new ArrayList<>(); for (T t : scenario.strictlyOrderedList) { if (t != null) { newList.add(t); @@ -1025,16 +1031,16 @@ Scenario mutate(Scenario scenario) { }, ON_RESULT_OF { @Override - Scenario mutate(final Scenario scenario) { + Scenario mutate(Scenario scenario) { Ordering ordering = scenario.ordering.onResultOf( new Function() { @Override - public T apply(@NullableDecl Integer from) { + public T apply(Integer from) { return scenario.strictlyOrderedList.get(from); } }); - List list = Lists.newArrayList(); + List list = new ArrayList<>(); for (int i = 0; i < scenario.strictlyOrderedList.size(); i++) { list.add(i); } @@ -1042,10 +1048,10 @@ public T apply(@NullableDecl Integer from) { } }, COMPOUND_THIS_WITH_NATURAL { - @SuppressWarnings("unchecked") // raw array + @SuppressWarnings("unchecked") // generic arrays @Override - Scenario mutate(Scenario scenario) { - List> composites = Lists.newArrayList(); + Scenario mutate(Scenario scenario) { + List> composites = new ArrayList<>(); for (T t : scenario.strictlyOrderedList) { composites.add(new Composite(t, 1)); composites.add(new Composite(t, 2)); @@ -1055,14 +1061,15 @@ Scenario mutate(Scenario scenario) { .ordering .onResultOf(Composite.getValueFunction()) .compound(Ordering.natural()); - return new Scenario>(ordering, composites, new Composite[0]); + return new Scenario>( + ordering, composites, (Composite[]) new Composite[0]); } }, COMPOUND_NATURAL_WITH_THIS { - @SuppressWarnings("unchecked") // raw array + @SuppressWarnings("unchecked") // generic arrays @Override - Scenario mutate(Scenario scenario) { - List> composites = Lists.newArrayList(); + Scenario mutate(Scenario scenario) { + List> composites = new ArrayList<>(); for (T t : scenario.strictlyOrderedList) { composites.add(new Composite(t, 1)); } @@ -1070,37 +1077,38 @@ Scenario mutate(Scenario scenario) { composites.add(new Composite(t, 2)); } Ordering> ordering = - Ordering.natural() + Ordering.>natural() .compound(scenario.ordering.onResultOf(Composite.getValueFunction())); - return new Scenario>(ordering, composites, new Composite[0]); + return new Scenario>( + ordering, composites, (Composite[]) new Composite[0]); } }, LEXICOGRAPHICAL { - @SuppressWarnings("unchecked") // dang varargs + @SuppressWarnings("unchecked") // generic arrays @Override - Scenario mutate(Scenario scenario) { - List> words = Lists.newArrayList(); + Scenario mutate(Scenario scenario) { + List> words = new ArrayList<>(); words.add(Collections.emptyList()); for (T t : scenario.strictlyOrderedList) { - words.add(Arrays.asList(t)); + words.add(asList(t)); for (T s : scenario.strictlyOrderedList) { - words.add(Arrays.asList(t, s)); + words.add(asList(t, s)); } } return new Scenario>( - scenario.ordering.lexicographical(), words, new Iterable[0]); + scenario.ordering.lexicographical(), words, (Iterable[]) new Iterable[0]); } }, ; - abstract Scenario mutate(Scenario scenario); + abstract Scenario mutate(Scenario scenario); } /** * A dummy object we create so that we can have something meaningful to have a compound ordering * over. */ - private static class Composite implements Comparable> { + private static class Composite implements Comparable> { final T value; final int rank; @@ -1113,10 +1121,10 @@ private static class Composite implements Comparable> { // order of 't'. @Override public int compareTo(Composite that) { - return Ints.compare(rank, that.rank); + return Integer.compare(rank, that.rank); } - static Function, T> getValueFunction() { + static Function, T> getValueFunction() { return new Function, T>() { @Override public T apply(Composite from) { @@ -1126,6 +1134,7 @@ public T apply(Composite from) { } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); @@ -1135,9 +1144,9 @@ public void testNullPointerExceptions() { tester.testAllPublicInstanceMethods(Ordering.usingToString().nullsFirst()); } - private static List shuffledCopy(List in, Random random) { - List mutable = newArrayList(in); - List out = newArrayList(); + private static List shuffledCopy(List in, Random random) { + List mutable = new ArrayList<>(in); + List out = new ArrayList<>(); while (!mutable.isEmpty()) { out.add(mutable.remove(random.nextInt(mutable.size()))); } diff --git a/android/guava-tests/test/com/google/common/collect/PackageSanityTests.java b/android/guava-tests/test/com/google/common/collect/PackageSanityTests.java index c847140d573e..52ebabb173f1 100644 --- a/android/guava-tests/test/com/google/common/collect/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/collect/PackageSanityTests.java @@ -17,6 +17,7 @@ package com.google.common.collect; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** * Covers basic sanity checks for the entire package. @@ -24,6 +25,7 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() { publicApiOnly(); // Many package-private classes are tested through the public API. diff --git a/android/guava-tests/test/com/google/common/collect/PeekingIteratorTest.java b/android/guava-tests/test/com/google/common/collect/PeekingIteratorTest.java index 0f79985bcf39..fae3d9bdd673 100644 --- a/android/guava-tests/test/com/google/common/collect/PeekingIteratorTest.java +++ b/android/guava-tests/test/com/google/common/collect/PeekingIteratorTest.java @@ -17,19 +17,23 @@ package com.google.common.collect; import static com.google.common.collect.Iterators.peekingIterator; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.IteratorTester; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link PeekingIterator}. @@ -37,7 +41,8 @@ * @author Mick Killianey */ @SuppressWarnings("serial") // No serialization is used in this test -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class PeekingIteratorTest extends TestCase { /** @@ -47,13 +52,13 @@ public class PeekingIteratorTest extends TestCase { * *

    This IteratorTester makes copies of the master so that it can later verify that {@link * PeekingIterator#remove()} removes the same elements as the reference's iterator {@code - * #remove()}. + * remove()}. */ - private static class PeekingIteratorTester extends IteratorTester { - private Iterable master; - private List targetList; + private static class PeekingIteratorTester extends IteratorTester { + private final Iterable master; + private @Nullable List targetList; - public PeekingIteratorTester(Collection master) { + PeekingIteratorTester(Collection master) { super(master.size() + 3, MODIFIABLE, master, IteratorTester.KnownOrder.KNOWN_ORDER); this.master = master; } @@ -63,7 +68,7 @@ protected Iterator newTargetIterator() { // make copy from master to verify later targetList = Lists.newArrayList(master); Iterator iterator = targetList.iterator(); - return Iterators.peekingIterator(iterator); + return peekingIterator(iterator); } @Override @@ -73,7 +78,7 @@ protected void verify(List elements) { } } - private void actsLikeIteratorHelper(final List list) { + private void actsLikeIteratorHelper(List list) { // Check with modifiable copies of the list new PeekingIteratorTester(list).test(); @@ -82,18 +87,18 @@ private void actsLikeIteratorHelper(final List list) { list.size() * 2 + 2, UNMODIFIABLE, list, IteratorTester.KnownOrder.KNOWN_ORDER) { @Override protected Iterator newTargetIterator() { - Iterator iterator = Collections.unmodifiableList(list).iterator(); - return Iterators.peekingIterator(iterator); + Iterator iterator = unmodifiableList(list).iterator(); + return peekingIterator(iterator); } }.test(); } public void testPeekingIteratorBehavesLikeIteratorOnEmptyIterable() { - actsLikeIteratorHelper(Collections.emptyList()); + actsLikeIteratorHelper(emptyList()); } public void testPeekingIteratorBehavesLikeIteratorOnSingletonIterable() { - actsLikeIteratorHelper(Collections.singletonList(new Object())); + actsLikeIteratorHelper(singletonList(new Object())); } // TODO(cpovirk): instead of skipping, use a smaller number of steps @@ -104,26 +109,21 @@ public void testPeekingIteratorBehavesLikeIteratorOnThreeElementIterable() { @GwtIncompatible // works but takes 5 minutes to run public void testPeekingIteratorAcceptsNullElements() { - actsLikeIteratorHelper(Lists.newArrayList(null, "A", null)); + actsLikeIteratorHelper(Lists.<@Nullable String>newArrayList(null, "A", null)); } public void testPeekOnEmptyList() { - List list = Collections.emptyList(); + List list = emptyList(); Iterator iterator = list.iterator(); - PeekingIterator peekingIterator = Iterators.peekingIterator(iterator); + PeekingIterator peekingIterator = peekingIterator(iterator); - try { - peekingIterator.peek(); - fail("Should throw NoSuchElementException if nothing to peek()"); - } catch (NoSuchElementException e) { - /* expected */ - } + assertThrows(NoSuchElementException.class, () -> peekingIterator.peek()); } public void testPeekDoesntChangeIteration() { List list = Lists.newArrayList("A", "B", "C"); Iterator iterator = list.iterator(); - PeekingIterator peekingIterator = Iterators.peekingIterator(iterator); + PeekingIterator peekingIterator = peekingIterator(iterator); assertEquals("Should be able to peek() at first element", "A", peekingIterator.peek()); assertEquals( @@ -143,41 +143,21 @@ public void testPeekDoesntChangeIteration() { assertEquals( "next() should still return last element after peeking", "C", peekingIterator.next()); - try { - peekingIterator.peek(); - fail("Should throw exception if no next to peek()"); - } catch (NoSuchElementException e) { - /* expected */ - } - try { - peekingIterator.peek(); - fail("Should continue to throw exception if no next to peek()"); - } catch (NoSuchElementException e) { - /* expected */ - } - try { - peekingIterator.next(); - fail("next() should still throw exception after the end of iteration"); - } catch (NoSuchElementException e) { - /* expected */ - } + assertThrows(NoSuchElementException.class, () -> peekingIterator.peek()); + assertThrows(NoSuchElementException.class, () -> peekingIterator.peek()); + assertThrows(NoSuchElementException.class, () -> peekingIterator.next()); } public void testCantRemoveAfterPeek() { List list = Lists.newArrayList("A", "B", "C"); Iterator iterator = list.iterator(); - PeekingIterator peekingIterator = Iterators.peekingIterator(iterator); + PeekingIterator peekingIterator = peekingIterator(iterator); assertEquals("A", peekingIterator.next()); assertEquals("B", peekingIterator.peek()); /* Should complain on attempt to remove() after peek(). */ - try { - peekingIterator.remove(); - fail("remove() should throw IllegalStateException after a peek()"); - } catch (IllegalStateException e) { - /* expected */ - } + assertThrows(IllegalStateException.class, () -> peekingIterator.remove()); assertEquals( "After remove() throws exception, peek should still be ok", "B", peekingIterator.peek()); diff --git a/android/guava-tests/test/com/google/common/collect/QueuesTest.java b/android/guava-tests/test/com/google/common/collect/QueuesTest.java index 819700e4c167..0362e920ebae 100644 --- a/android/guava-tests/test/com/google/common/collect/QueuesTest.java +++ b/android/guava-tests/test/com/google/common/collect/QueuesTest.java @@ -16,7 +16,6 @@ package com.google.common.collect; -import static com.google.common.collect.Lists.newArrayList; import static com.google.common.truth.Truth.assertThat; import static java.lang.Long.MAX_VALUE; import static java.lang.Thread.currentThread; @@ -24,8 +23,11 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.base.Stopwatch; +import java.time.Duration; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; @@ -40,13 +42,15 @@ import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Queues}. * * @author Dimitris Andreou */ - +@NullUnmarked public class QueuesTest extends TestCase { /* * All the following tests relate to BlockingQueue methods in Queues. @@ -83,14 +87,14 @@ public void tearDown() throws InterruptedException { private static int drain( BlockingQueue q, Collection buffer, - int maxElements, + int numElements, long timeout, TimeUnit unit, boolean interruptibly) throws InterruptedException { return interruptibly - ? Queues.drain(q, buffer, maxElements, timeout, unit) - : Queues.drainUninterruptibly(q, buffer, maxElements, timeout, unit); + ? Queues.drain(q, buffer, numElements, timeout, unit) + : Queues.drainUninterruptibly(q, buffer, numElements, timeout, unit); } public void testMultipleProducers() throws Exception { @@ -112,7 +116,7 @@ private void testMultipleProducers(BlockingQueue q) throws InterruptedEx @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError4 = threadPool.submit(new Producer(q, 20)); - List buf = newArrayList(); + List buf = new ArrayList<>(); int elements = drain(q, buf, 100, MAX_VALUE, NANOSECONDS, interruptibly); assertEquals(100, elements); assertEquals(100, buf.size()); @@ -122,11 +126,11 @@ private void testMultipleProducers(BlockingQueue q) throws InterruptedEx public void testDrainTimesOut() throws Exception { for (BlockingQueue q : blockingQueues()) { - testDrainTimesOut(q); + checkDrainTimesOut(q); } } - private void testDrainTimesOut(BlockingQueue q) throws Exception { + private void checkDrainTimesOut(BlockingQueue q) throws Exception { for (boolean interruptibly : new boolean[] {true, false}) { assertEquals(0, Queues.drain(q, ImmutableList.of(), 1, 10, MILLISECONDS)); @@ -138,7 +142,7 @@ private void testDrainTimesOut(BlockingQueue q) throws Exception { // make sure we time out Stopwatch timer = Stopwatch.createStarted(); - int drained = drain(q, newArrayList(), 2, 10, MILLISECONDS, interruptibly); + int drained = drain(q, new ArrayList<>(), 2, 10, MILLISECONDS, interruptibly); assertThat(drained).isAtMost(1); assertThat(timer.elapsed(MILLISECONDS)).isAtLeast(10L); @@ -152,13 +156,22 @@ private void testDrainTimesOut(BlockingQueue q) throws Exception { } } + public void testDrainOverflow() throws InterruptedException { + Queues.drain(new SynchronousQueue<>(), new ArrayList<>(), /* numElements= */ 0, MAX_DURATION); + } + + public void testDrainUninterruptiblyOverflow() { + Queues.drainUninterruptibly( + new SynchronousQueue<>(), new ArrayList<>(), /* numElements= */ 0, MAX_DURATION); + } + public void testZeroElements() throws Exception { for (BlockingQueue q : blockingQueues()) { - testZeroElements(q); + checkZeroElements(q); } } - private void testZeroElements(BlockingQueue q) throws InterruptedException { + private void checkZeroElements(BlockingQueue q) throws InterruptedException { for (boolean interruptibly : new boolean[] {true, false}) { // asking to drain zero elements assertEquals(0, drain(q, ImmutableList.of(), 0, 10, MILLISECONDS, interruptibly)); @@ -167,25 +180,25 @@ private void testZeroElements(BlockingQueue q) throws InterruptedExcepti public void testEmpty() throws Exception { for (BlockingQueue q : blockingQueues()) { - testEmpty(q); + checkEmpty(q); } } - private void testEmpty(BlockingQueue q) { + private void checkEmpty(BlockingQueue q) { assertDrained(q); } public void testNegativeMaxElements() throws Exception { for (BlockingQueue q : blockingQueues()) { - testNegativeMaxElements(q); + checkNegativeMaxElements(q); } } - private void testNegativeMaxElements(BlockingQueue q) throws InterruptedException { + private void checkNegativeMaxElements(BlockingQueue q) throws InterruptedException { @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError = threadPool.submit(new Producer(q, 1)); - List buf = newArrayList(); + List buf = new ArrayList<>(); int elements = Queues.drain(q, buf, -1, MAX_VALUE, NANOSECONDS); assertEquals(0, elements); assertThat(buf).isEmpty(); @@ -196,11 +209,11 @@ private void testNegativeMaxElements(BlockingQueue q) throws Interrupted public void testDrain_throws() throws Exception { for (BlockingQueue q : blockingQueues()) { - testDrain_throws(q); + checkDrainThrows(q); } } - private void testDrain_throws(BlockingQueue q) { + private void checkDrainThrows(BlockingQueue q) { @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError = threadPool.submit(new Interrupter(currentThread())); try { @@ -212,24 +225,25 @@ private void testDrain_throws(BlockingQueue q) { public void testDrainUninterruptibly_doesNotThrow() throws Exception { for (BlockingQueue q : blockingQueues()) { - testDrainUninterruptibly_doesNotThrow(q); + testDrainUninterruptiblyDoesNotThrow(q); } } - private void testDrainUninterruptibly_doesNotThrow(final BlockingQueue q) { - final Thread mainThread = currentThread(); + private void testDrainUninterruptiblyDoesNotThrow(BlockingQueue q) { + Thread mainThread = currentThread(); @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError = threadPool.submit( - new Callable() { - public Void call() throws InterruptedException { + new Callable<@Nullable Void>() { + @Override + public @Nullable Void call() throws InterruptedException { new Producer(q, 50).call(); new Interrupter(mainThread).run(); new Producer(q, 50).call(); return null; } }); - List buf = newArrayList(); + List buf = new ArrayList<>(); int elements = Queues.drainUninterruptibly(q, buf, 100, MAX_VALUE, NANOSECONDS); // so when this drains all elements, we know the thread has also been interrupted in between assertTrue(Thread.interrupted()); @@ -238,30 +252,20 @@ public Void call() throws InterruptedException { } public void testNewLinkedBlockingDequeCapacity() { - try { - Queues.newLinkedBlockingDeque(0); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // any capacity less than 1 should throw IllegalArgumentException - } + assertThrows(IllegalArgumentException.class, () -> Queues.newLinkedBlockingDeque(0)); assertEquals(1, Queues.newLinkedBlockingDeque(1).remainingCapacity()); assertEquals(11, Queues.newLinkedBlockingDeque(11).remainingCapacity()); } public void testNewLinkedBlockingQueueCapacity() { - try { - Queues.newLinkedBlockingQueue(0); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // any capacity less than 1 should throw IllegalArgumentException - } + assertThrows(IllegalArgumentException.class, () -> Queues.newLinkedBlockingQueue(0)); assertEquals(1, Queues.newLinkedBlockingQueue(1).remainingCapacity()); assertEquals(11, Queues.newLinkedBlockingQueue(11).remainingCapacity()); } /** Checks that #drain() invocations behave correctly for a drained (empty) queue. */ private void assertDrained(BlockingQueue q) { - assertNull(q.peek()); + assertThat(q.peek()).isNull(); assertInterruptibleDrained(q); assertUninterruptibleDrained(q); } @@ -279,7 +283,7 @@ private void assertInterruptibleDrained(BlockingQueue q) { Future possiblyIgnoredError = threadPool.submit(new Interrupter(currentThread())); try { // if waiting works, this should get stuck - Queues.drain(q, newArrayList(), 1, MAX_VALUE, NANOSECONDS); + Queues.drain(q, new ArrayList<>(), 1, MAX_VALUE, NANOSECONDS); fail(); } catch (InterruptedException expected) { // we indeed waited; a slow thread had enough time to interrupt us @@ -287,6 +291,7 @@ private void assertInterruptibleDrained(BlockingQueue q) { } // same as above; uninterruptible version + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. private void assertUninterruptibleDrained(BlockingQueue q) { assertEquals(0, Queues.drainUninterruptibly(q, ImmutableList.of(), 0, 10, MILLISECONDS)); @@ -295,7 +300,7 @@ private void assertUninterruptibleDrained(BlockingQueue q) { Future possiblyIgnoredError = threadPool.submit(new Interrupter(currentThread())); Stopwatch timer = Stopwatch.createStarted(); - Queues.drainUninterruptibly(q, newArrayList(), 1, 10, MILLISECONDS); + Queues.drainUninterruptibly(q, new ArrayList<>(), 1, 10, MILLISECONDS); assertThat(timer.elapsed(MILLISECONDS)).isAtLeast(10L); // wait for interrupted status and clear it while (!Thread.interrupted()) { @@ -303,7 +308,7 @@ private void assertUninterruptibleDrained(BlockingQueue q) { } } - private static class Producer implements Callable { + private static class Producer implements Callable<@Nullable Void> { final BlockingQueue q; final int elements; final CountDownLatch beganProducing = new CountDownLatch(1); @@ -315,7 +320,7 @@ private static class Producer implements Callable { } @Override - public Void call() throws InterruptedException { + public @Nullable Void call() throws InterruptedException { try { beganProducing.countDown(); for (int i = 0; i < elements; i++) { @@ -346,4 +351,6 @@ public void run() { } } } + + private static final Duration MAX_DURATION = Duration.ofSeconds(Long.MAX_VALUE, 999_999_999); } diff --git a/android/guava-tests/test/com/google/common/collect/RangeNonGwtTest.java b/android/guava-tests/test/com/google/common/collect/RangeNonGwtTest.java index 666624a305f1..e669fc41cf7f 100644 --- a/android/guava-tests/test/com/google/common/collect/RangeNonGwtTest.java +++ b/android/guava-tests/test/com/google/common/collect/RangeNonGwtTest.java @@ -18,6 +18,7 @@ import com.google.common.testing.NullPointerTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test cases for {@link Range} which cannot run as GWT tests. @@ -25,6 +26,7 @@ * @author Gregory Kick * @see RangeTest */ +@NullUnmarked public class RangeNonGwtTest extends TestCase { public void testNullPointers() { diff --git a/android/guava-tests/test/com/google/common/collect/RangeTest.java b/android/guava-tests/test/com/google/common/collect/RangeTest.java index 9a2cd74161a8..c0c4b982fbb8 100644 --- a/android/guava-tests/test/com/google/common/collect/RangeTest.java +++ b/android/guava-tests/test/com/google/common/collect/RangeTest.java @@ -19,26 +19,30 @@ import static com.google.common.collect.BoundType.CLOSED; import static com.google.common.collect.BoundType.OPEN; import static com.google.common.collect.DiscreteDomain.integers; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.testing.Helpers.testCompareToAndEquals; import static com.google.common.testing.SerializableTester.reserializeAndAssert; +import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Predicate; -import com.google.common.collect.testing.Helpers; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.EqualsTester; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Range}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class RangeTest extends TestCase { public void testOpen() { Range range = Range.open(4, 8); @@ -55,16 +59,8 @@ public void testOpen() { } public void testOpen_invalid() { - try { - Range.open(4, 3); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - Range.open(3, 3); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Range.open(4, 3)); + assertThrows(IllegalArgumentException.class, () -> Range.open(3, 3)); } public void testClosed() { @@ -82,11 +78,7 @@ public void testClosed() { } public void testClosed_invalid() { - try { - Range.closed(4, 3); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Range.closed(4, 3)); } public void testOpenClosed() { @@ -288,9 +280,10 @@ public void testOrderingCuts() { Cut e = Range.greaterThan(1).lowerBound; Cut f = Range.greaterThan(1).upperBound; - Helpers.testCompareToAndEquals(ImmutableList.of(a, b, c, d, e, f)); + testCompareToAndEquals(ImmutableList.of(a, b, c, d, e, f)); } + @SuppressWarnings("DistinctVarargsChecker") public void testContainsAll() { Range range = Range.closed(3, 5); assertTrue(range.containsAll(asList(3, 3, 4, 5))); @@ -345,38 +338,35 @@ public void testIntersection_empty() { Range range = Range.closedOpen(3, 3); assertEquals(range, range.intersection(range)); - try { - range.intersection(Range.open(3, 5)); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - range.intersection(Range.closed(0, 2)); - fail(); - } catch (IllegalArgumentException expected) { - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> range.intersection(Range.open(3, 5))); + assertThat(expected).hasMessageThat().contains("connected"); + expected = + assertThrows(IllegalArgumentException.class, () -> range.intersection(Range.closed(0, 2))); + assertThat(expected).hasMessageThat().contains("connected"); } public void testIntersection_deFactoEmpty() { - Range range = Range.open(3, 4); - assertEquals(range, range.intersection(range)); - - assertEquals(Range.openClosed(3, 3), range.intersection(Range.atMost(3))); - assertEquals(Range.closedOpen(4, 4), range.intersection(Range.atLeast(4))); - - try { - range.intersection(Range.lessThan(3)); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - range.intersection(Range.greaterThan(4)); - fail(); - } catch (IllegalArgumentException expected) { + { + Range range = Range.open(3, 4); + assertEquals(range, range.intersection(range)); + + assertEquals(Range.openClosed(3, 3), range.intersection(Range.atMost(3))); + assertEquals(Range.closedOpen(4, 4), range.intersection(Range.atLeast(4))); + + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> range.intersection(Range.lessThan(3))); + assertThat(expected).hasMessageThat().contains("connected"); + expected = + assertThrows( + IllegalArgumentException.class, () -> range.intersection(Range.greaterThan(4))); + assertThat(expected).hasMessageThat().contains("connected"); } - range = Range.closed(3, 4); - assertEquals(Range.openClosed(4, 4), range.intersection(Range.greaterThan(4))); + { + Range range = Range.closed(3, 4); + assertEquals(Range.openClosed(4, 4), range.intersection(Range.greaterThan(4))); + } } public void testIntersection_singleton() { @@ -391,27 +381,21 @@ public void testIntersection_singleton() { assertEquals(Range.closedOpen(3, 3), range.intersection(Range.lessThan(3))); assertEquals(Range.openClosed(3, 3), range.intersection(Range.greaterThan(3))); - try { - range.intersection(Range.atLeast(4)); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - range.intersection(Range.atMost(2)); - fail(); - } catch (IllegalArgumentException expected) { - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> range.intersection(Range.atLeast(4))); + assertThat(expected).hasMessageThat().contains("connected"); + expected = + assertThrows(IllegalArgumentException.class, () -> range.intersection(Range.atMost(2))); + assertThat(expected).hasMessageThat().contains("connected"); } public void testIntersection_general() { Range range = Range.closed(4, 8); // separate below - try { - range.intersection(Range.closed(0, 2)); - fail(); - } catch (IllegalArgumentException expected) { - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> range.intersection(Range.closed(0, 2))); + assertThat(expected).hasMessageThat().contains("connected"); // adjacent below assertEquals(Range.closedOpen(4, 4), range.intersection(Range.closedOpen(2, 4))); @@ -447,57 +431,28 @@ public void testIntersection_general() { assertEquals(Range.openClosed(8, 8), range.intersection(Range.openClosed(8, 10))); // separate above - try { - range.intersection(Range.closed(10, 12)); - fail(); - } catch (IllegalArgumentException expected) { - } + expected = + assertThrows( + IllegalArgumentException.class, () -> range.intersection(Range.closed(10, 12))); + assertThat(expected).hasMessageThat().contains("connected"); } public void testGap_overlapping() { Range range = Range.closedOpen(3, 5); - try { - range.gap(Range.closed(4, 6)); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - range.gap(Range.closed(2, 4)); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - range.gap(Range.closed(2, 3)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> range.gap(Range.closed(4, 6))); + assertThrows(IllegalArgumentException.class, () -> range.gap(Range.closed(2, 4))); + assertThrows(IllegalArgumentException.class, () -> range.gap(Range.closed(2, 3))); } public void testGap_invalidRangesWithInfinity() { - try { - Range.atLeast(1).gap(Range.atLeast(2)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Range.atLeast(1).gap(Range.atLeast(2))); - try { - Range.atLeast(2).gap(Range.atLeast(1)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Range.atLeast(2).gap(Range.atLeast(1))); - try { - Range.atMost(1).gap(Range.atMost(2)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Range.atMost(1).gap(Range.atMost(2))); - try { - Range.atMost(2).gap(Range.atMost(1)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Range.atMost(2).gap(Range.atMost(1))); } public void testGap_connectedAdjacentYieldsEmpty() { @@ -582,8 +537,10 @@ public void testSpan_general() { assertEquals(Range.atLeast(4), range.span(Range.atLeast(10))); } - public void testApply() { - Predicate predicate = Range.closed(2, 3); + @SuppressWarnings({"InlineMeInliner", "deprecation"}) // intentional test of depecated method + public void testPredicateMethods() { + Range predicate = Range.closed(2, 3); + assertFalse(predicate.apply(1)); assertTrue(predicate.apply(2)); assertTrue(predicate.apply(3)); @@ -600,11 +557,12 @@ public void testEquals() { } @GwtIncompatible // TODO(b/148207871): Restore once Eclipse compiler no longer flakes for this. + @J2ktIncompatible // TODO(b/148207871): Likewise, or once J2KT uses javac instead of Eclipse. public void testLegacyComparable() { - Range range = Range.closed(LegacyComparable.X, LegacyComparable.Y); + Range unused = Range.closed(LegacyComparable.X, LegacyComparable.Y); } - static final DiscreteDomain UNBOUNDED_DOMAIN = + private static final DiscreteDomain UNBOUNDED_DOMAIN = new DiscreteDomain() { @Override public Integer next(Integer value) { @@ -650,32 +608,20 @@ public void testCanonical_unboundedDomain() { } public void testEncloseAll() { - assertEquals(Range.closed(0, 0), Range.encloseAll(Arrays.asList(0))); - assertEquals(Range.closed(-3, 5), Range.encloseAll(Arrays.asList(5, -3))); - assertEquals(Range.closed(-3, 5), Range.encloseAll(Arrays.asList(1, 2, 2, 2, 5, -3, 0, -1))); + assertEquals(Range.closed(0, 0), Range.encloseAll(asList(0))); + assertEquals(Range.closed(-3, 5), Range.encloseAll(asList(5, -3))); + assertEquals(Range.closed(-3, 5), Range.encloseAll(asList(1, 2, 2, 2, 5, -3, 0, -1))); } public void testEncloseAll_empty() { - try { - Range.encloseAll(ImmutableSet.of()); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> Range.encloseAll(ImmutableSet.of())); } public void testEncloseAll_nullValue() { - List nullFirst = Lists.newArrayList(null, 0); - try { - Range.encloseAll(nullFirst); - fail(); - } catch (NullPointerException expected) { - } - List nullNotFirst = Lists.newArrayList(0, null); - try { - Range.encloseAll(nullNotFirst); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Integer> nullFirst = Lists.newArrayList(null, 0); + assertThrows(NullPointerException.class, () -> Range.encloseAll((List) nullFirst)); + List<@Nullable Integer> nullNotFirst = Lists.newArrayList(0, null); + assertThrows(NullPointerException.class, () -> Range.encloseAll((List) nullNotFirst)); } public void testEquivalentFactories() { diff --git a/android/guava-tests/test/com/google/common/collect/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/collect/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..ef42d27e4a0e --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ReflectionFreeAssertThrows.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.Ordering.IncomparableValueException; +import com.google.common.collect.TestExceptions.SomeCheckedException; +import com.google.common.collect.TestExceptions.SomeError; +import com.google.common.collect.TestExceptions.SomeOtherCheckedException; +import com.google.common.collect.TestExceptions.SomeUncheckedException; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IncomparableValueException.class, e -> e instanceof IncomparableValueException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(SomeCheckedException.class, e -> e instanceof SomeCheckedException) + .put(SomeError.class, e -> e instanceof SomeError) + .put(SomeOtherCheckedException.class, e -> e instanceof SomeOtherCheckedException) + .put(SomeUncheckedException.class, e -> e instanceof SomeUncheckedException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/collect/RegularImmutableAsListTest.java b/android/guava-tests/test/com/google/common/collect/RegularImmutableAsListTest.java index b6c2358c590a..9c1a09c1a052 100644 --- a/android/guava-tests/test/com/google/common/collect/RegularImmutableAsListTest.java +++ b/android/guava-tests/test/com/google/common/collect/RegularImmutableAsListTest.java @@ -16,6 +16,8 @@ import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link RegularImmutableAsList}. @@ -23,6 +25,7 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public class RegularImmutableAsListTest extends TestCase { /** * RegularImmutableAsList should assume its input is null-free without checking, because it only @@ -30,7 +33,8 @@ public class RegularImmutableAsListTest extends TestCase { */ public void testDoesntCheckForNull() { ImmutableSet set = ImmutableSet.of(1, 2, 3); - new RegularImmutableAsList(set, new Object[] {null, null, null}); + ImmutableList unused = + new RegularImmutableAsList(set, new @Nullable Object[] {null, null, null}); // shouldn't throw! } } diff --git a/android/guava-tests/test/com/google/common/collect/RegularImmutableMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/RegularImmutableMapMapInterfaceTest.java new file mode 100644 index 000000000000..f888c7677904 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/RegularImmutableMapMapInterfaceTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class RegularImmutableMapMapInterfaceTest + extends AbstractImmutableMapMapInterfaceTest { + @Override + protected Map makeEmptyMap() { + return ImmutableMap.of(); + } + + @Override + protected Map makePopulatedMap() { + return ImmutableMap.of("one", 1, "two", 2, "three", 3); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/RegularImmutableMapWithUnhashableValuesMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/RegularImmutableMapWithUnhashableValuesMapInterfaceTest.java new file mode 100644 index 000000000000..641c99d8f42f --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/RegularImmutableMapWithUnhashableValuesMapInterfaceTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.testing.SampleElements.Unhashables; +import com.google.common.collect.testing.UnhashableObject; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // GWT's ImmutableMap emulation is backed by java.util.HashMap. +@NullUnmarked +public class RegularImmutableMapWithUnhashableValuesMapInterfaceTest + extends AbstractImmutableMapMapInterfaceTest { + @Override + protected Map makeEmptyMap() { + return ImmutableMap.of(); + } + + @Override + protected Map makePopulatedMap() { + Unhashables unhashables = new Unhashables(); + return ImmutableMap.of(0, unhashables.e0(), 1, unhashables.e1(), 2, unhashables.e2()); + } + + @Override + protected Integer getKeyNotInPopulatedMap() { + return 3; + } + + @Override + protected UnhashableObject getValueNotInPopulatedMap() { + return new Unhashables().e3(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/RegularImmutableSortedMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/RegularImmutableSortedMapMapInterfaceTest.java new file mode 100644 index 000000000000..ca09158f053e --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/RegularImmutableSortedMapMapInterfaceTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class RegularImmutableSortedMapMapInterfaceTest + extends AbstractImmutableSortedMapMapInterfaceTest { + @Override + protected SortedMap makeEmptyMap() { + return ImmutableSortedMap.of(); + } + + @Override + protected SortedMap makePopulatedMap() { + return ImmutableSortedMap.of("one", 1, "two", 2, "three", 3); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/RegularImmutableTableTest.java b/android/guava-tests/test/com/google/common/collect/RegularImmutableTableTest.java index 82d7b39b9ff5..5524dcf5fd07 100644 --- a/android/guava-tests/test/com/google/common/collect/RegularImmutableTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/RegularImmutableTableTest.java @@ -16,19 +16,22 @@ package com.google.common.collect; +import static com.google.common.collect.Tables.immutableCell; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Table.Cell; +import org.jspecify.annotations.NullMarked; -/** @author Gregory Kick */ +/** + * @author Gregory Kick + */ @GwtCompatible +@NullMarked public class RegularImmutableTableTest extends AbstractImmutableTableTest { private static final ImmutableSet> CELLS = ImmutableSet.of( - Tables.immutableCell('a', 1, "foo"), - Tables.immutableCell('b', 1, "bar"), - Tables.immutableCell('a', 2, "baz")); + immutableCell('a', 1, "foo"), immutableCell('b', 1, "bar"), immutableCell('a', 2, "baz")); private static final ImmutableSet ROW_SPACE = ImmutableSet.of('a', 'b'); @@ -83,9 +86,9 @@ public void testForCells() { assertTrue( RegularImmutableTable.forCells( ImmutableSet.of( - Tables.immutableCell('a', 1, "blah"), - Tables.immutableCell('b', 2, "blah"), - Tables.immutableCell('c', 3, "blah"))) + immutableCell('a', 1, "blah"), + immutableCell('b', 2, "blah"), + immutableCell('c', 3, "blah"))) instanceof SparseImmutableTable); } @@ -94,8 +97,8 @@ public void testGet() { assertEquals("foo", testInstance.get('a', 1)); assertEquals("bar", testInstance.get('b', 1)); assertEquals("baz", testInstance.get('a', 2)); - assertNull(testInstance.get('b', 2)); - assertNull(testInstance.get('c', 3)); + assertThat(testInstance.get('b', 2)).isNull(); + assertThat(testInstance.get('c', 3)).isNull(); } } diff --git a/android/guava-tests/test/com/google/common/collect/ReserializedImmutableMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ReserializedImmutableMapMapInterfaceTest.java new file mode 100644 index 000000000000..a09be5094498 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ReserializedImmutableMapMapInterfaceTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.testing.SerializableTester; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // SerializableTester +@NullUnmarked +public class ReserializedImmutableMapMapInterfaceTest + extends AbstractImmutableMapMapInterfaceTest { + @Override + protected Map makePopulatedMap() { + return SerializableTester.reserialize(ImmutableMap.of("one", 1, "two", 2, "three", 3)); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/ReserializedImmutableSortedMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/ReserializedImmutableSortedMapMapInterfaceTest.java new file mode 100644 index 000000000000..01e24ffb9e7c --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/ReserializedImmutableSortedMapMapInterfaceTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.testing.SerializableTester; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // SerializableTester +@NullUnmarked +public class ReserializedImmutableSortedMapMapInterfaceTest + extends AbstractImmutableSortedMapMapInterfaceTest { + @Override + protected SortedMap makePopulatedMap() { + return SerializableTester.reserialize(ImmutableSortedMap.of("one", 1, "two", 2, "three", 3)); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/SetOperationsTest.java b/android/guava-tests/test/com/google/common/collect/SetOperationsTest.java deleted file mode 100644 index 7a1ec3a6590c..000000000000 --- a/android/guava-tests/test/com/google/common/collect/SetOperationsTest.java +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) 2008 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Arrays.asList; - -import com.google.common.annotations.GwtCompatible; -import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.SetTestSuiteBuilder; -import com.google.common.collect.testing.TestStringSetGenerator; -import com.google.common.collect.testing.features.CollectionFeature; -import com.google.common.collect.testing.features.CollectionSize; -import java.util.HashSet; -import java.util.Set; -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit tests for {@link Sets#union}, {@link Sets#intersection} and {@link Sets#difference}. - * - * @author Kevin Bourrillion - */ -@GwtCompatible(emulated = true) -public class SetOperationsTest extends TestCase { - @GwtIncompatible // suite - public static Test suite() { - TestSuite suite = new TestSuite(); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.union(Sets.newHashSet(), Sets.newHashSet()); - } - }) - .named("empty U empty") - .withFeatures( - CollectionSize.ZERO, CollectionFeature.NONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - checkArgument(elements.length == 1); - return Sets.union(Sets.newHashSet(elements), Sets.newHashSet(elements)); - } - }) - .named("singleton U itself") - .withFeatures(CollectionSize.ONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.union(Sets.newHashSet(), Sets.newHashSet(elements)); - } - }) - .named("empty U set") - .withFeatures( - CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.union(Sets.newHashSet(elements), Sets.newHashSet()); - } - }) - .named("set U empty") - .withFeatures( - CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - checkArgument(elements.length == 3); - // Put the sets in different orders for the hell of it - return Sets.union( - Sets.newLinkedHashSet(asList(elements)), - Sets.newLinkedHashSet(asList(elements[1], elements[0], elements[2]))); - } - }) - .named("set U itself") - .withFeatures(CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - checkArgument(elements.length == 3); - return Sets.union( - Sets.newHashSet(elements[0]), Sets.newHashSet(elements[1], elements[2])); - } - }) - .named("union of disjoint") - .withFeatures(CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.union( - Sets.newHashSet(elements[0], elements[1]), - Sets.newHashSet(elements[1], elements[2])); - } - }) - .named("venn") - .withFeatures(CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.intersection(Sets.newHashSet(), Sets.newHashSet()); - } - }) - .named("empty & empty") - .withFeatures( - CollectionSize.ZERO, CollectionFeature.NONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.intersection( - Sets.newHashSet(), Sets.newHashSet((String) null)); - } - }) - .named("empty & singleton") - .withFeatures( - CollectionSize.ZERO, CollectionFeature.NONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.intersection(Sets.newHashSet("a", "b"), Sets.newHashSet("c", "d")); - } - }) - .named("intersection of disjoint") - .withFeatures( - CollectionSize.ZERO, CollectionFeature.NONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.intersection(Sets.newHashSet(elements), Sets.newHashSet(elements)); - } - }) - .named("set & itself") - .withFeatures( - CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.intersection( - Sets.newHashSet("a", elements[0], "b"), - Sets.newHashSet("c", elements[0], "d")); - } - }) - .named("intersection with overlap of one") - .withFeatures(CollectionSize.ONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.difference(Sets.newHashSet(), Sets.newHashSet()); - } - }) - .named("empty - empty") - .withFeatures( - CollectionSize.ZERO, CollectionFeature.NONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.difference(Sets.newHashSet("a"), Sets.newHashSet("a")); - } - }) - .named("singleton - itself") - .withFeatures( - CollectionSize.ZERO, CollectionFeature.NONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - Set set = Sets.newHashSet("b", "c"); - Set other = Sets.newHashSet("a", "b", "c", "d"); - return Sets.difference(set, other); - } - }) - .named("set - superset") - .withFeatures( - CollectionSize.ZERO, CollectionFeature.NONE, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - Set set = Sets.newHashSet(elements); - Set other = Sets.newHashSet("wz", "xq"); - set.addAll(other); - other.add("pq"); - return Sets.difference(set, other); - } - }) - .named("set - set") - .withFeatures( - CollectionSize.ANY, - CollectionFeature.ALLOWS_NULL_VALUES, - CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.difference(Sets.newHashSet(elements), Sets.newHashSet()); - } - }) - .named("set - empty") - .withFeatures( - CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - return Sets.difference( - Sets.newHashSet(elements), Sets.newHashSet("xx", "xq")); - } - }) - .named("set - disjoint") - .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTestSuite(MoreTests.class); - return suite; - } - - public static class MoreTests extends TestCase { - Set friends; - Set enemies; - - @Override - public void setUp() { - friends = Sets.newHashSet("Tom", "Joe", "Dave"); - enemies = Sets.newHashSet("Dick", "Harry", "Tom"); - } - - public void testUnion() { - Set all = Sets.union(friends, enemies); - assertEquals(5, all.size()); - - ImmutableSet immut = Sets.union(friends, enemies).immutableCopy(); - HashSet mut = Sets.union(friends, enemies).copyInto(new HashSet()); - - enemies.add("Buck"); - assertEquals(6, all.size()); - assertEquals(5, immut.size()); - assertEquals(5, mut.size()); - } - - public void testIntersection() { - Set friends = Sets.newHashSet("Tom", "Joe", "Dave"); - Set enemies = Sets.newHashSet("Dick", "Harry", "Tom"); - - Set frenemies = Sets.intersection(friends, enemies); - assertEquals(1, frenemies.size()); - - ImmutableSet immut = Sets.intersection(friends, enemies).immutableCopy(); - HashSet mut = Sets.intersection(friends, enemies).copyInto(new HashSet()); - - enemies.add("Joe"); - assertEquals(2, frenemies.size()); - assertEquals(1, immut.size()); - assertEquals(1, mut.size()); - } - - public void testDifference() { - Set friends = Sets.newHashSet("Tom", "Joe", "Dave"); - Set enemies = Sets.newHashSet("Dick", "Harry", "Tom"); - - Set goodFriends = Sets.difference(friends, enemies); - assertEquals(2, goodFriends.size()); - - ImmutableSet immut = Sets.difference(friends, enemies).immutableCopy(); - HashSet mut = Sets.difference(friends, enemies).copyInto(new HashSet()); - - enemies.add("Dave"); - assertEquals(1, goodFriends.size()); - assertEquals(2, immut.size()); - assertEquals(2, mut.size()); - } - - public void testSymmetricDifference() { - Set friends = Sets.newHashSet("Tom", "Joe", "Dave"); - Set enemies = Sets.newHashSet("Dick", "Harry", "Tom"); - - Set symmetricDifferenceFriendsFirst = Sets.symmetricDifference(friends, enemies); - assertEquals(4, symmetricDifferenceFriendsFirst.size()); - - Set symmetricDifferenceEnemiesFirst = Sets.symmetricDifference(enemies, friends); - assertEquals(4, symmetricDifferenceEnemiesFirst.size()); - - assertEquals(symmetricDifferenceFriendsFirst, symmetricDifferenceEnemiesFirst); - - ImmutableSet immut = Sets.symmetricDifference(friends, enemies).immutableCopy(); - HashSet mut = - Sets.symmetricDifference(friends, enemies).copyInto(new HashSet()); - - enemies.add("Dave"); - assertEquals(3, symmetricDifferenceFriendsFirst.size()); - assertEquals(4, immut.size()); - assertEquals(4, mut.size()); - - immut = Sets.symmetricDifference(enemies, friends).immutableCopy(); - mut = Sets.symmetricDifference(enemies, friends).copyInto(new HashSet()); - friends.add("Harry"); - assertEquals(2, symmetricDifferenceEnemiesFirst.size()); - assertEquals(3, immut.size()); - assertEquals(3, mut.size()); - } - } -} diff --git a/android/guava-tests/test/com/google/common/collect/SetViewTest.java b/android/guava-tests/test/com/google/common/collect/SetViewTest.java new file mode 100644 index 000000000000..e6e7ef6c1564 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/SetViewTest.java @@ -0,0 +1,742 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Sets.difference; +import static com.google.common.collect.Sets.intersection; +import static com.google.common.collect.Sets.newHashSet; +import static com.google.common.collect.Sets.symmetricDifference; +import static com.google.common.collect.Sets.union; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.collect.Sets.SetView; +import com.google.common.collect.testing.SetTestSuiteBuilder; +import com.google.common.collect.testing.TestStringSetGenerator; +import com.google.common.collect.testing.features.CollectionFeature; +import com.google.common.collect.testing.features.CollectionSize; +import com.google.common.testing.EqualsTester; +import java.util.AbstractSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Unit tests for {@link SetView}s: {@link Sets#union}, {@link Sets#intersection}, {@link + * Sets#difference}, and {@link Sets#symmetricDifference}. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +@NullMarked +public class SetViewTest extends TestCase { + @J2ktIncompatible + @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders + public static Test suite() { + TestSuite suite = new TestSuite(); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return union(emptySet(), emptySet()); + } + }) + .named("empty U empty") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return union(singleton(elements[0]), singleton(elements[0])); + } + }) + .named("singleton U itself") + .withFeatures(CollectionSize.ONE, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return union(emptySet(), newHashSet(elements)); + } + }) + .named("empty U set") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return union(newHashSet(elements), emptySet()); + } + }) + .named("set U empty") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + // Put the sets in different orders for the hell of it + return union( + new LinkedHashSet<>(asList(elements[0], elements[1], elements[2])), + new LinkedHashSet<>(asList(elements[1], elements[0], elements[2]))); + } + }) + .named("set U itself") + .withFeatures(CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return union(newHashSet(elements[0], elements[1]), newHashSet(elements[2])); + } + }) + .named("set U disjoint") + .withFeatures(CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return union( + newHashSet(elements[0], elements[1]), newHashSet(elements[1], elements[2])); + } + }) + .named("set U set") + .withFeatures(CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return intersection(emptySet(), emptySet()); + } + }) + .named("empty & empty") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return intersection(emptySet(), newHashSet(samples())); + } + }) + .named("empty & set") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return intersection(newHashSet(samples()), emptySet()); + } + }) + .named("set & empty") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return intersection( + newHashSet(samples().e1(), samples().e3()), + newHashSet(samples().e2(), samples().e4())); + } + }) + .named("set & disjoint") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return intersection(newHashSet(elements), newHashSet(elements)); + } + }) + .named("set & itself") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + Set set1 = newHashSet(elements); + set1.add(samples().e3()); + Set set2 = newHashSet(elements); + set2.add(samples().e4()); + return intersection(set1, set2); + } + }) + .named("set & set") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return difference(emptySet(), emptySet()); + } + }) + .named("empty - empty") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return difference(emptySet(), newHashSet(samples())); + } + }) + .named("empty - set") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return difference(newHashSet(samples()), newHashSet(samples())); + } + }) + .named("set - itself") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return difference( + newHashSet(samples().e3(), samples().e4()), newHashSet(samples())); + } + }) + .named("set - superset") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + Set difference = newHashSet(elements); + Set set = newHashSet(samples()); + set.addAll(difference); + Set subset = newHashSet(samples()); + subset.removeAll(difference); + return difference(set, subset); + } + }) + .named("set - subset") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + Set set = newHashSet(elements); + set.add(samples().e3()); + return difference(set, newHashSet(samples().e3(), samples().e4())); + } + }) + .named("set - set") + .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return difference(newHashSet(elements), emptySet()); + } + }) + .named("set - empty") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return difference( + newHashSet(elements), newHashSet(samples().e3(), samples().e4())); + } + }) + .named("set - disjoint") + .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return symmetricDifference(emptySet(), emptySet()); + } + }) + .named("empty ^ empty") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return symmetricDifference(newHashSet(samples()), newHashSet(samples())); + } + }) + .named("set ^ itself") + .withFeatures(CollectionSize.ZERO, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return symmetricDifference(emptySet(), newHashSet(elements)); + } + }) + .named("empty ^ set") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return symmetricDifference(newHashSet(elements), emptySet()); + } + }) + .named("set ^ empty") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + Set difference = newHashSet(elements); + Set set = newHashSet(samples()); + set.removeAll(difference); + Set superset = newHashSet(samples()); + superset.addAll(difference); + return symmetricDifference(set, superset); + } + }) + .named("set ^ superset") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + Set difference = newHashSet(elements); + Set set = newHashSet(samples()); + set.addAll(difference); + Set subset = newHashSet(samples()); + subset.removeAll(difference); + return symmetricDifference(set, subset); + } + }) + .named("set ^ subset") + .withFeatures( + CollectionSize.ONE, CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return symmetricDifference( + newHashSet(elements[0], elements[1]), newHashSet(elements[2])); + } + }) + .named("set ^ disjoint") + .withFeatures(CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTest( + SetTestSuiteBuilder.using( + new TestStringSetGenerator() { + @Override + protected Set create(String[] elements) { + return symmetricDifference( + newHashSet(elements[0], elements[1], samples().e3(), samples().e4()), + newHashSet(elements[2], samples().e3(), samples().e4())); + } + }) + .named("set ^ set") + .withFeatures(CollectionSize.SEVERAL, CollectionFeature.ALLOWS_NULL_VALUES) + .createTestSuite()); + + suite.addTestSuite(SetViewTest.class); + return suite; + } + + public void testUnion_isView() { + Set set1 = newHashSet(1, 2); + Set set2 = newHashSet(2, 3); + Set union = union(set1, set2); + assertThat(union).containsExactly(1, 2, 3); + set1.add(0); + assertThat(union).containsExactly(0, 1, 2, 3); + set2.remove(3); + assertThat(union).containsExactly(0, 1, 2); + } + + public void testIntersection_isView() { + Set set1 = newHashSet(1, 2); + Set set2 = newHashSet(2, 3); + Set intersection = intersection(set1, set2); + assertThat(intersection).containsExactly(2); + set1.add(3); + assertThat(intersection).containsExactly(2, 3); + set2.add(1); + assertThat(intersection).containsExactly(1, 2, 3); + } + + public void testDifference_isView() { + Set set1 = newHashSet(1, 2); + Set set2 = newHashSet(2, 3); + Set difference = difference(set1, set2); + assertThat(difference).containsExactly(1); + set1.add(0); + assertThat(difference).containsExactly(0, 1); + set2.remove(2); + assertThat(difference).containsExactly(0, 1, 2); + } + + public void testSymmetricDifference_isView() { + Set set1 = newHashSet(1, 2); + Set set2 = newHashSet(2, 3); + Set difference = symmetricDifference(set1, set2); + assertThat(difference).containsExactly(1, 3); + set1.add(0); + assertThat(difference).containsExactly(0, 1, 3); + set2.remove(2); + assertThat(difference).containsExactly(0, 1, 2, 3); + } + + public void testImmutableCopy_empty() { + assertThat(union(emptySet(), emptySet()).immutableCopy()).isEmpty(); + assertThat(intersection(newHashSet(1, 2), newHashSet(3, 4)).immutableCopy()).isEmpty(); + assertThat(difference(newHashSet(1, 2), newHashSet(1, 2)).immutableCopy()).isEmpty(); + assertThat(symmetricDifference(newHashSet(1, 2), newHashSet(1, 2)).immutableCopy()).isEmpty(); + } + + public void testImmutableCopy() { + assertThat(union(newHashSet(1, 2), newHashSet(2, 3)).immutableCopy()).containsExactly(1, 2, 3); + assertThat(intersection(newHashSet(1, 2), newHashSet(2, 3)).immutableCopy()).containsExactly(2); + assertThat(difference(newHashSet(1, 2), newHashSet(2, 3)).immutableCopy()).containsExactly(1); + assertThat(symmetricDifference(newHashSet(1, 2), newHashSet(2, 3)).immutableCopy()) + .containsExactly(1, 3); + } + + public void testCopyInto_returnsSameInstance() { + Set set = new HashSet<>(); + assertThat(union(emptySet(), emptySet()).copyInto(set)).isSameInstanceAs(set); + assertThat(intersection(emptySet(), emptySet()).copyInto(set)).isSameInstanceAs(set); + assertThat(difference(emptySet(), emptySet()).copyInto(set)).isSameInstanceAs(set); + assertThat(symmetricDifference(emptySet(), emptySet()).copyInto(set)).isSameInstanceAs(set); + } + + public void testCopyInto_emptySet() { + assertThat(union(newHashSet(1, 2), newHashSet(2, 3)).copyInto(new HashSet<>())) + .containsExactly(1, 2, 3); + assertThat(intersection(newHashSet(1, 2), newHashSet(2, 3)).copyInto(new HashSet<>())) + .containsExactly(2); + assertThat(difference(newHashSet(1, 2), newHashSet(2, 3)).copyInto(new HashSet<>())) + .containsExactly(1); + assertThat(symmetricDifference(newHashSet(1, 2), newHashSet(2, 3)).copyInto(new HashSet<>())) + .containsExactly(1, 3); + } + + public void testCopyInto_nonEmptySet() { + assertThat(union(newHashSet(1, 2), newHashSet(2, 3)).copyInto(newHashSet(0, 1))) + .containsExactly(0, 1, 2, 3); + assertThat(intersection(newHashSet(1, 2), newHashSet(2, 3)).copyInto(newHashSet(0, 1))) + .containsExactly(0, 1, 2); + assertThat(difference(newHashSet(1, 2), newHashSet(2, 3)).copyInto(newHashSet(0, 1))) + .containsExactly(0, 1); + assertThat(symmetricDifference(newHashSet(1, 2), newHashSet(2, 3)).copyInto(newHashSet(0, 1))) + .containsExactly(0, 1, 3); + } + + public void testUnion_minSize() { + assertMinSize(union(emptySet(), emptySet()), 0); + assertMinSize(union(setSize(2), setSize(3)), 3); + assertMinSize(union(setSize(3), setSize(2)), 3); + assertMinSize(union(setSizeRange(10, 20), setSizeRange(11, 12)), 11); + assertMinSize(union(setSizeRange(11, 12), setSizeRange(10, 20)), 11); + } + + public void testUnion_maxSize() { + assertMaxSize(union(emptySet(), emptySet()), 0); + assertMaxSize(union(setSize(2), setSize(3)), 5); + assertMaxSize(union(setSize(3), setSize(2)), 5); + assertMaxSize(union(setSizeRange(10, 20), setSizeRange(11, 12)), 32); + assertMaxSize(union(setSizeRange(11, 12), setSizeRange(10, 20)), 32); + } + + public void testUnion_maxSize_saturated() { + assertThat(union(setSize(Integer.MAX_VALUE), setSize(1)).maxSize()) + .isEqualTo(Integer.MAX_VALUE); + assertThat(union(setSize(1), setSize(Integer.MAX_VALUE)).maxSize()) + .isEqualTo(Integer.MAX_VALUE); + } + + public void testIntersection_minSize() { + assertMinSize(intersection(emptySet(), emptySet()), 0); + assertMinSize(intersection(setSize(2), setSize(3)), 0); + assertMinSize(intersection(setSize(3), setSize(2)), 0); + assertMinSize(intersection(setSizeRange(10, 20), setSizeRange(11, 12)), 0); + assertMinSize(intersection(setSizeRange(11, 12), setSizeRange(10, 20)), 0); + } + + public void testIntersection_maxSize() { + assertMaxSize(intersection(emptySet(), emptySet()), 0); + assertMaxSize(intersection(setSize(2), setSize(3)), 2); + assertMaxSize(intersection(setSize(3), setSize(2)), 2); + assertMaxSize(intersection(setSizeRange(10, 20), setSizeRange(11, 12)), 12); + assertMaxSize(intersection(setSizeRange(11, 12), setSizeRange(10, 20)), 12); + } + + public void testDifference_minSize() { + assertMinSize(difference(emptySet(), emptySet()), 0); + assertMinSize(difference(setSize(2), setSize(3)), 0); + assertMinSize(difference(setSize(3), setSize(2)), 1); + assertMinSize(difference(setSizeRange(10, 20), setSizeRange(1, 2)), 8); + assertMinSize(difference(setSizeRange(1, 2), setSizeRange(10, 20)), 0); + assertMinSize(difference(setSizeRange(10, 20), setSizeRange(11, 12)), 0); + assertMinSize(difference(setSizeRange(11, 12), setSizeRange(10, 20)), 0); + } + + public void testDifference_maxSize() { + assertMaxSize(difference(emptySet(), emptySet()), 0); + assertMaxSize(difference(setSize(2), setSize(3)), 2); + assertMaxSize(difference(setSize(3), setSize(2)), 3); + assertMaxSize(difference(setSizeRange(10, 20), setSizeRange(1, 2)), 20); + assertMaxSize(difference(setSizeRange(1, 2), setSizeRange(10, 20)), 2); + assertMaxSize(difference(setSizeRange(10, 20), setSizeRange(11, 12)), 20); + assertMaxSize(difference(setSizeRange(11, 12), setSizeRange(10, 20)), 12); + } + + public void testSymmetricDifference_minSize() { + assertMinSize(symmetricDifference(emptySet(), emptySet()), 0); + assertMinSize(symmetricDifference(setSize(2), setSize(3)), 1); + assertMinSize(symmetricDifference(setSize(3), setSize(2)), 1); + assertMinSize(symmetricDifference(setSizeRange(10, 20), setSizeRange(1, 2)), 8); + assertMinSize(symmetricDifference(setSizeRange(1, 2), setSizeRange(10, 20)), 8); + assertMinSize(symmetricDifference(setSizeRange(10, 20), setSizeRange(11, 12)), 0); + assertMinSize(symmetricDifference(setSizeRange(11, 12), setSizeRange(10, 20)), 0); + } + + public void testSymmetricDifference_maxSize() { + assertMaxSize(symmetricDifference(emptySet(), emptySet()), 0); + assertMaxSize(symmetricDifference(setSize(2), setSize(3)), 5); + assertMaxSize(symmetricDifference(setSize(3), setSize(2)), 5); + assertMaxSize(symmetricDifference(setSizeRange(10, 20), setSizeRange(1, 2)), 22); + assertMaxSize(symmetricDifference(setSizeRange(1, 2), setSizeRange(10, 20)), 22); + assertMaxSize(symmetricDifference(setSizeRange(10, 20), setSizeRange(11, 12)), 32); + assertMaxSize(symmetricDifference(setSizeRange(11, 12), setSizeRange(10, 20)), 32); + } + + public void testSymmetricDifference_maxSize_saturated() { + assertThat(symmetricDifference(setSize(Integer.MAX_VALUE), setSize(1)).maxSize()) + .isEqualTo(Integer.MAX_VALUE); + assertThat(symmetricDifference(setSize(1), setSize(Integer.MAX_VALUE)).maxSize()) + .isEqualTo(Integer.MAX_VALUE); + } + + public void testEquals() { + new EqualsTester() + .addEqualityGroup( + emptySet(), + union(emptySet(), emptySet()), + intersection(newHashSet(1, 2), newHashSet(3, 4)), + difference(newHashSet(1, 2), newHashSet(1, 2)), + symmetricDifference(newHashSet(1, 2), newHashSet(1, 2))) + .addEqualityGroup( + singleton(1), + union(singleton(1), singleton(1)), + intersection(newHashSet(1, 2), newHashSet(1, 3)), + difference(newHashSet(1, 2), newHashSet(2, 3)), + symmetricDifference(newHashSet(1, 2, 3), newHashSet(2, 3))) + .addEqualityGroup( + singleton(2), + union(singleton(2), singleton(2)), + intersection(newHashSet(1, 2), newHashSet(2, 3)), + difference(newHashSet(1, 2), newHashSet(1, 3)), + symmetricDifference(newHashSet(1, 2, 3), newHashSet(1, 3))) + .addEqualityGroup( + newHashSet(1, 2), + union(singleton(1), singleton(2)), + intersection(newHashSet(1, 2), newHashSet(1, 2, 3)), + difference(newHashSet(1, 2, 3), newHashSet(3)), + symmetricDifference(newHashSet(1, 3), newHashSet(2, 3))) + .addEqualityGroup( + newHashSet(3, 2), + union(singleton(3), singleton(2)), + intersection(newHashSet(3, 2), newHashSet(3, 2, 1)), + difference(newHashSet(3, 2, 1), newHashSet(1)), + symmetricDifference(newHashSet(3, 1), newHashSet(2, 1))) + .addEqualityGroup( + newHashSet(1, 2, 3), + union(newHashSet(1, 2), newHashSet(2, 3)), + intersection(newHashSet(1, 2, 3), newHashSet(1, 2, 3)), + difference(newHashSet(1, 2, 3), emptySet()), + symmetricDifference(emptySet(), newHashSet(1, 2, 3))) + .testEquals(); + } + + public void testEquals_otherSetContainsThrows() { + new EqualsTester() + .addEqualityGroup(new SetContainsThrows()) + .addEqualityGroup(intersection(singleton(null), singleton(null))) // NPE + .addEqualityGroup(intersection(singleton(0), singleton(0))) // CCE + .testEquals(); + } + + /** Returns a {@link Set} with a {@link Set#size()} of {@code size}. */ + private static ContiguousSet setSize(int size) { + checkArgument(size >= 0); + ContiguousSet set = ContiguousSet.closedOpen(0, size); + checkState(set.size() == size); + return set; + } + + /** + * Returns a {@link SetView} with a {@link SetView#minSize()} of {@code min} and a {@link + * SetView#maxSize()} of {@code max}. + */ + private static SetView setSizeRange(int min, int max) { + checkArgument(min >= 0 && max >= min); + SetView set = difference(setSize(max), setSize(max - min)); + checkState(set.minSize() == min && set.maxSize() == max); + return set; + } + + /** + * Asserts that {@code code} has a {@link SetView#minSize()} of {@code min} and a {@link + * Set#size()} of at least {@code min}. + */ + private static void assertMinSize(SetView set, int min) { + assertThat(set.minSize()).isEqualTo(min); + assertThat(set.size()).isAtLeast(min); + } + + /** + * Asserts that {@code code} has a {@link SetView#maxSize()} of {@code max} and a {@link + * Set#size()} of at most {@code max}. + */ + private static void assertMaxSize(SetView set, int max) { + assertThat(set.maxSize()).isEqualTo(max); + assertThat(set.size()).isAtMost(max); + } + + /** + * A {@link Set} that throws {@link NullPointerException} and {@link ClassCastException} from + * {@link #contains}. + */ + private static final class SetContainsThrows extends AbstractSet { + @Override + public boolean contains(@Nullable Object o) { + throw o == null ? new NullPointerException() : new ClassCastException(); + } + + @Override + public int size() { + return 0; + } + + @Override + public Iterator iterator() { + return emptyIterator(); + } + } +} diff --git a/android/guava-tests/test/com/google/common/collect/SetsFilterHashSetTest.java b/android/guava-tests/test/com/google/common/collect/SetsFilterHashSetTest.java new file mode 100644 index 000000000000..6a74bf9726ef --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/SetsFilterHashSetTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Sets.newHashSet; + +import com.google.common.base.Predicate; +import com.google.common.collect.FilteredCollectionsTestUtil.AbstractFilteredSetTest; +import java.util.Set; +import org.jspecify.annotations.NullUnmarked; + +@NullUnmarked +public final class SetsFilterHashSetTest extends AbstractFilteredSetTest> { + @Override + Set createUnfiltered(Iterable contents) { + return newHashSet(contents); + } + + @Override + Set filter(Set elements, Predicate predicate) { + return Sets.filter(elements, predicate); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/SetsFilterNavigableSetTest.java b/android/guava-tests/test/com/google/common/collect/SetsFilterNavigableSetTest.java new file mode 100644 index 000000000000..2090e78098c6 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/SetsFilterNavigableSetTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Predicate; +import com.google.common.collect.FilteredCollectionsTestUtil.AbstractFilteredNavigableSetTest; +import java.util.NavigableSet; +import org.jspecify.annotations.NullUnmarked; + +@NullUnmarked +public final class SetsFilterNavigableSetTest extends AbstractFilteredNavigableSetTest { + @Override + NavigableSet createUnfiltered(Iterable contents) { + return Sets.newTreeSet(contents); + } + + @Override + NavigableSet filter( + NavigableSet elements, Predicate predicate) { + return Sets.filter(elements, predicate); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/SetsFilterSortedSetTest.java b/android/guava-tests/test/com/google/common/collect/SetsFilterSortedSetTest.java new file mode 100644 index 000000000000..6adf439ef663 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/SetsFilterSortedSetTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.base.Predicate; +import com.google.common.collect.FilteredCollectionsTestUtil.AbstractFilteredSortedSetTest; +import java.util.SortedSet; +import java.util.TreeSet; +import org.jspecify.annotations.NullUnmarked; + +@NullUnmarked +public final class SetsFilterSortedSetTest + extends AbstractFilteredSortedSetTest> { + @Override + SortedSet createUnfiltered(Iterable contents) { + TreeSet result = Sets.newTreeSet(contents); + // we have to make the result not Navigable + return new ForwardingSortedSet() { + @Override + protected SortedSet delegate() { + return result; + } + }; + } + + @Override + SortedSet filter(SortedSet elements, Predicate predicate) { + return Sets.filter(elements, predicate); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/SetsTest.java b/android/guava-tests/test/com/google/common/collect/SetsTest.java index 8aa8d8312452..89e66079133d 100644 --- a/android/guava-tests/test/com/google/common/collect/SetsTest.java +++ b/android/guava-tests/test/com/google/common/collect/SetsTest.java @@ -17,9 +17,10 @@ package com.google.common.collect; import static com.google.common.collect.Iterables.unmodifiableIterable; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Sets.cartesianProduct; import static com.google.common.collect.Sets.newEnumSet; import static com.google.common.collect.Sets.newHashSet; -import static com.google.common.collect.Sets.newLinkedHashSet; import static com.google.common.collect.Sets.powerSet; import static com.google.common.collect.Sets.unmodifiableNavigableSet; import static com.google.common.collect.testing.IteratorFeature.UNMODIFIABLE; @@ -27,11 +28,14 @@ import static com.google.common.truth.Truth.assertWithMessage; import static java.io.ObjectStreamConstants.TC_REFERENCE; import static java.io.ObjectStreamConstants.baseWireHandle; +import static java.lang.System.arraycopy; +import static java.util.Arrays.asList; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Predicate; import com.google.common.collect.testing.AnEnum; import com.google.common.collect.testing.IteratorTester; @@ -52,7 +56,6 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.Serializable; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -76,7 +79,8 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@code Sets}. @@ -84,7 +88,8 @@ * @author Kevin Bourrillion * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class SetsTest extends TestCase { private static final IteratorTester.KnownOrder KNOWN_ORDER = @@ -92,7 +97,7 @@ public class SetsTest extends TestCase { private static final Collection EMPTY_COLLECTION = Arrays.asList(); - private static final Collection SOME_COLLECTION = Arrays.asList(0, 1, 1); + private static final Collection SOME_COLLECTION = asList(0, 1, 1); private static final Iterable SOME_ITERABLE = new Iterable() { @@ -102,11 +107,13 @@ public Iterator iterator() { } }; - private static final List LONGER_LIST = Arrays.asList(8, 6, 7, 5, 3, 0, 9); + private static final List LONGER_LIST = asList(8, 6, 7, 5, 3, 0, 9); private static final Comparator SOME_COMPARATOR = Collections.reverseOrder(); + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(SetsTest.class); @@ -116,74 +123,20 @@ public static Test suite() { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return Sets.newConcurrentHashSet(Arrays.asList(elements)); + return Sets.newConcurrentHashSet(asList(elements)); } }) .named("Sets.newConcurrentHashSet") .withFeatures(CollectionSize.ANY, SetFeature.GENERAL_PURPOSE) .createTestSuite()); - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - int size = elements.length; - // Remove last element, if size > 1 - Set set1 = - (size > 1) - ? Sets.newHashSet(Arrays.asList(elements).subList(0, size - 1)) - : Sets.newHashSet(elements); - // Remove first element, if size > 0 - Set set2 = - (size > 0) - ? Sets.newHashSet(Arrays.asList(elements).subList(1, size)) - : Sets.newHashSet(); - return Sets.union(set1, set2); - } - }) - .named("Sets.union") - .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - Set set1 = Sets.newHashSet(elements); - set1.add(samples().e3()); - Set set2 = Sets.newHashSet(elements); - set2.add(samples().e4()); - return Sets.intersection(set1, set2); - } - }) - .named("Sets.intersection") - .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - - suite.addTest( - SetTestSuiteBuilder.using( - new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - Set set1 = Sets.newHashSet(elements); - set1.add(samples().e3()); - Set set2 = Sets.newHashSet(samples().e3()); - return Sets.difference(set1, set2); - } - }) - .named("Sets.difference") - .withFeatures(CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_VALUES) - .createTestSuite()); - suite.addTest( SetTestSuiteBuilder.using( new TestEnumSetGenerator() { @Override protected Set create(AnEnum[] elements) { AnEnum[] otherElements = new AnEnum[elements.length - 1]; - System.arraycopy(elements, 1, otherElements, 0, otherElements.length); + arraycopy(elements, 1, otherElements, 0, otherElements.length); return Sets.immutableEnumSet(elements[0], otherElements); } }) @@ -197,8 +150,8 @@ protected Set create(AnEnum[] elements) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - SafeTreeSet set = new SafeTreeSet<>(Arrays.asList(elements)); - return Sets.unmodifiableNavigableSet(set); + SafeTreeSet set = new SafeTreeSet<>(asList(elements)); + return unmodifiableNavigableSet(set); } @Override @@ -218,13 +171,15 @@ public List order(List insertionOrder) { return suite; } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForFilter() { return SetTestSuiteBuilder.using( new TestStringSetGenerator() { @Override public Set create(String[] elements) { - Set unfiltered = Sets.newLinkedHashSet(); + Set unfiltered = new LinkedHashSet<>(); unfiltered.add("yyy"); Collections.addAll(unfiltered, elements); unfiltered.add("zzz"); @@ -241,7 +196,9 @@ public Set create(String[] elements) { .createTestSuite(); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForFilterNoNulls() { TestSuite suite = new TestSuite(); suite.addTest( @@ -249,7 +206,7 @@ private static Test testsForFilterNoNulls() { new TestStringSetGenerator() { @Override public Set create(String[] elements) { - Set unfiltered = Sets.newLinkedHashSet(); + Set unfiltered = new LinkedHashSet<>(); unfiltered.add("yyy"); unfiltered.addAll(ImmutableList.copyOf(elements)); unfiltered.add("zzz"); @@ -292,13 +249,15 @@ public List order(List insertionOrder) { return suite; } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders private static Test testsForFilterFiltered() { return SetTestSuiteBuilder.using( new TestStringSetGenerator() { @Override public Set create(String[] elements) { - Set unfiltered = Sets.newLinkedHashSet(); + Set unfiltered = new LinkedHashSet<>(); unfiltered.add("yyy"); unfiltered.addAll(ImmutableList.copyOf(elements)); unfiltered.add("zzz"); @@ -325,22 +284,16 @@ private enum SomeEnum { D } + @SuppressWarnings("DoNotCall") public void testImmutableEnumSet() { Set units = Sets.immutableEnumSet(SomeEnum.D, SomeEnum.B); assertThat(units).containsExactly(SomeEnum.B, SomeEnum.D).inOrder(); - try { - units.remove(SomeEnum.B); - fail("ImmutableEnumSet should throw an exception on remove()"); - } catch (UnsupportedOperationException expected) { - } - try { - units.add(SomeEnum.C); - fail("ImmutableEnumSet should throw an exception on add()"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> units.remove(SomeEnum.B)); + assertThrows(UnsupportedOperationException.class, () -> units.add(SomeEnum.C)); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testImmutableEnumSet_serialized() { Set units = Sets.immutableEnumSet(SomeEnum.D, SomeEnum.B); @@ -362,8 +315,9 @@ public void testImmutableEnumSet_fromIterable() { assertThat(two).containsExactly(SomeEnum.B, SomeEnum.D).inOrder(); } - @GwtIncompatible // java serialization not supported in GWT. - public void testImmutableEnumSet_deserializationMakesDefensiveCopy() throws Exception { + @GwtIncompatible + @J2ktIncompatible + public void testImmutableEnumSet_deserializationMakesDefensiveCopy() throws Exception { ImmutableSet original = Sets.immutableEnumSet(SomeEnum.A, SomeEnum.B); int handleOffset = 6; byte[] serializedForm = serializeWithBackReference(original, handleOffset); @@ -377,8 +331,9 @@ public void testImmutableEnumSet_deserializationMakesDefensiveCopy() throws Exce assertTrue(deserialized.contains(SomeEnum.A)); } - @GwtIncompatible // java serialization not supported in GWT. - private static byte[] serializeWithBackReference(Object original, int handleOffset) + @GwtIncompatible + @J2ktIncompatible + private static byte[] serializeWithBackReference(Object original, int handleOffset) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); @@ -395,7 +350,7 @@ private static byte[] serializeWithBackReference(Object original, int handleOffs private static byte[] prepended(byte b, byte[] array) { byte[] out = new byte[array.length + 1]; out[0] = b; - System.arraycopy(array, 0, out, 1, array.length); + arraycopy(array, 0, out, 1, array.length); return out; } @@ -425,22 +380,24 @@ public void testNewEnumSet_iterable() { } public void testNewHashSetEmpty() { - HashSet set = Sets.newHashSet(); + @SuppressWarnings("UseCollectionConstructor") // test of factory method + HashSet set = newHashSet(); verifySetContents(set, EMPTY_COLLECTION); } public void testNewHashSetVarArgs() { - HashSet set = Sets.newHashSet(0, 1, 1); - verifySetContents(set, Arrays.asList(0, 1)); + HashSet set = newHashSet(0, 1, 1); + verifySetContents(set, asList(0, 1)); } public void testNewHashSetFromCollection() { - HashSet set = Sets.newHashSet(SOME_COLLECTION); + @SuppressWarnings("UseCollectionConstructor") // test of factory method + HashSet set = newHashSet(SOME_COLLECTION); verifySetContents(set, SOME_COLLECTION); } public void testNewHashSetFromIterable() { - HashSet set = Sets.newHashSet(SOME_ITERABLE); + HashSet set = newHashSet(SOME_ITERABLE); verifySetContents(set, SOME_ITERABLE); } @@ -455,7 +412,7 @@ public void testNewHashSetWithExpectedSizeLarge() { } public void testNewHashSetFromIterator() { - HashSet set = Sets.newHashSet(SOME_COLLECTION.iterator()); + HashSet set = newHashSet(SOME_COLLECTION.iterator()); verifySetContents(set, SOME_COLLECTION); } @@ -470,11 +427,13 @@ public void testNewConcurrentHashSetFromCollection() { } public void testNewLinkedHashSetEmpty() { + @SuppressWarnings("UseCollectionConstructor") // test of factory method LinkedHashSet set = Sets.newLinkedHashSet(); verifyLinkedHashSetContents(set, EMPTY_COLLECTION); } public void testNewLinkedHashSetFromCollection() { + @SuppressWarnings("UseCollectionConstructor") // test of factory method LinkedHashSet set = Sets.newLinkedHashSet(LONGER_LIST); verifyLinkedHashSetContents(set, LONGER_LIST); } @@ -535,14 +494,14 @@ public void testNewTreeSetFromIterable() { } public void testNewTreeSetFromIterableDerived() { - Iterable iterable = Arrays.asList(new Derived("foo"), new Derived("bar")); + Iterable iterable = asList(new Derived("foo"), new Derived("bar")); TreeSet set = Sets.newTreeSet(iterable); assertThat(set).containsExactly(new Derived("bar"), new Derived("foo")).inOrder(); } public void testNewTreeSetFromIterableNonGeneric() { Iterable iterable = - Arrays.asList(new LegacyComparable("foo"), new LegacyComparable("bar")); + asList(new LegacyComparable("foo"), new LegacyComparable("bar")); TreeSet set = Sets.newTreeSet(iterable); assertThat(set) .containsExactly(new LegacyComparable("bar"), new LegacyComparable("foo")) @@ -565,69 +524,84 @@ public void testNewIdentityHashSet() { assertEquals(2, set.size()); } + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArraySet public void testNewCOWASEmpty() { CopyOnWriteArraySet set = Sets.newCopyOnWriteArraySet(); verifySetContents(set, EMPTY_COLLECTION); } + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArraySet public void testNewCOWASFromIterable() { CopyOnWriteArraySet set = Sets.newCopyOnWriteArraySet(SOME_ITERABLE); verifySetContents(set, SOME_COLLECTION); } + @J2ktIncompatible + @GwtIncompatible // complementOf public void testComplementOfEnumSet() { Set units = EnumSet.of(SomeEnum.B, SomeEnum.D); EnumSet otherUnits = Sets.complementOf(units); verifySetContents(otherUnits, EnumSet.of(SomeEnum.A, SomeEnum.C)); } + @J2ktIncompatible + @GwtIncompatible // complementOf public void testComplementOfEnumSetWithType() { Set units = EnumSet.of(SomeEnum.B, SomeEnum.D); EnumSet otherUnits = Sets.complementOf(units, SomeEnum.class); verifySetContents(otherUnits, EnumSet.of(SomeEnum.A, SomeEnum.C)); } + @J2ktIncompatible + @GwtIncompatible // complementOf public void testComplementOfRegularSet() { - Set units = Sets.newHashSet(SomeEnum.B, SomeEnum.D); + Set units = newHashSet(SomeEnum.B, SomeEnum.D); EnumSet otherUnits = Sets.complementOf(units); verifySetContents(otherUnits, EnumSet.of(SomeEnum.A, SomeEnum.C)); } + @J2ktIncompatible + @GwtIncompatible // complementOf public void testComplementOfRegularSetWithType() { - Set units = Sets.newHashSet(SomeEnum.B, SomeEnum.D); + Set units = newHashSet(SomeEnum.B, SomeEnum.D); EnumSet otherUnits = Sets.complementOf(units, SomeEnum.class); verifySetContents(otherUnits, EnumSet.of(SomeEnum.A, SomeEnum.C)); } + @J2ktIncompatible + @GwtIncompatible // complementOf public void testComplementOfEmptySet() { - Set noUnits = Collections.emptySet(); + Set noUnits = emptySet(); EnumSet allUnits = Sets.complementOf(noUnits, SomeEnum.class); verifySetContents(EnumSet.allOf(SomeEnum.class), allUnits); } + @J2ktIncompatible + @GwtIncompatible // complementOf public void testComplementOfFullSet() { - Set allUnits = Sets.newHashSet(SomeEnum.values()); + Set allUnits = newHashSet(SomeEnum.values()); EnumSet noUnits = Sets.complementOf(allUnits, SomeEnum.class); verifySetContents(noUnits, EnumSet.noneOf(SomeEnum.class)); } + @J2ktIncompatible + @GwtIncompatible // complementOf public void testComplementOfEmptyEnumSetWithoutType() { Set noUnits = EnumSet.noneOf(SomeEnum.class); EnumSet allUnits = Sets.complementOf(noUnits); verifySetContents(allUnits, EnumSet.allOf(SomeEnum.class)); } + @J2ktIncompatible + @GwtIncompatible // complementOf public void testComplementOfEmptySetWithoutTypeDoesntWork() { - Set set = Collections.emptySet(); - try { - Sets.complementOf(set); - fail(); - } catch (IllegalArgumentException expected) { - } + Set set = emptySet(); + assertThrows(IllegalArgumentException.class, () -> Sets.complementOf(set)); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointerExceptions() { new NullPointerTester() @@ -637,60 +611,52 @@ public void testNullPointerExceptions() { } public void testNewSetFromMap() { + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method Set set = Sets.newSetFromMap(new HashMap()); set.addAll(SOME_COLLECTION); verifySetContents(set, SOME_COLLECTION); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testNewSetFromMapSerialization() { + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method Set set = Sets.newSetFromMap(new LinkedHashMap()); set.addAll(SOME_COLLECTION); Set copy = SerializableTester.reserializeAndAssert(set); assertThat(copy).containsExactly(0, 1).inOrder(); } + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testNewSetFromMapIllegal() { Map map = new LinkedHashMap<>(); map.put(2, true); - try { - Sets.newSetFromMap(map); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Sets.newSetFromMap(map)); } - // TODO: the overwhelming number of suppressions below suggests that maybe - // it's not worth having a varargs form of this method at all... - /** The 0-ary cartesian product is a single empty list. */ - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_zeroary() { - assertThat(Sets.cartesianProduct()).containsExactly(list()); + assertThat(cartesianProduct()).containsExactly(list()); } /** A unary cartesian product is one list of size 1 for each element in the input set. */ - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_unary() { - assertThat(Sets.cartesianProduct(set(1, 2))).containsExactly(list(1), list(2)); + assertThat(cartesianProduct(set(1, 2))).containsExactly(list(1), list(2)); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary0x0() { Set mt = emptySet(); - assertEmpty(Sets.cartesianProduct(mt, mt)); + assertEmpty(cartesianProduct(mt, mt)); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary0x1() { Set mt = emptySet(); - assertEmpty(Sets.cartesianProduct(mt, set(1))); + assertEmpty(cartesianProduct(mt, set(1))); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary1x0() { Set mt = emptySet(); - assertEmpty(Sets.cartesianProduct(set(1), mt)); + assertEmpty(cartesianProduct(set(1), mt)); } private static void assertEmpty(Set> set) { @@ -699,28 +665,24 @@ private static void assertEmpty(Set> set) { assertFalse(set.iterator().hasNext()); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary1x1() { - assertThat(Sets.cartesianProduct(set(1), set(2))).contains(list(1, 2)); + assertThat(cartesianProduct(set(1), set(2))).contains(list(1, 2)); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary1x2() { - assertThat(Sets.cartesianProduct(set(1), set(2, 3))) + assertThat(cartesianProduct(set(1), set(2, 3))) .containsExactly(list(1, 2), list(1, 3)) .inOrder(); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_binary2x2() { - assertThat(Sets.cartesianProduct(set(1, 2), set(3, 4))) + assertThat(cartesianProduct(set(1, 2), set(3, 4))) .containsExactly(list(1, 3), list(1, 4), list(2, 3), list(2, 4)) .inOrder(); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_2x2x2() { - assertThat(Sets.cartesianProduct(set(0, 1), set(0, 1), set(0, 1))) + assertThat(cartesianProduct(set(0, 1), set(0, 1), set(0, 1))) .containsExactly( list(0, 0, 0), list(0, 0, 1), @@ -733,9 +695,8 @@ public void testCartesianProduct_2x2x2() { .inOrder(); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_contains() { - Set> actual = Sets.cartesianProduct(set(1, 2), set(3, 4)); + Set> actual = cartesianProduct(set(1, 2), set(3, 4)); assertTrue(actual.contains(list(1, 3))); assertTrue(actual.contains(list(1, 4))); assertTrue(actual.contains(list(2, 3))); @@ -743,7 +704,21 @@ public void testCartesianProduct_contains() { assertFalse(actual.contains(list(3, 1))); } - @SuppressWarnings("unchecked") // varargs! + public void testCartesianProduct_equals() { + Set> cartesian = cartesianProduct(set(1, 2), set(3, 4)); + ImmutableSet> equivalent = + ImmutableSet.of(ImmutableList.of(1, 3), ImmutableList.of(1, 4), list(2, 3), list(2, 4)); + ImmutableSet> different1 = + ImmutableSet.of(ImmutableList.of(0, 3), ImmutableList.of(1, 4), list(2, 3), list(2, 4)); + ImmutableSet> different2 = + ImmutableSet.of(ImmutableList.of(1, 3), ImmutableList.of(1, 4), list(2, 3)); + new EqualsTester() + .addEqualityGroup(cartesian, equivalent) + .addEqualityGroup(different1) + .addEqualityGroup(different2) + .testEquals(); + } + public void testCartesianProduct_unrelatedTypes() { Set x = set(1, 2); Set y = set("3", "4"); @@ -758,40 +733,34 @@ public void testCartesianProduct_unrelatedTypes() { .inOrder(); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProductTooBig() { Set set = ContiguousSet.create(Range.closed(0, 10000), DiscreteDomain.integers()); - try { - Sets.cartesianProduct(set, set, set, set, set); - fail("Expected IAE"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> cartesianProduct(set, set, set, set, set)); } - @SuppressWarnings("unchecked") // varargs! public void testCartesianProduct_hashCode() { // Run through the same cartesian products we tested above - Set> degenerate = Sets.cartesianProduct(); + Set> degenerate = cartesianProduct(); checkHashCode(degenerate); - checkHashCode(Sets.cartesianProduct(set(1, 2))); + checkHashCode(cartesianProduct(set(1, 2))); int num = Integer.MAX_VALUE / 3 * 2; // tickle overflow-related problems - checkHashCode(Sets.cartesianProduct(set(1, 2, num))); + checkHashCode(cartesianProduct(set(1, 2, num))); Set mt = emptySet(); - checkHashCode(Sets.cartesianProduct(mt, mt)); - checkHashCode(Sets.cartesianProduct(mt, set(num))); - checkHashCode(Sets.cartesianProduct(set(num), mt)); - checkHashCode(Sets.cartesianProduct(set(num), set(1))); - checkHashCode(Sets.cartesianProduct(set(1), set(2, num))); - checkHashCode(Sets.cartesianProduct(set(1, num), set(2, num - 1))); - checkHashCode(Sets.cartesianProduct(set(1, num), set(2, num - 1), set(3, num + 1))); + checkHashCode(cartesianProduct(mt, mt)); + checkHashCode(cartesianProduct(mt, set(num))); + checkHashCode(cartesianProduct(set(num), mt)); + checkHashCode(cartesianProduct(set(num), set(1))); + checkHashCode(cartesianProduct(set(1), set(2, num))); + checkHashCode(cartesianProduct(set(1, num), set(2, num - 1))); + checkHashCode(cartesianProduct(set(1, num), set(2, num - 1), set(3, num + 1))); // a bigger one checkHashCode( - Sets.cartesianProduct(set(1, num, num + 1), set(2), set(3, num + 2), set(4, 5, 6, 7, 8))); + cartesianProduct(set(1, num, num + 1), set(2), set(3, num + 2), set(4, 5, 6, 7, 8))); } public void testPowerSetEmpty() { @@ -808,7 +777,7 @@ public void testPowerSetContents() { assertEquals(8, powerSet.size()); assertEquals(4 * 1 + 4 * 2 + 4 * 3, powerSet.hashCode()); - Set> expected = newHashSet(); + Set> expected = new HashSet<>(); expected.add(ImmutableSet.of()); expected.add(ImmutableSet.of(1)); expected.add(ImmutableSet.of(2)); @@ -818,7 +787,7 @@ public void testPowerSetContents() { expected.add(ImmutableSet.of(2, 3)); expected.add(ImmutableSet.of(1, 2, 3)); - Set> almostPowerSet = newHashSet(expected); + Set> almostPowerSet = new HashSet<>(expected); almostPowerSet.remove(ImmutableSet.of(1, 2, 3)); almostPowerSet.add(ImmutableSet.of(1, 2, 4)); @@ -832,7 +801,7 @@ public void testPowerSetContents() { assertTrue(powerSet.contains(subset)); } assertFalse(powerSet.contains(ImmutableSet.of(1, 2, 4))); - assertFalse(powerSet.contains(singleton(null))); + assertFalse(powerSet.contains(Collections.<@Nullable Integer>singleton(null))); assertFalse(powerSet.contains(null)); assertFalse(powerSet.contains((Object) "notASet")); } @@ -851,24 +820,20 @@ public void testPowerSetIteration_manual() { assertEquals(ImmutableSet.of(3, 2), i.next()); assertEquals(ImmutableSet.of(3, 2, 1), i.next()); assertFalse(i.hasNext()); - try { - i.next(); - fail(); - } catch (NoSuchElementException expected) { - } + assertThrows(NoSuchElementException.class, () -> i.next()); } @GwtIncompatible // too slow for GWT public void testPowerSetIteration_iteratorTester() { ImmutableSet elements = ImmutableSet.of(1, 2); - Set> expected = newLinkedHashSet(); + Set> expected = new LinkedHashSet<>(); expected.add(ImmutableSet.of()); expected.add(ImmutableSet.of(1)); expected.add(ImmutableSet.of(2)); expected.add(ImmutableSet.of(1, 2)); - final Set> powerSet = powerSet(elements); + Set> powerSet = powerSet(elements); new IteratorTester>(6, UNMODIFIABLE, expected, KNOWN_ORDER) { @Override protected Iterator> newTargetIterator() { @@ -880,13 +845,13 @@ protected Iterator> newTargetIterator() { public void testPowerSetIteration_iteratorTester_fast() { ImmutableSet elements = ImmutableSet.of(1, 2); - Set> expected = newLinkedHashSet(); + Set> expected = new LinkedHashSet<>(); expected.add(ImmutableSet.of()); expected.add(ImmutableSet.of(1)); expected.add(ImmutableSet.of(2)); expected.add(ImmutableSet.of(1, 2)); - final Set> powerSet = powerSet(elements); + Set> powerSet = powerSet(elements); new IteratorTester>(4, UNMODIFIABLE, expected, KNOWN_ORDER) { @Override protected Iterator> newTargetIterator() { @@ -907,27 +872,24 @@ public void testPowerSetSize() { } public void testPowerSetCreationErrors() { - try { - Set> unused = - powerSet( - newHashSet( - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', - 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5')); - fail(); - } catch (IllegalArgumentException expected) { - } - - try { - Set> unused = powerSet(ContiguousSet.closed(0, Integer.MAX_VALUE / 2)); - fail(); - } catch (IllegalArgumentException expected) { - } - - try { - powerSet(singleton(null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> { + Set> unused = + powerSet( + newHashSet( + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', + '5')); + }); + + assertThrows( + IllegalArgumentException.class, + () -> { + Set> unused = powerSet(ContiguousSet.closed(0, Integer.MAX_VALUE / 2)); + }); + + assertThrows(NullPointerException.class, () -> powerSet(singleton(null))); } public void testPowerSetEqualsAndHashCode_verifyAgainstHashSet() { @@ -936,7 +898,7 @@ public void testPowerSetEqualsAndHashCode_verifyAgainstHashSet() { 4233352, 3284593, 3794208, 3849533, 4013967, 2902658, 1886275, 2131109, 985872, 1843868); for (int i = 0; i < allElements.size(); i++) { - Set elements = newHashSet(allElements.subList(0, i)); + Set elements = new HashSet<>(allElements.subList(0, i)); Set> powerSet1 = powerSet(elements); Set> powerSet2 = powerSet(elements); new EqualsTester() @@ -984,23 +946,18 @@ public void testPowerSetShowOff() { } private static Set makeSetOfZeroToTwentyNine() { - // TODO: use Range once it's publicly available - Set zeroToTwentyNine = newHashSet(); - for (int i = 0; i < 30; i++) { - zeroToTwentyNine.add(i); - } - return zeroToTwentyNine; + return ContiguousSet.closedOpen(0, 30); } private static Set> toHashSets(Set> powerSet) { - Set> result = newHashSet(); + Set> result = new HashSet<>(); for (Set subset : powerSet) { result.add(new HashSet(subset)); } return result; } - private static Object objectWithHashCode(final int hashCode) { + private static Object objectWithHashCode(int hashCode) { return new Object() { @Override public int hashCode() { @@ -1009,7 +966,8 @@ public int hashCode() { }; } - private static void assertPowerSetHashCode(int expected, Set elements) { + // TODO b/327389044 - `Set elements` should be enough but J2KT needs the + private static void assertPowerSetHashCode(int expected, Set elements) { assertEquals(expected, powerSet(elements).hashCode()); } @@ -1018,7 +976,7 @@ private static void assertPowerSetSize(int i, Object... elements) { } private static void checkHashCode(Set set) { - assertEquals(Sets.newHashSet(set).hashCode(), set.hashCode()); + assertEquals(new HashSet<>(set).hashCode(), set.hashCode()); } public void testCombinations() { @@ -1029,7 +987,7 @@ public void testCombinations() { ImmutableSet.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); for (Set sampleSet : sampleSets) { for (int k = 0; k <= sampleSet.size(); k++) { - final int size = k; + int size = k; Set> expected = Sets.filter( Sets.powerSet(sampleSet), @@ -1076,7 +1034,7 @@ private static void verifyLinkedHashSetContents( * same as the given comparator. */ private static void verifySortedSetContents( - SortedSet set, Iterable iterable, @NullableDecl Comparator comparator) { + SortedSet set, Iterable iterable, @Nullable Comparator comparator) { assertSame(comparator, set.comparator()); verifySetContents(set, iterable); } @@ -1098,47 +1056,6 @@ private static void verifySetContents(Set set, Iterable contents) { assertEquals(expected, set); } - /** Simple base class to verify that we handle generics correctly. */ - static class Base implements Comparable, Serializable { - private final String s; - - public Base(String s) { - this.s = s; - } - - @Override - public int hashCode() { // delegate to 's' - return s.hashCode(); - } - - @Override - public boolean equals(Object other) { - if (other == null) { - return false; - } else if (other instanceof Base) { - return s.equals(((Base) other).s); - } else { - return false; - } - } - - @Override - public int compareTo(Base o) { - return s.compareTo(o.s); - } - - private static final long serialVersionUID = 0; - } - - /** Simple derived class to verify that we handle generics correctly. */ - static class Derived extends Base { - public Derived(String s) { - super(s); - } - - private static final long serialVersionUID = 0; - } - @GwtIncompatible // NavigableSet public void testUnmodifiableNavigableSet() { TreeSet mod = Sets.newTreeSet(); @@ -1164,21 +1081,9 @@ public void testUnmodifiableNavigableSet() { /* UnsupportedOperationException on indirect modifications. */ NavigableSet reverse = unmod.descendingSet(); - try { - reverse.add(4); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - reverse.addAll(Collections.singleton(4)); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } - try { - reverse.remove(4); - fail("UnsupportedOperationException expected"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> reverse.add(4)); + assertThrows(UnsupportedOperationException.class, () -> reverse.addAll(singleton(4))); + assertThrows(UnsupportedOperationException.class, () -> reverse.remove(4)); } void ensureNotDirectlyModifiable(SortedSet unmod) { @@ -1193,7 +1098,7 @@ void ensureNotDirectlyModifiable(SortedSet unmod) { } catch (UnsupportedOperationException expected) { } try { - unmod.addAll(Collections.singleton(4)); + unmod.addAll(singleton(4)); fail("UnsupportedOperationException expected"); } catch (UnsupportedOperationException expected) { } @@ -1219,7 +1124,7 @@ void ensureNotDirectlyModifiable(NavigableSet unmod) { } catch (UnsupportedOperationException expected) { } try { - unmod.addAll(Collections.singleton(4)); + unmod.addAll(singleton(4)); fail("UnsupportedOperationException expected"); } catch (UnsupportedOperationException expected) { } @@ -1317,11 +1222,7 @@ public void testSubSet_unnaturalOrdering() { ImmutableSortedSet set = ImmutableSortedSet.reverseOrder().add(2, 4, 6, 8, 10).build(); - try { - Sets.subSet(set, Range.closed(4, 8)); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Sets.subSet(set, Range.closed(4, 8))); // These results are all incorrect, but there's no way (short of iterating over the result) // to verify that with an arbitrary ordering or comparator. diff --git a/android/guava-tests/test/com/google/common/collect/SimpleAbstractMultisetTest.java b/android/guava-tests/test/com/google/common/collect/SimpleAbstractMultisetTest.java index 830536dfc5db..02080fa085c0 100644 --- a/android/guava-tests/test/com/google/common/collect/SimpleAbstractMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/SimpleAbstractMultisetTest.java @@ -15,23 +15,27 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.google.MultisetTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringMultisetGenerator; import java.io.Serializable; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link AbstractMultiset}. @@ -40,9 +44,12 @@ * @author Louis Wasserman */ @SuppressWarnings("serial") // No serialization is used in this test -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class SimpleAbstractMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(SimpleAbstractMultisetTest.class); @@ -65,8 +72,9 @@ protected Multiset create(String[] elements) { return suite; } + @SuppressWarnings("ModifiedButNotUsed") public void testFastAddAllMultiset() { - final AtomicInteger addCalls = new AtomicInteger(); + AtomicInteger addCalls = new AtomicInteger(); Multiset multiset = new NoRemoveMultiset() { @Override @@ -84,16 +92,13 @@ public int add(String element, int occurrences) { public void testRemoveUnsupported() { Multiset multiset = new NoRemoveMultiset<>(); multiset.add("a"); - try { - multiset.remove("a"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> multiset.remove("a")); assertTrue(multiset.contains("a")); } - private static class NoRemoveMultiset extends AbstractMultiset implements Serializable { - final Map backingMap = Maps.newHashMap(); + private static class NoRemoveMultiset extends AbstractMultiset + implements Serializable { + final Map backingMap = new HashMap<>(); @Override public int size() { @@ -106,9 +111,9 @@ public void clear() { } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { for (Entry entry : entrySet()) { - if (Objects.equal(entry.getElement(), element)) { + if (Objects.equals(entry.getElement(), element)) { return entry.getCount(); } } @@ -116,12 +121,9 @@ public int count(@NullableDecl Object element) { } @Override - public int add(@NullableDecl E element, int occurrences) { + public int add(E element, int occurrences) { checkArgument(occurrences >= 0); - Integer frequency = backingMap.get(element); - if (frequency == null) { - frequency = 0; - } + Integer frequency = backingMap.getOrDefault(element, 0); if (occurrences == 0) { return frequency; } @@ -137,7 +139,7 @@ Iterator elementIterator() { @Override Iterator> entryIterator() { - final Iterator> backingEntries = backingMap.entrySet().iterator(); + Iterator> backingEntries = backingMap.entrySet().iterator(); return new UnmodifiableIterator>() { @Override public boolean hasNext() { @@ -146,7 +148,7 @@ public boolean hasNext() { @Override public Multiset.Entry next() { - final Map.Entry mapEntry = backingEntries.next(); + Map.Entry mapEntry = backingEntries.next(); return new Multisets.AbstractEntry() { @Override public E getElement() { diff --git a/android/guava-tests/test/com/google/common/collect/SingletonImmutableMapMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/SingletonImmutableMapMapInterfaceTest.java new file mode 100644 index 000000000000..45be28eb17b9 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/SingletonImmutableMapMapInterfaceTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class SingletonImmutableMapMapInterfaceTest + extends AbstractImmutableMapMapInterfaceTest { + @Override + protected Map makePopulatedMap() { + return ImmutableMap.of("one", 1); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/SingletonImmutableMapWithUnhashableValueMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/SingletonImmutableMapWithUnhashableValueMapInterfaceTest.java new file mode 100644 index 000000000000..0891fa2952c2 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/SingletonImmutableMapWithUnhashableValueMapInterfaceTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.testing.SampleElements.Unhashables; +import com.google.common.collect.testing.UnhashableObject; +import java.util.Map; +import org.jspecify.annotations.NullUnmarked; + +@GwtIncompatible // GWT's ImmutableMap emulation is backed by java.util.HashMap. +@NullUnmarked +public class SingletonImmutableMapWithUnhashableValueMapInterfaceTest + extends RegularImmutableMapWithUnhashableValuesMapInterfaceTest { + @Override + protected Map makePopulatedMap() { + Unhashables unhashables = new Unhashables(); + return ImmutableMap.of(0, unhashables.e0()); + } +} diff --git a/guava/src/com/google/common/collect/GwtTransient.java b/android/guava-tests/test/com/google/common/collect/SingletonImmutableSortedMapMapInterfaceTest.java similarity index 54% rename from guava/src/com/google/common/collect/GwtTransient.java rename to android/guava-tests/test/com/google/common/collect/SingletonImmutableSortedMapMapInterfaceTest.java index 9c09c53c946f..ae4f7d8962fe 100644 --- a/guava/src/com/google/common/collect/GwtTransient.java +++ b/android/guava-tests/test/com/google/common/collect/SingletonImmutableSortedMapMapInterfaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Guava Authors + * Copyright (C) 2009 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,26 @@ package com.google.common.collect; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import com.google.common.annotations.GwtCompatible; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; -/** - * Private replacement for {@link com.google.gwt.user.client.rpc.GwtTransient} to work around - * build-system quirks. This annotation should be used only in {@code - * com.google.common.collect}. - */ -@Documented @GwtCompatible -@Retention(RUNTIME) -@Target(FIELD) -@interface GwtTransient {} +@NullUnmarked +public class SingletonImmutableSortedMapMapInterfaceTest + extends AbstractImmutableSortedMapMapInterfaceTest { + @Override + protected SortedMap makePopulatedMap() { + return ImmutableSortedMap.of("one", 1); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "minus one"; + } + + @Override + protected Integer getValueNotInPopulatedMap() { + return -1; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/SingletonImmutableTableTest.java b/android/guava-tests/test/com/google/common/collect/SingletonImmutableTableTest.java index 16c5ecbbf959..d256cfd5861c 100644 --- a/android/guava-tests/test/com/google/common/collect/SingletonImmutableTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/SingletonImmutableTableTest.java @@ -16,29 +16,32 @@ package com.google.common.collect; +import static com.google.common.collect.Tables.immutableCell; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; import com.google.common.testing.EqualsTester; +import java.util.Objects; +import org.jspecify.annotations.NullMarked; /** * Tests {@link SingletonImmutableTable}. * * @author Gregory Kick */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class SingletonImmutableTableTest extends AbstractImmutableTableTest { private final ImmutableTable testTable = new SingletonImmutableTable<>('a', 1, "blah"); public void testHashCode() { - assertEquals(Objects.hashCode('a', 1, "blah"), testTable.hashCode()); + assertEquals(Objects.hash('a', 1, "blah"), testTable.hashCode()); } public void testCellSet() { - assertEquals(ImmutableSet.of(Tables.immutableCell('a', 1, "blah")), testTable.cellSet()); + assertEquals(ImmutableSet.of(immutableCell('a', 1, "blah")), testTable.cellSet()); } public void testColumn() { @@ -111,9 +114,9 @@ public void testContainsValue() { public void testGet() { assertEquals("blah", testTable.get('a', 1)); - assertNull(testTable.get('a', 2)); - assertNull(testTable.get('A', 1)); - assertNull(testTable.get('A', 2)); + assertThat(testTable.get('a', 2)).isNull(); + assertThat(testTable.get('A', 1)).isNull(); + assertThat(testTable.get('A', 2)).isNull(); } public void testIsEmpty() { diff --git a/android/guava-tests/test/com/google/common/collect/SortedIterablesTest.java b/android/guava-tests/test/com/google/common/collect/SortedIterablesTest.java index 543199d2e1ad..9b1d04bba477 100644 --- a/android/guava-tests/test/com/google/common/collect/SortedIterablesTest.java +++ b/android/guava-tests/test/com/google/common/collect/SortedIterablesTest.java @@ -15,8 +15,8 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import java.util.SortedSet; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@code SortedIterables}. @@ -24,13 +24,11 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public class SortedIterablesTest extends TestCase { public void testSameComparator() { assertTrue(SortedIterables.hasSameComparator(Ordering.natural(), Sets.newTreeSet())); - // Before JDK6 (including under GWT), the TreeMap keySet is a plain Set. - if (Maps.newTreeMap().keySet() instanceof SortedSet) { - assertTrue(SortedIterables.hasSameComparator(Ordering.natural(), Maps.newTreeMap().keySet())); - } + assertTrue(SortedIterables.hasSameComparator(Ordering.natural(), Maps.newTreeMap().keySet())); assertTrue( SortedIterables.hasSameComparator( Ordering.natural().reverse(), Sets.newTreeSet(Ordering.natural().reverse()))); diff --git a/android/guava-tests/test/com/google/common/collect/SortedListsTest.java b/android/guava-tests/test/com/google/common/collect/SortedListsTest.java index a6cddcb723cc..238f29fb8aac 100644 --- a/android/guava-tests/test/com/google/common/collect/SortedListsTest.java +++ b/android/guava-tests/test/com/google/common/collect/SortedListsTest.java @@ -16,18 +16,21 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.SortedLists.KeyAbsentBehavior; import com.google.common.collect.SortedLists.KeyPresentBehavior; import com.google.common.testing.NullPointerTester; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for SortedLists. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class SortedListsTest extends TestCase { private static final ImmutableList LIST_WITH_DUPS = ImmutableList.of(1, 1, 2, 4, 4, 4, 8); @@ -71,8 +74,6 @@ void assertModelAgrees( return; } break; - default: - throw new AssertionError(); } // key is not present int nextHigherIndex = list.size(); @@ -89,9 +90,8 @@ void assertModelAgrees( case INVERTED_INSERTION_INDEX: assertEquals(-1 - nextHigherIndex, answer); return; - default: - throw new AssertionError(); } + throw new AssertionError(); } public void testWithoutDups() { @@ -124,6 +124,7 @@ public void testWithDups() { } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(SortedLists.class); diff --git a/android/guava-tests/test/com/google/common/collect/SpecialRandom.java b/android/guava-tests/test/com/google/common/collect/SpecialRandom.java index 5996e5bfe26c..571a061f74ae 100644 --- a/android/guava-tests/test/com/google/common/collect/SpecialRandom.java +++ b/android/guava-tests/test/com/google/common/collect/SpecialRandom.java @@ -17,6 +17,7 @@ package com.google.common.collect; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Utility class for being able to seed a {@link Random} value with a passed in seed from a @@ -26,6 +27,7 @@ * * @author Nicholaus Shupe */ +@NullUnmarked public final class SpecialRandom extends Random { public static SpecialRandom valueOf(String s) { return (s.length() == 0) ? new SpecialRandom() : new SpecialRandom(Long.parseLong(s)); diff --git a/android/guava-tests/test/com/google/common/collect/SubMapMultimapAsMapImplementsMapTest.java b/android/guava-tests/test/com/google/common/collect/SubMapMultimapAsMapImplementsMapTest.java index 1cb7abceb4cd..f97c6e792ecc 100644 --- a/android/guava-tests/test/com/google/common/collect/SubMapMultimapAsMapImplementsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/SubMapMultimapAsMapImplementsMapTest.java @@ -16,11 +16,13 @@ package com.google.common.collect; +import static java.util.Collections.singleton; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.testing.MapInterfaceTest; import java.util.Collection; -import java.util.Collections; import java.util.Map; +import org.jspecify.annotations.NullMarked; /** * Test {@code TreeMultimap.asMap().subMap()} with {@link MapInterfaceTest}. @@ -28,6 +30,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class SubMapMultimapAsMapImplementsMapTest extends AbstractMultimapAsMapImplementsMapTest { public SubMapMultimapAsMapImplementsMapTest() { @@ -66,7 +69,7 @@ protected String getKeyNotInPopulatedMap() { @Override protected Collection getValueNotInPopulatedMap() { - return Collections.singleton(-2); + return singleton(-2); } @Override diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedBiMapTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedBiMapTest.java index 1e422b2b483e..c599d10c5618 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedBiMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedBiMapTest.java @@ -29,14 +29,18 @@ import java.util.Map.Entry; import java.util.Set; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code Synchronized#biMap}. * * @author Mike Bostock */ +@NullUnmarked public class SynchronizedBiMapTest extends SynchronizedMapTest { + @AndroidIncompatible // test-suite builders public static TestSuite suite() { TestSuite suite = new TestSuite(SynchronizedBiMapTest.class); suite.addTest( @@ -75,10 +79,10 @@ protected BiMap create() { return outer; } + @AndroidIncompatible // test-suite builders public static final class SynchronizedHashBiMapGenerator extends TestStringBiMapGenerator { @Override protected BiMap create(Entry[] entries) { - Object mutex = new Object(); BiMap result = HashBiMap.create(); for (Entry entry : entries) { checkArgument(!result.containsKey(entry.getKey())); @@ -88,6 +92,7 @@ protected BiMap create(Entry[] entries) { } } + @AndroidIncompatible // test-suite builders public static final class SynchTestingBiMapGenerator extends TestStringBiMapGenerator { @Override protected BiMap create(Entry[] entries) { @@ -111,7 +116,7 @@ public TestBiMap(BiMap delegate, Object mutex) { } @Override - public V forcePut(K key, V value) { + public @Nullable V forcePut(K key, V value) { assertTrue(Thread.holdsLock(mutex)); return delegate.forcePut(key, value); } diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedDequeTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedDequeTest.java index 0a11b27ab9ba..b4f64dc62d9e 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedDequeTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedDequeTest.java @@ -20,13 +20,17 @@ import java.util.Collection; import java.util.Deque; import java.util.Iterator; +import java.util.LinkedList; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Synchronized#deque} and {@link Queues#synchronizedDeque}. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class SynchronizedDequeTest extends TestCase { protected Deque create() { @@ -37,8 +41,8 @@ protected Deque create() { } private static final class TestDeque implements Deque { - private final Deque delegate = Lists.newLinkedList(); - public final Object mutex = new Integer(1); // something Serializable + private final Deque delegate = new LinkedList<>(); + private final Object mutex = new Object[0]; // something Serializable @Override public boolean offer(E o) { @@ -47,7 +51,7 @@ public boolean offer(E o) { } @Override - public E poll() { + public @Nullable E poll() { assertTrue(Thread.holdsLock(mutex)); return delegate.poll(); } @@ -65,7 +69,7 @@ public boolean remove(Object object) { } @Override - public E peek() { + public @Nullable E peek() { assertTrue(Thread.holdsLock(mutex)); return delegate.peek(); } @@ -186,13 +190,13 @@ public E removeLast() { } @Override - public E pollFirst() { + public @Nullable E pollFirst() { assertTrue(Thread.holdsLock(mutex)); return delegate.pollFirst(); } @Override - public E pollLast() { + public @Nullable E pollLast() { assertTrue(Thread.holdsLock(mutex)); return delegate.pollLast(); } @@ -210,13 +214,13 @@ public E getLast() { } @Override - public E peekFirst() { + public @Nullable E peekFirst() { assertTrue(Thread.holdsLock(mutex)); return delegate.peekFirst(); } @Override - public E peekLast() { + public @Nullable E peekLast() { assertTrue(Thread.holdsLock(mutex)); return delegate.peekLast(); } @@ -254,6 +258,7 @@ public Iterator descendingIterator() { private static final long serialVersionUID = 0; } + @SuppressWarnings("CheckReturnValue") public void testHoldsLockOnAllOperations() { create().element(); create().offer("foo"); @@ -263,8 +268,8 @@ public void testHoldsLockOnAllOperations() { create().add("foo"); create().addAll(ImmutableList.of("foo")); create().clear(); - boolean unused = create().contains("foo"); - boolean unused2 = create().containsAll(ImmutableList.of("foo")); + create().contains("foo"); + create().containsAll(ImmutableList.of("foo")); create().equals(new ArrayDeque<>(ImmutableList.of("foo"))); create().hashCode(); create().isEmpty(); diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedMapTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedMapTest.java index 34d1c6f4a067..5e77c2472db7 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedMapTest.java @@ -28,14 +28,17 @@ import java.util.Map.Entry; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code Synchronized#map}. * * @author Mike Bostock */ +@NullUnmarked public class SynchronizedMapTest extends TestCase { - public final Object mutex = new Integer(1); // something Serializable + public final Object mutex = new Object[0]; // something Serializable protected Map create() { TestMap inner = new TestMap<>(new HashMap(), mutex); @@ -45,7 +48,7 @@ protected Map create() { static class TestMap extends ForwardingMap implements Serializable { public final Object mutex; - private Map delegate; + private final Map delegate; public TestMap(Map delegate, Object mutex) { checkNotNull(mutex); @@ -71,7 +74,7 @@ public boolean isEmpty() { } @Override - public V remove(Object object) { + public @Nullable V remove(Object object) { assertTrue(Thread.holdsLock(mutex)); return super.remove(object); } @@ -95,13 +98,13 @@ public boolean containsValue(Object value) { } @Override - public V get(Object key) { + public @Nullable V get(Object key) { assertTrue(Thread.holdsLock(mutex)); return super.get(key); } @Override - public V put(K key, V value) { + public @Nullable V put(K key, V value) { assertTrue(Thread.holdsLock(mutex)); return super.put(key, value); } @@ -131,7 +134,7 @@ public Set> entrySet() { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { assertTrue(Thread.holdsLock(mutex)); return super.equals(obj); } @@ -159,11 +162,11 @@ public String toString() { */ public void testSize() { - create().size(); + int unused = create().size(); } public void testIsEmpty() { - create().isEmpty(); + boolean unused = create().isEmpty(); } public void testRemove() { @@ -183,7 +186,7 @@ public void testContainsValue() { } public void testGet() { - create().get(null); + Object unused = create().get(null); } public void testPut() { @@ -216,15 +219,15 @@ public void testEntrySet() { } public void testEquals() { - create().equals(new HashMap()); + boolean unused = create().equals(new HashMap()); } public void testHashCode() { - create().hashCode(); + int unused = create().hashCode(); } public void testToString() { - create().toString(); + String unused = create().toString(); } public void testSerialization() { diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedMultimapTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedMultimapTest.java index 14111da785f4..f26e00dc89b6 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedMultimapTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedMultimapTest.java @@ -16,7 +16,10 @@ package com.google.common.collect; +import static com.google.common.collect.Multimaps.synchronizedListMultimap; +import static com.google.common.collect.Multimaps.synchronizedSortedSetMultimap; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; @@ -24,7 +27,6 @@ import com.google.common.collect.testing.google.SetMultimapTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringSetMultimapGenerator; import java.io.Serializable; -import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; @@ -33,15 +35,18 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code Synchronized#multimap}. * * @author Mike Bostock */ +@NullUnmarked public class SynchronizedMultimapTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(SynchronizedMultimapTest.class); @@ -74,8 +79,8 @@ protected SetMultimap create(Entry[] entries) { private static final class TestMultimap extends ForwardingSetMultimap implements Serializable { - final SetMultimap delegate = HashMultimap.create(); - public final Object mutex = new Integer(1); // something Serializable + private final SetMultimap delegate = HashMultimap.create(); + private final Object mutex = new Object[0]; // something Serializable @Override protected SetMultimap delegate() { @@ -89,7 +94,7 @@ public String toString() { } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { assertTrue(Thread.holdsLock(mutex)); return super.equals(o); } @@ -113,27 +118,27 @@ public boolean isEmpty() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { assertTrue(Thread.holdsLock(mutex)); return super.containsKey(key); } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { assertTrue(Thread.holdsLock(mutex)); return super.containsValue(value); } @Override - public boolean containsEntry(@NullableDecl Object key, @NullableDecl Object value) { + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { assertTrue(Thread.holdsLock(mutex)); return super.containsEntry(key, value); } @Override - public Set get(@NullableDecl K key) { + public Set get(@Nullable K key) { assertTrue(Thread.holdsLock(mutex)); - /* TODO: verify that the Collection is also synchronized? */ + /* TODO: verify that the Set is also synchronized? */ return super.get(key); } @@ -144,7 +149,7 @@ public boolean put(K key, V value) { } @Override - public boolean putAll(@NullableDecl K key, Iterable values) { + public boolean putAll(@Nullable K key, Iterable values) { assertTrue(Thread.holdsLock(mutex)); return super.putAll(key, values); } @@ -156,19 +161,19 @@ public boolean putAll(Multimap map) { } @Override - public Set replaceValues(@NullableDecl K key, Iterable values) { + public Set replaceValues(@Nullable K key, Iterable values) { assertTrue(Thread.holdsLock(mutex)); return super.replaceValues(key, values); } @Override - public boolean remove(@NullableDecl Object key, @NullableDecl Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { assertTrue(Thread.holdsLock(mutex)); return super.remove(key, value); } @Override - public Set removeAll(@NullableDecl Object key) { + public Set removeAll(@Nullable Object key) { assertTrue(Thread.holdsLock(mutex)); return super.removeAll(key); } @@ -189,7 +194,7 @@ public Set keySet() { @Override public Multiset keys() { assertTrue(Thread.holdsLock(mutex)); - /* TODO: verify that the Set is also synchronized? */ + /* TODO: verify that the Multiset is also synchronized? */ return super.keys(); } @@ -203,7 +208,7 @@ public Collection values() { @Override public Set> entries() { assertTrue(Thread.holdsLock(mutex)); - /* TODO: verify that the Collection is also synchronized? */ + /* TODO: verify that the Set is also synchronized? */ return super.entries(); } @@ -219,27 +224,23 @@ public Map> asMap() { public void testSynchronizedListMultimap() { ListMultimap multimap = - Multimaps.synchronizedListMultimap(ArrayListMultimap.create()); - multimap.putAll("foo", Arrays.asList(3, -1, 2, 4, 1)); - multimap.putAll("bar", Arrays.asList(1, 2, 3, 1)); + synchronizedListMultimap(ArrayListMultimap.create()); + multimap.putAll("foo", asList(3, -1, 2, 4, 1)); + multimap.putAll("bar", asList(1, 2, 3, 1)); assertThat(multimap.removeAll("foo")).containsExactly(3, -1, 2, 4, 1).inOrder(); assertFalse(multimap.containsKey("foo")); - assertThat(multimap.replaceValues("bar", Arrays.asList(6, 5))) - .containsExactly(1, 2, 3, 1) - .inOrder(); + assertThat(multimap.replaceValues("bar", asList(6, 5))).containsExactly(1, 2, 3, 1).inOrder(); assertThat(multimap.get("bar")).containsExactly(6, 5).inOrder(); } public void testSynchronizedSortedSetMultimap() { SortedSetMultimap multimap = - Multimaps.synchronizedSortedSetMultimap(TreeMultimap.create()); - multimap.putAll("foo", Arrays.asList(3, -1, 2, 4, 1)); - multimap.putAll("bar", Arrays.asList(1, 2, 3, 1)); + synchronizedSortedSetMultimap(TreeMultimap.create()); + multimap.putAll("foo", asList(3, -1, 2, 4, 1)); + multimap.putAll("bar", asList(1, 2, 3, 1)); assertThat(multimap.removeAll("foo")).containsExactly(-1, 1, 2, 3, 4).inOrder(); assertFalse(multimap.containsKey("foo")); - assertThat(multimap.replaceValues("bar", Arrays.asList(6, 5))) - .containsExactly(1, 2, 3) - .inOrder(); + assertThat(multimap.replaceValues("bar", asList(6, 5))).containsExactly(1, 2, 3).inOrder(); assertThat(multimap.get("bar")).containsExactly(5, 6).inOrder(); } @@ -247,7 +248,7 @@ public void testSynchronizedArrayListMultimapRandomAccess() { ListMultimap delegate = ArrayListMultimap.create(); delegate.put("foo", 1); delegate.put("foo", 3); - ListMultimap multimap = Multimaps.synchronizedListMultimap(delegate); + ListMultimap multimap = synchronizedListMultimap(delegate); assertTrue(multimap.get("foo") instanceof RandomAccess); assertTrue(multimap.get("bar") instanceof RandomAccess); } @@ -256,7 +257,7 @@ public void testSynchronizedLinkedListMultimapRandomAccess() { ListMultimap delegate = LinkedListMultimap.create(); delegate.put("foo", 1); delegate.put("foo", 3); - ListMultimap multimap = Multimaps.synchronizedListMultimap(delegate); + ListMultimap multimap = synchronizedListMultimap(delegate); assertFalse(multimap.get("foo") instanceof RandomAccess); assertFalse(multimap.get("bar") instanceof RandomAccess); } diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableMapTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableMapTest.java index 41b597aac10f..36b09353f99e 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableMapTest.java @@ -16,6 +16,8 @@ package com.google.common.collect; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.Synchronized.SynchronizedNavigableMap; import com.google.common.collect.Synchronized.SynchronizedNavigableSet; import com.google.common.collect.Synchronized.SynchronizedSortedMap; @@ -33,12 +35,15 @@ import java.util.NavigableSet; import java.util.SortedMap; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Maps#synchronizedNavigableMap(NavigableMap)}. * * @author Louis Wasserman */ +@NullUnmarked public class SynchronizedNavigableMapTest extends SynchronizedMapTest { @Override protected NavigableMap create() { @@ -65,7 +70,7 @@ protected Entry delegate() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { assertTrue(Thread.holdsLock(mutex)); return super.equals(object); } @@ -110,13 +115,13 @@ protected NavigableMap delegate() { } @Override - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(K key) { assertTrue(Thread.holdsLock(mutex)); return delegate().ceilingEntry(key); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(K key) { assertTrue(Thread.holdsLock(mutex)); return delegate().ceilingKey(key); } @@ -134,19 +139,19 @@ public NavigableMap descendingMap() { } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { assertTrue(Thread.holdsLock(mutex)); return delegate().firstEntry(); } @Override - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(K key) { assertTrue(Thread.holdsLock(mutex)); return delegate().floorEntry(key); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(K key) { assertTrue(Thread.holdsLock(mutex)); return delegate().floorKey(key); } @@ -163,31 +168,31 @@ public SortedMap headMap(K toKey) { } @Override - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(K key) { assertTrue(Thread.holdsLock(mutex)); return delegate().higherEntry(key); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(K key) { assertTrue(Thread.holdsLock(mutex)); return delegate().higherKey(key); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { assertTrue(Thread.holdsLock(mutex)); return delegate().lastEntry(); } @Override - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(K key) { assertTrue(Thread.holdsLock(mutex)); return delegate().lowerEntry(key); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(K key) { assertTrue(Thread.holdsLock(mutex)); return delegate().lowerKey(key); } @@ -199,13 +204,13 @@ public NavigableSet navigableKeySet() { } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { assertTrue(Thread.holdsLock(mutex)); return delegate().pollFirstEntry(); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { assertTrue(Thread.holdsLock(mutex)); return delegate().pollLastEntry(); } @@ -254,13 +259,14 @@ public K lastKey() { private static final long serialVersionUID = 0; } + @AndroidIncompatible // test-suite builders public static TestSuite suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(SynchronizedNavigableMapTest.class); suite.addTest( NavigableMapTestSuiteBuilder.using( new TestStringSortedMapGenerator() { - private final Object mutex = new Integer(1); + private final Object mutex = new Object[0]; // something Serializable @Override protected SortedMap create(Entry[] entries) { @@ -286,15 +292,15 @@ protected SortedMap create(Entry[] entries) { } public void testComparator() { - create().comparator(); + assertThat(create().comparator()).isNotNull(); } public void testCeilingEntry() { - create().ceilingEntry("a"); + assertThat(create().ceilingEntry("a")).isNull(); } public void testCeilingKey() { - create().ceilingKey("a"); + assertThat(create().ceilingKey("a")).isNull(); } public void testDescendingKeySet() { @@ -312,31 +318,31 @@ public void testDescendingMap() { } public void testFirstEntry() { - create().firstEntry(); + assertThat(create().firstEntry()).isNull(); } public void testFirstKey() { NavigableMap map = create(); map.put("a", 1); - map.firstKey(); + assertThat(map.firstKey()).isNotNull(); } public void testFloorEntry() { - create().floorEntry("a"); + assertThat(create().floorEntry("a")).isNull(); } public void testFloorKey() { - create().floorKey("a"); + assertThat(create().floorKey("a")).isNull(); } - public void testHeadMap_K() { + public void testHeadMap_k() { NavigableMap map = create(); SortedMap headMap = map.headMap("a"); assertTrue(headMap instanceof SynchronizedSortedMap); assertSame(mutex, ((SynchronizedSortedMap) headMap).mutex); } - public void testHeadMap_K_B() { + public void testHeadMap_k_b() { NavigableMap map = create(); NavigableMap headMap = map.headMap("a", true); assertTrue(headMap instanceof SynchronizedNavigableMap); @@ -344,29 +350,29 @@ public void testHeadMap_K_B() { } public void testHigherEntry() { - create().higherEntry("a"); + assertThat(create().higherEntry("a")).isNull(); } public void testHigherKey() { - create().higherKey("a"); + assertThat(create().higherKey("a")).isNull(); } public void testLastEntry() { - create().lastEntry(); + assertThat(create().lastEntry()).isNull(); } public void testLastKey() { NavigableMap map = create(); map.put("a", 1); - map.lastKey(); + assertThat(map.lastKey()).isNotNull(); } public void testLowerEntry() { - create().lowerEntry("a"); + assertThat(create().lowerEntry("a")).isNull(); } public void testLowerKey() { - create().lowerKey("a"); + assertThat(create().lowerKey("a")).isNull(); } public void testNavigableKeySet() { @@ -384,28 +390,28 @@ public void testPollLastEntry() { create().pollLastEntry(); } - public void testSubMap_K_K() { + public void testSubMap_k_k() { NavigableMap map = create(); SortedMap subMap = map.subMap("a", "b"); assertTrue(subMap instanceof SynchronizedSortedMap); assertSame(mutex, ((SynchronizedSortedMap) subMap).mutex); } - public void testSubMap_K_B_K_B() { + public void testSubMap_k_b_k_b() { NavigableMap map = create(); NavigableMap subMap = map.subMap("a", true, "b", false); assertTrue(subMap instanceof SynchronizedNavigableMap); assertSame(mutex, ((SynchronizedNavigableMap) subMap).mutex); } - public void testTailMap_K() { + public void testTailMap_k() { NavigableMap map = create(); SortedMap subMap = map.tailMap("a"); assertTrue(subMap instanceof SynchronizedSortedMap); assertSame(mutex, ((SynchronizedSortedMap) subMap).mutex); } - public void testTailMap_K_B() { + public void testTailMap_k_b() { NavigableMap map = create(); NavigableMap subMap = map.tailMap("a", true); assertTrue(subMap instanceof SynchronizedNavigableMap); diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableSetTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableSetTest.java index 8638b0d1769d..287599ff6243 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedNavigableSetTest.java @@ -32,26 +32,29 @@ import java.util.TreeSet; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Sets#synchronizedNavigableSet(NavigableSet)}. * * @author Louis Wasserman */ +@NullUnmarked public class SynchronizedNavigableSetTest extends TestCase { - private static final Object MUTEX = new Integer(1); // something Serializable + private static final Object MUTEX = new Object[0]; // something Serializable - @SuppressWarnings("unchecked") - protected NavigableSet create() { - TestSet inner = - new TestSet<>(new TreeSet((Comparator) Ordering.natural().nullsFirst()), MUTEX); + protected > NavigableSet create() { + LockHeldAssertingNavigableSet inner = + new LockHeldAssertingNavigableSet<>(new TreeSet<>(Ordering.natural().nullsFirst()), MUTEX); NavigableSet outer = Synchronized.navigableSet(inner, MUTEX); return outer; } - static class TestSet extends SynchronizedSetTest.TestSet implements NavigableSet { + static class LockHeldAssertingNavigableSet extends LockHeldAssertingSet + implements NavigableSet { - TestSet(NavigableSet delegate, Object mutex) { + LockHeldAssertingNavigableSet(NavigableSet delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -61,7 +64,7 @@ protected NavigableSet delegate() { } @Override - public E ceiling(E e) { + public @Nullable E ceiling(E e) { assertTrue(Thread.holdsLock(mutex)); return delegate().ceiling(e); } @@ -78,7 +81,7 @@ public NavigableSet descendingSet() { } @Override - public E floor(E e) { + public @Nullable E floor(E e) { assertTrue(Thread.holdsLock(mutex)); return delegate().floor(e); } @@ -95,24 +98,24 @@ public SortedSet headSet(E toElement) { } @Override - public E higher(E e) { + public @Nullable E higher(E e) { assertTrue(Thread.holdsLock(mutex)); return delegate().higher(e); } @Override - public E lower(E e) { + public @Nullable E lower(E e) { return delegate().lower(e); } @Override - public E pollFirst() { + public @Nullable E pollFirst() { assertTrue(Thread.holdsLock(mutex)); return delegate().pollFirst(); } @Override - public E pollLast() { + public @Nullable E pollLast() { assertTrue(Thread.holdsLock(mutex)); return delegate().pollLast(); } @@ -161,6 +164,7 @@ public E last() { private static final long serialVersionUID = 0; } + @AndroidIncompatible // test-suite builders public static TestSuite suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(SynchronizedNavigableSetTest.class); @@ -172,7 +176,8 @@ public static TestSuite suite() { protected NavigableSet create(String[] elements) { NavigableSet innermost = new SafeTreeSet<>(); Collections.addAll(innermost, elements); - TestSet inner = new TestSet<>(innermost, MUTEX); + LockHeldAssertingNavigableSet inner = + new LockHeldAssertingNavigableSet<>(innermost, MUTEX); NavigableSet outer = Synchronized.navigableSet(inner, MUTEX); return outer; } @@ -194,50 +199,50 @@ public List order(List insertionOrder) { } public void testDescendingSet() { - NavigableSet map = create(); - NavigableSet descendingSet = map.descendingSet(); + NavigableSet set = create(); + NavigableSet descendingSet = set.descendingSet(); assertTrue(descendingSet instanceof SynchronizedNavigableSet); assertSame(MUTEX, ((SynchronizedNavigableSet) descendingSet).mutex); } - public void testHeadSet_E() { - NavigableSet map = create(); - SortedSet headSet = map.headSet("a"); + public void testHeadSet_e() { + NavigableSet set = create(); + SortedSet headSet = set.headSet("a"); assertTrue(headSet instanceof SynchronizedSortedSet); assertSame(MUTEX, ((SynchronizedSortedSet) headSet).mutex); } - public void testHeadSet_E_B() { - NavigableSet map = create(); - NavigableSet headSet = map.headSet("a", true); + public void testHeadSet_e_b() { + NavigableSet set = create(); + NavigableSet headSet = set.headSet("a", true); assertTrue(headSet instanceof SynchronizedNavigableSet); assertSame(MUTEX, ((SynchronizedNavigableSet) headSet).mutex); } - public void testSubSet_E_E() { - NavigableSet map = create(); - SortedSet subSet = map.subSet("a", "b"); + public void testSubSet_e_e() { + NavigableSet set = create(); + SortedSet subSet = set.subSet("a", "b"); assertTrue(subSet instanceof SynchronizedSortedSet); assertSame(MUTEX, ((SynchronizedSortedSet) subSet).mutex); } - public void testSubSet_E_B_E_B() { - NavigableSet map = create(); - NavigableSet subSet = map.subSet("a", false, "b", true); + public void testSubSet_e_b_e_b() { + NavigableSet set = create(); + NavigableSet subSet = set.subSet("a", false, "b", true); assertTrue(subSet instanceof SynchronizedNavigableSet); assertSame(MUTEX, ((SynchronizedNavigableSet) subSet).mutex); } - public void testTailSet_E() { - NavigableSet map = create(); - SortedSet tailSet = map.tailSet("a"); + public void testTailSet_e() { + NavigableSet set = create(); + SortedSet tailSet = set.tailSet("a"); assertTrue(tailSet instanceof SynchronizedSortedSet); assertSame(MUTEX, ((SynchronizedSortedSet) tailSet).mutex); } - public void testTailSet_E_B() { - NavigableSet map = create(); - NavigableSet tailSet = map.tailSet("a", true); + public void testTailSet_e_b() { + NavigableSet set = create(); + NavigableSet tailSet = set.tailSet("a", true); assertTrue(tailSet instanceof SynchronizedNavigableSet); assertSame(MUTEX, ((SynchronizedNavigableSet) tailSet).mutex); } diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedQueueTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedQueueTest.java index 70ff7740a114..7da59185a931 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedQueueTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedQueueTest.java @@ -19,14 +19,18 @@ import java.util.ArrayDeque; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; import java.util.Queue; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Synchronized#queue} and {@link Queues#synchronizedQueue}. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class SynchronizedQueueTest extends TestCase { protected Queue create() { @@ -37,8 +41,8 @@ protected Queue create() { } private static final class TestQueue implements Queue { - private final Queue delegate = Lists.newLinkedList(); - public final Object mutex = new Integer(1); // something Serializable + private final Queue delegate = new LinkedList<>(); + private final Object mutex = new Object[0]; // something Serializable @Override public boolean offer(E o) { @@ -47,7 +51,7 @@ public boolean offer(E o) { } @Override - public E poll() { + public @Nullable E poll() { assertTrue(Thread.holdsLock(mutex)); return delegate.poll(); } @@ -65,7 +69,7 @@ public boolean remove(Object object) { } @Override - public E peek() { + public @Nullable E peek() { assertTrue(Thread.holdsLock(mutex)); return delegate.peek(); } @@ -152,6 +156,7 @@ public T[] toArray(T[] array) { private static final long serialVersionUID = 0; } + @SuppressWarnings("CheckReturnValue") public void testHoldsLockOnAllOperations() { create().element(); create().offer("foo"); @@ -161,8 +166,8 @@ public void testHoldsLockOnAllOperations() { create().add("foo"); create().addAll(ImmutableList.of("foo")); create().clear(); - boolean unused = create().contains("foo"); - boolean unused2 = create().containsAll(ImmutableList.of("foo")); + create().contains("foo"); + create().containsAll(ImmutableList.of("foo")); create().equals(new ArrayDeque<>(ImmutableList.of("foo"))); create().hashCode(); create().isEmpty(); diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedSetTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedSetTest.java index 746ad3fc1cc6..323480fe3f62 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedSetTest.java @@ -16,36 +16,36 @@ package com.google.common.collect; -import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.testing.SetTestSuiteBuilder; import com.google.common.collect.testing.TestStringSetGenerator; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; -import java.io.Serializable; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import junit.framework.Test; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code Synchronized#set}. * * @author Mike Bostock */ +@NullUnmarked +@AndroidIncompatible // test-suite builders public class SynchronizedSetTest extends TestCase { - public static final Object MUTEX = new Integer(1); // something Serializable + public static final Object MUTEX = new Object[0]; // something Serializable public static Test suite() { return SetTestSuiteBuilder.using( new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - TestSet inner = new TestSet<>(new HashSet(), MUTEX); + LockHeldAssertingSet inner = + new LockHeldAssertingSet<>(new HashSet(), MUTEX); Set outer = Synchronized.set(inner, inner.mutex); Collections.addAll(outer, elements); return outer; @@ -59,114 +59,4 @@ protected Set create(String[] elements) { CollectionFeature.SERIALIZABLE) .createTestSuite(); } - - static class TestSet extends ForwardingSet implements Serializable { - final Set delegate; - public final Object mutex; - - public TestSet(Set delegate, Object mutex) { - checkNotNull(mutex); - this.delegate = delegate; - this.mutex = mutex; - } - - @Override - protected Set delegate() { - return delegate; - } - - @Override - public String toString() { - assertTrue(Thread.holdsLock(mutex)); - return super.toString(); - } - - @Override - public boolean equals(@NullableDecl Object o) { - assertTrue(Thread.holdsLock(mutex)); - return super.equals(o); - } - - @Override - public int hashCode() { - assertTrue(Thread.holdsLock(mutex)); - return super.hashCode(); - } - - @Override - public boolean add(@NullableDecl E o) { - assertTrue(Thread.holdsLock(mutex)); - return super.add(o); - } - - @Override - public boolean addAll(Collection c) { - assertTrue(Thread.holdsLock(mutex)); - return super.addAll(c); - } - - @Override - public void clear() { - assertTrue(Thread.holdsLock(mutex)); - super.clear(); - } - - @Override - public boolean contains(@NullableDecl Object o) { - assertTrue(Thread.holdsLock(mutex)); - return super.contains(o); - } - - @Override - public boolean containsAll(Collection c) { - assertTrue(Thread.holdsLock(mutex)); - return super.containsAll(c); - } - - @Override - public boolean isEmpty() { - assertTrue(Thread.holdsLock(mutex)); - return super.isEmpty(); - } - - /* Don't test iterator(); it may or may not hold the mutex. */ - - @Override - public boolean remove(@NullableDecl Object o) { - assertTrue(Thread.holdsLock(mutex)); - return super.remove(o); - } - - @Override - public boolean removeAll(Collection c) { - assertTrue(Thread.holdsLock(mutex)); - return super.removeAll(c); - } - - @Override - public boolean retainAll(Collection c) { - assertTrue(Thread.holdsLock(mutex)); - return super.retainAll(c); - } - - @Override - public int size() { - assertTrue(Thread.holdsLock(mutex)); - return super.size(); - } - - @Override - public Object[] toArray() { - assertTrue(Thread.holdsLock(mutex)); - return super.toArray(); - } - - @Override - public T[] toArray(T[] a) { - assertTrue(Thread.holdsLock(mutex)); - return super.toArray(a); - } - - private static final long serialVersionUID = 0; - } } diff --git a/android/guava-tests/test/com/google/common/collect/SynchronizedTableTest.java b/android/guava-tests/test/com/google/common/collect/SynchronizedTableTest.java index f418367eae76..0e9f5a0133c9 100644 --- a/android/guava-tests/test/com/google/common/collect/SynchronizedTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/SynchronizedTableTest.java @@ -20,12 +20,14 @@ import java.util.Collection; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; -public class SynchronizedTableTest extends AbstractTableTest { +@NullUnmarked +public class SynchronizedTableTest extends AbstractTableTest { private static final class TestTable implements Table, Serializable { - final Table delegate = HashBasedTable.create(); - public final Object mutex = new Integer(1); // something Serializable + private final Table delegate = HashBasedTable.create(); + private final Object mutex = new Object[0]; // something Serializable @Override public String toString() { @@ -34,7 +36,7 @@ public String toString() { } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { assertTrue(Thread.holdsLock(mutex)); return delegate.equals(o); } @@ -58,7 +60,7 @@ public boolean isEmpty() { } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { assertTrue(Thread.holdsLock(mutex)); return delegate.containsValue(value); } @@ -119,13 +121,13 @@ public boolean containsRow(Object rowKey) { } @Override - public V get(Object rowKey, Object columnKey) { + public @Nullable V get(Object rowKey, Object columnKey) { assertTrue(Thread.holdsLock(mutex)); return delegate.get(rowKey, columnKey); } @Override - public V put(R rowKey, C columnKey, V value) { + public @Nullable V put(R rowKey, C columnKey, V value) { assertTrue(Thread.holdsLock(mutex)); return delegate.put(rowKey, columnKey, value); } @@ -137,7 +139,7 @@ public void putAll(Table table) { } @Override - public V remove(Object rowKey, Object columnKey) { + public @Nullable V remove(Object rowKey, Object columnKey) { assertTrue(Thread.holdsLock(mutex)); return delegate.remove(rowKey, columnKey); } @@ -164,7 +166,7 @@ public Map> rowMap() { } @Override - protected Table create(Object... data) { + protected Table create(@Nullable Object... data) { TestTable table = new TestTable<>(); Table synced = Synchronized.table(table, table.mutex); populate(synced, data); diff --git a/android/guava-tests/test/com/google/common/collect/TableCollectionTest.java b/android/guava-tests/test/com/google/common/collect/TableCollectionTest.java index 7c944441a0ec..32038b8d7839 100644 --- a/android/guava-tests/test/com/google/common/collect/TableCollectionTest.java +++ b/android/guava-tests/test/com/google/common/collect/TableCollectionTest.java @@ -17,9 +17,17 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Tables.immutableCell; +import static com.google.common.collect.Tables.transformValues; +import static com.google.common.collect.Tables.transpose; +import static com.google.common.collect.Tables.unmodifiableRowSortedTable; +import static com.google.common.collect.Tables.unmodifiableTable; +import static java.util.Collections.sort; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.Table.Cell; @@ -35,17 +43,17 @@ import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.Feature; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedMap; import java.util.SortedSet; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Collection tests for {@link Table} implementations. @@ -53,21 +61,26 @@ * @author Jared Levy * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class TableCollectionTest extends TestCase { + @J2ktIncompatible private static final Feature[] COLLECTION_FEATURES = { CollectionSize.ANY, CollectionFeature.ALLOWS_NULL_QUERIES }; + @J2ktIncompatible private static final Feature[] COLLECTION_FEATURES_ORDER = { CollectionSize.ANY, CollectionFeature.KNOWN_ORDER, CollectionFeature.ALLOWS_NULL_QUERIES }; + @J2ktIncompatible private static final Feature[] COLLECTION_FEATURES_REMOVE = { CollectionSize.ANY, CollectionFeature.SUPPORTS_REMOVE, CollectionFeature.ALLOWS_NULL_QUERIES }; + @J2ktIncompatible private static final Feature[] COLLECTION_FEATURES_REMOVE_ORDER = { CollectionSize.ANY, CollectionFeature.KNOWN_ORDER, @@ -75,38 +88,11 @@ public class TableCollectionTest extends TestCase { CollectionFeature.ALLOWS_NULL_QUERIES }; + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); - suite.addTestSuite(ArrayRowTests.class); - suite.addTestSuite(HashRowTests.class); - suite.addTestSuite(TreeRowTests.class); - suite.addTestSuite(TransposeRowTests.class); - suite.addTestSuite(TransformValueRowTests.class); - suite.addTestSuite(UnmodifiableHashRowTests.class); - suite.addTestSuite(UnmodifiableTreeRowTests.class); - suite.addTestSuite(ArrayColumnTests.class); - suite.addTestSuite(HashColumnTests.class); - suite.addTestSuite(TreeColumnTests.class); - suite.addTestSuite(TransposeColumnTests.class); - suite.addTestSuite(TransformValueColumnTests.class); - suite.addTestSuite(UnmodifiableHashColumnTests.class); - suite.addTestSuite(UnmodifiableTreeColumnTests.class); - suite.addTestSuite(ArrayRowMapTests.class); - suite.addTestSuite(HashRowMapTests.class); - suite.addTestSuite(TreeRowMapTests.class); - suite.addTestSuite(TreeRowMapHeadMapTests.class); - suite.addTestSuite(TreeRowMapTailMapTests.class); - suite.addTestSuite(TreeRowMapSubMapTests.class); - suite.addTestSuite(TransformValueRowMapTests.class); - suite.addTestSuite(UnmodifiableHashRowMapTests.class); - suite.addTestSuite(UnmodifiableTreeRowMapTests.class); - suite.addTestSuite(ArrayColumnMapTests.class); - suite.addTestSuite(HashColumnMapTests.class); - suite.addTestSuite(TreeColumnMapTests.class); - suite.addTestSuite(TransformValueColumnMapTests.class); - suite.addTestSuite(UnmodifiableHashColumnMapTests.class); - suite.addTestSuite(UnmodifiableTreeColumnMapTests.class); // Not testing rowKeySet() or columnKeySet() of Table.transformValues() // since the transformation doesn't affect the row and column key sets. @@ -158,7 +144,7 @@ protected SortedSet create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -174,7 +160,7 @@ public List order(List insertionOrder) { protected Set create(String[] elements) { Table table = HashBasedTable.create(); populateForRowKeySet(table, elements); - return Tables.unmodifiableTable(table).rowKeySet(); + return unmodifiableTable(table).rowKeySet(); } }) .named("unmodifiableTable[HashBasedTable].rowKeySet") @@ -188,12 +174,12 @@ protected Set create(String[] elements) { protected Set create(String[] elements) { RowSortedTable table = TreeBasedTable.create(); populateForRowKeySet(table, elements); - return Tables.unmodifiableRowSortedTable(table).rowKeySet(); + return unmodifiableRowSortedTable(table).rowKeySet(); } @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -247,7 +233,7 @@ protected Set create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -262,7 +248,7 @@ public List order(List insertionOrder) { protected Set create(String[] elements) { Table table = HashBasedTable.create(); populateForColumnKeySet(table, elements); - return Tables.unmodifiableTable(table).columnKeySet(); + return unmodifiableTable(table).columnKeySet(); } }) .named("unmodifiableTable[HashBasedTable].columnKeySet") @@ -276,12 +262,12 @@ protected Set create(String[] elements) { protected Set create(String[] elements) { RowSortedTable table = TreeBasedTable.create(); populateForColumnKeySet(table, elements); - return Tables.unmodifiableRowSortedTable(table).columnKeySet(); + return unmodifiableRowSortedTable(table).columnKeySet(); } @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -294,7 +280,7 @@ public List order(List insertionOrder) { new TestStringCollectionGenerator() { @Override protected Collection create(String[] elements) { - List rowKeys = Lists.newArrayList(); + List rowKeys = new ArrayList<>(); for (int i = 0; i < elements.length; i++) { rowKeys.add(i); } @@ -346,7 +332,7 @@ protected Collection create(String[] elements) { .withFeatures(CollectionFeature.SUPPORTS_ITERATOR_REMOVE) .createTestSuite()); - final Function removeFirstCharacter = + Function removeFirstCharacter = new Function() { @Override public String apply(String input) { @@ -363,7 +349,7 @@ protected Collection create(String[] elements) { for (int i = 0; i < elements.length; i++) { table.put(i, 'a', "x" + checkNotNull(elements[i])); } - return Tables.transformValues(table, removeFirstCharacter).values(); + return transformValues(table, removeFirstCharacter).values(); } }) .named("TransformValues.values") @@ -380,7 +366,7 @@ protected Collection create(String[] elements) { table.put(1, 'a', "foo"); table.clear(); populateForValues(table, elements); - return Tables.unmodifiableTable(table).values(); + return unmodifiableTable(table).values(); } }) .named("unmodifiableTable[HashBasedTable].values") @@ -396,7 +382,7 @@ protected Collection create(String[] elements) { table.put(1, 'a', "foo"); table.clear(); populateForValues(table, elements); - return Tables.unmodifiableRowSortedTable(table).values(); + return unmodifiableRowSortedTable(table).values(); } }) .named("unmodifiableTable[TreeBasedTable].values") @@ -409,16 +395,16 @@ protected Collection create(String[] elements) { @Override public SampleElements> samples() { return new SampleElements<>( - Tables.immutableCell("bar", 1, 'a'), - Tables.immutableCell("bar", 2, 'b'), - Tables.immutableCell("bar", 3, (Character) null), - Tables.immutableCell("bar", 4, 'b'), - Tables.immutableCell("bar", 5, 'b')); + immutableCell("bar", 1, 'a'), + immutableCell("bar", 2, 'b'), + immutableCell("bar", 3, (Character) null), + immutableCell("bar", 4, 'b'), + immutableCell("bar", 5, 'b')); } @Override public Set> create(Object... elements) { - List columnKeys = Lists.newArrayList(); + List columnKeys = new ArrayList<>(); for (Object element : elements) { @SuppressWarnings("unchecked") Cell cell = @@ -486,7 +472,7 @@ Table createTable() { @Override Table createTable() { Table original = TreeBasedTable.create(); - return Tables.transpose(original); + return transpose(original); } }) .named("TransposedTable.cellSet") @@ -513,7 +499,7 @@ public Set> create(Object... elements) { (Cell) element; table.put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); } - return Tables.transformValues(table, Functions.identity()).cellSet(); + return transformValues(table, Functions.identity()).cellSet(); } }) .named("TransformValues.cellSet") @@ -528,8 +514,7 @@ public Set> create(Object... elements) { new TestCellSetGenerator() { @Override Table createTable() { - return Tables.unmodifiableTable( - HashBasedTable.create()); + return unmodifiableTable(HashBasedTable.create()); } @Override @@ -541,7 +526,7 @@ public Set> create(Object... elements) { (Cell) element; table.put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); } - return Tables.unmodifiableTable(table).cellSet(); + return unmodifiableTable(table).cellSet(); } }) .named("unmodifiableTable[HashBasedTable].cellSet") @@ -553,7 +538,7 @@ public Set> create(Object... elements) { new TestCellSetGenerator() { @Override RowSortedTable createTable() { - return Tables.unmodifiableRowSortedTable( + return unmodifiableRowSortedTable( TreeBasedTable.create()); } @@ -566,7 +551,7 @@ public Set> create(Object... elements) { (Cell) element; table.put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); } - return Tables.unmodifiableRowSortedTable(table).cellSet(); + return unmodifiableRowSortedTable(table).cellSet(); } }) .named("unmodifiableRowSortedTable[TreeBasedTable].cellSet") @@ -620,7 +605,7 @@ protected Set create(String[] elements) { @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -635,9 +620,7 @@ public List order(List insertionOrder) { protected Set create(String[] elements) { Table table = HashBasedTable.create(); populateForRowKeySet(table, elements); - return Tables.transformValues(table, Functions.toStringFunction()) - .column(1) - .keySet(); + return transformValues(table, Functions.toStringFunction()).column(1).keySet(); } }) .named("TransformValues.column.keySet") @@ -651,7 +634,7 @@ protected Set create(String[] elements) { protected Set create(String[] elements) { Table table = HashBasedTable.create(); populateForRowKeySet(table, elements); - return Tables.unmodifiableTable(table).column(1).keySet(); + return unmodifiableTable(table).column(1).keySet(); } }) .named("unmodifiableTable[HashBasedTable].column.keySet") @@ -665,12 +648,12 @@ protected Set create(String[] elements) { protected Set create(String[] elements) { RowSortedTable table = TreeBasedTable.create(); populateForRowKeySet(table, elements); - return Tables.unmodifiableRowSortedTable(table).column(1).keySet(); + return unmodifiableRowSortedTable(table).column(1).keySet(); } @Override public List order(List insertionOrder) { - Collections.sort(insertionOrder); + sort(insertionOrder); return insertionOrder; } }) @@ -704,16 +687,17 @@ private static void populateForValues( } } + @J2ktIncompatible private abstract static class TestCellSetGenerator implements TestSetGenerator> { @Override public SampleElements> samples() { return new SampleElements<>( - Tables.immutableCell("bar", 1, 'a'), - Tables.immutableCell("bar", 2, 'b'), - Tables.immutableCell("foo", 3, 'c'), - Tables.immutableCell("bar", 1, 'b'), - Tables.immutableCell("cat", 2, 'b')); + immutableCell("bar", 1, 'a'), + immutableCell("bar", 2, 'b'), + immutableCell("foo", 3, 'c'), + immutableCell("bar", 1, 'b'), + immutableCell("cat", 2, 'b')); } @Override @@ -770,7 +754,7 @@ protected Integer getValueNotInPopulatedMap() { } } - private abstract static class RowTests extends MapTests { + abstract static class RowTests extends MapTests { RowTests( boolean allowsNullValues, boolean supportsPut, @@ -798,138 +782,15 @@ protected Map makePopulatedMap() { } } - @GwtIncompatible // TODO(hhchan): ArrayTable - public static class ArrayRowTests extends RowTests { - public ArrayRowTests() { - super(true, true, false, false, false); - } - - @Override - protected String getKeyNotInPopulatedMap() { - throw new UnsupportedOperationException(); - } - - @Override - protected Map makeEmptyMap() { - throw new UnsupportedOperationException(); - } - - @Override - protected Table makeTable() { - return ArrayTable.create( - Arrays.asList('a', 'b', 'c'), Arrays.asList("one", "two", "three", "four")); - } - } - - public static class HashRowTests extends RowTests { - public HashRowTests() { - super(false, true, true, true, true); - } - - @Override - Table makeTable() { - return HashBasedTable.create(); - } - } - - public static class TreeRowTests extends RowTests { - public TreeRowTests() { - super(false, true, true, true, true); - } - - @Override - Table makeTable() { - return TreeBasedTable.create(); - } - } - - public static class TransposeRowTests extends RowTests { - public TransposeRowTests() { - super(false, true, true, true, false); - } - - @Override - Table makeTable() { - Table original = TreeBasedTable.create(); - return Tables.transpose(original); - } - } - - private static final Function DIVIDE_BY_2 = - new Function() { + static final Function<@Nullable Integer, @Nullable Integer> DIVIDE_BY_2 = + new Function<@Nullable Integer, @Nullable Integer>() { @Override - public Integer apply(Integer input) { + public @Nullable Integer apply(@Nullable Integer input) { return (input == null) ? null : input / 2; } }; - public static class TransformValueRowTests extends RowTests { - public TransformValueRowTests() { - super(false, false, true, true, true); - } - - @Override - Table makeTable() { - Table table = HashBasedTable.create(); - return Tables.transformValues(table, DIVIDE_BY_2); - } - - @Override - protected Map makePopulatedMap() { - Table table = HashBasedTable.create(); - table.put('a', "one", 2); - table.put('a', "two", 4); - table.put('a', "three", 6); - table.put('b', "four", 8); - return Tables.transformValues(table, DIVIDE_BY_2).row('a'); - } - } - - public static class UnmodifiableHashRowTests extends RowTests { - public UnmodifiableHashRowTests() { - super(false, false, false, false, false); - } - - @Override - Table makeTable() { - Table table = HashBasedTable.create(); - return Tables.unmodifiableTable(table); - } - - @Override - protected Map makePopulatedMap() { - Table table = HashBasedTable.create(); - table.put('a', "one", 1); - table.put('a', "two", 2); - table.put('a', "three", 3); - table.put('b', "four", 4); - return Tables.unmodifiableTable(table).row('a'); - } - } - - public static class UnmodifiableTreeRowTests extends RowTests { - public UnmodifiableTreeRowTests() { - super(false, false, false, false, false); - } - - @Override - Table makeTable() { - RowSortedTable table = TreeBasedTable.create(); - return Tables.unmodifiableRowSortedTable(table); - } - - @Override - protected Map makePopulatedMap() { - RowSortedTable table = TreeBasedTable.create(); - table.put('a', "one", 1); - table.put('a', "two", 2); - table.put('a', "three", 3); - table.put('b', "four", 4); - return Tables.unmodifiableRowSortedTable(table).row('a'); - } - } - - private abstract static class ColumnTests extends MapTests { + abstract static class ColumnTests extends MapTests { ColumnTests( boolean allowsNullValues, boolean supportsPut, @@ -957,129 +818,6 @@ protected Map makePopulatedMap() { } } - @GwtIncompatible // TODO(hhchan): ArrayTable - public static class ArrayColumnTests extends ColumnTests { - public ArrayColumnTests() { - super(true, true, false, false, false); - } - - @Override - protected String getKeyNotInPopulatedMap() { - throw new UnsupportedOperationException(); - } - - @Override - protected Map makeEmptyMap() { - throw new UnsupportedOperationException(); - } - - @Override - Table makeTable() { - return ArrayTable.create( - Arrays.asList("one", "two", "three", "four"), Arrays.asList('a', 'b', 'c')); - } - } - - public static class HashColumnTests extends ColumnTests { - public HashColumnTests() { - super(false, true, true, true, false); - } - - @Override - Table makeTable() { - return HashBasedTable.create(); - } - } - - public static class TreeColumnTests extends ColumnTests { - public TreeColumnTests() { - super(false, true, true, true, false); - } - - @Override - Table makeTable() { - return TreeBasedTable.create(); - } - } - - public static class TransposeColumnTests extends ColumnTests { - public TransposeColumnTests() { - super(false, true, true, true, true); - } - - @Override - Table makeTable() { - Table original = TreeBasedTable.create(); - return Tables.transpose(original); - } - } - - public static class TransformValueColumnTests extends ColumnTests { - public TransformValueColumnTests() { - super(false, false, true, true, false); - } - - @Override - Table makeTable() { - Table table = HashBasedTable.create(); - return Tables.transformValues(table, DIVIDE_BY_2); - } - - @Override - protected Map makePopulatedMap() { - Table table = HashBasedTable.create(); - table.put("one", 'a', 1); - table.put("two", 'a', 2); - table.put("three", 'a', 3); - table.put("four", 'b', 4); - return Tables.transformValues(table, DIVIDE_BY_2).column('a'); - } - } - - public static class UnmodifiableHashColumnTests extends ColumnTests { - public UnmodifiableHashColumnTests() { - super(false, false, false, false, false); - } - - @Override - Table makeTable() { - Table table = HashBasedTable.create(); - return Tables.unmodifiableTable(table); - } - - @Override - protected Map makePopulatedMap() { - Table table = HashBasedTable.create(); - table.put("one", 'a', 1); - table.put("two", 'a', 2); - table.put("three", 'a', 3); - table.put("four", 'b', 4); - return Tables.unmodifiableTable(table).column('a'); - } - } - - public static class UnmodifiableTreeColumnTests extends ColumnTests { - public UnmodifiableTreeColumnTests() { - super(false, false, false, false, false); - } - - @Override - Table makeTable() { - RowSortedTable table = TreeBasedTable.create(); - return Tables.unmodifiableRowSortedTable(table); - } - - @Override - protected Map makePopulatedMap() { - RowSortedTable table = TreeBasedTable.create(); - table.put("one", 'a', 1); - table.put("two", 'a', 2); - table.put("three", 'a', 3); - table.put("four", 'b', 4); - return Tables.unmodifiableRowSortedTable(table).column('a'); - } - } - private abstract static class MapMapTests extends MapInterfaceTest> { @@ -1109,34 +847,29 @@ protected Map getValueNotInPopulatedMap() { */ @Override public void testRemove() { - final Map> map; - final String keyToRemove; + Map> map; try { map = makePopulatedMap(); } catch (UnsupportedOperationException e) { return; } - keyToRemove = map.keySet().iterator().next(); + String keyToRemove = map.keySet().iterator().next(); if (supportsRemove) { int initialSize = map.size(); - map.get(keyToRemove); + // var oldValue = map.get(keyToRemove); map.remove(keyToRemove); // This line doesn't hold - see the Javadoc comments above. // assertEquals(expectedValue, oldValue); assertFalse(map.containsKey(keyToRemove)); assertEquals(initialSize - 1, map.size()); } else { - try { - map.remove(keyToRemove); - fail("Expected UnsupportedOperationException."); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> map.remove(keyToRemove)); } assertInvariants(map); } } - private abstract static class RowMapTests extends MapMapTests { + abstract static class RowMapTests extends MapMapTests { RowMapTests( boolean allowsNullValues, boolean supportsRemove, @@ -1154,7 +887,8 @@ protected Map> makePopulatedMap() { return table.rowMap(); } - void populateTable(Table table) { + // `protected` to work around b/320650932 / KT-67447 runtime crash + protected final void populateTable(Table table) { table.put("foo", 1, 'a'); table.put("bar", 1, 'b'); table.put("foo", 3, 'c'); @@ -1166,208 +900,15 @@ protected Map> makeEmptyMap() { } } - @GwtIncompatible // TODO(hhchan): ArrayTable - public static class ArrayRowMapTests extends RowMapTests { - public ArrayRowMapTests() { - super(true, false, false, false); - } - - @Override - Table makeTable() { - return ArrayTable.create(Arrays.asList("foo", "bar", "dog"), Arrays.asList(1, 2, 3)); - } - - @Override - protected Map> makeEmptyMap() { - throw new UnsupportedOperationException(); - } - } - - public static class HashRowMapTests extends RowMapTests { - public HashRowMapTests() { - super(false, true, true, true); - } - - @Override - Table makeTable() { - return HashBasedTable.create(); - } - } - - public static class TreeRowMapTests extends RowMapTests { - public TreeRowMapTests() { - super(false, true, true, true); - } - - @Override - Table makeTable() { - return TreeBasedTable.create(); - } - } - - public static class TreeRowMapHeadMapTests extends RowMapTests { - public TreeRowMapHeadMapTests() { - super(false, true, true, true); - } - - @Override - TreeBasedTable makeTable() { - TreeBasedTable table = TreeBasedTable.create(); - table.put("z", 1, 'a'); - return table; - } - - @Override - protected Map> makePopulatedMap() { - TreeBasedTable table = makeTable(); - populateTable(table); - return table.rowMap().headMap("x"); - } - - @Override - protected Map> makeEmptyMap() { - return makeTable().rowMap().headMap("x"); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "z"; - } - } - - public static class TreeRowMapTailMapTests extends RowMapTests { - public TreeRowMapTailMapTests() { - super(false, true, true, true); - } - - @Override - TreeBasedTable makeTable() { - TreeBasedTable table = TreeBasedTable.create(); - table.put("a", 1, 'a'); - return table; - } - - @Override - protected Map> makePopulatedMap() { - TreeBasedTable table = makeTable(); - populateTable(table); - return table.rowMap().tailMap("b"); - } - - @Override - protected Map> makeEmptyMap() { - return makeTable().rowMap().tailMap("b"); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "a"; - } - } - - public static class TreeRowMapSubMapTests extends RowMapTests { - public TreeRowMapSubMapTests() { - super(false, true, true, true); - } - - @Override - TreeBasedTable makeTable() { - TreeBasedTable table = TreeBasedTable.create(); - table.put("a", 1, 'a'); - table.put("z", 1, 'a'); - return table; - } - - @Override - protected Map> makePopulatedMap() { - TreeBasedTable table = makeTable(); - populateTable(table); - return table.rowMap().subMap("b", "x"); - } - - @Override - protected Map> makeEmptyMap() { - return makeTable().rowMap().subMap("b", "x"); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "z"; - } - } - - private static final Function FIRST_CHARACTER = - new Function() { + static final Function<@Nullable String, @Nullable Character> FIRST_CHARACTER = + new Function<@Nullable String, @Nullable Character>() { @Override - public Character apply(String input) { + public @Nullable Character apply(@Nullable String input) { return input == null ? null : input.charAt(0); } }; - public static class TransformValueRowMapTests extends RowMapTests { - public TransformValueRowMapTests() { - super(false, true, true, true); - } - - @Override - Table makeTable() { - Table original = HashBasedTable.create(); - return Tables.transformValues(original, FIRST_CHARACTER); - } - - @Override - protected Map> makePopulatedMap() { - Table table = HashBasedTable.create(); - table.put("foo", 1, "apple"); - table.put("bar", 1, "banana"); - table.put("foo", 3, "cat"); - return Tables.transformValues(table, FIRST_CHARACTER).rowMap(); - } - } - - public static class UnmodifiableHashRowMapTests extends RowMapTests { - public UnmodifiableHashRowMapTests() { - super(false, false, false, false); - } - - @Override - Table makeTable() { - Table original = HashBasedTable.create(); - return Tables.unmodifiableTable(original); - } - - @Override - protected Map> makePopulatedMap() { - Table table = HashBasedTable.create(); - table.put("foo", 1, 'a'); - table.put("bar", 1, 'b'); - table.put("foo", 3, 'c'); - return Tables.unmodifiableTable(table).rowMap(); - } - } - - public static class UnmodifiableTreeRowMapTests extends RowMapTests { - public UnmodifiableTreeRowMapTests() { - super(false, false, false, false); - } - - @Override - RowSortedTable makeTable() { - RowSortedTable original = TreeBasedTable.create(); - return Tables.unmodifiableRowSortedTable(original); - } - - @Override - protected SortedMap> makePopulatedMap() { - RowSortedTable table = TreeBasedTable.create(); - table.put("foo", 1, 'a'); - table.put("bar", 1, 'b'); - table.put("foo", 3, 'c'); - return Tables.unmodifiableRowSortedTable(table).rowMap(); - } - } - - private abstract static class ColumnMapTests extends MapMapTests { + abstract static class ColumnMapTests extends MapMapTests { ColumnMapTests( boolean allowsNullValues, boolean supportsRemove, @@ -1392,106 +933,4 @@ protected Map> makeEmptyMap() { return makeTable().columnMap(); } } - - @GwtIncompatible // TODO(hhchan): ArrayTable - public static class ArrayColumnMapTests extends ColumnMapTests { - public ArrayColumnMapTests() { - super(true, false, false, false); - } - - @Override - Table makeTable() { - return ArrayTable.create(Arrays.asList(1, 2, 3), Arrays.asList("foo", "bar", "dog")); - } - - @Override - protected Map> makeEmptyMap() { - throw new UnsupportedOperationException(); - } - } - - public static class HashColumnMapTests extends ColumnMapTests { - public HashColumnMapTests() { - super(false, true, true, false); - } - - @Override - Table makeTable() { - return HashBasedTable.create(); - } - } - - public static class TreeColumnMapTests extends ColumnMapTests { - public TreeColumnMapTests() { - super(false, true, true, false); - } - - @Override - Table makeTable() { - return TreeBasedTable.create(); - } - } - - public static class TransformValueColumnMapTests extends ColumnMapTests { - public TransformValueColumnMapTests() { - super(false, true, true, false); - } - - @Override - Table makeTable() { - Table original = HashBasedTable.create(); - return Tables.transformValues(original, FIRST_CHARACTER); - } - - @Override - protected Map> makePopulatedMap() { - Table table = HashBasedTable.create(); - table.put(1, "foo", "apple"); - table.put(1, "bar", "banana"); - table.put(3, "foo", "cat"); - return Tables.transformValues(table, FIRST_CHARACTER).columnMap(); - } - } - - public static class UnmodifiableHashColumnMapTests extends ColumnMapTests { - public UnmodifiableHashColumnMapTests() { - super(false, false, false, false); - } - - @Override - Table makeTable() { - Table original = HashBasedTable.create(); - return Tables.unmodifiableTable(original); - } - - @Override - protected Map> makePopulatedMap() { - Table table = HashBasedTable.create(); - table.put(1, "foo", 'a'); - table.put(1, "bar", 'b'); - table.put(3, "foo", 'c'); - return Tables.unmodifiableTable(table).columnMap(); - } - } - - public static class UnmodifiableTreeColumnMapTests extends ColumnMapTests { - public UnmodifiableTreeColumnMapTests() { - super(false, false, false, false); - } - - @Override - Table makeTable() { - RowSortedTable original = TreeBasedTable.create(); - return Tables.unmodifiableRowSortedTable(original); - } - - @Override - protected Map> makePopulatedMap() { - RowSortedTable table = TreeBasedTable.create(); - table.put(1, "foo", 'a'); - table.put(1, "bar", 'b'); - table.put(3, "foo", 'c'); - return Tables.unmodifiableRowSortedTable(table).columnMap(); - } - } } diff --git a/android/guava-tests/test/com/google/common/collect/TableCollectorsTest.java b/android/guava-tests/test/com/google/common/collect/TableCollectorsTest.java new file mode 100644 index 000000000000..2d4510f4d79a --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TableCollectorsTest.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.TableCollectors.toImmutableTable; +import static com.google.common.collect.Tables.immutableCell; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Table.Cell; +import com.google.common.testing.CollectorTester; +import java.util.function.BiPredicate; +import java.util.function.BinaryOperator; +import java.util.stream.Collector; +import java.util.stream.Stream; +import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Unit tests for {@link TableCollectors}. */ +@GwtCompatible +@NullMarked +public class TableCollectorsTest extends TestCase { + public void testToImmutableTable() { + Collector, ?, ImmutableTable> collector = + toImmutableTable(Cell::getRowKey, Cell::getColumnKey, Cell::getValue); + BiPredicate, ImmutableTable> + equivalence = pairwiseOnResultOf(ImmutableTable::cellSet); + CollectorTester.of(collector, equivalence) + .expectCollects( + new ImmutableTable.Builder() + .put("one", "uno", 1) + .put("two", "dos", 2) + .put("three", "tres", 3) + .buildOrThrow(), + immutableCell("one", "uno", 1), + immutableCell("two", "dos", 2), + immutableCell("three", "tres", 3)); + } + + public void testToImmutableTableConflict() { + Collector, ?, ImmutableTable> collector = + toImmutableTable(Cell::getRowKey, Cell::getColumnKey, Cell::getValue); + assertThrows( + IllegalArgumentException.class, + () -> + Stream.of(immutableCell("one", "uno", 1), immutableCell("one", "uno", 2)) + .collect(collector)); + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when rowFunction result (null) is unboxed + @J2ktIncompatible + public void testToImmutableTableNullRowKey() { + Collector, ?, ImmutableTable> collector = + toImmutableTable(t -> null, Cell::getColumnKey, Cell::getValue); + assertThrows( + NullPointerException.class, + () -> Stream.of(immutableCell("one", "uno", 1)).collect(collector)); + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when columnFunction result (null) is + // unboxed + @J2ktIncompatible + public void testToImmutableTableNullColumnKey() { + Collector, ?, ImmutableTable> collector = + toImmutableTable(Cell::getRowKey, t -> null, Cell::getValue); + assertThrows( + NullPointerException.class, + () -> Stream.of(immutableCell("one", "uno", 1)).collect(collector)); + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when getValue result (null) is unboxed + @J2ktIncompatible + public void testToImmutableTableNullValue() { + { + Collector, ?, ImmutableTable> + collector = toImmutableTable(Cell::getRowKey, Cell::getColumnKey, t -> null); + assertThrows( + NullPointerException.class, + () -> Stream.of(immutableCell("one", "uno", 1)).collect(collector)); + } + { + Collector, ?, ImmutableTable> + collector = toImmutableTable(Cell::getRowKey, Cell::getColumnKey, Cell::getValue); + assertThrows( + NullPointerException.class, + () -> + Stream.of(immutableCell("one", "uno", 1), immutableCell("one", "uno", (Integer) null)) + .collect(collector)); + } + } + + public void testToImmutableTableMerging() { + Collector, ?, ImmutableTable> collector = + toImmutableTable(Cell::getRowKey, Cell::getColumnKey, Cell::getValue, Integer::sum); + BiPredicate, ImmutableTable> + equivalence = pairwiseOnResultOf(ImmutableTable::cellSet); + CollectorTester.of(collector, equivalence) + .expectCollects( + new ImmutableTable.Builder() + .put("one", "uno", 1) + .put("two", "dos", 6) + .put("three", "tres", 3) + .buildOrThrow(), + immutableCell("one", "uno", 1), + immutableCell("two", "dos", 2), + immutableCell("three", "tres", 3), + immutableCell("two", "dos", 4)); + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when rowFunction result (null) is unboxed + @J2ktIncompatible + public void testToImmutableTableMergingNullRowKey() { + Collector, ?, ImmutableTable> collector = + toImmutableTable(t -> null, Cell::getColumnKey, Cell::getValue, Integer::sum); + assertThrows( + NullPointerException.class, + () -> Stream.of(immutableCell("one", "uno", 1)).collect(collector)); + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when columnFunction result (null) is + // unboxed + @J2ktIncompatible + public void testToImmutableTableMergingNullColumnKey() { + Collector, ?, ImmutableTable> collector = + toImmutableTable(Cell::getRowKey, t -> null, Cell::getValue, Integer::sum); + assertThrows( + NullPointerException.class, + () -> Stream.of(immutableCell("one", "uno", 1)).collect(collector)); + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when valueFunction result (null) is + // unboxed + @J2ktIncompatible + public void testToImmutableTableMergingNullValue() { + { + Collector, ?, ImmutableTable> + collector = + toImmutableTable(Cell::getRowKey, Cell::getColumnKey, t -> null, Integer::sum); + assertThrows( + NullPointerException.class, + () -> Stream.of(immutableCell("one", "uno", 1)).collect(collector)); + } + { + Collector, ?, ImmutableTable> + collector = + toImmutableTable( + Cell::getRowKey, + Cell::getColumnKey, + Cell::getValue, + (i, j) -> MoreObjects.firstNonNull(i, 0) + MoreObjects.firstNonNull(j, 0)); + assertThrows( + NullPointerException.class, + () -> + Stream.of(immutableCell("one", "uno", 1), immutableCell("one", "uno", (Integer) null)) + .collect(collector)); + } + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when mergeFunction result (null) is + // unboxed + @J2ktIncompatible + public void testToImmutableTableMergingNullMerge() { + Collector, ?, ImmutableTable> collector = + toImmutableTable(Cell::getRowKey, Cell::getColumnKey, Cell::getValue, (v1, v2) -> null); + assertThrows( + NullPointerException.class, + () -> + Stream.of(immutableCell("one", "uno", 1), immutableCell("one", "uno", 2)) + .collect(collector)); + } + + public void testToTable() { + Collector, ?, Table> collector = + TableCollectors.toTable( + Cell::getRowKey, Cell::getColumnKey, Cell::getValue, HashBasedTable::create); + BiPredicate, Table> equivalence = + pairwiseOnResultOf(Table::cellSet); + CollectorTester.of(collector, equivalence) + .expectCollects( + new ImmutableTable.Builder() + .put("one", "uno", 1) + .put("two", "dos", 2) + .put("three", "tres", 3) + .buildOrThrow(), + immutableCell("one", "uno", 1), + immutableCell("two", "dos", 2), + immutableCell("three", "tres", 3)); + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when mergeFunction result (null) is + // unboxed + @J2ktIncompatible + public void testToTableNullMerge() { + // TODO github.com/google/guava/issues/6824 - the null merge feature is not compatible with the + // current nullness annotation of the mergeFunction parameter. Work around with casts. + BinaryOperator<@Nullable Integer> mergeFunction = (v1, v2) -> null; + Collector, ?, Table> collector = + TableCollectors.toTable( + Cell::getRowKey, + Cell::getColumnKey, + Cell::getValue, + (BinaryOperator) mergeFunction, + HashBasedTable::create); + BiPredicate, Table> equivalence = + pairwiseOnResultOf(Table::cellSet); + CollectorTester.of(collector, equivalence) + .expectCollects( + ImmutableTable.of(), immutableCell("one", "uno", 1), immutableCell("one", "uno", 2)); + } + + // https://youtrack.jetbrains.com/issue/KT-58242/. Crash when getValue result (null) is unboxed + @J2ktIncompatible + public void testToTableNullValues() { + Collector, ?, Table> collector = + TableCollectors.toTable( + Cell::getRowKey, + Cell::getColumnKey, + Cell::getValue, + () -> { + Table table = + ArrayTable.create(ImmutableList.of("one"), ImmutableList.of("uno")); + return (Table) table; + }); + Cell cell = immutableCell("one", "uno", null); + assertThrows( + NullPointerException.class, + () -> Stream.of((Cell) cell).collect(collector)); + } + + public void testToTableConflict() { + Collector, ?, Table> collector = + TableCollectors.toTable( + Cell::getRowKey, Cell::getColumnKey, Cell::getValue, HashBasedTable::create); + assertThrows( + IllegalStateException.class, + () -> + Stream.of(immutableCell("one", "uno", 1), immutableCell("one", "uno", 2)) + .collect(collector)); + } + + public void testToTableMerging() { + Collector, ?, Table> collector = + TableCollectors.toTable( + Cell::getRowKey, + Cell::getColumnKey, + Cell::getValue, + Integer::sum, + HashBasedTable::create); + BiPredicate, Table> equivalence = + pairwiseOnResultOf(Table::cellSet); + CollectorTester.of(collector, equivalence) + .expectCollects( + new ImmutableTable.Builder() + .put("one", "uno", 1) + .put("two", "dos", 6) + .put("three", "tres", 3) + .buildOrThrow(), + immutableCell("one", "uno", 1), + immutableCell("two", "dos", 2), + immutableCell("three", "tres", 3), + immutableCell("two", "dos", 4)); + } + + // This function specifically returns a BiPredicate, because Guava7’s Equivalence class does not + // actually implement BiPredicate, and CollectorTests expects a BiPredicate. + static > + BiPredicate pairwiseOnResultOf(Function arg) { + Equivalence equivalence = Equivalence.equals().pairwise().onResultOf(arg); + return equivalence::equivalent; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TablesTest.java b/android/guava-tests/test/com/google/common/collect/TablesTest.java index 1cce12f27fb8..c3d241348d5a 100644 --- a/android/guava-tests/test/com/google/common/collect/TablesTest.java +++ b/android/guava-tests/test/com/google/common/collect/TablesTest.java @@ -16,56 +16,71 @@ package com.google.common.collect; +import static com.google.common.collect.Tables.immutableCell; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.Table.Cell; import com.google.common.testing.EqualsTester; import com.google.common.testing.SerializableTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Tables}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class TablesTest extends TestCase { @GwtIncompatible // SerializableTester public void testImmutableEntrySerialization() { - Cell entry = Tables.immutableCell("foo", 1, 'a'); + Cell entry = immutableCell("foo", 1, 'a'); SerializableTester.reserializeAndAssert(entry); } public void testImmutableEntryToString() { - Cell entry = Tables.immutableCell("foo", 1, 'a'); + Cell entry = immutableCell("foo", 1, 'a'); assertEquals("(foo,1)=a", entry.toString()); - Cell nullEntry = Tables.immutableCell(null, null, null); + Cell<@Nullable String, @Nullable Integer, @Nullable Character> nullEntry = + immutableCell(null, null, null); assertEquals("(null,null)=null", nullEntry.toString()); } public void testEntryEquals() { - Cell entry = Tables.immutableCell("foo", 1, 'a'); + Cell entry = immutableCell("foo", 1, 'a'); new EqualsTester() - .addEqualityGroup(entry, Tables.immutableCell("foo", 1, 'a')) - .addEqualityGroup(Tables.immutableCell("bar", 1, 'a')) - .addEqualityGroup(Tables.immutableCell("foo", 2, 'a')) - .addEqualityGroup(Tables.immutableCell("foo", 1, 'b')) - .addEqualityGroup(Tables.immutableCell(null, null, null)) + .addEqualityGroup(entry, immutableCell("foo", 1, 'a')) + .addEqualityGroup(immutableCell("bar", 1, 'a')) + .addEqualityGroup(immutableCell("foo", 2, 'a')) + .addEqualityGroup(immutableCell("foo", 1, 'b')) + .addEqualityGroup( + Tables.<@Nullable Object, @Nullable Object, @Nullable Object>immutableCell( + null, null, null)) .testEquals(); } public void testEntryEqualsNull() { - Cell entry = Tables.immutableCell(null, null, null); + Cell<@Nullable String, @Nullable Integer, @Nullable Character> entry = + immutableCell(null, null, null); new EqualsTester() - .addEqualityGroup(entry, Tables.immutableCell(null, null, null)) - .addEqualityGroup(Tables.immutableCell("bar", null, null)) - .addEqualityGroup(Tables.immutableCell(null, 2, null)) - .addEqualityGroup(Tables.immutableCell(null, null, 'b')) - .addEqualityGroup(Tables.immutableCell("foo", 1, 'a')) + .addEqualityGroup( + entry, + Tables.<@Nullable Object, @Nullable Object, @Nullable Object>immutableCell( + null, null, null)) + .addEqualityGroup( + Tables.immutableCell("bar", null, null)) + .addEqualityGroup( + Tables.<@Nullable Object, Integer, @Nullable Object>immutableCell(null, 2, null)) + .addEqualityGroup( + Tables.<@Nullable Object, @Nullable Object, Character>immutableCell(null, null, 'b')) + .addEqualityGroup(immutableCell("foo", 1, 'a')) .testEquals(); } } diff --git a/android/guava-tests/test/com/google/common/collect/TablesTransformValuesColumnMapTest.java b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesColumnMapTest.java new file mode 100644 index 000000000000..e5887875d742 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesColumnMapTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.TableCollectionTest.FIRST_CHARACTER; +import static com.google.common.collect.Tables.transformValues; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnMapTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TablesTransformValuesColumnMapTest extends ColumnMapTests { + public TablesTransformValuesColumnMapTest() { + super(false, true, true, false); + } + + @Override + Table makeTable() { + Table original = HashBasedTable.create(); + return transformValues(original, FIRST_CHARACTER); + } + + @Override + protected Map> makePopulatedMap() { + Table table = HashBasedTable.create(); + table.put(1, "foo", "apple"); + table.put(1, "bar", "banana"); + table.put(3, "foo", "cat"); + return transformValues(table, FIRST_CHARACTER).columnMap(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TablesTransformValuesColumnTest.java b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesColumnTest.java new file mode 100644 index 000000000000..c1a140092acc --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesColumnTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.TableCollectionTest.DIVIDE_BY_2; +import static com.google.common.collect.Tables.transformValues; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TablesTransformValuesColumnTest extends ColumnTests { + public TablesTransformValuesColumnTest() { + super(false, false, true, true, false); + } + + @Override + Table makeTable() { + Table table = HashBasedTable.create(); + return transformValues(table, DIVIDE_BY_2); + } + + @Override + protected Map makePopulatedMap() { + Table table = HashBasedTable.create(); + table.put("one", 'a', 1); + table.put("two", 'a', 2); + table.put("three", 'a', 3); + table.put("four", 'b', 4); + return transformValues(table, DIVIDE_BY_2).column('a'); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TablesTransformValuesRowMapTest.java b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesRowMapTest.java new file mode 100644 index 000000000000..0a3918b39ef6 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesRowMapTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.transformValues; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TablesTransformValuesRowMapTest extends RowMapTests { + public TablesTransformValuesRowMapTest() { + super(false, true, true, true); + } + + @Override + Table makeTable() { + Table original = HashBasedTable.create(); + return transformValues(original, TableCollectionTest.FIRST_CHARACTER); + } + + @Override + protected Map> makePopulatedMap() { + Table table = HashBasedTable.create(); + table.put("foo", 1, "apple"); + table.put("bar", 1, "banana"); + table.put("foo", 3, "cat"); + return transformValues(table, TableCollectionTest.FIRST_CHARACTER).rowMap(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TablesTransformValuesRowTest.java b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesRowTest.java new file mode 100644 index 000000000000..de34b485640d --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesRowTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.transformValues; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TablesTransformValuesRowTest extends RowTests { + public TablesTransformValuesRowTest() { + super(false, false, true, true, true); + } + + @Override + Table makeTable() { + Table table = HashBasedTable.create(); + return transformValues(table, TableCollectionTest.DIVIDE_BY_2); + } + + @Override + protected Map makePopulatedMap() { + Table table = HashBasedTable.create(); + table.put('a', "one", 2); + table.put('a', "two", 4); + table.put('a', "three", 6); + table.put('b', "four", 8); + return transformValues(table, TableCollectionTest.DIVIDE_BY_2).row('a'); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TablesTransformValuesTest.java b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesTest.java index 6730b3f519d4..379eaa076070 100644 --- a/android/guava-tests/test/com/google/common/collect/TablesTransformValuesTest.java +++ b/android/guava-tests/test/com/google/common/collect/TablesTransformValuesTest.java @@ -17,39 +17,46 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.collect.Tables.transformValues; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link Tables#transformValues}. * * @author Jared Levy */ -@GwtCompatible(emulated = true) -public class TablesTransformValuesTest extends AbstractTableTest { +@GwtCompatible +@NullMarked +public class TablesTransformValuesTest extends AbstractTableTest { - private static final Function FIRST_CHARACTER = - new Function() { + private static final Function<@Nullable String, @Nullable Character> FIRST_CHARACTER = + new Function<@Nullable String, @Nullable Character>() { @Override - public Character apply(String input) { + public @Nullable Character apply(@Nullable String input) { return input == null ? null : input.charAt(0); } }; @Override - protected Table create(Object... data) { + protected Table create(@Nullable Object... data) { Table table = HashBasedTable.create(); checkArgument(data.length % 3 == 0); for (int i = 0; i < data.length; i += 3) { String value = (data[i + 2] == null) ? null : (data[i + 2] + "transformed"); table.put((String) data[i], (Integer) data[i + 1], value); } - return Tables.transformValues(table, FIRST_CHARACTER); + return transformValues(table, FIRST_CHARACTER); } // Null support depends on the underlying table and function. + @J2ktIncompatible @GwtIncompatible // NullPointerTester @Override public void testNullPointerInstance() {} @@ -57,11 +64,7 @@ public void testNullPointerInstance() {} // put() and putAll() aren't supported. @Override public void testPut() { - try { - table.put("foo", 1, 'a'); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> table.put("foo", 1, 'a')); assertSize(0); } @@ -72,11 +75,7 @@ public void testPutAllTable() { other.put("foo", 1, 'd'); other.put("bar", 2, 'e'); other.put("cat", 2, 'f'); - try { - table.putAll(other); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> table.putAll(other)); assertEquals((Character) 'a', table.get("foo", 1)); assertEquals((Character) 'b', table.get("bar", 1)); assertEquals((Character) 'c', table.get("foo", 3)); diff --git a/android/guava/src/com/google/common/collect/GwtTransient.java b/android/guava-tests/test/com/google/common/collect/TablesTransposeColumnTest.java similarity index 54% rename from android/guava/src/com/google/common/collect/GwtTransient.java rename to android/guava-tests/test/com/google/common/collect/TablesTransposeColumnTest.java index 9c09c53c946f..f8e67f990829 100644 --- a/android/guava/src/com/google/common/collect/GwtTransient.java +++ b/android/guava-tests/test/com/google/common/collect/TablesTransposeColumnTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Guava Authors + * Copyright (C) 2008 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,22 @@ package com.google.common.collect; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static com.google.common.collect.Tables.transpose; import com.google.common.annotations.GwtCompatible; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; +import com.google.common.collect.TableCollectionTest.ColumnTests; +import org.jspecify.annotations.NullMarked; -/** - * Private replacement for {@link com.google.gwt.user.client.rpc.GwtTransient} to work around - * build-system quirks. This annotation should be used only in {@code - * com.google.common.collect}. - */ -@Documented @GwtCompatible -@Retention(RUNTIME) -@Target(FIELD) -@interface GwtTransient {} +@NullMarked +public class TablesTransposeColumnTest extends ColumnTests { + public TablesTransposeColumnTest() { + super(false, true, true, true, true); + } + + @Override + Table makeTable() { + Table original = TreeBasedTable.create(); + return transpose(original); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TablesTransposeRowTest.java b/android/guava-tests/test/com/google/common/collect/TablesTransposeRowTest.java new file mode 100644 index 000000000000..524ed112d70a --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TablesTransposeRowTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.transpose; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TablesTransposeRowTest extends RowTests { + public TablesTransposeRowTest() { + super(false, true, true, true, false); + } + + @Override + Table makeTable() { + Table original = TreeBasedTable.create(); + return transpose(original); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TestExceptions.java b/android/guava-tests/test/com/google/common/collect/TestExceptions.java new file mode 100644 index 000000000000..eb0025b49260 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TestExceptions.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullUnmarked; + +/** Exception classes for use in tests. */ +@GwtCompatible +@NullUnmarked +final class TestExceptions { + static class SomeError extends Error {} + + static class SomeCheckedException extends Exception {} + + static class SomeOtherCheckedException extends Exception {} + + static class YetAnotherCheckedException extends Exception {} + + static class SomeUncheckedException extends RuntimeException {} + + static class SomeChainingException extends RuntimeException { + public SomeChainingException(Throwable cause) { + super(cause); + } + } + + private TestExceptions() {} +} diff --git a/android/guava-tests/test/com/google/common/collect/TopKSelectorTest.java b/android/guava-tests/test/com/google/common/collect/TopKSelectorTest.java index e21f81703c30..8e95c508ac20 100644 --- a/android/guava-tests/test/com/google/common/collect/TopKSelectorTest.java +++ b/android/guava-tests/test/com/google/common/collect/TopKSelectorTest.java @@ -16,44 +16,34 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.nCopies; +import com.google.common.annotations.GwtCompatible; import com.google.common.math.IntMath; import com.google.common.primitives.Ints; import java.math.RoundingMode; -import java.util.Collections; import java.util.Comparator; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code TopKSelector}. * * @author Louis Wasserman */ +@GwtCompatible +@NullUnmarked public class TopKSelectorTest extends TestCase { public void testNegativeK() { - try { - TopKSelector.least(-1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - TopKSelector.greatest(-1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - TopKSelector.least(-1, Ordering.natural()); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - TopKSelector.greatest(-1, Ordering.natural()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> TopKSelector.least(-1)); + assertThrows(IllegalArgumentException.class, () -> TopKSelector.greatest(-1)); + assertThrows(IllegalArgumentException.class, () -> TopKSelector.least(-1, Ordering.natural())); + assertThrows( + IllegalArgumentException.class, () -> TopKSelector.greatest(-1, Ordering.natural())); } public void testZeroK() { @@ -102,7 +92,7 @@ public void testDifferentComparator() { public void testWorstCase() { int n = 2000000; int k = 200000; - final long[] compareCalls = {0}; + long[] compareCalls = {0}; Comparator cmp = new Comparator() { @Override @@ -116,7 +106,16 @@ public int compare(Integer o1, Integer o2) { for (int i = 1; i < n; i++) { top.offer(0); } - assertThat(top.topK()).containsExactlyElementsIn(Collections.nCopies(k, 0)); + assertThat(top.topK()).containsExactlyElementsIn(nCopies(k, 0)); assertThat(compareCalls[0]).isAtMost(10L * n * IntMath.log2(k, RoundingMode.CEILING)); } + + public void testExceedMaxIteration() { + /* + * Bug #5692 occurred when TopKSelector called Arrays.sort incorrectly. + */ + TopKSelector top = TopKSelector.least(7); + top.offerAll(Ints.asList(5, 7, 6, 2, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0)); + assertThat(top.topK()).isEqualTo(Ints.asList(0, 0, 0, 0, 0, 0, 0)); + } } diff --git a/android/guava-tests/test/com/google/common/collect/TransposedTableTest.java b/android/guava-tests/test/com/google/common/collect/TransposedTableTest.java index 7233cf175374..49a4910161cc 100644 --- a/android/guava-tests/test/com/google/common/collect/TransposedTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/TransposedTableTest.java @@ -16,7 +16,11 @@ package com.google.common.collect; +import static com.google.common.collect.Tables.transpose; + import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link Tables#transpose}. @@ -24,12 +28,13 @@ * @author Jared Levy */ @GwtCompatible -public class TransposedTableTest extends AbstractTableTest { +@NullMarked +public class TransposedTableTest extends AbstractTableTest { @Override - protected Table create(Object... data) { + protected Table create(@Nullable Object... data) { Table original = HashBasedTable.create(); - Table table = Tables.transpose(original); + Table table = transpose(original); table.clear(); populate(table, data); return table; @@ -37,26 +42,26 @@ protected Table create(Object... data) { public void testTransposeTransposed() { Table original = HashBasedTable.create(); - assertSame(original, Tables.transpose(Tables.transpose(original))); + assertSame(original, transpose(transpose(original))); } public void testPutOriginalModifiesTranspose() { Table original = HashBasedTable.create(); - Table transpose = Tables.transpose(original); + Table transpose = transpose(original); original.put(1, "foo", 'a'); assertEquals((Character) 'a', transpose.get("foo", 1)); } public void testPutTransposeModifiesOriginal() { Table original = HashBasedTable.create(); - Table transpose = Tables.transpose(original); + Table transpose = transpose(original); transpose.put("foo", 1, 'a'); assertEquals((Character) 'a', original.get(1, "foo")); } public void testTransposedViews() { Table original = HashBasedTable.create(); - Table transpose = Tables.transpose(original); + Table transpose = transpose(original); original.put(1, "foo", 'a'); assertSame(original.columnKeySet(), transpose.rowKeySet()); assertSame(original.rowKeySet(), transpose.columnKeySet()); diff --git a/android/guava-tests/test/com/google/common/collect/TreeBasedTableColumnMapTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableColumnMapTest.java new file mode 100644 index 000000000000..f27017d6cfa2 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableColumnMapTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnMapTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TreeBasedTableColumnMapTest extends ColumnMapTests { + public TreeBasedTableColumnMapTest() { + super(false, true, true, false); + } + + @Override + Table makeTable() { + return TreeBasedTable.create(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TreeBasedTableColumnTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableColumnTest.java new file mode 100644 index 000000000000..b4e6008e38cd --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableColumnTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TreeBasedTableColumnTest extends ColumnTests { + public TreeBasedTableColumnTest() { + super(false, true, true, true, false); + } + + @Override + Table makeTable() { + return TreeBasedTable.create(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapHeadMapTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapHeadMapTest.java new file mode 100644 index 000000000000..59ba288793de --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapHeadMapTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TreeBasedTableRowMapHeadMapTest extends RowMapTests { + public TreeBasedTableRowMapHeadMapTest() { + super(false, true, true, true); + } + + @Override + TreeBasedTable makeTable() { + TreeBasedTable table = TreeBasedTable.create(); + table.put("z", 1, 'a'); + return table; + } + + @Override + protected Map> makePopulatedMap() { + TreeBasedTable table = makeTable(); + populateTable(table); + return table.rowMap().headMap("x"); + } + + @Override + protected Map> makeEmptyMap() { + return makeTable().rowMap().headMap("x"); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "z"; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapInterfaceTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapInterfaceTest.java new file mode 100644 index 000000000000..70ed7faca268 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapInterfaceTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.testing.SortedMapInterfaceTest; +import java.util.SortedMap; +import org.jspecify.annotations.NullUnmarked; + +@GwtCompatible +@NullUnmarked +public class TreeBasedTableRowMapInterfaceTest extends SortedMapInterfaceTest { + public TreeBasedTableRowMapInterfaceTest() { + super(false, false, true, true, true); + } + + @Override + protected SortedMap makeEmptyMap() { + TreeBasedTable table = TreeBasedTable.create(); + table.put("a", "b", "c"); + table.put("c", "b", "a"); + table.put("a", "a", "d"); + return table.row("b"); + } + + @Override + protected SortedMap makePopulatedMap() { + TreeBasedTable table = TreeBasedTable.create(); + table.put("a", "b", "c"); + table.put("c", "b", "a"); + table.put("b", "b", "x"); + table.put("b", "c", "y"); + table.put("b", "x", "n"); + table.put("a", "a", "d"); + return table.row("b"); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "q"; + } + + @Override + protected String getValueNotInPopulatedMap() { + return "p"; + } + + public void testClearSubMapOfRowMap() { + TreeBasedTable table = TreeBasedTable.create(); + table.put("a", "b", "c"); + table.put("c", "b", "a"); + table.put("b", "b", "x"); + table.put("b", "c", "y"); + table.put("b", "x", "n"); + table.put("a", "a", "d"); + table.row("b").subMap("c", "x").clear(); + assertEquals(table.row("b"), ImmutableMap.of("b", "x", "x", "n")); + table.row("b").subMap("b", "y").clear(); + assertEquals(table.row("b"), ImmutableMap.of()); + assertFalse(table.backingMap.containsKey("b")); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapSubMapTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapSubMapTest.java new file mode 100644 index 000000000000..d9c5ba1e457a --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapSubMapTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TreeBasedTableRowMapSubMapTest extends RowMapTests { + public TreeBasedTableRowMapSubMapTest() { + super(false, true, true, true); + } + + @Override + TreeBasedTable makeTable() { + TreeBasedTable table = TreeBasedTable.create(); + table.put("a", 1, 'a'); + table.put("z", 1, 'a'); + return table; + } + + @Override + protected Map> makePopulatedMap() { + TreeBasedTable table = makeTable(); + populateTable(table); + return table.rowMap().subMap("b", "x"); + } + + @Override + protected Map> makeEmptyMap() { + return makeTable().rowMap().subMap("b", "x"); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "z"; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapTailMapTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapTailMapTest.java new file mode 100644 index 000000000000..cf25c9d5972d --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapTailMapTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TreeBasedTableRowMapTailMapTest extends RowMapTests { + public TreeBasedTableRowMapTailMapTest() { + super(false, true, true, true); + } + + @Override + TreeBasedTable makeTable() { + TreeBasedTable table = TreeBasedTable.create(); + table.put("a", 1, 'a'); + return table; + } + + @Override + protected Map> makePopulatedMap() { + TreeBasedTable table = makeTable(); + populateTable(table); + return table.rowMap().tailMap("b"); + } + + @Override + protected Map> makeEmptyMap() { + return makeTable().rowMap().tailMap("b"); + } + + @Override + protected String getKeyNotInPopulatedMap() { + return "a"; + } +} diff --git a/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapTest.java new file mode 100644 index 000000000000..e9c891fa24ef --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowMapTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class TreeBasedTableRowMapTest extends RowMapTests { + public TreeBasedTableRowMapTest() { + super(false, true, true, true); + } + + @Override + Table makeTable() { + return TreeBasedTable.create(); + } +} diff --git a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowTest.java similarity index 61% rename from guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java rename to android/guava-tests/test/com/google/common/collect/TreeBasedTableRowTest.java index 6410801d9d7a..990732ee3c67 100644 --- a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableRowTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Guava Authors + * Copyright (C) 2008 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,16 +17,18 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import java.util.Collection; -import java.util.Map; +import com.google.common.collect.TableCollectionTest.RowTests; +import org.jspecify.annotations.NullMarked; -@GwtCompatible(emulated = true) -abstract class LinkedHashMultimapGwtSerializationDependencies - extends AbstractSetMultimap { - LinkedHashMultimapGwtSerializationDependencies(Map> map) { - super(map); +@GwtCompatible +@NullMarked +public class TreeBasedTableRowTest extends RowTests { + public TreeBasedTableRowTest() { + super(false, true, true, true, true); } - K dummyKey; - V dummyValue; + @Override + Table makeTable() { + return TreeBasedTable.create(); + } } diff --git a/android/guava-tests/test/com/google/common/collect/TreeBasedTableTest.java b/android/guava-tests/test/com/google/common/collect/TreeBasedTableTest.java index 084e6492fb72..91527c092887 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeBasedTableTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeBasedTableTest.java @@ -16,11 +16,13 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.singleton; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.collect.testing.SortedMapInterfaceTest; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.SortedMapTestSuiteBuilder; import com.google.common.collect.testing.TestStringSortedMapGenerator; import com.google.common.collect.testing.features.CollectionFeature; @@ -35,6 +37,8 @@ import java.util.SortedMap; import junit.framework.Test; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Test cases for {@link TreeBasedTable}. @@ -42,13 +46,15 @@ * @author Jared Levy * @author Louis Wasserman */ -@GwtCompatible(emulated = true) -public class TreeBasedTableTest extends AbstractTableTest { +@GwtCompatible +@NullMarked +public class TreeBasedTableTest extends AbstractTableTest { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(TreeBasedTableTest.class); - suite.addTestSuite(TreeRowTest.class); suite.addTest( SortedMapTestSuiteBuilder.using( new TestStringSortedMapGenerator() { @@ -73,58 +79,6 @@ protected SortedMap create(Entry[] entries) { return suite; } - public static class TreeRowTest extends SortedMapInterfaceTest { - public TreeRowTest() { - super(false, false, true, true, true); - } - - @Override - protected SortedMap makeEmptyMap() { - TreeBasedTable table = TreeBasedTable.create(); - table.put("a", "b", "c"); - table.put("c", "b", "a"); - table.put("a", "a", "d"); - return table.row("b"); - } - - @Override - protected SortedMap makePopulatedMap() { - TreeBasedTable table = TreeBasedTable.create(); - table.put("a", "b", "c"); - table.put("c", "b", "a"); - table.put("b", "b", "x"); - table.put("b", "c", "y"); - table.put("b", "x", "n"); - table.put("a", "a", "d"); - return table.row("b"); - } - - @Override - protected String getKeyNotInPopulatedMap() { - return "q"; - } - - @Override - protected String getValueNotInPopulatedMap() { - return "p"; - } - - public void testClearSubMapOfRowMap() { - TreeBasedTable table = TreeBasedTable.create(); - table.put("a", "b", "c"); - table.put("c", "b", "a"); - table.put("b", "b", "x"); - table.put("b", "c", "y"); - table.put("b", "x", "n"); - table.put("a", "a", "d"); - table.row("b").subMap("c", "x").clear(); - assertEquals(table.row("b"), ImmutableMap.of("b", "x", "x", "n")); - table.row("b").subMap("b", "y").clear(); - assertEquals(table.row("b"), ImmutableMap.of()); - assertFalse(table.backingMap.containsKey("b")); - } - } - private TreeBasedTable sortedTable; protected TreeBasedTable create( @@ -141,7 +95,7 @@ protected TreeBasedTable create( } @Override - protected TreeBasedTable create(Object... data) { + protected TreeBasedTable create(@Nullable Object... data) { TreeBasedTable table = TreeBasedTable.create(); table.put("foo", 4, 'a'); table.put("cat", 1, 'b'); @@ -173,6 +127,7 @@ public void testCreateCopy() { assertEquals(original, table); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { table = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); @@ -200,6 +155,7 @@ public void testValuesToString_ordered() { assertEquals("[b, a, c]", table.values().toString()); } + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testRowComparator() { sortedTable = TreeBasedTable.create(); assertSame(Ordering.natural(), sortedTable.rowComparator()); @@ -247,25 +203,25 @@ public void testRowKeySetLast() { public void testRowKeySetHeadSet() { sortedTable = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); Set set = sortedTable.rowKeySet().headSet("cat"); - assertEquals(Collections.singleton("bar"), set); + assertEquals(singleton("bar"), set); set.clear(); assertTrue(set.isEmpty()); - assertEquals(Collections.singleton("foo"), sortedTable.rowKeySet()); + assertEquals(singleton("foo"), sortedTable.rowKeySet()); } public void testRowKeySetTailSet() { sortedTable = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c'); Set set = sortedTable.rowKeySet().tailSet("cat"); - assertEquals(Collections.singleton("foo"), set); + assertEquals(singleton("foo"), set); set.clear(); assertTrue(set.isEmpty()); - assertEquals(Collections.singleton("bar"), sortedTable.rowKeySet()); + assertEquals(singleton("bar"), sortedTable.rowKeySet()); } public void testRowKeySetSubSet() { sortedTable = create("foo", 1, 'a', "bar", 1, 'b', "foo", 3, 'c', "dog", 2, 'd'); Set set = sortedTable.rowKeySet().subSet("cat", "egg"); - assertEquals(Collections.singleton("dog"), set); + assertEquals(singleton("dog"), set); set.clear(); assertTrue(set.isEmpty()); assertEquals(ImmutableSet.of("bar", "foo"), sortedTable.rowKeySet()); @@ -296,7 +252,7 @@ public void testRowKeyMapHeadMap() { assertEquals(ImmutableMap.of(1, 'b'), map.get("bar")); map.clear(); assertTrue(map.isEmpty()); - assertEquals(Collections.singleton("foo"), sortedTable.rowKeySet()); + assertEquals(singleton("foo"), sortedTable.rowKeySet()); } public void testRowKeyMapTailMap() { @@ -306,7 +262,7 @@ public void testRowKeyMapTailMap() { assertEquals(ImmutableMap.of(1, 'a', 3, 'c'), map.get("foo")); map.clear(); assertTrue(map.isEmpty()); - assertEquals(Collections.singleton("bar"), sortedTable.rowKeySet()); + assertEquals(singleton("bar"), sortedTable.rowKeySet()); } public void testRowKeyMapSubMap() { @@ -335,7 +291,7 @@ public void testColumnKeySet_isSortedWithRealComparator() { table = create( String.CASE_INSENSITIVE_ORDER, - Ordering.natural().reverse(), + Ordering.natural().reverse(), "a", 2, 'X', @@ -400,13 +356,13 @@ public void testRowEntrySetContains() { 20, 'X', "d", 15, 'X', "d", 20, 'X', "d", 1, 'X', "e", 5, 'X'); SortedMap row = sortedTable.row("c"); Set> entrySet = row.entrySet(); - assertTrue(entrySet.contains(Maps.immutableEntry(10, 'X'))); - assertTrue(entrySet.contains(Maps.immutableEntry(20, 'X'))); - assertFalse(entrySet.contains(Maps.immutableEntry(15, 'X'))); + assertTrue(entrySet.contains(immutableEntry(10, 'X'))); + assertTrue(entrySet.contains(immutableEntry(20, 'X'))); + assertFalse(entrySet.contains(immutableEntry(15, 'X'))); entrySet = row.tailMap(15).entrySet(); - assertFalse(entrySet.contains(Maps.immutableEntry(10, 'X'))); - assertTrue(entrySet.contains(Maps.immutableEntry(20, 'X'))); - assertFalse(entrySet.contains(Maps.immutableEntry(15, 'X'))); + assertFalse(entrySet.contains(immutableEntry(10, 'X'))); + assertTrue(entrySet.contains(immutableEntry(20, 'X'))); + assertFalse(entrySet.contains(immutableEntry(15, 'X'))); } public void testRowEntrySetRemove() { @@ -417,13 +373,13 @@ public void testRowEntrySetRemove() { 20, 'X', "d", 15, 'X', "d", 20, 'X', "d", 1, 'X', "e", 5, 'X'); SortedMap row = sortedTable.row("c"); Set> entrySet = row.tailMap(15).entrySet(); - assertFalse(entrySet.remove(Maps.immutableEntry(10, 'X'))); - assertTrue(entrySet.remove(Maps.immutableEntry(20, 'X'))); - assertFalse(entrySet.remove(Maps.immutableEntry(15, 'X'))); + assertFalse(entrySet.remove(immutableEntry(10, 'X'))); + assertTrue(entrySet.remove(immutableEntry(20, 'X'))); + assertFalse(entrySet.remove(immutableEntry(15, 'X'))); entrySet = row.entrySet(); - assertTrue(entrySet.remove(Maps.immutableEntry(10, 'X'))); - assertFalse(entrySet.remove(Maps.immutableEntry(20, 'X'))); - assertFalse(entrySet.remove(Maps.immutableEntry(15, 'X'))); + assertTrue(entrySet.remove(immutableEntry(10, 'X'))); + assertFalse(entrySet.remove(immutableEntry(20, 'X'))); + assertFalse(entrySet.remove(immutableEntry(15, 'X'))); } public void testRowSize() { diff --git a/android/guava-tests/test/com/google/common/collect/TreeMultimapExplicitTest.java b/android/guava-tests/test/com/google/common/collect/TreeMultimapExplicitTest.java index 92a3d83a7d8c..000c8a046e96 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeMultimapExplicitTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeMultimapExplicitTest.java @@ -16,36 +16,41 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Sets.newHashSet; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.testing.SerializableTester; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.Map.Entry; import java.util.SortedSet; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit tests for {@code TreeMultimap} with explicit comparators. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class TreeMultimapExplicitTest extends TestCase { /** * Compare strings lengths, and if the lengths are equal compare the strings. A {@code null} is * less than any non-null value. */ - private enum StringLength implements Comparator { + private enum StringLength implements Comparator<@Nullable String> { COMPARATOR; @Override - public int compare(String first, String second) { + public int compare(@Nullable String first, @Nullable String second) { if (first == second) { return 0; } else if (first == null) { @@ -61,16 +66,16 @@ public int compare(String first, String second) { } /** Decreasing integer values. A {@code null} comes before any non-null value. */ - private static final Comparator DECREASING_INT_COMPARATOR = - Ordering.natural().reverse().nullsFirst(); + private static final Comparator<@Nullable Integer> DECREASING_INT_COMPARATOR = + Ordering.natural().reverse().nullsFirst(); private SetMultimap create() { return TreeMultimap.create(StringLength.COMPARATOR, DECREASING_INT_COMPARATOR); } /** Create and populate a {@code TreeMultimap} with explicit comparators. */ - private TreeMultimap createPopulate() { - TreeMultimap multimap = + private TreeMultimap<@Nullable String, @Nullable Integer> createPopulate() { + TreeMultimap<@Nullable String, @Nullable Integer> multimap = TreeMultimap.create(StringLength.COMPARATOR, DECREASING_INT_COMPARATOR); multimap.put("google", 2); multimap.put("google", 6); @@ -106,32 +111,32 @@ public void testToString() { Multimap multimap = create(); multimap.put("foo", 3); multimap.put("bar", 1); - multimap.putAll("foo", Arrays.asList(-1, 2, 4)); - multimap.putAll("bar", Arrays.asList(2, 3)); + multimap.putAll("foo", asList(-1, 2, 4)); + multimap.putAll("bar", asList(2, 3)); multimap.put("foo", 1); assertEquals("{bar=[3, 2, 1], foo=[4, 3, 2, 1, -1]}", multimap.toString()); } public void testGetComparator() { - TreeMultimap multimap = createPopulate(); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); assertEquals(StringLength.COMPARATOR, multimap.keyComparator()); assertEquals(DECREASING_INT_COMPARATOR, multimap.valueComparator()); } public void testOrderedGet() { - TreeMultimap multimap = createPopulate(); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); assertThat(multimap.get(null)).containsExactly(7, 3, 1).inOrder(); assertThat(multimap.get("google")).containsExactly(6, 2).inOrder(); assertThat(multimap.get("tree")).containsExactly(null, 0).inOrder(); } public void testOrderedKeySet() { - TreeMultimap multimap = createPopulate(); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); assertThat(multimap.keySet()).containsExactly(null, "tree", "google").inOrder(); } public void testOrderedAsMapEntries() { - TreeMultimap multimap = createPopulate(); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); Iterator>> iterator = multimap.asMap().entrySet().iterator(); Entry> entry = iterator.next(); assertEquals(null, entry.getKey()); @@ -145,26 +150,26 @@ public void testOrderedAsMapEntries() { } public void testOrderedEntries() { - TreeMultimap multimap = createPopulate(); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); assertThat(multimap.entries()) .containsExactly( - Maps.immutableEntry((String) null, 7), - Maps.immutableEntry((String) null, 3), - Maps.immutableEntry((String) null, 1), - Maps.immutableEntry("tree", (Integer) null), - Maps.immutableEntry("tree", 0), - Maps.immutableEntry("google", 6), - Maps.immutableEntry("google", 2)) + Maps.<@Nullable String, Integer>immutableEntry(null, 7), + Maps.<@Nullable String, Integer>immutableEntry(null, 3), + Maps.<@Nullable String, Integer>immutableEntry(null, 1), + Maps.immutableEntry("tree", null), + immutableEntry("tree", 0), + immutableEntry("google", 6), + immutableEntry("google", 2)) .inOrder(); } public void testOrderedValues() { - TreeMultimap multimap = createPopulate(); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); assertThat(multimap.values()).containsExactly(7, 3, 1, null, 0, 6, 2).inOrder(); } public void testComparator() { - TreeMultimap multimap = createPopulate(); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); assertEquals(DECREASING_INT_COMPARATOR, multimap.get("foo").comparator()); assertEquals(DECREASING_INT_COMPARATOR, multimap.get("missing").comparator()); } @@ -173,8 +178,8 @@ public void testMultimapComparators() { Multimap multimap = create(); multimap.put("foo", 3); multimap.put("bar", 1); - multimap.putAll("foo", Arrays.asList(-1, 2, 4)); - multimap.putAll("bar", Arrays.asList(2, 3)); + multimap.putAll("foo", asList(-1, 2, 4)); + multimap.putAll("bar", asList(2, 3)); multimap.put("foo", 1); TreeMultimap copy = TreeMultimap.create(StringLength.COMPARATOR, DECREASING_INT_COMPARATOR); @@ -185,21 +190,22 @@ public void testMultimapComparators() { } public void testSortedKeySet() { - TreeMultimap multimap = createPopulate(); - SortedSet keySet = multimap.keySet(); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); + SortedSet<@Nullable String> keySet = multimap.keySet(); assertEquals(null, keySet.first()); assertEquals("google", keySet.last()); assertEquals(StringLength.COMPARATOR, keySet.comparator()); - assertEquals(Sets.newHashSet(null, "tree"), keySet.headSet("yahoo")); - assertEquals(Sets.newHashSet("google"), keySet.tailSet("yahoo")); - assertEquals(Sets.newHashSet("tree"), keySet.subSet("ask", "yahoo")); + assertEquals(Sets.<@Nullable String>newHashSet(null, "tree"), keySet.headSet("yahoo")); + assertEquals(newHashSet("google"), keySet.tailSet("yahoo")); + assertEquals(newHashSet("tree"), keySet.subSet("ask", "yahoo")); } @GwtIncompatible // SerializableTester public void testExplicitComparatorSerialization() { - TreeMultimap multimap = createPopulate(); - TreeMultimap copy = SerializableTester.reserializeAndAssert(multimap); + TreeMultimap<@Nullable String, @Nullable Integer> multimap = createPopulate(); + TreeMultimap<@Nullable String, @Nullable Integer> copy = + SerializableTester.reserializeAndAssert(multimap); assertThat(copy.values()).containsExactly(7, 3, 1, null, 0, 6, 2).inOrder(); assertThat(copy.keySet()).containsExactly(null, "tree", "google").inOrder(); assertEquals(multimap.keyComparator(), copy.keyComparator()); diff --git a/android/guava-tests/test/com/google/common/collect/TreeMultimapNaturalTest.java b/android/guava-tests/test/com/google/common/collect/TreeMultimapNaturalTest.java index addcfb74f856..a5611a0877ba 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeMultimapNaturalTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeMultimapNaturalTest.java @@ -17,12 +17,15 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.testing.Helpers.mapEntry; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.DerivedComparable; -import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.NavigableMapTestSuiteBuilder; import com.google.common.collect.testing.NavigableSetTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -36,7 +39,6 @@ import com.google.common.collect.testing.google.TestStringSetMultimapGenerator; import com.google.common.testing.SerializableTester; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; @@ -50,16 +52,20 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; /** * Unit tests for {@code TreeMultimap} with natural ordering. * * @author Jared Levy */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class TreeMultimapNaturalTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); // TODO(lowasser): should we force TreeMultimap to be more thorough about checking nulls? @@ -141,27 +147,24 @@ public String[] createKeyArray(int length) { @SuppressWarnings("unchecked") @Override public Collection[] createValueArray(int length) { - return new Collection[length]; + return (Collection[]) new Collection[length]; } @Override public SampleElements>> samples() { return new SampleElements<>( - Helpers.mapEntry("a", (Collection) ImmutableSortedSet.of("alex")), - Helpers.mapEntry( - "b", (Collection) ImmutableSortedSet.of("bob", "bagel")), - Helpers.mapEntry( - "c", (Collection) ImmutableSortedSet.of("carl", "carol")), - Helpers.mapEntry( - "d", (Collection) ImmutableSortedSet.of("david", "dead")), - Helpers.mapEntry( + mapEntry("a", (Collection) ImmutableSortedSet.of("alex")), + mapEntry("b", (Collection) ImmutableSortedSet.of("bob", "bagel")), + mapEntry("c", (Collection) ImmutableSortedSet.of("carl", "carol")), + mapEntry("d", (Collection) ImmutableSortedSet.of("david", "dead")), + mapEntry( "e", (Collection) ImmutableSortedSet.of("eric", "elaine"))); } @SuppressWarnings("unchecked") @Override public Entry>[] createArray(int length) { - return new Entry[length]; + return (Entry>[]) new Entry[length]; } @Override @@ -190,26 +193,22 @@ public NavigableMap> create(Object... elements) { @Override public Entry> belowSamplesLesser() { - return Helpers.mapEntry( - "-- a", (Collection) ImmutableSortedSet.of("--below")); + return mapEntry("-- a", (Collection) ImmutableSortedSet.of("--below")); } @Override public Entry> belowSamplesGreater() { - return Helpers.mapEntry( - "-- b", (Collection) ImmutableSortedSet.of("--below")); + return mapEntry("-- b", (Collection) ImmutableSortedSet.of("--below")); } @Override public Entry> aboveSamplesLesser() { - return Helpers.mapEntry( - "~~ b", (Collection) ImmutableSortedSet.of("~above")); + return mapEntry("~~ b", (Collection) ImmutableSortedSet.of("~above")); } @Override public Entry> aboveSamplesGreater() { - return Helpers.mapEntry( - "~~ c", (Collection) ImmutableSortedSet.of("~above")); + return mapEntry("~~ c", (Collection) ImmutableSortedSet.of("~above")); } }) .named("TreeMultimap.asMap") @@ -227,7 +226,7 @@ public Entry> aboveSamplesGreater() { protected Set create(String[] elements) { TreeMultimap multimap = TreeMultimap.create(Ordering.natural(), Ordering.natural().nullsFirst()); - multimap.putAll(1, Arrays.asList(elements)); + multimap.putAll(1, asList(elements)); return multimap.get(1); } @@ -250,7 +249,7 @@ public List order(List insertionOrder) { protected Set create(String[] elements) { TreeMultimap multimap = TreeMultimap.create(Ordering.natural(), Ordering.natural().nullsFirst()); - multimap.putAll(1, Arrays.asList(elements)); + multimap.putAll(1, asList(elements)); return (Set) multimap.asMap().entrySet().iterator().next().getValue(); } @@ -290,8 +289,8 @@ private TreeMultimap createPopulate() { public void testToString() { SetMultimap multimap = create(); - multimap.putAll("bar", Arrays.asList(3, 1, 2)); - multimap.putAll("foo", Arrays.asList(2, 3, 1, -1, 4)); + multimap.putAll("bar", asList(3, 1, 2)); + multimap.putAll("foo", asList(2, 3, 1, -1, 4)); assertEquals("{bar=[1, 2, 3], foo=[-1, 1, 2, 3, 4]}", multimap.toString()); } @@ -325,13 +324,13 @@ public void testOrderedEntries() { TreeMultimap multimap = createPopulate(); assertThat(multimap.entries()) .containsExactly( - Maps.immutableEntry("foo", 1), - Maps.immutableEntry("foo", 3), - Maps.immutableEntry("foo", 7), - Maps.immutableEntry("google", 2), - Maps.immutableEntry("google", 6), - Maps.immutableEntry("tree", 0), - Maps.immutableEntry("tree", 4)) + immutableEntry("foo", 1), + immutableEntry("foo", 3), + immutableEntry("foo", 7), + immutableEntry("google", 2), + immutableEntry("google", 6), + immutableEntry("tree", 0), + immutableEntry("tree", 4)) .inOrder(); } @@ -342,8 +341,8 @@ public void testOrderedValues() { public void testMultimapConstructor() { SetMultimap multimap = create(); - multimap.putAll("bar", Arrays.asList(3, 1, 2)); - multimap.putAll("foo", Arrays.asList(2, 3, 1, -1, 4)); + multimap.putAll("bar", asList(3, 1, 2)); + multimap.putAll("foo", asList(2, 3, 1, -1, 4)); TreeMultimap copy = TreeMultimap.create(multimap); assertEquals(multimap, copy); } @@ -351,7 +350,7 @@ public void testMultimapConstructor() { private static final Comparator KEY_COMPARATOR = Ordering.natural(); private static final Comparator VALUE_COMPARATOR = - Ordering.natural().reverse().nullsFirst(); + Ordering.natural().reverse().nullsFirst(); /** * Test that creating one TreeMultimap from another does not copy the comparators from the source @@ -407,6 +406,7 @@ public void testComparators() { assertEquals(Ordering.natural(), multimap.valueComparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testExplicitComparatorSerialization() { TreeMultimap multimap = createPopulate(); @@ -417,6 +417,7 @@ public void testExplicitComparatorSerialization() { assertEquals(multimap.valueComparator(), copy.valueComparator()); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testTreeMultimapDerived() { TreeMultimap multimap = TreeMultimap.create(); @@ -443,6 +444,7 @@ public void testTreeMultimapDerived() { SerializableTester.reserializeAndAssert(multimap); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testTreeMultimapNonGeneric() { TreeMultimap multimap = TreeMultimap.create(); @@ -500,6 +502,7 @@ public void testTailSetClear() { assertEquals(4, multimap.keys().size()); } + @J2ktIncompatible @GwtIncompatible // reflection public void testKeySetBridgeMethods() { for (Method m : TreeMultimap.class.getMethods()) { @@ -510,6 +513,7 @@ public void testKeySetBridgeMethods() { fail("No bridge method found"); } + @J2ktIncompatible @GwtIncompatible // reflection public void testAsMapBridgeMethods() { for (Method m : TreeMultimap.class.getMethods()) { @@ -519,6 +523,7 @@ public void testAsMapBridgeMethods() { } } + @J2ktIncompatible @GwtIncompatible // reflection public void testGetBridgeMethods() { for (Method m : TreeMultimap.class.getMethods()) { diff --git a/android/guava-tests/test/com/google/common/collect/TreeMultisetTest.java b/android/guava-tests/test/com/google/common/collect/TreeMultisetTest.java index 12da1f1de0e1..b8a11059333a 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeMultisetTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeMultisetTest.java @@ -18,10 +18,12 @@ import static com.google.common.collect.BoundType.CLOSED; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; import static java.util.Collections.sort; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.Helpers.NullsBeforeB; import com.google.common.collect.testing.NavigableSetTestSuiteBuilder; import com.google.common.collect.testing.TestStringSetGenerator; @@ -31,7 +33,7 @@ import com.google.common.collect.testing.google.SortedMultisetTestSuiteBuilder; import com.google.common.collect.testing.google.TestStringMultisetGenerator; import java.lang.reflect.Method; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -40,16 +42,21 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link TreeMultiset}. * * @author Neal Kanodia */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class TreeMultisetTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -57,7 +64,7 @@ public static Test suite() { new TestStringMultisetGenerator() { @Override protected Multiset create(String[] elements) { - return TreeMultiset.create(Arrays.asList(elements)); + return TreeMultiset.create(asList(elements)); } @Override @@ -104,12 +111,12 @@ public List order(List insertionOrder) { new TestStringSetGenerator() { @Override protected Set create(String[] elements) { - return TreeMultiset.create(Arrays.asList(elements)).elementSet(); + return TreeMultiset.create(asList(elements)).elementSet(); } @Override public List order(List insertionOrder) { - return Lists.newArrayList(Sets.newTreeSet(insertionOrder)); + return new ArrayList<>(Sets.newTreeSet(insertionOrder)); } }) .named("TreeMultiset[Ordering.natural].elementSet") @@ -142,7 +149,7 @@ public void testCreateWithComparator() { } public void testCreateFromIterable() { - Multiset multiset = TreeMultiset.create(Arrays.asList("foo", "bar", "foo")); + Multiset multiset = TreeMultiset.create(asList("foo", "bar", "foo")); assertEquals(3, multiset.size()); assertEquals(2, multiset.count("foo")); assertEquals("[bar, foo x 2]", multiset.toString()); @@ -212,7 +219,7 @@ public void testElementSetSubsetRemoveAll() { SortedSet subset = elementSet.subSet("b", "f"); assertThat(subset).containsExactly("b", "c", "d", "e").inOrder(); - assertTrue(subset.removeAll(Arrays.asList("a", "c"))); + assertTrue(subset.removeAll(asList("a", "c"))); assertThat(elementSet).containsExactly("a", "b", "d", "e", "f").inOrder(); assertThat(subset).containsExactly("b", "d", "e").inOrder(); assertEquals(10, ms.size()); @@ -232,7 +239,7 @@ public void testElementSetSubsetRetainAll() { SortedSet subset = elementSet.subSet("b", "f"); assertThat(subset).containsExactly("b", "c", "d", "e").inOrder(); - assertTrue(subset.retainAll(Arrays.asList("a", "c"))); + assertTrue(subset.retainAll(asList("a", "c"))); assertThat(elementSet).containsExactly("a", "c", "f").inOrder(); assertThat(subset).containsExactly("c"); assertEquals(5, ms.size()); @@ -283,8 +290,8 @@ public int compare(String o1, String o2) { } public void testNullAcceptingComparator() throws Exception { - Comparator comparator = Ordering.natural().nullsFirst(); - TreeMultiset ms = TreeMultiset.create(comparator); + Comparator<@Nullable String> comparator = Ordering.natural().nullsFirst(); + TreeMultiset<@Nullable String> ms = TreeMultiset.create(comparator); ms.add("b"); ms.add(null); @@ -295,7 +302,7 @@ public void testNullAcceptingComparator() throws Exception { assertThat(ms).containsExactly(null, null, null, "a", "b", "b").inOrder(); assertEquals(3, ms.count(null)); - SortedSet elementSet = ms.elementSet(); + SortedSet<@Nullable String> elementSet = ms.elementSet(); assertEquals(null, elementSet.first()); assertEquals("b", elementSet.last()); assertEquals(comparator, elementSet.comparator()); @@ -355,6 +362,7 @@ public void testSubMultisetSize() { assertEquals(Integer.MAX_VALUE, ms.tailMultiset("a", CLOSED).size()); } + @J2ktIncompatible @GwtIncompatible // reflection @AndroidIncompatible // Reflection bug, or actual binary compatibility problem? public void testElementSetBridgeMethods() { diff --git a/android/guava-tests/test/com/google/common/collect/TreeRangeMapTest.java b/android/guava-tests/test/com/google/common/collect/TreeRangeMapTest.java index db68b89ec30d..7b060f495528 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeRangeMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeRangeMapTest.java @@ -16,6 +16,8 @@ import static com.google.common.collect.BoundType.OPEN; import static com.google.common.collect.testing.Helpers.mapEntry; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.testing.MapTestSuiteBuilder; @@ -24,6 +26,8 @@ import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.CollectionSize; import com.google.common.collect.testing.features.MapFeature; +import com.google.common.testing.EqualsTester; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -31,6 +35,7 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code TreeRangeMap}. @@ -38,7 +43,9 @@ * @author Louis Wasserman */ @GwtIncompatible // NavigableMap +@NullUnmarked public class TreeRangeMapTest extends TestCase { + @AndroidIncompatible // test-suite builders public static Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(TreeRangeMapTest.class); @@ -69,7 +76,7 @@ public Map, String> create(Object... elements) { @SuppressWarnings("unchecked") @Override public Entry, String>[] createArray(int length) { - return new Entry[length]; + return (Entry, String>[]) new Entry[length]; } @Override @@ -81,7 +88,7 @@ public Iterable, String>> order( @SuppressWarnings("unchecked") @Override public Range[] createKeyArray(int length) { - return new Range[length]; + return (Range[]) new Range[length]; } @Override @@ -125,7 +132,7 @@ public Map, String> create(Object... elements) { @SuppressWarnings("unchecked") @Override public Entry, String>[] createArray(int length) { - return new Entry[length]; + return (Entry, String>[]) new Entry[length]; } @Override @@ -137,7 +144,7 @@ public Iterable, String>> order( @SuppressWarnings("unchecked") @Override public Range[] createKeyArray(int length) { - return new Range[length]; + return (Range[]) new Range[length]; } @Override @@ -180,7 +187,7 @@ public Map, String> create(Object... elements) { @SuppressWarnings("unchecked") @Override public Entry, String>[] createArray(int length) { - return new Entry[length]; + return (Entry, String>[]) new Entry[length]; } @Override @@ -195,7 +202,7 @@ public Iterable, String>> order( @SuppressWarnings("unchecked") @Override public Range[] createKeyArray(int length) { - return new Range[length]; + return (Range[]) new Range[length]; } @Override @@ -239,7 +246,7 @@ public Map, String> create(Object... elements) { @SuppressWarnings("unchecked") @Override public Entry, String>[] createArray(int length) { - return new Entry[length]; + return (Entry, String>[]) new Entry[length]; } @Override @@ -254,7 +261,7 @@ public Iterable, String>> order( @SuppressWarnings("unchecked") @Override public Range[] createKeyArray(int length) { - return new Range[length]; + return (Range[]) new Range[length]; } @Override @@ -343,9 +350,9 @@ public void testSpanTwoRanges() { try { assertEquals(expected, rangeMap.span()); - assertNotNull(expected); + assertThat(expected).isNotNull(); } catch (NoSuchElementException e) { - assertNull(expected); + assertThat(expected).isNull(); } } } @@ -353,7 +360,7 @@ public void testSpanTwoRanges() { public void testAllRangesAlone() { for (Range range : RANGES) { - Map model = Maps.newHashMap(); + Map model = new HashMap<>(); putModel(model, range, 1); RangeMap test = TreeRangeMap.create(); test.put(range, 1); @@ -364,7 +371,7 @@ public void testAllRangesAlone() { public void testAllRangePairs() { for (Range range1 : RANGES) { for (Range range2 : RANGES) { - Map model = Maps.newHashMap(); + Map model = new HashMap<>(); putModel(model, range1, 1); putModel(model, range2, 2); RangeMap test = TreeRangeMap.create(); @@ -379,7 +386,7 @@ public void testAllRangeTriples() { for (Range range1 : RANGES) { for (Range range2 : RANGES) { for (Range range3 : RANGES) { - Map model = Maps.newHashMap(); + Map model = new HashMap<>(); putModel(model, range1, 1); putModel(model, range2, 2); putModel(model, range3, 3); @@ -397,7 +404,7 @@ public void testPutAll() { for (Range range1 : RANGES) { for (Range range2 : RANGES) { for (Range range3 : RANGES) { - Map model = Maps.newHashMap(); + Map model = new HashMap<>(); putModel(model, range1, 1); putModel(model, range2, 2); putModel(model, range3, 3); @@ -417,7 +424,7 @@ public void testPutAll() { public void testPutAndRemove() { for (Range rangeToPut : RANGES) { for (Range rangeToRemove : RANGES) { - Map model = Maps.newHashMap(); + Map model = new HashMap<>(); putModel(model, rangeToPut, 1); removeModel(model, rangeToRemove); RangeMap test = TreeRangeMap.create(); @@ -432,7 +439,7 @@ public void testPutTwoAndRemove() { for (Range rangeToPut1 : RANGES) { for (Range rangeToPut2 : RANGES) { for (Range rangeToRemove : RANGES) { - Map model = Maps.newHashMap(); + Map model = new HashMap<>(); putModel(model, rangeToPut1, 1); putModel(model, rangeToPut2, 2); removeModel(model, rangeToRemove); @@ -452,7 +459,7 @@ public void testPutCoalescingTwoAndRemove() { for (Range rangeToPut1 : RANGES) { for (Range rangeToPut2 : RANGES) { for (Range rangeToRemove : RANGES) { - Map model = Maps.newHashMap(); + Map model = new HashMap<>(); putModel(model, rangeToPut1, 1); putModel(model, rangeToPut2, 2); removeModel(model, rangeToRemove); @@ -617,14 +624,10 @@ public void testSubRangeMapPut() { 3), rangeMap.asMapOfRanges()); - try { - sub.put(Range.open(9, 12), 5); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> sub.put(Range.open(9, 12), 5)); - sub = sub.subRangeMap(Range.closedOpen(5, 5)); - sub.put(Range.closedOpen(5, 5), 6); // should be a no-op + RangeMap subSub = sub.subRangeMap(Range.closedOpen(5, 5)); + subSub.put(Range.closedOpen(5, 5), 6); // should be a no-op assertEquals( ImmutableMap.of( Range.open(3, 7), @@ -668,11 +671,7 @@ public void testSubRangeMapPutCoalescing() { 3), rangeMap.asMapOfRanges()); - try { - sub.putCoalescing(Range.open(9, 12), 5); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> sub.putCoalescing(Range.open(9, 12), 5)); } public void testSubRangeMapRemove() { @@ -709,6 +708,53 @@ public void testSubRangeMapClear() { ImmutableMap.of(Range.open(3, 5), 1, Range.closed(12, 16), 3), rangeMap.asMapOfRanges()); } + public void testCopyOfTreeRangeMap() { + RangeMap rangeMap = TreeRangeMap.create(); + rangeMap.put(Range.open(3, 7), 1); + rangeMap.put(Range.closed(9, 10), 2); + rangeMap.put(Range.closed(12, 16), 3); + + RangeMap copy = TreeRangeMap.copyOf(rangeMap); + + assertEquals(rangeMap.asMapOfRanges(), copy.asMapOfRanges()); + } + + public void testCopyOfImmutableRangeMap() { + ImmutableRangeMap rangeMap = + ImmutableRangeMap.builder() + .put(Range.open(3, 7), 1) + .put(Range.closed(9, 10), 2) + .put(Range.closed(12, 16), 3) + .build(); + + RangeMap copy = TreeRangeMap.copyOf(rangeMap); + + assertEquals(rangeMap.asMapOfRanges(), copy.asMapOfRanges()); + } + + // Overriding testEquals because it seems that we get spurious failures when it things empty + // should be unequal to empty. + public void testEquals() { + TreeRangeMap empty = TreeRangeMap.create(); + TreeRangeMap nonEmpty = TreeRangeMap.create(); + nonEmpty.put(Range.all(), 1); + TreeRangeMap coalesced = TreeRangeMap.create(); + coalesced.put(Range.atLeast(1), 1); + coalesced.putCoalescing(Range.atMost(1), 1); + TreeRangeMap differentValues = TreeRangeMap.create(); + differentValues.put(Range.closedOpen(1, 2), 2); + differentValues.put(Range.closedOpen(3, 4), 2); + TreeRangeMap differentTypes = TreeRangeMap.create(); + differentTypes.put(Range.closedOpen(1.0, 2.0), 2); + differentTypes.put(Range.closedOpen(3.0, 4.0), 2); + new EqualsTester() + .addEqualityGroup(empty, TreeRangeMap.create()) + .addEqualityGroup(nonEmpty, coalesced) + .addEqualityGroup(differentValues) + .addEqualityGroup(differentTypes) + .testEquals(); + } + private void verify(Map model, RangeMap test) { for (int i = MIN_BOUND - 1; i <= MAX_BOUND + 1; i++) { assertEquals(model.get(i), test.get(i)); diff --git a/android/guava-tests/test/com/google/common/collect/TreeRangeSetTest.java b/android/guava-tests/test/com/google/common/collect/TreeRangeSetTest.java index b33ab2ddea4b..367ecf5fb107 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeRangeSetTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeRangeSetTest.java @@ -17,12 +17,14 @@ import static com.google.common.collect.BoundType.OPEN; import static com.google.common.collect.Range.range; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtIncompatible; import com.google.common.testing.SerializableTester; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.NavigableMap; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link TreeRangeSet}. @@ -31,6 +33,7 @@ * @author Chris Povirk */ @GwtIncompatible // TreeRangeSet +@NullUnmarked public class TreeRangeSetTest extends AbstractRangeSetTest { // TODO(cpovirk): test all of these with the ranges added in the reverse order @@ -86,7 +89,7 @@ void testViewAgainstExpected(RangeSet expected, RangeSet view) private static final ImmutableList> CUTS_TO_TEST; static { - List> cutsToTest = Lists.newArrayList(); + List> cutsToTest = new ArrayList<>(); for (int i = MIN_BOUND - 1; i <= MAX_BOUND + 1; i++) { cutsToTest.add(Cut.belowValue(i)); cutsToTest.add(Cut.aboveValue(i)); @@ -648,7 +651,7 @@ public void testRangeContaining1() { rangeSet.add(Range.closed(3, 10)); assertEquals(Range.closed(3, 10), rangeSet.rangeContaining(5)); assertTrue(rangeSet.contains(5)); - assertNull(rangeSet.rangeContaining(1)); + assertThat(rangeSet.rangeContaining(1)).isNull(); assertFalse(rangeSet.contains(1)); } @@ -660,21 +663,21 @@ public void testRangeContaining2() { assertTrue(rangeSet.contains(5)); assertEquals(Range.closed(7, 10), rangeSet.rangeContaining(8)); assertTrue(rangeSet.contains(8)); - assertNull(rangeSet.rangeContaining(6)); + assertThat(rangeSet.rangeContaining(6)).isNull(); assertFalse(rangeSet.contains(6)); } public void testAddAll() { RangeSet rangeSet = TreeRangeSet.create(); rangeSet.add(Range.closed(3, 10)); - rangeSet.addAll(Arrays.asList(Range.open(1, 3), Range.closed(5, 8), Range.closed(9, 11))); + rangeSet.addAll(asList(Range.open(1, 3), Range.closed(5, 8), Range.closed(9, 11))); assertThat(rangeSet.asRanges()).containsExactly(Range.openClosed(1, 11)).inOrder(); } public void testRemoveAll() { RangeSet rangeSet = TreeRangeSet.create(); rangeSet.add(Range.closed(3, 10)); - rangeSet.removeAll(Arrays.asList(Range.open(1, 3), Range.closed(5, 8), Range.closed(9, 11))); + rangeSet.removeAll(asList(Range.open(1, 3), Range.closed(5, 8), Range.closed(9, 11))); assertThat(rangeSet.asRanges()) .containsExactly(Range.closedOpen(3, 5), Range.open(8, 9)) .inOrder(); diff --git a/android/guava-tests/test/com/google/common/collect/TreeTraverserTest.java b/android/guava-tests/test/com/google/common/collect/TreeTraverserTest.java index 4c2abc7683ea..428650e42f81 100644 --- a/android/guava-tests/test/com/google/common/collect/TreeTraverserTest.java +++ b/android/guava-tests/test/com/google/common/collect/TreeTraverserTest.java @@ -15,21 +15,26 @@ package com.google.common.collect; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.testing.NullPointerTester; -import java.util.Arrays; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for {@code TreeTraverser}. * + * @deprecated Use {@link com.google.common.graph.Traverser} instead. * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked +@Deprecated // Use com.google.common.graph.Traverser instead. public class TreeTraverserTest extends TestCase { private static class Node { final char value; @@ -42,9 +47,9 @@ private static class Node { private static final class Tree extends Node { final List children; - public Tree(char value, Tree... children) { + Tree(char value, Tree... children) { super(value); - this.children = Arrays.asList(children); + this.children = asList(children); } } @@ -105,6 +110,7 @@ public void testUsing() { assertThat(iterationOrder(ADAPTER_USING_USING.preOrderTraversal(h))).isEqualTo("hdabcegf"); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { NullPointerTester tester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableIteratorTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableIteratorTest.java index 77ecbf730362..5859004c7d36 100644 --- a/android/guava-tests/test/com/google/common/collect/UnmodifiableIteratorTest.java +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableIteratorTest.java @@ -16,10 +16,13 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import java.util.Iterator; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for {@link UnmodifiableIterator}. @@ -27,10 +30,12 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class UnmodifiableIteratorTest extends TestCase { + @SuppressWarnings("DoNotCall") public void testRemove() { - final String[] array = {"a", "b", "c"}; + String[] array = {"a", "b", "c"}; Iterator iterator = new UnmodifiableIterator() { @@ -52,10 +57,6 @@ public String next() { assertTrue(iterator.hasNext()); assertEquals("a", iterator.next()); - try { - iterator.remove(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); } } diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableListIteratorTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableListIteratorTest.java index c9d3068e0773..af8aa03b0b63 100644 --- a/android/guava-tests/test/com/google/common/collect/UnmodifiableListIteratorTest.java +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableListIteratorTest.java @@ -16,11 +16,14 @@ package com.google.common.collect; +import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows; + import com.google.common.annotations.GwtCompatible; import java.util.Iterator; import java.util.ListIterator; import java.util.NoSuchElementException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for UnmodifiableListIterator. @@ -28,19 +31,18 @@ * @author Louis Wasserman */ @GwtCompatible +@NullMarked public class UnmodifiableListIteratorTest extends TestCase { + @SuppressWarnings("DoNotCall") public void testRemove() { Iterator iterator = create(); assertTrue(iterator.hasNext()); assertEquals("a", iterator.next()); - try { - iterator.remove(); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); } + @SuppressWarnings("DoNotCall") public void testAdd() { ListIterator iterator = create(); @@ -48,13 +50,10 @@ public void testAdd() { assertEquals("a", iterator.next()); assertEquals("b", iterator.next()); assertEquals("b", iterator.previous()); - try { - iterator.add("c"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iterator.add("c")); } + @SuppressWarnings("DoNotCall") public void testSet() { ListIterator iterator = create(); @@ -62,15 +61,11 @@ public void testSet() { assertEquals("a", iterator.next()); assertEquals("b", iterator.next()); assertEquals("b", iterator.previous()); - try { - iterator.set("c"); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> iterator.set("c")); } UnmodifiableListIterator create() { - final String[] array = {"a", "b", "c"}; + String[] array = {"a", "b", "c"}; return new UnmodifiableListIterator() { int i; diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableMultimapAsMapImplementsMapTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableMultimapAsMapImplementsMapTest.java index d03d7e351b65..6296f494b9ea 100644 --- a/android/guava-tests/test/com/google/common/collect/UnmodifiableMultimapAsMapImplementsMapTest.java +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableMultimapAsMapImplementsMapTest.java @@ -20,6 +20,7 @@ import com.google.common.collect.testing.MapInterfaceTest; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.NullMarked; /** * Test {@link Multimap#asMap()} for an unmodifiable multimap with {@link MapInterfaceTest}. @@ -27,6 +28,7 @@ * @author Jared Levy */ @GwtCompatible +@NullMarked public class UnmodifiableMultimapAsMapImplementsMapTest extends AbstractMultimapAsMapImplementsMapTest { diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableColumnMapTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableColumnMapTest.java new file mode 100644 index 000000000000..dcd19b050459 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableColumnMapTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.unmodifiableRowSortedTable; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnMapTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class UnmodifiableRowSortedTableColumnMapTest extends ColumnMapTests { + public UnmodifiableRowSortedTableColumnMapTest() { + super(false, false, false, false); + } + + @Override + Table makeTable() { + RowSortedTable original = TreeBasedTable.create(); + return unmodifiableRowSortedTable(original); + } + + @Override + protected Map> makePopulatedMap() { + RowSortedTable table = TreeBasedTable.create(); + table.put(1, "foo", 'a'); + table.put(1, "bar", 'b'); + table.put(3, "foo", 'c'); + return unmodifiableRowSortedTable(table).columnMap(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableColumnTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableColumnTest.java new file mode 100644 index 000000000000..a18944d4141d --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableColumnTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.unmodifiableRowSortedTable; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class UnmodifiableRowSortedTableColumnTest extends ColumnTests { + public UnmodifiableRowSortedTableColumnTest() { + super(false, false, false, false, false); + } + + @Override + Table makeTable() { + RowSortedTable table = TreeBasedTable.create(); + return unmodifiableRowSortedTable(table); + } + + @Override + protected Map makePopulatedMap() { + RowSortedTable table = TreeBasedTable.create(); + table.put("one", 'a', 1); + table.put("two", 'a', 2); + table.put("three", 'a', 3); + table.put("four", 'b', 4); + return unmodifiableRowSortedTable(table).column('a'); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableRowMapTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableRowMapTest.java new file mode 100644 index 000000000000..5fa5c14c9884 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableRowMapTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.unmodifiableRowSortedTable; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import java.util.Map; +import java.util.SortedMap; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class UnmodifiableRowSortedTableRowMapTest extends RowMapTests { + public UnmodifiableRowSortedTableRowMapTest() { + super(false, false, false, false); + } + + @Override + RowSortedTable makeTable() { + RowSortedTable original = TreeBasedTable.create(); + return unmodifiableRowSortedTable(original); + } + + @Override + protected SortedMap> makePopulatedMap() { + RowSortedTable table = TreeBasedTable.create(); + table.put("foo", 1, 'a'); + table.put("bar", 1, 'b'); + table.put("foo", 3, 'c'); + return unmodifiableRowSortedTable(table).rowMap(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableRowTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableRowTest.java new file mode 100644 index 000000000000..511eb2afb34b --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableRowSortedTableRowTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.unmodifiableRowSortedTable; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class UnmodifiableRowSortedTableRowTest extends RowTests { + public UnmodifiableRowSortedTableRowTest() { + super(false, false, false, false, false); + } + + @Override + Table makeTable() { + RowSortedTable table = TreeBasedTable.create(); + return unmodifiableRowSortedTable(table); + } + + @Override + protected Map makePopulatedMap() { + RowSortedTable table = TreeBasedTable.create(); + table.put('a', "one", 1); + table.put('a', "two", 2); + table.put('a', "three", 3); + table.put('b', "four", 4); + return unmodifiableRowSortedTable(table).row('a'); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableTableColumnMapTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableTableColumnMapTest.java new file mode 100644 index 000000000000..a75a0437ee25 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableTableColumnMapTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.unmodifiableTable; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnMapTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class UnmodifiableTableColumnMapTest extends ColumnMapTests { + public UnmodifiableTableColumnMapTest() { + super(false, false, false, false); + } + + @Override + Table makeTable() { + Table original = HashBasedTable.create(); + return unmodifiableTable(original); + } + + @Override + protected Map> makePopulatedMap() { + Table table = HashBasedTable.create(); + table.put(1, "foo", 'a'); + table.put(1, "bar", 'b'); + table.put(3, "foo", 'c'); + return unmodifiableTable(table).columnMap(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableTableColumnTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableTableColumnTest.java new file mode 100644 index 000000000000..70a8a7347fbc --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableTableColumnTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.unmodifiableTable; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.ColumnTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class UnmodifiableTableColumnTest extends ColumnTests { + public UnmodifiableTableColumnTest() { + super(false, false, false, false, false); + } + + @Override + Table makeTable() { + Table table = HashBasedTable.create(); + return unmodifiableTable(table); + } + + @Override + protected Map makePopulatedMap() { + Table table = HashBasedTable.create(); + table.put("one", 'a', 1); + table.put("two", 'a', 2); + table.put("three", 'a', 3); + table.put("four", 'b', 4); + return unmodifiableTable(table).column('a'); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableTableRowMapTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableTableRowMapTest.java new file mode 100644 index 000000000000..904e6a8482b6 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableTableRowMapTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.unmodifiableTable; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowMapTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class UnmodifiableTableRowMapTest extends RowMapTests { + public UnmodifiableTableRowMapTest() { + super(false, false, false, false); + } + + @Override + Table makeTable() { + Table original = HashBasedTable.create(); + return unmodifiableTable(original); + } + + @Override + protected Map> makePopulatedMap() { + Table table = HashBasedTable.create(); + table.put("foo", 1, 'a'); + table.put("bar", 1, 'b'); + table.put("foo", 3, 'c'); + return unmodifiableTable(table).rowMap(); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/UnmodifiableTableRowTest.java b/android/guava-tests/test/com/google/common/collect/UnmodifiableTableRowTest.java new file mode 100644 index 000000000000..e01db0878541 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/UnmodifiableTableRowTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Tables.unmodifiableTable; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.TableCollectionTest.RowTests; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@GwtCompatible +@NullMarked +public class UnmodifiableTableRowTest extends RowTests { + public UnmodifiableTableRowTest() { + super(false, false, false, false, false); + } + + @Override + Table makeTable() { + Table table = HashBasedTable.create(); + return unmodifiableTable(table); + } + + @Override + protected Map makePopulatedMap() { + Table table = HashBasedTable.create(); + table.put('a', "one", 1); + table.put('a', "two", 2); + table.put('a', "three", 3); + table.put('b', "four", 4); + return unmodifiableTable(table).row('a'); + } +} diff --git a/android/guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java b/android/guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java new file mode 100644 index 000000000000..381b0e386915 --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/WriteReplaceOverridesTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.truth.Truth.assertWithMessage; +import static java.lang.reflect.Modifier.PRIVATE; +import static java.lang.reflect.Modifier.PROTECTED; +import static java.lang.reflect.Modifier.PUBLIC; +import static java.util.Arrays.asList; + +import com.google.common.base.Optional; +import com.google.common.reflect.ClassPath; +import com.google.common.reflect.ClassPath.ClassInfo; +import com.google.common.reflect.TypeToken; +import java.lang.reflect.Method; +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +/** + * Tests that all package-private {@code writeReplace} methods are overridden in any existing + * subclasses. Without such overrides, optimizers might put a {@code writeReplace}-containing class + * and its subclass in different packages, causing the serialization system to fail to invoke {@code + * writeReplace} when serializing an instance of the subclass. For an example of this problem, see + * b/310253115. + */ +@NullUnmarked +public class WriteReplaceOverridesTest extends TestCase { + private static final ImmutableSet GUAVA_PACKAGES = + FluentIterable.of( + "base", + "cache", + "collect", + "escape", + "eventbus", + "graph", + "hash", + "html", + "io", + "math", + "net", + "primitives", + "reflect", + "util.concurrent", + "xml") + .transform("com.google.common."::concat) + .toSet(); + + public void testClassesHaveOverrides() throws Exception { + for (ClassInfo info : ClassPath.from(getClass().getClassLoader()).getAllClasses()) { + if (!GUAVA_PACKAGES.contains(info.getPackageName())) { + continue; + } + if ( + /* + * At least one of the classes nested inside TypeResolverTest triggers a bug under older JDKs: + * https://bugs.openjdk.org/browse/JDK-8215328 -> https://bugs.openjdk.org/browse/JDK-8215470 + * https://github.com/google/guava/blob/4f12c5891a7adedbaa1d99fc9f77d8cc4e9da206/guava-tests/test/com/google/common/reflect/TypeResolverTest.java#L201 + */ + info.getName().contains("TypeResolverTest") + /* + * And at least one of the classes inside TypeTokenTest ends up with a null value in + * TypeMappingIntrospector.mappings. That happens only under older JDKs, too, so it may + * well be a JDK bug. + */ + || info.getName().contains("TypeTokenTest") + /* + * "IllegalAccess tried to access class + * com.google.common.collect.testing.AbstractIteratorTester from class + * com.google.common.collect.MultimapsTest" + * + * ...when we build with JDK 22 and run under JDK 8. + */ + || info.getName().contains("MultimapsTest") + /* + * Luckily, we don't care about analyzing tests at all. We'd skip them all if we could do so + * trivially, but it's enough to skip these ones. + */ + ) { + continue; + } + Class clazz = info.load(); + try { + Method unused = clazz.getDeclaredMethod("writeReplace"); + continue; // It overrides writeReplace, so it's safe. + } catch (NoSuchMethodException e) { + // This is a class whose supertypes we want to examine. We'll do that below. + } + Optional> supersWithPackagePrivateWriteReplace = + FluentIterable.from(TypeToken.of(clazz).getTypes()) + .transform(TypeToken::getRawType) + .transformAndConcat(c -> asList(c.getDeclaredMethods())) + .firstMatch( + m -> + m.getName().equals("writeReplace") + && m.getParameterTypes().length == 0 + // Only package-private methods are a problem. + && (m.getModifiers() & (PUBLIC | PROTECTED | PRIVATE)) == 0) + .transform(Method::getDeclaringClass); + if (!supersWithPackagePrivateWriteReplace.isPresent()) { + continue; + } + assertWithMessage( + "To help optimizers, any class that inherits a package-private writeReplace() method" + + " should override that method.\n" + + "(An override that delegates to the supermethod is fine.)\n" + + "%s has no such override despite inheriting writeReplace() from %s", + clazz.getName(), supersWithPackagePrivateWriteReplace.get().getName()) + .fail(); + } + } +} diff --git a/android/guava-tests/test/com/google/common/escape/ArrayBasedCharEscaperTest.java b/android/guava-tests/test/com/google/common/escape/ArrayBasedCharEscaperTest.java index f0983ef98cfb..9030ff5bf9d9 100644 --- a/android/guava-tests/test/com/google/common/escape/ArrayBasedCharEscaperTest.java +++ b/android/guava-tests/test/com/google/common/escape/ArrayBasedCharEscaperTest.java @@ -16,14 +16,20 @@ package com.google.common.escape; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableMap; import com.google.common.escape.testing.EscaperAsserts; import java.io.IOException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; -/** @author David Beaumont */ +/** + * @author David Beaumont + */ @GwtCompatible +@NullMarked public class ArrayBasedCharEscaperTest extends TestCase { private static final ImmutableMap NO_REPLACEMENTS = ImmutableMap.of(); private static final ImmutableMap SIMPLE_REPLACEMENTS = @@ -43,7 +49,7 @@ protected char[] escapeUnsafe(char c) { }; EscaperAsserts.assertBasic(wrappingEscaper); // '[' and '@' lie either side of [A-Z]. - assertEquals("{[}FOO{@}BAR{]}", wrappingEscaper.escape("[FOO@BAR]")); + assertThat(wrappingEscaper.escape("[FOO@BAR]")).isEqualTo("{[}FOO{@}BAR{]}"); } public void testSafeRange_maxLessThanMin() throws IOException { @@ -57,7 +63,7 @@ protected char[] escapeUnsafe(char c) { }; EscaperAsserts.assertBasic(wrappingEscaper); // escape everything. - assertEquals("{[}{F}{O}{O}{]}", wrappingEscaper.escape("[FOO]")); + assertThat(wrappingEscaper.escape("[FOO]")).isEqualTo("{[}{F}{O}{O}{]}"); } public void testDeleteUnsafeChars() throws IOException { @@ -71,11 +77,11 @@ protected char[] escapeUnsafe(char c) { } }; EscaperAsserts.assertBasic(deletingEscaper); - assertEquals( - "Everything outside the printable ASCII range is deleted.", - deletingEscaper.escape( - "\tEverything\0 outside the\uD800\uDC00 " - + "printable ASCII \uFFFFrange is \u007Fdeleted.\n")); + assertThat( + deletingEscaper.escape( + "\tEverything\0 outside the\uD800\uDC00 " + + "printable ASCII \uFFFFrange is \u007Fdeleted.\n")) + .isEqualTo("Everything outside the printable ASCII range is deleted."); } public void testReplacementPriority() throws IOException { @@ -92,7 +98,7 @@ protected char[] escapeUnsafe(char c) { // Replacements are applied first regardless of whether the character is in // the safe range or not ('&' is a safe char while '\t' and '\n' are not). - assertEquals( - "Fish ? Chips?", replacingEscaper.escape("\tFish &\0 Chips\r\n")); + assertThat(replacingEscaper.escape("\tFish &\0 Chips\r\n")) + .isEqualTo("Fish ? Chips?"); } } diff --git a/android/guava-tests/test/com/google/common/escape/ArrayBasedEscaperMapTest.java b/android/guava-tests/test/com/google/common/escape/ArrayBasedEscaperMapTest.java index 6d9c1d89de8a..c56b4cbefd62 100644 --- a/android/guava-tests/test/com/google/common/escape/ArrayBasedEscaperMapTest.java +++ b/android/guava-tests/test/com/google/common/escape/ArrayBasedEscaperMapTest.java @@ -16,28 +16,30 @@ package com.google.common.escape; +import static com.google.common.escape.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableMap; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author David Beaumont */ +/** + * @author David Beaumont + */ @GwtCompatible +@NullUnmarked public class ArrayBasedEscaperMapTest extends TestCase { public void testNullMap() { - try { - ArrayBasedEscaperMap.create(null); - fail("expected exception did not occur"); - } catch (NullPointerException e) { - // pass - } + assertThrows(NullPointerException.class, () -> ArrayBasedEscaperMap.create(null)); } public void testEmptyMap() { Map map = ImmutableMap.of(); ArrayBasedEscaperMap fem = ArrayBasedEscaperMap.create(map); // Non-null array of zero length. - assertEquals(0, fem.getReplacementArray().length); + assertThat(fem.getReplacementArray()).isEmpty(); } public void testMapLength() { @@ -47,7 +49,7 @@ public void testMapLength() { 'z', "last"); ArrayBasedEscaperMap fem = ArrayBasedEscaperMap.create(map); // Array length is highest character value + 1 - assertEquals('z' + 1, fem.getReplacementArray().length); + assertThat(fem.getReplacementArray()).hasLength('z' + 1); } public void testMapping() { @@ -61,16 +63,17 @@ public void testMapping() { ArrayBasedEscaperMap fem = ArrayBasedEscaperMap.create(map); char[][] replacementArray = fem.getReplacementArray(); // Array length is highest character value + 1 - assertEquals(65536, replacementArray.length); - // The final element should always be non null. - assertNotNull(replacementArray[replacementArray.length - 1]); + assertThat(replacementArray).hasLength(65536); + // The final element should always be non-null. + assertThat(replacementArray[replacementArray.length - 1]).isNotNull(); // Exhaustively check all mappings (an int index avoids wrapping). - for (int n = 0; n < replacementArray.length; ++n) { + for (int n = 0; n < replacementArray.length; n++) { char c = (char) n; - if (replacementArray[n] != null) { - assertEquals(map.get(c), new String(replacementArray[n])); + String expected = map.get(c); + if (expected == null) { + assertThat(replacementArray[n]).isNull(); } else { - assertFalse(map.containsKey(c)); + assertThat(new String(replacementArray[n])).isEqualTo(expected); } } } diff --git a/android/guava-tests/test/com/google/common/escape/ArrayBasedUnicodeEscaperTest.java b/android/guava-tests/test/com/google/common/escape/ArrayBasedUnicodeEscaperTest.java index 3243240a18cd..f0337566e630 100644 --- a/android/guava-tests/test/com/google/common/escape/ArrayBasedUnicodeEscaperTest.java +++ b/android/guava-tests/test/com/google/common/escape/ArrayBasedUnicodeEscaperTest.java @@ -16,14 +16,21 @@ package com.google.common.escape; +import static com.google.common.escape.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableMap; import com.google.common.escape.testing.EscaperAsserts; import java.io.IOException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; -/** @author David Beaumont */ +/** + * @author David Beaumont + */ @GwtCompatible +@NullMarked public class ArrayBasedUnicodeEscaperTest extends TestCase { private static final ImmutableMap NO_REPLACEMENTS = ImmutableMap.of(); private static final ImmutableMap SIMPLE_REPLACEMENTS = @@ -45,20 +52,15 @@ protected char[] escapeUnsafe(int c) { } }; EscaperAsserts.assertBasic(escaper); - assertEquals("Fish Chips", escaper.escape("\tFish & Chips\n")); + assertThat(escaper.escape("\tFish & Chips\n")).isEqualTo("Fish Chips"); // Verify that everything else is left unescaped. String safeChars = "\0\u0100\uD800\uDC00\uFFFF"; - assertEquals(safeChars, escaper.escape(safeChars)); + assertThat(escaper.escape(safeChars)).isEqualTo(safeChars); // Ensure that Unicode escapers behave correctly wrt badly formed input. String badUnicode = "\uDC00\uD800"; - try { - escaper.escape(badUnicode); - fail("should fail for bad Unicode"); - } catch (IllegalArgumentException e) { - // Pass - } + assertThrows(IllegalArgumentException.class, () -> escaper.escape(badUnicode)); } public void testSafeRange() throws IOException { @@ -72,7 +74,7 @@ protected char[] escapeUnsafe(int c) { }; EscaperAsserts.assertBasic(wrappingEscaper); // '[' and '@' lie either side of [A-Z]. - assertEquals("{[}FOO{@}BAR{]}", wrappingEscaper.escape("[FOO@BAR]")); + assertThat(wrappingEscaper.escape("[FOO@BAR]")).isEqualTo("{[}FOO{@}BAR{]}"); } public void testDeleteUnsafeChars() throws IOException { @@ -84,11 +86,11 @@ protected char[] escapeUnsafe(int c) { } }; EscaperAsserts.assertBasic(deletingEscaper); - assertEquals( - "Everything outside the printable ASCII range is deleted.", - deletingEscaper.escape( - "\tEverything\0 outside the\uD800\uDC00 " - + "printable ASCII \uFFFFrange is \u007Fdeleted.\n")); + assertThat( + deletingEscaper.escape( + "\tEverything\0 outside the\uD800\uDC00 " + + "printable ASCII \uFFFFrange is \u007Fdeleted.\n")) + .isEqualTo("Everything outside the printable ASCII range is deleted."); } public void testReplacementPriority() throws IOException { @@ -105,8 +107,8 @@ protected char[] escapeUnsafe(int c) { // Replacements are applied first regardless of whether the character is in // the safe range or not ('&' is a safe char while '\t' and '\n' are not). - assertEquals( - "Fish ? Chips?", replacingEscaper.escape("\tFish &\0 Chips\r\n")); + assertThat(replacingEscaper.escape("\tFish &\0 Chips\r\n")) + .isEqualTo("Fish ? Chips?"); } public void testCodePointsFromSurrogatePairs() throws IOException { @@ -123,12 +125,12 @@ protected char[] escapeUnsafe(int c) { // A surrogate pair defining a code point within the safe range. String safeInput = "\uD800\uDC00"; // 0x10000 - assertEquals(safeInput, surrogateEscaper.escape(safeInput)); + assertThat(surrogateEscaper.escape(safeInput)).isEqualTo(safeInput); // A surrogate pair defining a code point outside the safe range (but both // of the surrogate characters lie within the safe range). It is important // not to accidentally treat this as a sequence of safe characters. String unsafeInput = "\uDBFF\uDFFF"; // 0x10FFFF - assertEquals("X", surrogateEscaper.escape(unsafeInput)); + assertThat(surrogateEscaper.escape(unsafeInput)).isEqualTo("X"); } } diff --git a/android/guava-tests/test/com/google/common/escape/CharEscaperBuilderTest.java b/android/guava-tests/test/com/google/common/escape/CharEscaperBuilderTest.java index 087e3177ab7d..ccd1c6c4ea29 100644 --- a/android/guava-tests/test/com/google/common/escape/CharEscaperBuilderTest.java +++ b/android/guava-tests/test/com/google/common/escape/CharEscaperBuilderTest.java @@ -16,14 +16,18 @@ package com.google.common.escape; +import static com.google.common.truth.Truth.assertThat; + import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +@NullUnmarked public class CharEscaperBuilderTest extends TestCase { public void testAddEscapes() { char[] cs = {'a', 'b', 'c'}; CharEscaperBuilder builder = new CharEscaperBuilder().addEscapes(cs, "Z"); Escaper escaper = builder.toEscaper(); - assertEquals("ZZZdef", escaper.escape("abcdef")); + assertThat(escaper.escape("abcdef")).isEqualTo("ZZZdef"); } } diff --git a/android/guava-tests/test/com/google/common/escape/EscapersTest.java b/android/guava-tests/test/com/google/common/escape/EscapersTest.java index 245560af61d5..5bb4e32b3488 100644 --- a/android/guava-tests/test/com/google/common/escape/EscapersTest.java +++ b/android/guava-tests/test/com/google/common/escape/EscapersTest.java @@ -16,32 +16,39 @@ package com.google.common.escape; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableMap; import com.google.common.escape.testing.EscaperAsserts; import java.io.IOException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author David Beaumont */ +/** + * @author David Beaumont + */ @GwtCompatible +@NullUnmarked public class EscapersTest extends TestCase { public void testNullEscaper() throws IOException { Escaper escaper = Escapers.nullEscaper(); EscaperAsserts.assertBasic(escaper); String s = "\0\n\t\\az09~\uD800\uDC00\uFFFF"; - assertEquals("null escaper should have no effect", s, escaper.escape(s)); + assertWithMessage("null escaper should have no effect").that(escaper.escape(s)).isEqualTo(s); } public void testBuilderInitialStateNoReplacement() { // Unsafe characters aren't modified by default (unsafeReplacement == null). Escaper escaper = Escapers.builder().setSafeRange('a', 'z').build(); - assertEquals("The Quick Brown Fox", escaper.escape("The Quick Brown Fox")); + assertThat(escaper.escape("The Quick Brown Fox")).isEqualTo("The Quick Brown Fox"); } public void testBuilderInitialStateNoneUnsafe() { // No characters are unsafe by default (safeMin == 0, safeMax == 0xFFFF). Escaper escaper = Escapers.builder().setUnsafeReplacement("X").build(); - assertEquals("\0\uFFFF", escaper.escape("\0\uFFFF")); + assertThat(escaper.escape("\0\uFFFF")).isEqualTo("\0\uFFFF"); } public void testBuilderRetainsState() { @@ -49,18 +56,18 @@ public void testBuilderRetainsState() { Escapers.Builder builder = Escapers.builder(); builder.setSafeRange('a', 'z'); builder.setUnsafeReplacement("X"); - assertEquals("XheXXuickXXrownXXoxX", builder.build().escape("The Quick Brown Fox!")); + assertThat(builder.build().escape("The Quick Brown Fox!")).isEqualTo("XheXXuickXXrownXXoxX"); // Explicit replacements take priority over unsafe characters. builder.addEscape(' ', "_"); builder.addEscape('!', "_"); - assertEquals("Xhe_Xuick_Xrown_Xox_", builder.build().escape("The Quick Brown Fox!")); + assertThat(builder.build().escape("The Quick Brown Fox!")).isEqualTo("Xhe_Xuick_Xrown_Xox_"); // Explicit replacements take priority over safe characters. builder.setSafeRange(' ', '~'); - assertEquals("The_Quick_Brown_Fox_", builder.build().escape("The Quick Brown Fox!")); + assertThat(builder.build().escape("The Quick Brown Fox!")).isEqualTo("The_Quick_Brown_Fox_"); } public void testBuilderCreatesIndependentEscapers() { - // Setup a simple builder and create the first escaper. + // Set up a simple builder and create the first escaper. Escapers.Builder builder = Escapers.builder(); builder.setSafeRange('a', 'z'); builder.setUnsafeReplacement("X"); @@ -74,42 +81,12 @@ public void testBuilderCreatesIndependentEscapers() { builder.addEscape(' ', "*"); // Test both escapers after modifying the builder. - assertEquals("Xhe_Xuick_Xrown_XoxX", first.escape("The Quick Brown Fox!")); - assertEquals("Xhe-Xuick-Xrown-Xox$", second.escape("The Quick Brown Fox!")); - } - - public void testAsUnicodeEscaper() throws IOException { - CharEscaper charEscaper = - createSimpleCharEscaper( - ImmutableMap.builder() - .put('x', "".toCharArray()) - .put('\uD800', "".toCharArray()) - .put('\uDC00', "".toCharArray()) - .build()); - UnicodeEscaper unicodeEscaper = Escapers.asUnicodeEscaper(charEscaper); - EscaperAsserts.assertBasic(unicodeEscaper); - assertEquals("", charEscaper.escape("x\uD800\uDC00")); - assertEquals("", unicodeEscaper.escape("x\uD800\uDC00")); - - // Test that wrapped escapers acquire good Unicode semantics. - assertEquals("", charEscaper.escape("\uD800x\uDC00")); - try { - unicodeEscaper.escape("\uD800x\uDC00"); - fail("should have failed for bad Unicode input"); - } catch (IllegalArgumentException e) { - // pass - } - assertEquals("", charEscaper.escape("\uDC00\uD800")); - try { - unicodeEscaper.escape("\uDC00\uD800"); - fail("should have failed for bad Unicode input"); - } catch (IllegalArgumentException e) { - // pass - } + assertThat(first.escape("The Quick Brown Fox!")).isEqualTo("Xhe_Xuick_Xrown_XoxX"); + assertThat(second.escape("The Quick Brown Fox!")).isEqualTo("Xhe-Xuick-Xrown-Xox$"); } - // A trival non-optimized escaper for testing. - static CharEscaper createSimpleCharEscaper(final ImmutableMap replacementMap) { + // A trivial non-optimized escaper for testing. + static CharEscaper createSimpleCharEscaper(ImmutableMap replacementMap) { return new CharEscaper() { @Override protected char[] escape(char c) { @@ -118,9 +95,8 @@ protected char[] escape(char c) { }; } - // A trival non-optimized escaper for testing. - static UnicodeEscaper createSimpleUnicodeEscaper( - final ImmutableMap replacementMap) { + // A trivial non-optimized escaper for testing. + static UnicodeEscaper createSimpleUnicodeEscaper(ImmutableMap replacementMap) { return new UnicodeEscaper() { @Override protected char[] escape(int cp) { diff --git a/android/guava-tests/test/com/google/common/escape/PackageSanityTests.java b/android/guava-tests/test/com/google/common/escape/PackageSanityTests.java index c2af8b742d02..d284c28a6e54 100644 --- a/android/guava-tests/test/com/google/common/escape/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/escape/PackageSanityTests.java @@ -17,6 +17,7 @@ package com.google.common.escape; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** * Basic sanity tests for the entire package. @@ -24,4 +25,5 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests {} diff --git a/android/guava-tests/test/com/google/common/escape/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/escape/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..ec79e4500a95 --- /dev/null +++ b/android/guava-tests/test/com/google/common/escape/ReflectionFreeAssertThrows.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/escape/UnicodeEscaperTest.java b/android/guava-tests/test/com/google/common/escape/UnicodeEscaperTest.java index 96cfa10b0333..b2038013c0d5 100644 --- a/android/guava-tests/test/com/google/common/escape/UnicodeEscaperTest.java +++ b/android/guava-tests/test/com/google/common/escape/UnicodeEscaperTest.java @@ -16,8 +16,13 @@ package com.google.common.escape; +import static com.google.common.escape.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link UnicodeEscaper}. @@ -25,6 +30,7 @@ * @author David Beaumont */ @GwtCompatible +@NullUnmarked public class UnicodeEscaperTest extends TestCase { private static final String SMALLEST_SURROGATE = @@ -39,7 +45,7 @@ public class UnicodeEscaperTest extends TestCase { private static final UnicodeEscaper NOP_ESCAPER = new UnicodeEscaper() { @Override - protected char[] escape(int c) { + protected char @Nullable [] escape(int c) { return null; } }; @@ -48,7 +54,7 @@ protected char[] escape(int c) { private static final UnicodeEscaper SIMPLE_ESCAPER = new UnicodeEscaper() { @Override - protected char[] escape(int cp) { + protected char @Nullable [] escape(int cp) { return ('a' <= cp && cp <= 'z') || ('A' <= cp && cp <= 'Z') || ('0' <= cp && cp <= '9') ? null : ("[" + String.valueOf(cp) + "]").toCharArray(); @@ -57,7 +63,7 @@ protected char[] escape(int cp) { public void testNopEscaper() { UnicodeEscaper e = NOP_ESCAPER; - assertEquals(TEST_STRING, escapeAsString(e, TEST_STRING)); + assertThat(escapeAsString(e, TEST_STRING)).isEqualTo(TEST_STRING); } public void testSimpleEscaper() { @@ -70,7 +76,7 @@ public void testSimpleEscaper() { + "0189[" + Character.MAX_CODE_POINT + "]"; - assertEquals(expected, escapeAsString(e, TEST_STRING)); + assertThat(escapeAsString(e, TEST_STRING)).isEqualTo(expected); } public void testGrowBuffer() { // need to grow past an initial 1024 byte buffer @@ -80,20 +86,20 @@ public void testGrowBuffer() { // need to grow past an initial 1024 byte buffer input.append((char) i); expected.append("[" + i + "]"); } - assertEquals(expected.toString(), SIMPLE_ESCAPER.escape(input.toString())); + assertThat(SIMPLE_ESCAPER.escape(input.toString())).isEqualTo(expected.toString()); } public void testSurrogatePairs() { UnicodeEscaper e = SIMPLE_ESCAPER; // Build up a range of surrogate pair characters to test - final int min = Character.MIN_SUPPLEMENTARY_CODE_POINT; - final int max = Character.MAX_CODE_POINT; - final int range = max - min; - final int s1 = min + (1 * range) / 4; - final int s2 = min + (2 * range) / 4; - final int s3 = min + (3 * range) / 4; - final char[] dst = new char[12]; + int min = Character.MIN_SUPPLEMENTARY_CODE_POINT; + int max = Character.MAX_CODE_POINT; + int range = max - min; + int s1 = min + (1 * range) / 4; + int s2 = min + (2 * range) / 4; + int s3 = min + (3 * range) / 4; + char[] dst = new char[12]; // Put surrogate pairs at odd indices so they can be split easily dst[0] = 'x'; @@ -107,33 +113,18 @@ public void testSurrogatePairs() { // Get the expected result string String expected = "x[" + min + "][" + s1 + "][" + s2 + "][" + s3 + "][" + max + "]x"; - assertEquals(expected, escapeAsString(e, test)); + assertThat(escapeAsString(e, test)).isEqualTo(expected); } public void testTrailingHighSurrogate() { String test = "abc" + Character.MIN_HIGH_SURROGATE; - try { - escapeAsString(NOP_ESCAPER, test); - fail("Trailing high surrogate should cause exception"); - } catch (IllegalArgumentException expected) { - // Pass - } - try { - escapeAsString(SIMPLE_ESCAPER, test); - fail("Trailing high surrogate should cause exception"); - } catch (IllegalArgumentException expected) { - // Pass - } + assertThrows(IllegalArgumentException.class, () -> escapeAsString(NOP_ESCAPER, test)); + assertThrows(IllegalArgumentException.class, () -> escapeAsString(SIMPLE_ESCAPER, test)); } public void testNullInput() { UnicodeEscaper e = SIMPLE_ESCAPER; - try { - e.escape((String) null); - fail("Null string should cause exception"); - } catch (NullPointerException expected) { - // Pass - } + assertThrows(NullPointerException.class, () -> e.escape((String) null)); } public void testBadStrings() { @@ -149,12 +140,7 @@ public void testBadStrings() { "abc" + Character.MAX_LOW_SURROGATE + "xyz", }; for (String s : BAD_STRINGS) { - try { - escapeAsString(e, s); - fail("Isolated low surrogate should cause exception [" + s + "]"); - } catch (IllegalArgumentException expected) { - // Pass - } + assertThrows(IllegalArgumentException.class, () -> escapeAsString(e, s)); } } @@ -163,9 +149,10 @@ public void testFalsePositivesForNextEscapedIndex() { new UnicodeEscaper() { // Canonical escaper method that only escapes lower case ASCII letters. @Override - protected char[] escape(int cp) { + protected char @Nullable [] escape(int cp) { return ('a' <= cp && cp <= 'z') ? new char[] {Character.toUpperCase((char) cp)} : null; } + // Inefficient implementation that defines all letters as escapable. @Override protected int nextEscapeIndex(CharSequence csq, int index, int end) { @@ -175,15 +162,13 @@ protected int nextEscapeIndex(CharSequence csq, int index, int end) { return index; } }; - assertEquals("\0HELLO \uD800\uDC00 WORLD!\n", e.escape("\0HeLLo \uD800\uDC00 WorlD!\n")); + assertThat(e.escape("\0HeLLo \uD800\uDC00 WorlD!\n")) + .isEqualTo("\0HELLO \uD800\uDC00 WORLD!\n"); } - public void testCodePointAt_IndexOutOfBoundsException() { - try { - UnicodeEscaper.codePointAt("Testing...", 4, 2); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + public void testCodePointAt_indexOutOfBoundsException() { + assertThrows( + IndexOutOfBoundsException.class, () -> UnicodeEscaper.codePointAt("Testing...", 4, 2)); } private static String escapeAsString(Escaper e, String s) { diff --git a/android/guava-tests/test/com/google/common/eventbus/AsyncEventBusTest.java b/android/guava-tests/test/com/google/common/eventbus/AsyncEventBusTest.java index 208667d213f3..74ae5131089c 100644 --- a/android/guava-tests/test/com/google/common/eventbus/AsyncEventBusTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/AsyncEventBusTest.java @@ -16,16 +16,18 @@ package com.google.common.eventbus; -import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test case for {@link AsyncEventBus}. * * @author Cliff Biffle */ +@NullUnmarked public class AsyncEventBusTest extends TestCase { private static final String EVENT = "Hello"; @@ -68,7 +70,7 @@ public void testBasicDistribution() { * @author cbiffle */ public static class FakeExecutor implements Executor { - List tasks = Lists.newArrayList(); + List tasks = new ArrayList<>(); @Override public void execute(Runnable task) { diff --git a/android/guava-tests/test/com/google/common/eventbus/DispatcherTest.java b/android/guava-tests/test/com/google/common/eventbus/DispatcherTest.java index b63a99543edc..00ddfc588382 100644 --- a/android/guava-tests/test/com/google/common/eventbus/DispatcherTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/DispatcherTest.java @@ -19,19 +19,19 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Queues; import com.google.common.util.concurrent.Uninterruptibles; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Dispatcher} implementations. * * @author Colin Decker */ - +@NullUnmarked public class DispatcherTest extends TestCase { private final EventBus bus = new EventBus(); @@ -52,8 +52,7 @@ public class DispatcherTest extends TestCase { subscriber(bus, s1, "handleString", String.class), subscriber(bus, s2, "handleString", String.class)); - private final ConcurrentLinkedQueue dispatchedSubscribers = - Queues.newConcurrentLinkedQueue(); + private final ConcurrentLinkedQueue dispatchedSubscribers = new ConcurrentLinkedQueue<>(); private Dispatcher dispatcher; @@ -79,8 +78,8 @@ public void testPerThreadQueuedDispatcher() { public void testLegacyAsyncDispatcher() { dispatcher = Dispatcher.legacyAsync(); - final CyclicBarrier barrier = new CyclicBarrier(2); - final CountDownLatch latch = new CountDownLatch(2); + CyclicBarrier barrier = new CyclicBarrier(2); + CountDownLatch latch = new CountDownLatch(2); new Thread( new Runnable() { diff --git a/android/guava-tests/test/com/google/common/eventbus/EventBusTest.java b/android/guava-tests/test/com/google/common/eventbus/EventBusTest.java index 249d3b06c51b..d869d97b8f12 100644 --- a/android/guava-tests/test/com/google/common/eventbus/EventBusTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/EventBusTest.java @@ -16,20 +16,26 @@ package com.google.common.eventbus; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.junit.Assert.assertThrows; + import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test case for {@link EventBus}. * * @author Cliff Biffle */ +@NullUnmarked public class EventBusTest extends TestCase { private static final String EVENT = "Hello"; private static final String BUS_IDENTIFIER = "test-bus"; @@ -65,20 +71,18 @@ public void testPolymorphicDistribution() { // Comparable isa Object StringCatcher stringCatcher = new StringCatcher(); - final List objectEvents = Lists.newArrayList(); + List objectEvents = new ArrayList<>(); Object objCatcher = new Object() { - @SuppressWarnings("unused") @Subscribe public void eat(Object food) { objectEvents.add(food); } }; - final List> compEvents = Lists.newArrayList(); + List> compEvents = new ArrayList<>(); Object compCatcher = new Object() { - @SuppressWarnings("unused") @Subscribe public void eat(Comparable food) { compEvents.add(food); @@ -90,7 +94,7 @@ public void eat(Comparable food) { // Two additional event types: Object and Comparable (played by Integer) Object objEvent = new Object(); - Object compEvent = new Integer(6); + Object compEvent = 6; bus.post(EVENT); bus.post(objEvent); @@ -116,11 +120,10 @@ public void eat(Comparable food) { } public void testSubscriberThrowsException() throws Exception { - final RecordingSubscriberExceptionHandler handler = new RecordingSubscriberExceptionHandler(); - final EventBus eventBus = new EventBus(handler); - final RuntimeException exception = - new RuntimeException("but culottes have a tendancy to ride up!"); - final Object subscriber = + RecordingSubscriberExceptionHandler handler = new RecordingSubscriberExceptionHandler(); + EventBus eventBus = new EventBus(handler); + RuntimeException exception = new RuntimeException("but culottes have a tendency to ride up!"); + Object subscriber = new Object() { @Subscribe public void throwExceptionOn(String message) { @@ -141,7 +144,7 @@ public void throwExceptionOn(String message) { } public void testSubscriberThrowsExceptionHandlerThrowsException() throws Exception { - final EventBus eventBus = + EventBus eventBus = new EventBus( new SubscriberExceptionHandler() { @Override @@ -149,7 +152,7 @@ public void handleException(Throwable exception, SubscriberExceptionContext cont throw new RuntimeException(); } }); - final Object subscriber = + Object subscriber = new Object() { @Subscribe public void throwExceptionOn(String message) { @@ -157,18 +160,14 @@ public void throwExceptionOn(String message) { } }; eventBus.register(subscriber); - try { - eventBus.post(EVENT); - } catch (RuntimeException e) { - fail("Exception should not be thrown."); - } + eventBus.post(EVENT); } public void testDeadEventForwarding() { GhostCatcher catcher = new GhostCatcher(); bus.register(catcher); - // A String -- an event for which noone has registered. + // A String -- an event for which no one has registered. bus.post(EVENT); List events = catcher.getEvents(); @@ -194,19 +193,14 @@ public void testMissingSubscribe() { public void testUnregister() { StringCatcher catcher1 = new StringCatcher(); StringCatcher catcher2 = new StringCatcher(); - try { - bus.unregister(catcher1); - fail("Attempting to unregister an unregistered object succeeded"); - } catch (IllegalArgumentException expected) { - // OK. - } + assertThrows(IllegalArgumentException.class, () -> bus.unregister(catcher1)); bus.register(catcher1); bus.post(EVENT); bus.register(catcher2); bus.post(EVENT); - List expectedEvents = Lists.newArrayList(); + List expectedEvents = new ArrayList<>(); expectedEvents.add(EVENT); expectedEvents.add(EVENT); @@ -222,12 +216,7 @@ public void testUnregister() { "Shouldn't catch any more events when unregistered.", expectedEvents, catcher1.getEvents()); assertEquals("Two correct events should be delivered.", expectedEvents, catcher2.getEvents()); - try { - bus.unregister(catcher1); - fail("Attempting to unregister an unregistered object succeeded"); - } catch (IllegalArgumentException expected) { - // OK. - } + assertThrows(IllegalArgumentException.class, () -> bus.unregister(catcher1)); bus.unregister(catcher2); bus.post(EVENT); @@ -239,11 +228,10 @@ public void testUnregister() { // NOTE: This test will always pass if register() is thread-safe but may also // pass if it isn't, though this is unlikely. - public void testRegisterThreadSafety() throws Exception { - List catchers = Lists.newCopyOnWriteArrayList(); - List> futures = Lists.newArrayList(); - ExecutorService executor = Executors.newFixedThreadPool(10); + List catchers = new CopyOnWriteArrayList<>(); + List> futures = new ArrayList<>(); + ExecutorService executor = newFixedThreadPool(10); int numberOfCatchers = 10000; for (int i = 0; i < numberOfCatchers; i++) { futures.add(executor.submit(new Registrator(bus, catchers))); @@ -273,8 +261,10 @@ public void testToString() throws Exception { * methods to be subscribed (since both are annotated @Subscribe) without specifically checking * for bridge methods. */ + // We use an anonymous class to be sure that we generate two methods (bridge and original). + @SuppressWarnings("AnonymousToLambda") public void testRegistrationWithBridgeMethod() { - final AtomicInteger calls = new AtomicInteger(); + AtomicInteger calls = new AtomicInteger(); bus.register( new Callback() { @Subscribe @@ -294,19 +284,14 @@ class SubscribesToPrimitive { @Subscribe public void toInt(int i) {} } - try { - bus.register(new SubscribesToPrimitive()); - fail("should have thrown"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> bus.register(new SubscribesToPrimitive())); } /** Records thrown exception information. */ private static final class RecordingSubscriberExceptionHandler implements SubscriberExceptionHandler { - - public SubscriberExceptionContext context; - public Throwable exception; + private SubscriberExceptionContext context; + private Throwable exception; @Override public void handleException(Throwable exception, SubscriberExceptionContext context) { @@ -339,7 +324,7 @@ public void run() { * @author cbiffle */ public static class GhostCatcher { - private List events = Lists.newArrayList(); + private final List events = new ArrayList<>(); @Subscribe public void ohNoesIHaveDied(DeadEvent event) { diff --git a/android/guava-tests/test/com/google/common/eventbus/PackageSanityTests.java b/android/guava-tests/test/com/google/common/eventbus/PackageSanityTests.java index 5dee7ca45303..c1fc3d2512a5 100644 --- a/android/guava-tests/test/com/google/common/eventbus/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/eventbus/PackageSanityTests.java @@ -18,7 +18,8 @@ import com.google.common.testing.AbstractPackageSanityTests; import java.lang.reflect.Method; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Basic sanity tests for the entire package. @@ -26,6 +27,7 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() throws Exception { @@ -41,7 +43,7 @@ private static class DummySubscriber { private final EventBus eventBus = new EventBus(); @Subscribe - public void handle(@NullableDecl Object anything) {} + public void handle(@Nullable Object unused) {} Subscriber toSubscriber() throws Exception { return Subscriber.create(eventBus, this, subscriberMethod()); diff --git a/android/guava-tests/test/com/google/common/eventbus/ReentrantEventsTest.java b/android/guava-tests/test/com/google/common/eventbus/ReentrantEventsTest.java index f26f0c36f723..de30eb8d9932 100644 --- a/android/guava-tests/test/com/google/common/eventbus/ReentrantEventsTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/ReentrantEventsTest.java @@ -17,14 +17,17 @@ package com.google.common.eventbus; import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Validate that {@link EventBus} behaves carefully when listeners publish their own events. * * @author Jesse Wilson */ +@NullUnmarked public class ReentrantEventsTest extends TestCase { static final String FIRST = "one"; @@ -46,7 +49,7 @@ public void testNoReentrantEvents() { public class ReentrantEventsHater { boolean ready = true; - List eventsReceived = Lists.newArrayList(); + final List eventsReceived = new ArrayList<>(); @Subscribe public void listenForStrings(String event) { @@ -89,7 +92,7 @@ public void listenForStrings(String event) { } public class EventRecorder { - List eventsReceived = Lists.newArrayList(); + final List eventsReceived = new ArrayList<>(); @Subscribe public void listenForEverything(Object event) { diff --git a/android/guava-tests/test/com/google/common/eventbus/StringCatcher.java b/android/guava-tests/test/com/google/common/eventbus/StringCatcher.java index 282abe11bac3..d292473fde76 100644 --- a/android/guava-tests/test/com/google/common/eventbus/StringCatcher.java +++ b/android/guava-tests/test/com/google/common/eventbus/StringCatcher.java @@ -16,10 +16,11 @@ package com.google.common.eventbus; -import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; import junit.framework.Assert; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * A simple EventSubscriber mock that records Strings. @@ -29,15 +30,16 @@ * * @author Cliff Biffle */ +@NullUnmarked public class StringCatcher { - private List events = Lists.newArrayList(); + private final List events = new ArrayList<>(); @Subscribe - public void hereHaveAString(@NullableDecl String string) { + public void hereHaveAString(@Nullable String string) { events.add(string); } - public void methodWithoutAnnotation(@NullableDecl String string) { + public void methodWithoutAnnotation(@Nullable String string) { Assert.fail("Event bus must not call methods without @Subscribe!"); } diff --git a/android/guava-tests/test/com/google/common/eventbus/SubscriberRegistryTest.java b/android/guava-tests/test/com/google/common/eventbus/SubscriberRegistryTest.java index c9e5c9a5b492..21ff84066d04 100644 --- a/android/guava-tests/test/com/google/common/eventbus/SubscriberRegistryTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/SubscriberRegistryTest.java @@ -16,16 +16,20 @@ package com.google.common.eventbus; +import static org.junit.Assert.assertThrows; + import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; import java.util.Iterator; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link SubscriberRegistry}. * * @author Colin Decker */ +@NullUnmarked public class SubscriberRegistryTest extends TestCase { private final SubscriberRegistry registry = new SubscriberRegistry(new EventBus()); @@ -59,28 +63,15 @@ public void testUnregister() { } public void testUnregister_notRegistered() { - try { - registry.unregister(new StringSubscriber()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> registry.unregister(new StringSubscriber())); StringSubscriber s1 = new StringSubscriber(); registry.register(s1); - try { - registry.unregister(new StringSubscriber()); - fail(); - } catch (IllegalArgumentException expected) { - // a StringSubscriber was registered, but not the same one we tried to unregister - } + assertThrows(IllegalArgumentException.class, () -> registry.unregister(new StringSubscriber())); registry.unregister(s1); - try { - registry.unregister(s1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> registry.unregister(s1)); } public void testGetSubscribers() { diff --git a/android/guava-tests/test/com/google/common/eventbus/SubscriberTest.java b/android/guava-tests/test/com/google/common/eventbus/SubscriberTest.java index e2380df21349..4c9bbb7b6254 100644 --- a/android/guava-tests/test/com/google/common/eventbus/SubscriberTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/SubscriberTest.java @@ -17,11 +17,14 @@ package com.google.common.eventbus; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.testing.EqualsTester; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Subscriber}. @@ -29,13 +32,14 @@ * @author Cliff Biffle * @author Colin Decker */ +@NullUnmarked public class SubscriberTest extends TestCase { private static final Object FIXTURE_ARGUMENT = new Object(); private EventBus bus; private boolean methodCalled; - private Object methodArgument; + private @Nullable Object methodArgument; @Override protected void setUp() throws Exception { @@ -69,23 +73,18 @@ public void testInvokeSubscriberMethod_exceptionWrapping() throws Throwable { Method method = getTestSubscriberMethod("exceptionThrowingMethod"); Subscriber subscriber = Subscriber.create(bus, this, method); - try { - subscriber.invokeSubscriberMethod(FIXTURE_ARGUMENT); - fail("Subscribers whose methods throw must throw InvocationTargetException"); - } catch (InvocationTargetException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(IntentionalException.class); - } + InvocationTargetException expected = + assertThrows( + InvocationTargetException.class, + () -> subscriber.invokeSubscriberMethod(FIXTURE_ARGUMENT)); + assertThat(expected).hasCauseThat().isInstanceOf(IntentionalException.class); } public void testInvokeSubscriberMethod_errorPassthrough() throws Throwable { Method method = getTestSubscriberMethod("errorThrowingMethod"); Subscriber subscriber = Subscriber.create(bus, this, method); - try { - subscriber.invokeSubscriberMethod(FIXTURE_ARGUMENT); - fail("Subscribers whose methods throw Errors must rethrow them"); - } catch (JudgmentError expected) { - } + assertThrows(JudgmentError.class, () -> subscriber.invokeSubscriberMethod(FIXTURE_ARGUMENT)); } public void testEquals() throws Exception { diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/AbstractEventBusTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/AbstractEventBusTest.java new file mode 100644 index 000000000000..b8738c3cb8cb --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/AbstractEventBusTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus.outside; + +import com.google.common.eventbus.EventBus; +import junit.framework.TestCase; +import org.jspecify.annotations.Nullable; + +/** + * Abstract base class for tests that EventBus finds the correct subscribers. + * + *

    The actual tests are distributed among the other classes in this package based on whether they + * are annotated or abstract in the superclass. + * + *

    This test must be outside the c.g.c.eventbus package to test correctly. + * + * @author Louis Wasserman + */ +abstract class AbstractEventBusTest extends TestCase { + static final Object EVENT = new Object(); + + abstract H createSubscriber(); + + private @Nullable H subscriber; + + H getSubscriber() { + return subscriber; + } + + @Override + protected void setUp() throws Exception { + subscriber = createSubscriber(); + EventBus bus = new EventBus(); + bus.register(subscriber); + bus.post(EVENT); + } + + @Override + protected void tearDown() throws Exception { + subscriber = null; + } +} diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/AbstractNotAnnotatedInSuperclassTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/AbstractNotAnnotatedInSuperclassTest.java new file mode 100644 index 000000000000..d2ab3e2079e1 --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/AbstractNotAnnotatedInSuperclassTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus.outside; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.AbstractNotAnnotatedInSuperclassTest.SubClass; +import java.util.ArrayList; +import java.util.List; + +public class AbstractNotAnnotatedInSuperclassTest extends AbstractEventBusTest { + abstract static class SuperClass { + public abstract void overriddenInSubclassNowhereAnnotated(Object o); + + public abstract void overriddenAndAnnotatedInSubclass(Object o); + } + + static class SubClass extends SuperClass { + final List overriddenInSubclassNowhereAnnotatedEvents = new ArrayList<>(); + final List overriddenAndAnnotatedInSubclassEvents = new ArrayList<>(); + + @Override + public void overriddenInSubclassNowhereAnnotated(Object o) { + overriddenInSubclassNowhereAnnotatedEvents.add(o); + } + + @Subscribe + @Override + public void overriddenAndAnnotatedInSubclass(Object o) { + overriddenAndAnnotatedInSubclassEvents.add(o); + } + } + + public void testOverriddenAndAnnotatedInSubclass() { + assertThat(getSubscriber().overriddenAndAnnotatedInSubclassEvents).contains(EVENT); + } + + public void testOverriddenInSubclassNowhereAnnotated() { + assertThat(getSubscriber().overriddenInSubclassNowhereAnnotatedEvents).isEmpty(); + } + + @Override + SubClass createSubscriber() { + return new SubClass(); + } +} diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedAndAbstractInSuperclassTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedAndAbstractInSuperclassTest.java new file mode 100644 index 000000000000..3c3216b0550a --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedAndAbstractInSuperclassTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus.outside; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.AnnotatedAndAbstractInSuperclassTest.SubClass; +import java.util.ArrayList; +import java.util.List; + +public class AnnotatedAndAbstractInSuperclassTest extends AbstractEventBusTest { + abstract static class SuperClass { + @Subscribe + public abstract void overriddenAndAnnotatedInSubclass(Object o); + + @Subscribe + public abstract void overriddenInSubclass(Object o); + } + + static class SubClass extends SuperClass { + final List overriddenAndAnnotatedInSubclassEvents = new ArrayList<>(); + final List overriddenInSubclassEvents = new ArrayList<>(); + + @Subscribe + @Override + public void overriddenAndAnnotatedInSubclass(Object o) { + overriddenAndAnnotatedInSubclassEvents.add(o); + } + + @Override + public void overriddenInSubclass(Object o) { + overriddenInSubclassEvents.add(o); + } + } + + public void testOverriddenAndAnnotatedInSubclass() { + assertThat(getSubscriber().overriddenAndAnnotatedInSubclassEvents).contains(EVENT); + } + + public void testOverriddenNotAnnotatedInSubclass() { + assertThat(getSubscriber().overriddenInSubclassEvents).contains(EVENT); + } + + @Override + SubClass createSubscriber() { + return new SubClass(); + } +} diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedNotAbstractInSuperclassTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedNotAbstractInSuperclassTest.java new file mode 100644 index 000000000000..03575da229be --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedNotAbstractInSuperclassTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus.outside; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.AnnotatedNotAbstractInSuperclassTest.SubClass; +import java.util.ArrayList; +import java.util.List; + +public class AnnotatedNotAbstractInSuperclassTest extends AbstractEventBusTest { + static class SuperClass { + final List notOverriddenInSubclassEvents = new ArrayList<>(); + final List overriddenNotAnnotatedInSubclassEvents = new ArrayList<>(); + final List overriddenAndAnnotatedInSubclassEvents = new ArrayList<>(); + final List differentlyOverriddenNotAnnotatedInSubclassBadEvents = new ArrayList<>(); + final List differentlyOverriddenAnnotatedInSubclassBadEvents = new ArrayList<>(); + + @Subscribe + public void notOverriddenInSubclass(Object o) { + notOverriddenInSubclassEvents.add(o); + } + + @Subscribe + public void overriddenNotAnnotatedInSubclass(Object o) { + overriddenNotAnnotatedInSubclassEvents.add(o); + } + + @Subscribe + public void overriddenAndAnnotatedInSubclass(Object o) { + overriddenAndAnnotatedInSubclassEvents.add(o); + } + + @Subscribe + public void differentlyOverriddenNotAnnotatedInSubclass(Object o) { + // the subclass overrides this and does *not* call super.dONAIS(o) + differentlyOverriddenNotAnnotatedInSubclassBadEvents.add(o); + } + + @Subscribe + public void differentlyOverriddenAnnotatedInSubclass(Object o) { + // the subclass overrides this and does *not* call super.dOAIS(o) + differentlyOverriddenAnnotatedInSubclassBadEvents.add(o); + } + } + + static class SubClass extends SuperClass { + final List differentlyOverriddenNotAnnotatedInSubclassGoodEvents = new ArrayList<>(); + final List differentlyOverriddenAnnotatedInSubclassGoodEvents = new ArrayList<>(); + + @Override + public void overriddenNotAnnotatedInSubclass(Object o) { + super.overriddenNotAnnotatedInSubclass(o); + } + + @Subscribe + @Override + // We are testing how we treat an override with the same behavior and annotations. + @SuppressWarnings("RedundantOverride") + public void overriddenAndAnnotatedInSubclass(Object o) { + super.overriddenAndAnnotatedInSubclass(o); + } + + @Override + public void differentlyOverriddenNotAnnotatedInSubclass(Object o) { + differentlyOverriddenNotAnnotatedInSubclassGoodEvents.add(o); + } + + @Subscribe + @Override + public void differentlyOverriddenAnnotatedInSubclass(Object o) { + differentlyOverriddenAnnotatedInSubclassGoodEvents.add(o); + } + } + + public void testNotOverriddenInSubclass() { + assertThat(getSubscriber().notOverriddenInSubclassEvents).contains(EVENT); + } + + public void testOverriddenNotAnnotatedInSubclass() { + assertThat(getSubscriber().overriddenNotAnnotatedInSubclassEvents).contains(EVENT); + } + + public void testDifferentlyOverriddenNotAnnotatedInSubclass() { + assertThat(getSubscriber().differentlyOverriddenNotAnnotatedInSubclassGoodEvents) + .contains(EVENT); + assertThat(getSubscriber().differentlyOverriddenNotAnnotatedInSubclassBadEvents).isEmpty(); + } + + public void testOverriddenAndAnnotatedInSubclass() { + assertThat(getSubscriber().overriddenAndAnnotatedInSubclassEvents).contains(EVENT); + } + + public void testDifferentlyOverriddenAndAnnotatedInSubclass() { + assertThat(getSubscriber().differentlyOverriddenAnnotatedInSubclassGoodEvents).contains(EVENT); + assertThat(getSubscriber().differentlyOverriddenAnnotatedInSubclassBadEvents).isEmpty(); + } + + @Override + SubClass createSubscriber() { + return new SubClass(); + } +} diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedSubscriberFinderTests.java b/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedSubscriberFinderTests.java deleted file mode 100644 index a1cbb594481e..000000000000 --- a/android/guava-tests/test/com/google/common/eventbus/outside/AnnotatedSubscriberFinderTests.java +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2012 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.eventbus.outside; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.collect.Lists; -import com.google.common.eventbus.EventBus; -import com.google.common.eventbus.Subscribe; -import java.util.List; -import junit.framework.TestCase; - -/** - * Test that EventBus finds the correct subscribers. - * - *

    This test must be outside the c.g.c.eventbus package to test correctly. - * - * @author Louis Wasserman - */ -public class AnnotatedSubscriberFinderTests { - - private static final Object EVENT = new Object(); - - abstract static class AbstractEventBusTest extends TestCase { - abstract H createSubscriber(); - - private H subscriber; - - H getSubscriber() { - return subscriber; - } - - @Override - protected void setUp() throws Exception { - subscriber = createSubscriber(); - EventBus bus = new EventBus(); - bus.register(subscriber); - bus.post(EVENT); - } - - @Override - protected void tearDown() throws Exception { - subscriber = null; - } - } - - /* - * We break the tests up based on whether they are annotated or abstract in the superclass. - */ - public static class BaseSubscriberFinderTest - extends AbstractEventBusTest { - static class Subscriber { - final List nonSubscriberEvents = Lists.newArrayList(); - final List subscriberEvents = Lists.newArrayList(); - - public void notASubscriber(Object o) { - nonSubscriberEvents.add(o); - } - - @Subscribe - public void subscriber(Object o) { - subscriberEvents.add(o); - } - } - - public void testNonSubscriber() { - assertThat(getSubscriber().nonSubscriberEvents).isEmpty(); - } - - public void testSubscriber() { - assertThat(getSubscriber().subscriberEvents).contains(EVENT); - } - - @Override - Subscriber createSubscriber() { - return new Subscriber(); - } - } - - public static class AnnotatedAndAbstractInSuperclassTest - extends AbstractEventBusTest { - abstract static class SuperClass { - @Subscribe - public abstract void overriddenAndAnnotatedInSubclass(Object o); - - @Subscribe - public abstract void overriddenInSubclass(Object o); - } - - static class SubClass extends SuperClass { - final List overriddenAndAnnotatedInSubclassEvents = Lists.newArrayList(); - final List overriddenInSubclassEvents = Lists.newArrayList(); - - @Subscribe - @Override - public void overriddenAndAnnotatedInSubclass(Object o) { - overriddenAndAnnotatedInSubclassEvents.add(o); - } - - @Override - public void overriddenInSubclass(Object o) { - overriddenInSubclassEvents.add(o); - } - } - - public void testOverriddenAndAnnotatedInSubclass() { - assertThat(getSubscriber().overriddenAndAnnotatedInSubclassEvents).contains(EVENT); - } - - public void testOverriddenNotAnnotatedInSubclass() { - assertThat(getSubscriber().overriddenInSubclassEvents).contains(EVENT); - } - - @Override - SubClass createSubscriber() { - return new SubClass(); - } - } - - public static class AnnotatedNotAbstractInSuperclassTest - extends AbstractEventBusTest { - static class SuperClass { - final List notOverriddenInSubclassEvents = Lists.newArrayList(); - final List overriddenNotAnnotatedInSubclassEvents = Lists.newArrayList(); - final List overriddenAndAnnotatedInSubclassEvents = Lists.newArrayList(); - final List differentlyOverriddenNotAnnotatedInSubclassBadEvents = - Lists.newArrayList(); - final List differentlyOverriddenAnnotatedInSubclassBadEvents = Lists.newArrayList(); - - @Subscribe - public void notOverriddenInSubclass(Object o) { - notOverriddenInSubclassEvents.add(o); - } - - @Subscribe - public void overriddenNotAnnotatedInSubclass(Object o) { - overriddenNotAnnotatedInSubclassEvents.add(o); - } - - @Subscribe - public void overriddenAndAnnotatedInSubclass(Object o) { - overriddenAndAnnotatedInSubclassEvents.add(o); - } - - @Subscribe - public void differentlyOverriddenNotAnnotatedInSubclass(Object o) { - // the subclass overrides this and does *not* call super.dONAIS(o) - differentlyOverriddenNotAnnotatedInSubclassBadEvents.add(o); - } - - @Subscribe - public void differentlyOverriddenAnnotatedInSubclass(Object o) { - // the subclass overrides this and does *not* call super.dOAIS(o) - differentlyOverriddenAnnotatedInSubclassBadEvents.add(o); - } - } - - static class SubClass extends SuperClass { - final List differentlyOverriddenNotAnnotatedInSubclassGoodEvents = - Lists.newArrayList(); - final List differentlyOverriddenAnnotatedInSubclassGoodEvents = Lists.newArrayList(); - - @Override - public void overriddenNotAnnotatedInSubclass(Object o) { - super.overriddenNotAnnotatedInSubclass(o); - } - - @Subscribe - @Override - public void overriddenAndAnnotatedInSubclass(Object o) { - super.overriddenAndAnnotatedInSubclass(o); - } - - @Override - public void differentlyOverriddenNotAnnotatedInSubclass(Object o) { - differentlyOverriddenNotAnnotatedInSubclassGoodEvents.add(o); - } - - @Subscribe - @Override - public void differentlyOverriddenAnnotatedInSubclass(Object o) { - differentlyOverriddenAnnotatedInSubclassGoodEvents.add(o); - } - } - - public void testNotOverriddenInSubclass() { - assertThat(getSubscriber().notOverriddenInSubclassEvents).contains(EVENT); - } - - public void testOverriddenNotAnnotatedInSubclass() { - assertThat(getSubscriber().overriddenNotAnnotatedInSubclassEvents).contains(EVENT); - } - - public void testDifferentlyOverriddenNotAnnotatedInSubclass() { - assertThat(getSubscriber().differentlyOverriddenNotAnnotatedInSubclassGoodEvents) - .contains(EVENT); - assertThat(getSubscriber().differentlyOverriddenNotAnnotatedInSubclassBadEvents).isEmpty(); - } - - public void testOverriddenAndAnnotatedInSubclass() { - assertThat(getSubscriber().overriddenAndAnnotatedInSubclassEvents).contains(EVENT); - } - - public void testDifferentlyOverriddenAndAnnotatedInSubclass() { - assertThat(getSubscriber().differentlyOverriddenAnnotatedInSubclassGoodEvents) - .contains(EVENT); - assertThat(getSubscriber().differentlyOverriddenAnnotatedInSubclassBadEvents).isEmpty(); - } - - @Override - SubClass createSubscriber() { - return new SubClass(); - } - } - - public static class AbstractNotAnnotatedInSuperclassTest - extends AbstractEventBusTest { - abstract static class SuperClass { - public abstract void overriddenInSubclassNowhereAnnotated(Object o); - - public abstract void overriddenAndAnnotatedInSubclass(Object o); - } - - static class SubClass extends SuperClass { - final List overriddenInSubclassNowhereAnnotatedEvents = Lists.newArrayList(); - final List overriddenAndAnnotatedInSubclassEvents = Lists.newArrayList(); - - @Override - public void overriddenInSubclassNowhereAnnotated(Object o) { - overriddenInSubclassNowhereAnnotatedEvents.add(o); - } - - @Subscribe - @Override - public void overriddenAndAnnotatedInSubclass(Object o) { - overriddenAndAnnotatedInSubclassEvents.add(o); - } - } - - public void testOverriddenAndAnnotatedInSubclass() { - assertThat(getSubscriber().overriddenAndAnnotatedInSubclassEvents).contains(EVENT); - } - - public void testOverriddenInSubclassNowhereAnnotated() { - assertThat(getSubscriber().overriddenInSubclassNowhereAnnotatedEvents).isEmpty(); - } - - @Override - SubClass createSubscriber() { - return new SubClass(); - } - } - - public static class NeitherAbstractNorAnnotatedInSuperclassTest - extends AbstractEventBusTest { - static class SuperClass { - final List neitherOverriddenNorAnnotatedEvents = Lists.newArrayList(); - final List overriddenInSubclassNowhereAnnotatedEvents = Lists.newArrayList(); - final List overriddenAndAnnotatedInSubclassEvents = Lists.newArrayList(); - - public void neitherOverriddenNorAnnotated(Object o) { - neitherOverriddenNorAnnotatedEvents.add(o); - } - - public void overriddenInSubclassNowhereAnnotated(Object o) { - overriddenInSubclassNowhereAnnotatedEvents.add(o); - } - - public void overriddenAndAnnotatedInSubclass(Object o) { - overriddenAndAnnotatedInSubclassEvents.add(o); - } - } - - static class SubClass extends SuperClass { - @Override - public void overriddenInSubclassNowhereAnnotated(Object o) { - super.overriddenInSubclassNowhereAnnotated(o); - } - - @Subscribe - @Override - public void overriddenAndAnnotatedInSubclass(Object o) { - super.overriddenAndAnnotatedInSubclass(o); - } - } - - public void testNeitherOverriddenNorAnnotated() { - assertThat(getSubscriber().neitherOverriddenNorAnnotatedEvents).isEmpty(); - } - - public void testOverriddenInSubclassNowhereAnnotated() { - assertThat(getSubscriber().overriddenInSubclassNowhereAnnotatedEvents).isEmpty(); - } - - public void testOverriddenAndAnnotatedInSubclass() { - assertThat(getSubscriber().overriddenAndAnnotatedInSubclassEvents).contains(EVENT); - } - - @Override - SubClass createSubscriber() { - return new SubClass(); - } - } - - public static class DeepInterfaceTest - extends AbstractEventBusTest { - interface Interface1 { - @Subscribe - void annotatedIn1(Object o); - - @Subscribe - void annotatedIn1And2(Object o); - - @Subscribe - void annotatedIn1And2AndClass(Object o); - - void declaredIn1AnnotatedIn2(Object o); - - void declaredIn1AnnotatedInClass(Object o); - - void nowhereAnnotated(Object o); - } - - interface Interface2 extends Interface1 { - @Override - @Subscribe - void declaredIn1AnnotatedIn2(Object o); - - @Override - @Subscribe - void annotatedIn1And2(Object o); - - @Override - @Subscribe - void annotatedIn1And2AndClass(Object o); - - void declaredIn2AnnotatedInClass(Object o); - - @Subscribe - void annotatedIn2(Object o); - } - - static class SubscriberClass implements Interface2 { - final List annotatedIn1Events = Lists.newArrayList(); - final List annotatedIn1And2Events = Lists.newArrayList(); - final List annotatedIn1And2AndClassEvents = Lists.newArrayList(); - final List declaredIn1AnnotatedIn2Events = Lists.newArrayList(); - final List declaredIn1AnnotatedInClassEvents = Lists.newArrayList(); - final List declaredIn2AnnotatedInClassEvents = Lists.newArrayList(); - final List annotatedIn2Events = Lists.newArrayList(); - final List nowhereAnnotatedEvents = Lists.newArrayList(); - - @Override - public void annotatedIn1(Object o) { - annotatedIn1Events.add(o); - } - - @Subscribe - @Override - public void declaredIn1AnnotatedInClass(Object o) { - declaredIn1AnnotatedInClassEvents.add(o); - } - - @Override - public void declaredIn1AnnotatedIn2(Object o) { - declaredIn1AnnotatedIn2Events.add(o); - } - - @Override - public void annotatedIn1And2(Object o) { - annotatedIn1And2Events.add(o); - } - - @Subscribe - @Override - public void annotatedIn1And2AndClass(Object o) { - annotatedIn1And2AndClassEvents.add(o); - } - - @Subscribe - @Override - public void declaredIn2AnnotatedInClass(Object o) { - declaredIn2AnnotatedInClassEvents.add(o); - } - - @Override - public void annotatedIn2(Object o) { - annotatedIn2Events.add(o); - } - - @Override - public void nowhereAnnotated(Object o) { - nowhereAnnotatedEvents.add(o); - } - } - - public void testAnnotatedIn1() { - assertThat(getSubscriber().annotatedIn1Events).contains(EVENT); - } - - public void testAnnotatedIn2() { - assertThat(getSubscriber().annotatedIn2Events).contains(EVENT); - } - - public void testAnnotatedIn1And2() { - assertThat(getSubscriber().annotatedIn1And2Events).contains(EVENT); - } - - public void testAnnotatedIn1And2AndClass() { - assertThat(getSubscriber().annotatedIn1And2AndClassEvents).contains(EVENT); - } - - public void testDeclaredIn1AnnotatedIn2() { - assertThat(getSubscriber().declaredIn1AnnotatedIn2Events).contains(EVENT); - } - - public void testDeclaredIn1AnnotatedInClass() { - assertThat(getSubscriber().declaredIn1AnnotatedInClassEvents).contains(EVENT); - } - - public void testDeclaredIn2AnnotatedInClass() { - assertThat(getSubscriber().declaredIn2AnnotatedInClassEvents).contains(EVENT); - } - - public void testNowhereAnnotated() { - assertThat(getSubscriber().nowhereAnnotatedEvents).isEmpty(); - } - - @Override - SubscriberClass createSubscriber() { - return new SubscriberClass(); - } - } -} diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/BaseSubscriberFinderTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/BaseSubscriberFinderTest.java new file mode 100644 index 000000000000..eb7b8ba29bd4 --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/BaseSubscriberFinderTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus.outside; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.BaseSubscriberFinderTest.Subscriber; +import java.util.ArrayList; +import java.util.List; + +public class BaseSubscriberFinderTest extends AbstractEventBusTest { + static class Subscriber { + final List nonSubscriberEvents = new ArrayList<>(); + final List subscriberEvents = new ArrayList<>(); + + public void notASubscriber(Object o) { + nonSubscriberEvents.add(o); + } + + @Subscribe + public void subscriber(Object o) { + subscriberEvents.add(o); + } + } + + public void testNonSubscriber() { + assertThat(getSubscriber().nonSubscriberEvents).isEmpty(); + } + + public void testSubscriber() { + assertThat(getSubscriber().subscriberEvents).contains(EVENT); + } + + @Override + Subscriber createSubscriber() { + return new Subscriber(); + } +} diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/DeepInterfaceTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/DeepInterfaceTest.java new file mode 100644 index 000000000000..cf789b0a030a --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/DeepInterfaceTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus.outside; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.DeepInterfaceTest.SubscriberClass; +import java.util.ArrayList; +import java.util.List; + +public class DeepInterfaceTest extends AbstractEventBusTest { + interface Interface1 { + @Subscribe + void annotatedIn1(Object o); + + @Subscribe + void annotatedIn1And2(Object o); + + @Subscribe + void annotatedIn1And2AndClass(Object o); + + void declaredIn1AnnotatedIn2(Object o); + + void declaredIn1AnnotatedInClass(Object o); + + void nowhereAnnotated(Object o); + } + + interface Interface2 extends Interface1 { + @Override + @Subscribe + void declaredIn1AnnotatedIn2(Object o); + + @Override + @Subscribe + void annotatedIn1And2(Object o); + + @Override + @Subscribe + void annotatedIn1And2AndClass(Object o); + + void declaredIn2AnnotatedInClass(Object o); + + @Subscribe + void annotatedIn2(Object o); + } + + static class SubscriberClass implements Interface2 { + final List annotatedIn1Events = new ArrayList<>(); + final List annotatedIn1And2Events = new ArrayList<>(); + final List annotatedIn1And2AndClassEvents = new ArrayList<>(); + final List declaredIn1AnnotatedIn2Events = new ArrayList<>(); + final List declaredIn1AnnotatedInClassEvents = new ArrayList<>(); + final List declaredIn2AnnotatedInClassEvents = new ArrayList<>(); + final List annotatedIn2Events = new ArrayList<>(); + final List nowhereAnnotatedEvents = new ArrayList<>(); + + @Override + public void annotatedIn1(Object o) { + annotatedIn1Events.add(o); + } + + @Subscribe + @Override + public void declaredIn1AnnotatedInClass(Object o) { + declaredIn1AnnotatedInClassEvents.add(o); + } + + @Override + public void declaredIn1AnnotatedIn2(Object o) { + declaredIn1AnnotatedIn2Events.add(o); + } + + @Override + public void annotatedIn1And2(Object o) { + annotatedIn1And2Events.add(o); + } + + @Subscribe + @Override + public void annotatedIn1And2AndClass(Object o) { + annotatedIn1And2AndClassEvents.add(o); + } + + @Subscribe + @Override + public void declaredIn2AnnotatedInClass(Object o) { + declaredIn2AnnotatedInClassEvents.add(o); + } + + @Override + public void annotatedIn2(Object o) { + annotatedIn2Events.add(o); + } + + @Override + public void nowhereAnnotated(Object o) { + nowhereAnnotatedEvents.add(o); + } + } + + public void testAnnotatedIn1() { + assertThat(getSubscriber().annotatedIn1Events).contains(EVENT); + } + + public void testAnnotatedIn2() { + assertThat(getSubscriber().annotatedIn2Events).contains(EVENT); + } + + public void testAnnotatedIn1And2() { + assertThat(getSubscriber().annotatedIn1And2Events).contains(EVENT); + } + + public void testAnnotatedIn1And2AndClass() { + assertThat(getSubscriber().annotatedIn1And2AndClassEvents).contains(EVENT); + } + + public void testDeclaredIn1AnnotatedIn2() { + assertThat(getSubscriber().declaredIn1AnnotatedIn2Events).contains(EVENT); + } + + public void testDeclaredIn1AnnotatedInClass() { + assertThat(getSubscriber().declaredIn1AnnotatedInClassEvents).contains(EVENT); + } + + public void testDeclaredIn2AnnotatedInClass() { + assertThat(getSubscriber().declaredIn2AnnotatedInClassEvents).contains(EVENT); + } + + public void testNowhereAnnotated() { + assertThat(getSubscriber().nowhereAnnotatedEvents).isEmpty(); + } + + @Override + SubscriberClass createSubscriber() { + return new SubscriberClass(); + } +} diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/NeitherAbstractNorAnnotatedInSuperclassTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/NeitherAbstractNorAnnotatedInSuperclassTest.java new file mode 100644 index 000000000000..ab749b2796b1 --- /dev/null +++ b/android/guava-tests/test/com/google/common/eventbus/outside/NeitherAbstractNorAnnotatedInSuperclassTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.eventbus.outside; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.outside.NeitherAbstractNorAnnotatedInSuperclassTest.SubClass; +import java.util.ArrayList; +import java.util.List; + +public class NeitherAbstractNorAnnotatedInSuperclassTest extends AbstractEventBusTest { + static class SuperClass { + final List neitherOverriddenNorAnnotatedEvents = new ArrayList<>(); + final List overriddenInSubclassNowhereAnnotatedEvents = new ArrayList<>(); + final List overriddenAndAnnotatedInSubclassEvents = new ArrayList<>(); + + public void neitherOverriddenNorAnnotated(Object o) { + neitherOverriddenNorAnnotatedEvents.add(o); + } + + public void overriddenInSubclassNowhereAnnotated(Object o) { + overriddenInSubclassNowhereAnnotatedEvents.add(o); + } + + public void overriddenAndAnnotatedInSubclass(Object o) { + overriddenAndAnnotatedInSubclassEvents.add(o); + } + } + + static class SubClass extends SuperClass { + @Override + // We are testing how we treat an override with the same behavior and annotations. + @SuppressWarnings("RedundantOverride") + public void overriddenInSubclassNowhereAnnotated(Object o) { + super.overriddenInSubclassNowhereAnnotated(o); + } + + @Subscribe + @Override + public void overriddenAndAnnotatedInSubclass(Object o) { + super.overriddenAndAnnotatedInSubclass(o); + } + } + + public void testNeitherOverriddenNorAnnotated() { + assertThat(getSubscriber().neitherOverriddenNorAnnotatedEvents).isEmpty(); + } + + public void testOverriddenInSubclassNowhereAnnotated() { + assertThat(getSubscriber().overriddenInSubclassNowhereAnnotatedEvents).isEmpty(); + } + + public void testOverriddenAndAnnotatedInSubclass() { + assertThat(getSubscriber().overriddenAndAnnotatedInSubclassEvents).contains(EVENT); + } + + @Override + SubClass createSubscriber() { + return new SubClass(); + } +} diff --git a/android/guava-tests/test/com/google/common/eventbus/outside/OutsideEventBusTest.java b/android/guava-tests/test/com/google/common/eventbus/outside/OutsideEventBusTest.java index a1ec9effbb79..17be6ca1f48b 100644 --- a/android/guava-tests/test/com/google/common/eventbus/outside/OutsideEventBusTest.java +++ b/android/guava-tests/test/com/google/common/eventbus/outside/OutsideEventBusTest.java @@ -35,8 +35,8 @@ public class OutsideEventBusTest extends TestCase { * it can fail here. */ public void testAnonymous() { - final AtomicReference holder = new AtomicReference<>(); - final AtomicInteger deliveries = new AtomicInteger(); + AtomicReference holder = new AtomicReference<>(); + AtomicInteger deliveries = new AtomicInteger(); EventBus bus = new EventBus(); bus.register( new Object() { diff --git a/android/guava-tests/test/com/google/common/graph/AbstractGraphTest.java b/android/guava-tests/test/com/google/common/graph/AbstractGraphTest.java index 3a489a11c37b..071b040d8e9b 100644 --- a/android/guava-tests/test/com/google/common/graph/AbstractGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/AbstractGraphTest.java @@ -16,17 +16,18 @@ package com.google.common.graph; -import static com.google.common.graph.TestUtil.ERROR_NODE_NOT_IN_GRAPH; import static com.google.common.graph.TestUtil.assertNodeNotInGraphErrorMessage; +import static com.google.common.graph.TestUtil.assertNodeRemovedFromGraphErrorMessage; import static com.google.common.graph.TestUtil.assertStronglyEquivalent; import static com.google.common.graph.TestUtil.sanityCheckSet; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableSet; import java.util.HashSet; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -47,6 +48,7 @@ * TODO(user): Make this class generic (using ) for all node and edge types. * TODO(user): Differentiate between directed and undirected edge strings. */ +@NullUnmarked public abstract class AbstractGraphTest { Graph graph; @@ -119,6 +121,17 @@ static void validateGraph(Graph graph) { int edgeStart = graphString.indexOf("edges:"); String nodeString = graphString.substring(nodeStart, edgeStart); + Network> asNetwork = graph.asNetwork(); + // Don't call AbstractNetworkTest.validateNetwork(asNetwork). Mutual recursion. + assertThat(graph.nodes()).isEqualTo(asNetwork.nodes()); + assertThat(graph.edges()).hasSize(asNetwork.edges().size()); + assertThat(graph.nodeOrder()).isEqualTo(asNetwork.nodeOrder()); + assertThat(graph.isDirected()).isEqualTo(asNetwork.isDirected()); + assertThat(graph.allowsSelfLoops()).isEqualTo(asNetwork.allowsSelfLoops()); + assertThat(asNetwork.edgeOrder()).isEqualTo(ElementOrder.unordered()); + assertThat(asNetwork.allowsParallelEdges()).isFalse(); + assertThat(asNetwork.asGraph()).isEqualTo(graph); + Set> allEndpointPairs = new HashSet<>(); for (N node : sanityCheckSet(graph.nodes())) { @@ -235,12 +248,8 @@ public void adjacentNodes_noAdjacentNodes() { @Test public void adjacentNodes_nodeNotInGraph() { - try { - graph.adjacentNodes(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.adjacentNodes(NODE_NOT_IN_GRAPH))); } @Test @@ -251,12 +260,8 @@ public void predecessors_noPredecessors() { @Test public void predecessors_nodeNotInGraph() { - try { - graph.predecessors(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.predecessors(NODE_NOT_IN_GRAPH))); } @Test @@ -267,12 +272,8 @@ public void successors_noSuccessors() { @Test public void successors_nodeNotInGraph() { - try { - graph.successors(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.successors(NODE_NOT_IN_GRAPH))); } @Test @@ -283,12 +284,8 @@ public void incidentEdges_noIncidentEdges() { @Test public void incidentEdges_nodeNotInGraph() { - try { - graph.incidentEdges(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.incidentEdges(NODE_NOT_IN_GRAPH))); } @Test @@ -306,12 +303,8 @@ public void degree_isolatedNode() { @Test public void degree_nodeNotInGraph() { - try { - graph.degree(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.degree(NODE_NOT_IN_GRAPH))); } @Test @@ -322,12 +315,8 @@ public void inDegree_isolatedNode() { @Test public void inDegree_nodeNotInGraph() { - try { - graph.inDegree(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.inDegree(NODE_NOT_IN_GRAPH))); } @Test @@ -338,12 +327,8 @@ public void outDegree_isolatedNode() { @Test public void outDegree_nodeNotInGraph() { - try { - graph.outDegree(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.outDegree(NODE_NOT_IN_GRAPH))); } @Test @@ -373,8 +358,24 @@ public void removeNode_existingNode() { assertThat(graphAsMutableGraph.removeNode(N1)).isTrue(); assertThat(graphAsMutableGraph.removeNode(N1)).isFalse(); assertThat(graph.nodes()).containsExactly(N2, N4); + assertThat(graph.adjacentNodes(N2)).isEmpty(); + assertThat(graph.predecessors(N2)).isEmpty(); + assertThat(graph.successors(N2)).isEmpty(); + assertThat(graph.incidentEdges(N2)).isEmpty(); assertThat(graph.adjacentNodes(N4)).isEmpty(); + assertThat(graph.predecessors(N4)).isEmpty(); + assertThat(graph.successors(N4)).isEmpty(); + assertThat(graph.incidentEdges(N4)).isEmpty(); + + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.adjacentNodes(N1))); + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.predecessors(N1))); + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.successors(N1))); + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.incidentEdges(N1))); } @Test @@ -404,19 +405,48 @@ public void removeNode_nodeNotPresent() { } @Test - public void removeNode_queryAfterRemoval() { + public void queryAccessorSetAfterElementRemoval() { assume().that(graphIsMutable()).isTrue(); - addNode(N1); - @SuppressWarnings("unused") - Set unused = graph.adjacentNodes(N1); // ensure cache (if any) is populated + putEdge(N1, N2); + putEdge(N2, N1); + Set n1AdjacentNodes = graph.adjacentNodes(N1); + Set n2AdjacentNodes = graph.adjacentNodes(N2); + Set n1Predecessors = graph.predecessors(N1); + Set n2Predecessors = graph.predecessors(N2); + Set n1Successors = graph.successors(N1); + Set n2Successors = graph.successors(N2); + Set> n1IncidentEdges = graph.incidentEdges(N1); + Set> n2IncidentEdges = graph.incidentEdges(N2); assertThat(graphAsMutableGraph.removeNode(N1)).isTrue(); - try { - graph.adjacentNodes(N1); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + + // The choice of the size() method to call here is arbitrary. We assume that if any of the Set + // methods executes the validation check, they all will, and thus we only need to test one of + // them to ensure that the validation check happens and has the expected behavior. + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1AdjacentNodes::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1Predecessors::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1Successors::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1IncidentEdges::size)); + + assertThat(n2AdjacentNodes).isEmpty(); + assertThat(n2Predecessors).isEmpty(); + assertThat(n2Successors).isEmpty(); + assertThat(n2IncidentEdges).isEmpty(); + } + + @Test + public void queryGraphAfterElementRemoval() { + assume().that(graphIsMutable()).isTrue(); + + putEdge(N1, N2); + putEdge(N2, N1); + assertThat(graphAsMutableGraph.removeNode(N1)).isTrue(); + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> graph.adjacentNodes(N1))); } @Test diff --git a/android/guava-tests/test/com/google/common/graph/AbstractNetworkTest.java b/android/guava-tests/test/com/google/common/graph/AbstractNetworkTest.java index 776e4bfea01f..b9fa89379f68 100644 --- a/android/guava-tests/test/com/google/common/graph/AbstractNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/AbstractNetworkTest.java @@ -16,16 +16,16 @@ package com.google.common.graph; -import static com.google.common.graph.TestUtil.ERROR_NODE_NOT_IN_GRAPH; import static com.google.common.graph.TestUtil.assertEdgeNotInGraphErrorMessage; +import static com.google.common.graph.TestUtil.assertEdgeRemovedFromGraphErrorMessage; import static com.google.common.graph.TestUtil.assertNodeNotInGraphErrorMessage; +import static com.google.common.graph.TestUtil.assertNodeRemovedFromGraphErrorMessage; import static com.google.common.graph.TestUtil.assertStronglyEquivalent; import static com.google.common.graph.TestUtil.sanityCheckSet; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; import static java.util.concurrent.Executors.newFixedThreadPool; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; @@ -36,6 +36,8 @@ import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -56,6 +58,7 @@ * TODO(user): Make this class generic (using ) for all node and edge types. * TODO(user): Differentiate between directed and undirected edge strings. */ +@NullUnmarked public abstract class AbstractNetworkTest { Network network; @@ -182,8 +185,9 @@ static void validateNetwork(Network network) { for (N incidentNode : network.incidentNodes(edge)) { assertThat(network.nodes()).contains(incidentNode); for (E adjacentEdge : network.incidentEdges(incidentNode)) { - assertTrue( - edge.equals(adjacentEdge) || network.adjacentEdges(edge).contains(adjacentEdge)); + assertThat( + edge.equals(adjacentEdge) || network.adjacentEdges(edge).contains(adjacentEdge)) + .isTrue(); } } } @@ -258,12 +262,14 @@ static void validateNetwork(Network network) { } for (N adjacentNode : sanityCheckSet(network.adjacentNodes(node))) { - assertTrue( - network.predecessors(node).contains(adjacentNode) - || network.successors(node).contains(adjacentNode)); - assertTrue( - !network.edgesConnecting(node, adjacentNode).isEmpty() - || !network.edgesConnecting(adjacentNode, node).isEmpty()); + assertThat( + network.predecessors(node).contains(adjacentNode) + || network.successors(node).contains(adjacentNode)) + .isTrue(); + assertThat( + !network.edgesConnecting(node, adjacentNode).isEmpty() + || !network.edgesConnecting(adjacentNode, node).isEmpty()) + .isTrue(); } for (N predecessor : sanityCheckSet(network.predecessors(node))) { @@ -277,9 +283,10 @@ static void validateNetwork(Network network) { } for (E incidentEdge : sanityCheckSet(network.incidentEdges(node))) { - assertTrue( - network.inEdges(node).contains(incidentEdge) - || network.outEdges(node).contains(incidentEdge)); + assertThat( + network.inEdges(node).contains(incidentEdge) + || network.outEdges(node).contains(incidentEdge)) + .isTrue(); assertThat(network.edges()).contains(incidentEdge); assertThat(network.incidentNodes(incidentEdge)).contains(node); } @@ -415,12 +422,9 @@ public void incidentEdges_isolatedNode() { @Test public void incidentEdges_nodeNotInGraph() { - try { - network.incidentEdges(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> network.incidentEdges(NODE_NOT_IN_GRAPH))); } @Test @@ -431,12 +435,9 @@ public void incidentNodes_oneEdge() { @Test public void incidentNodes_edgeNotInGraph() { - try { - network.incidentNodes(EDGE_NOT_IN_GRAPH); - fail(ERROR_EDGE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertEdgeNotInGraphErrorMessage(e); - } + assertEdgeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> network.incidentNodes(EDGE_NOT_IN_GRAPH))); } @Test @@ -454,12 +455,9 @@ public void adjacentNodes_noAdjacentNodes() { @Test public void adjacentNodes_nodeNotInGraph() { - try { - network.adjacentNodes(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> network.adjacentNodes(NODE_NOT_IN_GRAPH))); } @Test @@ -480,12 +478,9 @@ public void adjacentEdges_noAdjacentEdges() { @Test public void adjacentEdges_edgeNotInGraph() { - try { - network.adjacentEdges(EDGE_NOT_IN_GRAPH); - fail(ERROR_EDGE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertEdgeNotInGraphErrorMessage(e); - } + assertEdgeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> network.adjacentEdges(EDGE_NOT_IN_GRAPH))); } @Test @@ -511,24 +506,16 @@ public void edgesConnecting_disconnectedNodes() { public void edgesConnecting_nodesNotInGraph() { addNode(N1); addNode(N2); - try { - network.edgesConnecting(N1, NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } - try { - network.edgesConnecting(NODE_NOT_IN_GRAPH, N2); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } - try { - network.edgesConnecting(NODE_NOT_IN_GRAPH, NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> network.edgesConnecting(N1, NODE_NOT_IN_GRAPH))); + assertNodeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> network.edgesConnecting(NODE_NOT_IN_GRAPH, N2))); + assertNodeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, + () -> network.edgesConnecting(NODE_NOT_IN_GRAPH, NODE_NOT_IN_GRAPH))); } @Test @@ -577,7 +564,7 @@ public void hasEdgeConnecting_disconnectedNodes() { } @Test - public void hasEdgesConnecting_nodesNotInGraph() { + public void hasEdgeConnecting_nodesNotInGraph() { addNode(N1); addNode(N2); assertThat(network.hasEdgeConnecting(N1, NODE_NOT_IN_GRAPH)).isFalse(); @@ -593,12 +580,8 @@ public void inEdges_noInEdges() { @Test public void inEdges_nodeNotInGraph() { - try { - network.inEdges(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> network.inEdges(NODE_NOT_IN_GRAPH))); } @Test @@ -609,12 +592,8 @@ public void outEdges_noOutEdges() { @Test public void outEdges_nodeNotInGraph() { - try { - network.outEdges(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> network.outEdges(NODE_NOT_IN_GRAPH))); } @Test @@ -625,12 +604,9 @@ public void predecessors_noPredecessors() { @Test public void predecessors_nodeNotInGraph() { - try { - network.predecessors(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> network.predecessors(NODE_NOT_IN_GRAPH))); } @Test @@ -641,19 +617,15 @@ public void successors_noSuccessors() { @Test public void successors_nodeNotInGraph() { - try { - network.successors(NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> network.successors(NODE_NOT_IN_GRAPH))); } @Test public void addNode_newNode() { assume().that(graphIsMutable()).isTrue(); - assertTrue(networkAsMutableNetwork.addNode(N1)); + assertThat(networkAsMutableNetwork.addNode(N1)).isTrue(); assertThat(networkAsMutableNetwork.nodes()).contains(N1); } @@ -663,7 +635,7 @@ public void addNode_existingNode() { addNode(N1); ImmutableSet nodes = ImmutableSet.copyOf(networkAsMutableNetwork.nodes()); - assertFalse(networkAsMutableNetwork.addNode(N1)); + assertThat(networkAsMutableNetwork.addNode(N1)).isFalse(); assertThat(networkAsMutableNetwork.nodes()).containsExactlyElementsIn(nodes); } @@ -673,11 +645,33 @@ public void removeNode_existingNode() { addEdge(N1, N2, E12); addEdge(N4, N1, E41); - assertTrue(networkAsMutableNetwork.removeNode(N1)); - assertFalse(networkAsMutableNetwork.removeNode(N1)); + assertThat(networkAsMutableNetwork.removeNode(N1)).isTrue(); + assertThat(networkAsMutableNetwork.removeNode(N1)).isFalse(); assertThat(networkAsMutableNetwork.nodes()).containsExactly(N2, N4); assertThat(networkAsMutableNetwork.edges()).doesNotContain(E12); assertThat(networkAsMutableNetwork.edges()).doesNotContain(E41); + + assertThat(network.adjacentNodes(N2)).isEmpty(); + assertThat(network.predecessors(N2)).isEmpty(); + assertThat(network.successors(N2)).isEmpty(); + assertThat(network.incidentEdges(N2)).isEmpty(); + assertThat(network.inEdges(N2)).isEmpty(); + assertThat(network.outEdges(N2)).isEmpty(); + assertThat(network.adjacentNodes(N4)).isEmpty(); + assertThat(network.predecessors(N4)).isEmpty(); + assertThat(network.successors(N4)).isEmpty(); + assertThat(network.incidentEdges(N4)).isEmpty(); + assertThat(network.inEdges(N4)).isEmpty(); + assertThat(network.outEdges(N4)).isEmpty(); + + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> network.adjacentNodes(N1))); + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> network.predecessors(N1))); + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> network.successors(N1))); + assertNodeNotInGraphErrorMessage( + assertThrows(IllegalArgumentException.class, () -> network.incidentEdges(N1))); } @Test @@ -686,25 +680,57 @@ public void removeNode_nodeNotPresent() { addNode(N1); ImmutableSet nodes = ImmutableSet.copyOf(networkAsMutableNetwork.nodes()); - assertFalse(networkAsMutableNetwork.removeNode(NODE_NOT_IN_GRAPH)); + assertThat(networkAsMutableNetwork.removeNode(NODE_NOT_IN_GRAPH)).isFalse(); assertThat(networkAsMutableNetwork.nodes()).containsExactlyElementsIn(nodes); } @Test - public void removeNode_queryAfterRemoval() { + public void queryAccessorSetAfterElementRemoval() { assume().that(graphIsMutable()).isTrue(); - addNode(N1); - @SuppressWarnings("unused") - Set unused = - networkAsMutableNetwork.adjacentNodes(N1); // ensure cache (if any) is populated - assertTrue(networkAsMutableNetwork.removeNode(N1)); - try { - networkAsMutableNetwork.adjacentNodes(N1); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + addEdge(N1, N2, E12); + Set n1AdjacentNodes = network.adjacentNodes(N1); + Set n2AdjacentNodes = network.adjacentNodes(N2); + Set n1Predecessors = network.predecessors(N1); + Set n2Predecessors = network.predecessors(N2); + Set n1Successors = network.successors(N1); + Set n2Successors = network.successors(N2); + Set n1IncidentEdges = network.incidentEdges(N1); + Set n2IncidentEdges = network.incidentEdges(N2); + Set n1InEdges = network.inEdges(N1); + Set n2InEdges = network.inEdges(N2); + Set n1OutEdges = network.outEdges(N1); + Set n2OutEdges = network.outEdges(N2); + Set e12AdjacentEdges = network.adjacentEdges(E12); + Set n12EdgesConnecting = network.edgesConnecting(N1, N2); + assertThat(networkAsMutableNetwork.removeNode(N1)).isTrue(); + + // The choice of the size() method to call here is arbitrary. We assume that if any of the Set + // methods executes the validation check, they all will, and thus we only need to test one of + // them to ensure that the validation check happens and has the expected behavior. + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1AdjacentNodes::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1Predecessors::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1Successors::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1IncidentEdges::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1InEdges::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n1OutEdges::size)); + assertEdgeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, e12AdjacentEdges::size)); + assertNodeRemovedFromGraphErrorMessage( + assertThrows(IllegalStateException.class, n12EdgesConnecting::size)); + + assertThat(n2AdjacentNodes).isEmpty(); + assertThat(n2Predecessors).isEmpty(); + assertThat(n2Successors).isEmpty(); + assertThat(n2IncidentEdges).isEmpty(); + assertThat(n2InEdges).isEmpty(); + assertThat(n2OutEdges).isEmpty(); } @Test @@ -712,8 +738,8 @@ public void removeEdge_existingEdge() { assume().that(graphIsMutable()).isTrue(); addEdge(N1, N2, E12); - assertTrue(networkAsMutableNetwork.removeEdge(E12)); - assertFalse(networkAsMutableNetwork.removeEdge(E12)); + assertThat(networkAsMutableNetwork.removeEdge(E12)).isTrue(); + assertThat(networkAsMutableNetwork.removeEdge(E12)).isFalse(); assertThat(networkAsMutableNetwork.edges()).doesNotContain(E12); assertThat(networkAsMutableNetwork.edgesConnecting(N1, N2)).isEmpty(); } @@ -726,7 +752,7 @@ public void removeEdge_oneOfMany() { addEdge(N1, N3, E13); addEdge(N1, N4, E14); assertThat(networkAsMutableNetwork.edges()).containsExactly(E12, E13, E14); - assertTrue(networkAsMutableNetwork.removeEdge(E13)); + assertThat(networkAsMutableNetwork.removeEdge(E13)).isTrue(); assertThat(networkAsMutableNetwork.edges()).containsExactly(E12, E14); } @@ -736,7 +762,7 @@ public void removeEdge_edgeNotPresent() { addEdge(N1, N2, E12); ImmutableSet edges = ImmutableSet.copyOf(networkAsMutableNetwork.edges()); - assertFalse(networkAsMutableNetwork.removeEdge(EDGE_NOT_IN_GRAPH)); + assertThat(networkAsMutableNetwork.removeEdge(EDGE_NOT_IN_GRAPH)).isFalse(); assertThat(networkAsMutableNetwork.edges()).containsExactlyElementsIn(edges); } @@ -748,13 +774,10 @@ public void removeEdge_queryAfterRemoval() { @SuppressWarnings("unused") EndpointPair unused = networkAsMutableNetwork.incidentNodes(E12); // ensure cache (if any) is populated - assertTrue(networkAsMutableNetwork.removeEdge(E12)); - try { - networkAsMutableNetwork.incidentNodes(E12); - fail(ERROR_EDGE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertEdgeNotInGraphErrorMessage(e); - } + assertThat(networkAsMutableNetwork.removeEdge(E12)).isTrue(); + assertEdgeNotInGraphErrorMessage( + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.incidentNodes(E12))); } @Test @@ -764,7 +787,7 @@ public void removeEdge_parallelEdge() { addEdge(N1, N2, E12); addEdge(N1, N2, E12_A); - assertTrue(networkAsMutableNetwork.removeEdge(E12_A)); + assertThat(networkAsMutableNetwork.removeEdge(E12_A)).isTrue(); assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12); } @@ -777,15 +800,14 @@ public void removeEdge_parallelSelfLoopEdge() { addEdge(N1, N1, E11); addEdge(N1, N1, E11_A); addEdge(N1, N2, E12); - assertTrue(networkAsMutableNetwork.removeEdge(E11_A)); + assertThat(networkAsMutableNetwork.removeEdge(E11_A)).isTrue(); assertThat(network.edgesConnecting(N1, N1)).containsExactly(E11); assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12); - assertTrue(networkAsMutableNetwork.removeEdge(E11)); + assertThat(networkAsMutableNetwork.removeEdge(E11)).isTrue(); assertThat(network.edgesConnecting(N1, N1)).isEmpty(); assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12); } - @Test public void concurrentIteration() throws Exception { addEdge(1, 2, "foo"); @@ -794,14 +816,14 @@ public void concurrentIteration() throws Exception { int threadCount = 20; ExecutorService executor = newFixedThreadPool(threadCount); - final CyclicBarrier barrier = new CyclicBarrier(threadCount); + CyclicBarrier barrier = new CyclicBarrier(threadCount); ImmutableList.Builder> futures = ImmutableList.builder(); for (int i = 0; i < threadCount; i++) { futures.add( executor.submit( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Object call() throws Exception { + public @Nullable Void call() throws Exception { barrier.await(); Integer first = network.nodes().iterator().next(); for (Integer node : network.nodes()) { @@ -835,7 +857,7 @@ public Object call() throws Exception { * synchronization actions.) * * All that said: I haven't actually managed to make this particular test produce a TSAN error - * for the field accesses in MapIteratorCache. This teset *has* found other TSAN errors, + * for the field accesses in MapIteratorCache. This test *has* found other TSAN errors, * including in MapRetrievalCache, so I'm not sure why this one is different. I did at least * confirm that my change to MapIteratorCache fixes the TSAN error in the (larger) test it was * originally reported in. diff --git a/android/guava-tests/test/com/google/common/graph/AbstractStandardDirectedGraphTest.java b/android/guava-tests/test/com/google/common/graph/AbstractStandardDirectedGraphTest.java index c50a7da673a5..313879f9f6ab 100644 --- a/android/guava-tests/test/com/google/common/graph/AbstractStandardDirectedGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/AbstractStandardDirectedGraphTest.java @@ -19,15 +19,16 @@ import static com.google.common.graph.GraphConstants.ENDPOINTS_MISMATCH; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; /** * Abstract base class for testing directed {@link Graph} implementations defined in this package. */ +@NullUnmarked public abstract class AbstractStandardDirectedGraphTest extends AbstractGraphTest { @Override @@ -36,13 +37,9 @@ public void nodes_checkReturnedSetMutability() { assume().that(graphIsMutable()).isTrue(); Set nodes = graph.nodes(); - try { - nodes.add(N2); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - addNode(N1); - assertThat(graph.nodes()).containsExactlyElementsIn(nodes); - } + assertThrows(UnsupportedOperationException.class, () -> nodes.add(N2)); + addNode(N1); + assertThat(graph.nodes()).containsExactlyElementsIn(nodes); } @Override @@ -52,13 +49,9 @@ public void adjacentNodes_checkReturnedSetMutability() { addNode(N1); Set adjacentNodes = graph.adjacentNodes(N1); - try { - adjacentNodes.add(N2); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - putEdge(N1, N2); - assertThat(graph.adjacentNodes(N1)).containsExactlyElementsIn(adjacentNodes); - } + assertThrows(UnsupportedOperationException.class, () -> adjacentNodes.add(N2)); + putEdge(N1, N2); + assertThat(graph.adjacentNodes(N1)).containsExactlyElementsIn(adjacentNodes); } @Override @@ -68,13 +61,9 @@ public void predecessors_checkReturnedSetMutability() { addNode(N2); Set predecessors = graph.predecessors(N2); - try { - predecessors.add(N1); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - putEdge(N1, N2); - assertThat(graph.predecessors(N2)).containsExactlyElementsIn(predecessors); - } + assertThrows(UnsupportedOperationException.class, () -> predecessors.add(N1)); + putEdge(N1, N2); + assertThat(graph.predecessors(N2)).containsExactlyElementsIn(predecessors); } @Override @@ -84,13 +73,9 @@ public void successors_checkReturnedSetMutability() { addNode(N1); Set successors = graph.successors(N1); - try { - successors.add(N2); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - putEdge(N1, N2); - assertThat(successors).containsExactlyElementsIn(graph.successors(N1)); - } + assertThrows(UnsupportedOperationException.class, () -> successors.add(N2)); + putEdge(N1, N2); + assertThat(successors).containsExactlyElementsIn(graph.successors(N1)); } @Override @@ -100,13 +85,10 @@ public void incidentEdges_checkReturnedSetMutability() { addNode(N1); Set> incidentEdges = graph.incidentEdges(N1); - try { - incidentEdges.add(EndpointPair.ordered(N1, N2)); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - putEdge(N1, N2); - assertThat(incidentEdges).containsExactlyElementsIn(graph.incidentEdges(N1)); - } + assertThrows( + UnsupportedOperationException.class, () -> incidentEdges.add(EndpointPair.ordered(N1, N2))); + putEdge(N1, N2); + assertThat(incidentEdges).containsExactlyElementsIn(graph.incidentEdges(N1)); } @Test @@ -168,6 +150,17 @@ public void hasEdgeConnecting_mismatch() { assertThat(graph.hasEdgeConnecting(EndpointPair.unordered(N2, N1))).isFalse(); } + @Test + public void hasEdgeConnecting_missingNode() { + // both nodes missing + assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(NODE_NOT_IN_GRAPH, NODE_NOT_IN_GRAPH))) + .isFalse(); + + // one node present, the other missing + assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(NODE_NOT_IN_GRAPH, N4))).isFalse(); + assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(N4, NODE_NOT_IN_GRAPH))).isFalse(); + } + @Test public void adjacentNodes_selfLoop() { assume().that(graph.allowsSelfLoops()).isTrue(); @@ -364,12 +357,9 @@ public void putEdge_orderMismatch() { assume().that(graphIsMutable()).isTrue(); EndpointPair endpoints = EndpointPair.unordered(N1, N2); - try { - graphAsMutableGraph.putEdge(endpoints); - fail("Expected IllegalArgumentException: " + ENDPOINTS_MISMATCH); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); - } + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> graphAsMutableGraph.putEdge(endpoints)); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } /** @@ -382,9 +372,9 @@ public void putEdge_nodesNotInGraph() { assume().that(graphIsMutable()).isTrue(); graphAsMutableGraph.addNode(N1); - assertTrue(graphAsMutableGraph.putEdge(N1, N5)); - assertTrue(graphAsMutableGraph.putEdge(N4, N1)); - assertTrue(graphAsMutableGraph.putEdge(N2, N3)); + assertThat(graphAsMutableGraph.putEdge(N1, N5)).isTrue(); + assertThat(graphAsMutableGraph.putEdge(N4, N1)).isTrue(); + assertThat(graphAsMutableGraph.putEdge(N2, N3)).isTrue(); assertThat(graph.nodes()).containsExactly(N1, N5, N4, N2, N3).inOrder(); assertThat(graph.successors(N1)).containsExactly(N5); assertThat(graph.successors(N2)).containsExactly(N3); @@ -398,12 +388,9 @@ public void putEdge_doesntAllowSelfLoops() { assume().that(graphIsMutable()).isTrue(); assume().that(graph.allowsSelfLoops()).isFalse(); - try { - graphAsMutableGraph.putEdge(N1, N1); - fail(ERROR_ADDED_SELF_LOOP); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); - } + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> graphAsMutableGraph.putEdge(N1, N1)); + assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); } @Test @@ -449,12 +436,10 @@ public void removeEdge_orderMismatch() { putEdge(N1, N2); EndpointPair endpoints = EndpointPair.unordered(N1, N2); - try { - graphAsMutableGraph.removeEdge(endpoints); - fail("Expected IllegalArgumentException: " + ENDPOINTS_MISMATCH); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> graphAsMutableGraph.removeEdge(endpoints)); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test diff --git a/android/guava-tests/test/com/google/common/graph/AbstractStandardDirectedNetworkTest.java b/android/guava-tests/test/com/google/common/graph/AbstractStandardDirectedNetworkTest.java index 98bc2fd6540b..acf39ef7e870 100644 --- a/android/guava-tests/test/com/google/common/graph/AbstractStandardDirectedNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/AbstractStandardDirectedNetworkTest.java @@ -20,18 +20,20 @@ import static com.google.common.graph.TestUtil.assertEdgeNotInGraphErrorMessage; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableSet; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.After; import org.junit.Test; /** * Abstract base class for testing directed {@link Network} implementations defined in this package. */ +@NullUnmarked public abstract class AbstractStandardDirectedNetworkTest extends AbstractNetworkTest { @After @@ -64,13 +66,9 @@ public void nodes_checkReturnedSetMutability() { assume().that(graphIsMutable()).isTrue(); Set nodes = network.nodes(); - try { - nodes.add(N2); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addNode(N1); - assertThat(network.nodes()).containsExactlyElementsIn(nodes); - } + assertThrows(UnsupportedOperationException.class, () -> nodes.add(N2)); + addNode(N1); + assertThat(network.nodes()).containsExactlyElementsIn(nodes); } @Override @@ -79,13 +77,9 @@ public void edges_checkReturnedSetMutability() { assume().that(graphIsMutable()).isTrue(); Set edges = network.edges(); - try { - edges.add(E12); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.edges()).containsExactlyElementsIn(edges); - } + assertThrows(UnsupportedOperationException.class, () -> edges.add(E12)); + addEdge(N1, N2, E12); + assertThat(network.edges()).containsExactlyElementsIn(edges); } @Override @@ -95,13 +89,9 @@ public void incidentEdges_checkReturnedSetMutability() { addNode(N1); Set incidentEdges = network.incidentEdges(N1); - try { - incidentEdges.add(E12); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.incidentEdges(N1)).containsExactlyElementsIn(incidentEdges); - } + assertThrows(UnsupportedOperationException.class, () -> incidentEdges.add(E12)); + addEdge(N1, N2, E12); + assertThat(network.incidentEdges(N1)).containsExactlyElementsIn(incidentEdges); } @Override @@ -111,13 +101,9 @@ public void adjacentNodes_checkReturnedSetMutability() { addNode(N1); Set adjacentNodes = network.adjacentNodes(N1); - try { - adjacentNodes.add(N2); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.adjacentNodes(N1)).containsExactlyElementsIn(adjacentNodes); - } + assertThrows(UnsupportedOperationException.class, () -> adjacentNodes.add(N2)); + addEdge(N1, N2, E12); + assertThat(network.adjacentNodes(N1)).containsExactlyElementsIn(adjacentNodes); } @Override @@ -143,13 +129,9 @@ public void edgesConnecting_checkReturnedSetMutability() { addNode(N1); addNode(N2); Set edgesConnecting = network.edgesConnecting(N1, N2); - try { - edgesConnecting.add(E23); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.edgesConnecting(N1, N2)).containsExactlyElementsIn(edgesConnecting); - } + assertThrows(UnsupportedOperationException.class, () -> edgesConnecting.add(E23)); + addEdge(N1, N2, E12); + assertThat(network.edgesConnecting(N1, N2)).containsExactlyElementsIn(edgesConnecting); } @Override @@ -159,13 +141,9 @@ public void inEdges_checkReturnedSetMutability() { addNode(N2); Set inEdges = network.inEdges(N2); - try { - inEdges.add(E12); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.inEdges(N2)).containsExactlyElementsIn(inEdges); - } + assertThrows(UnsupportedOperationException.class, () -> inEdges.add(E12)); + addEdge(N1, N2, E12); + assertThat(network.inEdges(N2)).containsExactlyElementsIn(inEdges); } @Override @@ -175,13 +153,9 @@ public void outEdges_checkReturnedSetMutability() { addNode(N1); Set outEdges = network.outEdges(N1); - try { - outEdges.add(E12); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.outEdges(N1)).containsExactlyElementsIn(outEdges); - } + assertThrows(UnsupportedOperationException.class, () -> outEdges.add(E12)); + addEdge(N1, N2, E12); + assertThat(network.outEdges(N1)).containsExactlyElementsIn(outEdges); } @Override @@ -191,13 +165,9 @@ public void predecessors_checkReturnedSetMutability() { addNode(N2); Set predecessors = network.predecessors(N2); - try { - predecessors.add(N1); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.predecessors(N2)).containsExactlyElementsIn(predecessors); - } + assertThrows(UnsupportedOperationException.class, () -> predecessors.add(N1)); + addEdge(N1, N2, E12); + assertThat(network.predecessors(N2)).containsExactlyElementsIn(predecessors); } @Override @@ -207,13 +177,9 @@ public void successors_checkReturnedSetMutability() { addNode(N1); Set successors = network.successors(N1); - try { - successors.add(N2); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(successors).containsExactlyElementsIn(network.successors(N1)); - } + assertThrows(UnsupportedOperationException.class, () -> successors.add(N2)); + addEdge(N1, N2, E12); + assertThat(successors).containsExactlyElementsIn(network.successors(N1)); } @Test @@ -228,23 +194,25 @@ public void edges_containsOrderMismatch() { @Test public void edgesConnecting_orderMismatch() { addEdge(N1, N2, E12); - try { - Set unused = network.edgesConnecting(EndpointPair.unordered(N1, N2)); - fail("Expected IllegalArgumentException: " + ENDPOINTS_MISMATCH); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> { + Set unused = network.edgesConnecting(EndpointPair.unordered(N1, N2)); + }); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test public void edgeConnectingOrNull_orderMismatch() { addEdge(N1, N2, E12); - try { - String unused = network.edgeConnectingOrNull(EndpointPair.unordered(N1, N2)); - fail("Expected IllegalArgumentException: " + ENDPOINTS_MISMATCH); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> { + String unused = network.edgeConnectingOrNull(EndpointPair.unordered(N1, N2)); + }); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Override @@ -304,12 +272,11 @@ public void source_oneEdge() { @Test public void source_edgeNotInGraph() { - try { - network.incidentNodes(EDGE_NOT_IN_GRAPH).source(); - fail(ERROR_EDGE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertEdgeNotInGraphErrorMessage(e); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> network.incidentNodes(EDGE_NOT_IN_GRAPH).source()); + assertEdgeNotInGraphErrorMessage(e); } @Test @@ -320,12 +287,11 @@ public void target_oneEdge() { @Test public void target_edgeNotInGraph() { - try { - network.incidentNodes(EDGE_NOT_IN_GRAPH).target(); - fail(ERROR_EDGE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertEdgeNotInGraphErrorMessage(e); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> network.incidentNodes(EDGE_NOT_IN_GRAPH).target()); + assertEdgeNotInGraphErrorMessage(e); } @Test @@ -516,20 +482,12 @@ public void addEdge_existingEdgeBetweenDifferentNodes() { assume().that(graphIsMutable()).isTrue(); addEdge(N1, N2, E12); - try { - // Edge between totally different nodes - networkAsMutableNetwork.addEdge(N4, N5, E12); - fail(ERROR_ADDED_EXISTING_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); - } - try { - // Edge between same nodes but in reverse direction - addEdge(N2, N1, E12); - fail(ERROR_ADDED_EXISTING_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N4, N5, E12)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); + e = assertThrows(IllegalArgumentException.class, () -> addEdge(N2, N1, E12)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); } @Test @@ -538,12 +496,11 @@ public void addEdge_parallelEdge_notAllowed() { assume().that(network.allowsParallelEdges()).isFalse(); addEdge(N1, N2, E12); - try { - networkAsMutableNetwork.addEdge(N1, N2, EDGE_NOT_IN_GRAPH); - fail(ERROR_ADDED_PARALLEL_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> networkAsMutableNetwork.addEdge(N1, N2, EDGE_NOT_IN_GRAPH)); + assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); } @Test @@ -551,8 +508,8 @@ public void addEdge_parallelEdge_allowsParallelEdges() { assume().that(graphIsMutable()).isTrue(); assume().that(network.allowsParallelEdges()).isTrue(); - assertTrue(networkAsMutableNetwork.addEdge(N1, N2, E12)); - assertTrue(networkAsMutableNetwork.addEdge(N1, N2, E12_A)); + assertThat(networkAsMutableNetwork.addEdge(N1, N2, E12)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N1, N2, E12_A)).isTrue(); assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12, E12_A); } @@ -561,12 +518,10 @@ public void addEdge_orderMismatch() { assume().that(graphIsMutable()).isTrue(); EndpointPair endpoints = EndpointPair.unordered(N1, N2); - try { - networkAsMutableNetwork.addEdge(endpoints, E12); - fail("Expected IllegalArgumentException: " + ENDPOINTS_MISMATCH); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(endpoints, E12)); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test @@ -574,12 +529,10 @@ public void addEdge_selfLoop_notAllowed() { assume().that(graphIsMutable()).isTrue(); assume().that(network.allowsSelfLoops()).isFalse(); - try { - networkAsMutableNetwork.addEdge(N1, N1, E11); - fail(ERROR_ADDED_SELF_LOOP); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N1, N1, E11)); + assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); } /** @@ -593,9 +546,9 @@ public void addEdge_nodesNotInGraph() { assume().that(graphIsMutable()).isTrue(); networkAsMutableNetwork.addNode(N1); - assertTrue(networkAsMutableNetwork.addEdge(N1, N5, E15)); - assertTrue(networkAsMutableNetwork.addEdge(N4, N1, E41)); - assertTrue(networkAsMutableNetwork.addEdge(N2, N3, E23)); + assertThat(networkAsMutableNetwork.addEdge(N1, N5, E15)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N4, N1, E41)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N2, N3, E23)).isTrue(); assertThat(network.nodes()).containsExactly(N1, N5, N4, N2, N3); assertThat(network.edges()).containsExactly(E15, E41, E23); assertThat(network.edgesConnecting(N1, N5)).containsExactly(E15); @@ -632,25 +585,19 @@ public void addEdge_existingEdgeBetweenDifferentNodes_selfLoops() { assume().that(network.allowsSelfLoops()).isTrue(); addEdge(N1, N1, E11); - try { - networkAsMutableNetwork.addEdge(N1, N2, E11); - fail("Reusing an existing self-loop edge to connect different nodes succeeded"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_REUSE_EDGE); - } - try { - networkAsMutableNetwork.addEdge(N2, N2, E11); - fail("Reusing an existing self-loop edge to make a different self-loop edge succeeded"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_REUSE_EDGE); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N1, N2, E11)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); + e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N2, N2, E11)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); addEdge(N1, N2, E12); - try { - networkAsMutableNetwork.addEdge(N1, N1, E12); - fail("Reusing an existing edge to add a self-loop edge between different nodes succeeded"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_REUSE_EDGE); - } + e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N1, N1, E12)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); } @Test @@ -660,12 +607,11 @@ public void addEdge_parallelSelfLoopEdge_notAllowed() { assume().that(network.allowsParallelEdges()).isFalse(); addEdge(N1, N1, E11); - try { - networkAsMutableNetwork.addEdge(N1, N1, EDGE_NOT_IN_GRAPH); - fail("Adding a parallel self-loop edge succeeded"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_PARALLEL_EDGE); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> networkAsMutableNetwork.addEdge(N1, N1, EDGE_NOT_IN_GRAPH)); + assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); } @Test @@ -674,8 +620,8 @@ public void addEdge_parallelSelfLoopEdge_allowsParallelEdges() { assume().that(network.allowsSelfLoops()).isTrue(); assume().that(network.allowsParallelEdges()).isTrue(); - assertTrue(networkAsMutableNetwork.addEdge(N1, N1, E11)); - assertTrue(networkAsMutableNetwork.addEdge(N1, N1, E11_A)); + assertThat(networkAsMutableNetwork.addEdge(N1, N1, E11)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N1, N1, E11_A)).isTrue(); assertThat(network.edgesConnecting(N1, N1)).containsExactly(E11, E11_A); } diff --git a/android/guava-tests/test/com/google/common/graph/AbstractStandardUndirectedGraphTest.java b/android/guava-tests/test/com/google/common/graph/AbstractStandardUndirectedGraphTest.java index a483f42d1720..d243858cc391 100644 --- a/android/guava-tests/test/com/google/common/graph/AbstractStandardUndirectedGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/AbstractStandardUndirectedGraphTest.java @@ -18,17 +18,18 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.testing.EqualsTester; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.After; import org.junit.Test; /** * Abstract base class for testing undirected {@link Graph} implementations defined in this package. */ +@NullUnmarked public abstract class AbstractStandardUndirectedGraphTest extends AbstractGraphTest { @After @@ -47,13 +48,9 @@ public void nodes_checkReturnedSetMutability() { assume().that(graphIsMutable()).isTrue(); Set nodes = graph.nodes(); - try { - nodes.add(N2); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - addNode(N1); - assertThat(graph.nodes()).containsExactlyElementsIn(nodes); - } + assertThrows(UnsupportedOperationException.class, () -> nodes.add(N2)); + addNode(N1); + assertThat(graph.nodes()).containsExactlyElementsIn(nodes); } @Override @@ -63,13 +60,9 @@ public void adjacentNodes_checkReturnedSetMutability() { addNode(N1); Set adjacentNodes = graph.adjacentNodes(N1); - try { - adjacentNodes.add(N2); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - putEdge(N1, N2); - assertThat(graph.adjacentNodes(N1)).containsExactlyElementsIn(adjacentNodes); - } + assertThrows(UnsupportedOperationException.class, () -> adjacentNodes.add(N2)); + putEdge(N1, N2); + assertThat(graph.adjacentNodes(N1)).containsExactlyElementsIn(adjacentNodes); } @Override @@ -79,13 +72,9 @@ public void predecessors_checkReturnedSetMutability() { addNode(N2); Set predecessors = graph.predecessors(N2); - try { - predecessors.add(N1); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - putEdge(N1, N2); - assertThat(graph.predecessors(N2)).containsExactlyElementsIn(predecessors); - } + assertThrows(UnsupportedOperationException.class, () -> predecessors.add(N1)); + putEdge(N1, N2); + assertThat(graph.predecessors(N2)).containsExactlyElementsIn(predecessors); } @Override @@ -95,13 +84,9 @@ public void successors_checkReturnedSetMutability() { addNode(N1); Set successors = graph.successors(N1); - try { - successors.add(N2); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - putEdge(N1, N2); - assertThat(graph.successors(N1)).containsExactlyElementsIn(successors); - } + assertThrows(UnsupportedOperationException.class, () -> successors.add(N2)); + putEdge(N1, N2); + assertThat(graph.successors(N1)).containsExactlyElementsIn(successors); } @Override @@ -111,13 +96,11 @@ public void incidentEdges_checkReturnedSetMutability() { addNode(N1); Set> incidentEdges = graph.incidentEdges(N1); - try { - incidentEdges.add(EndpointPair.unordered(N1, N2)); - fail(ERROR_MODIFIABLE_SET); - } catch (UnsupportedOperationException e) { - putEdge(N1, N2); - assertThat(incidentEdges).containsExactlyElementsIn(graph.incidentEdges(N1)); - } + assertThrows( + UnsupportedOperationException.class, + () -> incidentEdges.add(EndpointPair.unordered(N1, N2))); + putEdge(N1, N2); + assertThat(incidentEdges).containsExactlyElementsIn(graph.incidentEdges(N1)); } @Test @@ -166,8 +149,20 @@ public void hasEdgeConnecting_correct() { @Test public void hasEdgeConnecting_mismatch() { putEdge(N1, N2); - assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(N1, N2))).isTrue(); - assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(N2, N1))).isTrue(); + assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(N1, N2))).isFalse(); + assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(N2, N1))).isFalse(); + } + + @Test + public void hasEdgeConnecting_missingNode() { + // both nodes missing + assertThat( + graph.hasEdgeConnecting(EndpointPair.unordered(NODE_NOT_IN_GRAPH, NODE_NOT_IN_GRAPH))) + .isFalse(); + + // one node present, the other missing + assertThat(graph.hasEdgeConnecting(EndpointPair.unordered(NODE_NOT_IN_GRAPH, N4))).isFalse(); + assertThat(graph.hasEdgeConnecting(EndpointPair.unordered(N4, NODE_NOT_IN_GRAPH))).isFalse(); } @Test @@ -364,9 +359,9 @@ public void putEdge_nodesNotInGraph() { assume().that(graphIsMutable()).isTrue(); graphAsMutableGraph.addNode(N1); - assertTrue(graphAsMutableGraph.putEdge(N1, N5)); - assertTrue(graphAsMutableGraph.putEdge(N4, N1)); - assertTrue(graphAsMutableGraph.putEdge(N2, N3)); + assertThat(graphAsMutableGraph.putEdge(N1, N5)).isTrue(); + assertThat(graphAsMutableGraph.putEdge(N4, N1)).isTrue(); + assertThat(graphAsMutableGraph.putEdge(N2, N3)).isTrue(); assertThat(graph.nodes()).containsExactly(N1, N5, N4, N2, N3).inOrder(); assertThat(graph.adjacentNodes(N1)).containsExactly(N4, N5); assertThat(graph.adjacentNodes(N2)).containsExactly(N3); @@ -380,12 +375,9 @@ public void putEdge_doesntAllowSelfLoops() { assume().that(graphIsMutable()).isTrue(); assume().that(graph.allowsSelfLoops()).isFalse(); - try { - putEdge(N1, N1); - fail(ERROR_ADDED_SELF_LOOP); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); - } + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> putEdge(N1, N1)); + assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); } @Test diff --git a/android/guava-tests/test/com/google/common/graph/AbstractStandardUndirectedNetworkTest.java b/android/guava-tests/test/com/google/common/graph/AbstractStandardUndirectedNetworkTest.java index 5cda1c1bb767..516a99cb59dd 100644 --- a/android/guava-tests/test/com/google/common/graph/AbstractStandardUndirectedNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/AbstractStandardUndirectedNetworkTest.java @@ -16,14 +16,16 @@ package com.google.common.graph; +import static com.google.common.graph.GraphConstants.ENDPOINTS_MISMATCH; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableSet; import com.google.common.testing.EqualsTester; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.After; import org.junit.Test; @@ -31,6 +33,7 @@ * Abstract base class for testing undirected {@link Network} implementations defined in this * package. */ +@NullUnmarked public abstract class AbstractStandardUndirectedNetworkTest extends AbstractNetworkTest { private static final EndpointPair ENDPOINTS_N1N2 = EndpointPair.ordered(N1, N2); private static final EndpointPair ENDPOINTS_N2N1 = EndpointPair.ordered(N2, N1); @@ -58,26 +61,18 @@ public void validateUndirectedEdges() { @Test public void nodes_checkReturnedSetMutability() { Set nodes = network.nodes(); - try { - nodes.add(N2); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addNode(N1); - assertThat(network.nodes()).containsExactlyElementsIn(nodes); - } + assertThrows(UnsupportedOperationException.class, () -> nodes.add(N2)); + addNode(N1); + assertThat(network.nodes()).containsExactlyElementsIn(nodes); } @Override @Test public void edges_checkReturnedSetMutability() { Set edges = network.edges(); - try { - edges.add(E12); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.edges()).containsExactlyElementsIn(edges); - } + assertThrows(UnsupportedOperationException.class, () -> edges.add(E12)); + addEdge(N1, N2, E12); + assertThat(network.edges()).containsExactlyElementsIn(edges); } @Override @@ -85,13 +80,9 @@ public void edges_checkReturnedSetMutability() { public void incidentEdges_checkReturnedSetMutability() { addNode(N1); Set incidentEdges = network.incidentEdges(N1); - try { - incidentEdges.add(E12); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.incidentEdges(N1)).containsExactlyElementsIn(incidentEdges); - } + assertThrows(UnsupportedOperationException.class, () -> incidentEdges.add(E12)); + addEdge(N1, N2, E12); + assertThat(network.incidentEdges(N1)).containsExactlyElementsIn(incidentEdges); } @Override @@ -99,13 +90,9 @@ public void incidentEdges_checkReturnedSetMutability() { public void adjacentNodes_checkReturnedSetMutability() { addNode(N1); Set adjacentNodes = network.adjacentNodes(N1); - try { - adjacentNodes.add(N2); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.adjacentNodes(N1)).containsExactlyElementsIn(adjacentNodes); - } + assertThrows(UnsupportedOperationException.class, () -> adjacentNodes.add(N2)); + addEdge(N1, N2, E12); + assertThat(network.adjacentNodes(N1)).containsExactlyElementsIn(adjacentNodes); } @Override @@ -127,13 +114,9 @@ public void edgesConnecting_checkReturnedSetMutability() { addNode(N1); addNode(N2); Set edgesConnecting = network.edgesConnecting(N1, N2); - try { - edgesConnecting.add(E23); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.edgesConnecting(N1, N2)).containsExactlyElementsIn(edgesConnecting); - } + assertThrows(UnsupportedOperationException.class, () -> edgesConnecting.add(E23)); + addEdge(N1, N2, E12); + assertThat(network.edgesConnecting(N1, N2)).containsExactlyElementsIn(edgesConnecting); } @Override @@ -141,13 +124,9 @@ public void edgesConnecting_checkReturnedSetMutability() { public void inEdges_checkReturnedSetMutability() { addNode(N2); Set inEdges = network.inEdges(N2); - try { - inEdges.add(E12); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.inEdges(N2)).containsExactlyElementsIn(inEdges); - } + assertThrows(UnsupportedOperationException.class, () -> inEdges.add(E12)); + addEdge(N1, N2, E12); + assertThat(network.inEdges(N2)).containsExactlyElementsIn(inEdges); } @Override @@ -155,13 +134,9 @@ public void inEdges_checkReturnedSetMutability() { public void outEdges_checkReturnedSetMutability() { addNode(N1); Set outEdges = network.outEdges(N1); - try { - outEdges.add(E12); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.outEdges(N1)).containsExactlyElementsIn(outEdges); - } + assertThrows(UnsupportedOperationException.class, () -> outEdges.add(E12)); + addEdge(N1, N2, E12); + assertThat(network.outEdges(N1)).containsExactlyElementsIn(outEdges); } @Override @@ -169,13 +144,9 @@ public void outEdges_checkReturnedSetMutability() { public void predecessors_checkReturnedSetMutability() { addNode(N2); Set predecessors = network.predecessors(N2); - try { - predecessors.add(N1); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.predecessors(N2)).containsExactlyElementsIn(predecessors); - } + assertThrows(UnsupportedOperationException.class, () -> predecessors.add(N1)); + addEdge(N1, N2, E12); + assertThat(network.predecessors(N2)).containsExactlyElementsIn(predecessors); } @Override @@ -183,27 +154,40 @@ public void predecessors_checkReturnedSetMutability() { public void successors_checkReturnedSetMutability() { addNode(N1); Set successors = network.successors(N1); - try { - successors.add(N2); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - addEdge(N1, N2, E12); - assertThat(network.successors(N1)).containsExactlyElementsIn(successors); - } + assertThrows(UnsupportedOperationException.class, () -> successors.add(N2)); + addEdge(N1, N2, E12); + assertThat(network.successors(N1)).containsExactlyElementsIn(successors); } @Test public void edges_containsOrderMismatch() { addEdge(N1, N2, E12); - assertThat(network.asGraph().edges()).contains(ENDPOINTS_N2N1); - assertThat(network.asGraph().edges()).contains(ENDPOINTS_N1N2); + assertThat(network.asGraph().edges()).doesNotContain(ENDPOINTS_N2N1); + assertThat(network.asGraph().edges()).doesNotContain(ENDPOINTS_N1N2); + } + + @Test + public void edgesConnecting_orderMismatch() { + addEdge(N1, N2, E12); + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> { + Set unused = network.edgesConnecting(ENDPOINTS_N1N2); + }); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test public void edgeConnectingOrNull_orderMismatch() { addEdge(N1, N2, E12); - assertThat(network.edgeConnectingOrNull(ENDPOINTS_N2N1)).isEqualTo(E12); - assertThat(network.edgeConnectingOrNull(ENDPOINTS_N1N2)).isEqualTo(E12); + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> { + String unused = network.edgeConnectingOrNull(ENDPOINTS_N1N2); + }); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test @@ -413,13 +397,10 @@ public void addEdge_existingEdgeBetweenDifferentNodes() { assume().that(graphIsMutable()).isTrue(); addEdge(N1, N2, E12); - try { - // Edge between totally different nodes - networkAsMutableNetwork.addEdge(N4, N5, E12); - fail(ERROR_ADDED_EXISTING_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_REUSE_EDGE); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N4, N5, E12)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); } @Test @@ -428,18 +409,16 @@ public void addEdge_parallelEdge_notAllowed() { assume().that(network.allowsParallelEdges()).isFalse(); addEdge(N1, N2, E12); - try { - networkAsMutableNetwork.addEdge(N1, N2, EDGE_NOT_IN_GRAPH); - fail(ERROR_ADDED_PARALLEL_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_PARALLEL_EDGE); - } - try { - networkAsMutableNetwork.addEdge(N2, N1, EDGE_NOT_IN_GRAPH); - fail(ERROR_ADDED_PARALLEL_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_PARALLEL_EDGE); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> networkAsMutableNetwork.addEdge(N1, N2, EDGE_NOT_IN_GRAPH)); + assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); + e = + assertThrows( + IllegalArgumentException.class, + () -> networkAsMutableNetwork.addEdge(N2, N1, EDGE_NOT_IN_GRAPH)); + assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); } @Test @@ -447,9 +426,9 @@ public void addEdge_parallelEdge_allowsParallelEdges() { assume().that(graphIsMutable()).isTrue(); assume().that(network.allowsParallelEdges()).isTrue(); - assertTrue(networkAsMutableNetwork.addEdge(N1, N2, E12)); - assertTrue(networkAsMutableNetwork.addEdge(N2, N1, E21)); - assertTrue(networkAsMutableNetwork.addEdge(N1, N2, E12_A)); + assertThat(networkAsMutableNetwork.addEdge(N1, N2, E12)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N2, N1, E21)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N1, N2, E12_A)).isTrue(); assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12, E12_A, E21); } @@ -458,7 +437,10 @@ public void addEdge_orderMismatch() { assume().that(graphIsMutable()).isTrue(); EndpointPair endpoints = EndpointPair.ordered(N1, N2); - assertThat(networkAsMutableNetwork.addEdge(endpoints, E12)).isTrue(); + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(endpoints, E12)); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test @@ -466,12 +448,10 @@ public void addEdge_selfLoop_notAllowed() { assume().that(graphIsMutable()).isTrue(); assume().that(network.allowsSelfLoops()).isFalse(); - try { - networkAsMutableNetwork.addEdge(N1, N1, E11); - fail(ERROR_ADDED_SELF_LOOP); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N1, N1, E11)); + assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); } /** @@ -485,9 +465,9 @@ public void addEdge_nodesNotInGraph() { assume().that(graphIsMutable()).isTrue(); networkAsMutableNetwork.addNode(N1); - assertTrue(networkAsMutableNetwork.addEdge(N1, N5, E15)); - assertTrue(networkAsMutableNetwork.addEdge(N4, N1, E41)); - assertTrue(networkAsMutableNetwork.addEdge(N2, N3, E23)); + assertThat(networkAsMutableNetwork.addEdge(N1, N5, E15)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N4, N1, E41)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N2, N3, E23)).isTrue(); assertThat(network.nodes()).containsExactly(N1, N5, N4, N2, N3); assertThat(network.edges()).containsExactly(E15, E41, E23); assertThat(network.edgesConnecting(N1, N5)).containsExactly(E15); @@ -523,25 +503,19 @@ public void addEdge_existingEdgeBetweenDifferentNodes_selfLoops() { assume().that(network.allowsSelfLoops()).isTrue(); addEdge(N1, N1, E11); - try { - networkAsMutableNetwork.addEdge(N1, N2, E11); - fail("Reusing an existing self-loop edge to connect different nodes succeeded"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_REUSE_EDGE); - } - try { - networkAsMutableNetwork.addEdge(N2, N2, E11); - fail("Reusing an existing self-loop edge to make a different self-loop edge succeeded"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_REUSE_EDGE); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N1, N2, E11)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); + e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N2, N2, E11)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); addEdge(N1, N2, E12); - try { - networkAsMutableNetwork.addEdge(N1, N1, E12); - fail("Reusing an existing edge to add a self-loop edge between different nodes succeeded"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_REUSE_EDGE); - } + e = + assertThrows( + IllegalArgumentException.class, () -> networkAsMutableNetwork.addEdge(N1, N1, E12)); + assertThat(e).hasMessageThat().contains(ERROR_REUSE_EDGE); } @Test @@ -551,12 +525,11 @@ public void addEdge_parallelSelfLoopEdge_notAllowed() { assume().that(network.allowsParallelEdges()).isFalse(); addEdge(N1, N1, E11); - try { - networkAsMutableNetwork.addEdge(N1, N1, EDGE_NOT_IN_GRAPH); - fail("Adding a parallel self-loop edge succeeded"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_PARALLEL_EDGE); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> networkAsMutableNetwork.addEdge(N1, N1, EDGE_NOT_IN_GRAPH)); + assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); } @Test @@ -565,8 +538,8 @@ public void addEdge_parallelSelfLoopEdge_allowsParallelEdges() { assume().that(network.allowsSelfLoops()).isTrue(); assume().that(network.allowsParallelEdges()).isTrue(); - assertTrue(networkAsMutableNetwork.addEdge(N1, N1, E11)); - assertTrue(networkAsMutableNetwork.addEdge(N1, N1, E11_A)); + assertThat(networkAsMutableNetwork.addEdge(N1, N1, E11)).isTrue(); + assertThat(networkAsMutableNetwork.addEdge(N1, N1, E11_A)).isTrue(); assertThat(network.edgesConnecting(N1, N1)).containsExactly(E11, E11_A); } diff --git a/android/guava-tests/test/com/google/common/graph/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/graph/AndroidIncompatible.java index 239cafc8c8a2..489fea33f67d 100644 --- a/android/guava-tests/test/com/google/common/graph/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/graph/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/graph/DefaultNetworkImplementationsTest.java b/android/guava-tests/test/com/google/common/graph/DefaultNetworkImplementationsTest.java index eaddb949c002..bf5a387b0290 100644 --- a/android/guava-tests/test/com/google/common/graph/DefaultNetworkImplementationsTest.java +++ b/android/guava-tests/test/com/google/common/graph/DefaultNetworkImplementationsTest.java @@ -16,18 +16,17 @@ package com.google.common.graph; -import static com.google.common.graph.AbstractNetworkTest.ERROR_MODIFIABLE_COLLECTION; -import static com.google.common.graph.TestUtil.ERROR_NODE_NOT_IN_GRAPH; import static com.google.common.graph.TestUtil.EdgeType.DIRECTED; import static com.google.common.graph.TestUtil.EdgeType.UNDIRECTED; import static com.google.common.graph.TestUtil.assertNodeNotInGraphErrorMessage; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.graph.TestUtil.EdgeType; import java.util.Arrays; import java.util.Collection; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,6 +41,7 @@ @AndroidIncompatible // TODO(cpovirk): Figure out Android JUnit 4 support. Does it work with Gingerbread? @RunWith? @RunWith(Parameterized.class) +@NullUnmarked public final class DefaultNetworkImplementationsTest { private MutableNetwork network; private NetworkForTest networkForTest; @@ -89,24 +89,21 @@ public void edgesConnecting_disconnectedNodes() { public void edgesConnecting_nodesNotInGraph() { network.addNode(N1); network.addNode(N2); - try { - networkForTest.edgesConnecting(N1, NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } - try { - networkForTest.edgesConnecting(NODE_NOT_IN_GRAPH, N2); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } - try { - networkForTest.edgesConnecting(NODE_NOT_IN_GRAPH, NODE_NOT_IN_GRAPH); - fail(ERROR_NODE_NOT_IN_GRAPH); - } catch (IllegalArgumentException e) { - assertNodeNotInGraphErrorMessage(e); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> networkForTest.edgesConnecting(N1, NODE_NOT_IN_GRAPH)); + assertNodeNotInGraphErrorMessage(e); + e = + assertThrows( + IllegalArgumentException.class, + () -> networkForTest.edgesConnecting(NODE_NOT_IN_GRAPH, N2)); + assertNodeNotInGraphErrorMessage(e); + e = + assertThrows( + IllegalArgumentException.class, + () -> networkForTest.edgesConnecting(NODE_NOT_IN_GRAPH, NODE_NOT_IN_GRAPH)); + assertNodeNotInGraphErrorMessage(e); } @Test @@ -114,13 +111,9 @@ public void edgesConnecting_checkReturnedSetMutability() { network.addNode(N1); network.addNode(N2); Set edgesConnecting = network.edgesConnecting(N1, N2); - try { - edgesConnecting.add(E23); - fail(ERROR_MODIFIABLE_COLLECTION); - } catch (UnsupportedOperationException e) { - network.addEdge(N1, N2, E12); - assertThat(networkForTest.edgesConnecting(N1, N2)).containsExactlyElementsIn(edgesConnecting); - } + assertThrows(UnsupportedOperationException.class, () -> edgesConnecting.add(E23)); + network.addEdge(N1, N2, E12); + assertThat(networkForTest.edgesConnecting(N1, N2)).containsExactlyElementsIn(edgesConnecting); } @Test diff --git a/android/guava-tests/test/com/google/common/graph/ElementOrderTest.java b/android/guava-tests/test/com/google/common/graph/ElementOrderTest.java index 0557fb7babb9..a50b0400fe45 100644 --- a/android/guava-tests/test/com/google/common/graph/ElementOrderTest.java +++ b/android/guava-tests/test/com/google/common/graph/ElementOrderTest.java @@ -23,12 +23,14 @@ import com.google.common.collect.Ordering; import java.util.Comparator; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for ordering the elements of graphs. */ @RunWith(JUnit4.class) +@NullUnmarked public final class ElementOrderTest { // Node order tests @@ -150,7 +152,7 @@ public void edgeOrder_sorted() { // Combined node and edge order tests @Test - public void nodeOrderUnorderedandEdgesSorted() { + public void nodeOrderUnorderedAndEdgesSorted() { MutableNetwork network = NetworkBuilder.directed() .nodeOrder(unordered()) diff --git a/android/guava-tests/test/com/google/common/graph/EndpointPairTest.java b/android/guava-tests/test/com/google/common/graph/EndpointPairTest.java index 099be1d22bd0..6974f14b14d8 100644 --- a/android/guava-tests/test/com/google/common/graph/EndpointPairTest.java +++ b/android/guava-tests/test/com/google/common/graph/EndpointPairTest.java @@ -17,19 +17,21 @@ package com.google.common.graph; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.testing.EqualsTester; import java.util.Collection; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link EndpointPair} and {@link Graph#edges()}. */ @RunWith(JUnit4.class) +@NullUnmarked public final class EndpointPairTest { private static final Integer N0 = 0; private static final Integer N1 = 1; @@ -91,11 +93,7 @@ public void testAdjacentNode_nodeNotIncident() { for (MutableNetwork network : testNetworks) { network.addEdge(1, 2, "1-2"); EndpointPair endpointPair = network.incidentNodes("1-2"); - try { - endpointPair.adjacentNode(3); - fail("Should have rejected adjacentNode() called with a node not incident to edge."); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> endpointPair.adjacentNode(3)); } } @@ -195,11 +193,8 @@ public void endpointPair_unmodifiableView() { directedGraph.removeEdge(N2, N1); containsExactlySanityCheck(edges); - try { - edges.add(EndpointPair.ordered(N1, N2)); - fail("Set returned by edges() should be unmodifiable"); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> edges.add(EndpointPair.ordered(N1, N2))); } @Test @@ -214,8 +209,8 @@ public void endpointPair_undirected_contains() { assertThat(edges).contains(EndpointPair.unordered(N1, N2)); assertThat(edges).contains(EndpointPair.unordered(N2, N1)); // equal to unordered(N1, N2) - // ordered endpoints OK for undirected graph (because ordering is irrelevant) - assertThat(edges).contains(EndpointPair.ordered(N1, N2)); + // ordered endpoints not compatible with undirected graph + assertThat(edges).doesNotContain(EndpointPair.ordered(N1, N2)); assertThat(edges).doesNotContain(EndpointPair.unordered(N2, N2)); // edge not present assertThat(edges).doesNotContain(EndpointPair.unordered(N3, N4)); // nodes not in graph @@ -240,11 +235,13 @@ public void endpointPair_directed_contains() { assertThat(edges).doesNotContain(EndpointPair.ordered(N3, N4)); // nodes not in graph } + // We are testing our implementations of methods on Collection. + @SuppressWarnings({"CollectionSizeTruth", "CollectionContainsTruth"}) private static void containsExactlySanityCheck(Collection collection, Object... varargs) { - assertThat(collection).hasSize(varargs.length); + assertThat(collection.size()).isEqualTo(varargs.length); for (Object obj : varargs) { - assertThat(collection).contains(obj); + assertThat(collection.contains(obj)).isTrue(); } - assertThat(collection).containsExactly(varargs); + assertThat(ImmutableList.copyOf(collection.iterator())).containsExactlyElementsIn(varargs); } } diff --git a/android/guava-tests/test/com/google/common/graph/GraphEquivalenceTest.java b/android/guava-tests/test/com/google/common/graph/GraphEquivalenceTest.java index 38e3903aaa87..7d45f55913d5 100644 --- a/android/guava-tests/test/com/google/common/graph/GraphEquivalenceTest.java +++ b/android/guava-tests/test/com/google/common/graph/GraphEquivalenceTest.java @@ -23,6 +23,7 @@ import com.google.common.graph.TestUtil.EdgeType; import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -31,6 +32,7 @@ @AndroidIncompatible // TODO(cpovirk): Figure out Android JUnit 4 support. Does it work with Gingerbread? @RunWith? @RunWith(Parameterized.class) +@NullUnmarked public final class GraphEquivalenceTest { private static final Integer N1 = 1; private static final Integer N2 = 2; @@ -56,9 +58,8 @@ private static MutableGraph createGraph(EdgeType edgeType) { return GraphBuilder.undirected().allowsSelfLoops(true).build(); case DIRECTED: return GraphBuilder.directed().allowsSelfLoops(true).build(); - default: - throw new IllegalStateException("Unexpected edge type: " + edgeType); } + throw new IllegalStateException("Unexpected edge type: " + edgeType); } private static EdgeType oppositeType(EdgeType edgeType) { @@ -67,9 +68,8 @@ private static EdgeType oppositeType(EdgeType edgeType) { return EdgeType.DIRECTED; case DIRECTED: return EdgeType.UNDIRECTED; - default: - throw new IllegalStateException("Unexpected edge type: " + edgeType); } + throw new IllegalStateException("Unexpected edge type: " + edgeType); } @Test @@ -150,8 +150,6 @@ public void equivalent_edgeDirectionsDiffer() { case DIRECTED: assertThat(graph).isNotEqualTo(g2); break; - default: - throw new IllegalStateException("Unexpected edge type: " + edgeType); } } } diff --git a/android/guava-tests/test/com/google/common/graph/GraphMutationTest.java b/android/guava-tests/test/com/google/common/graph/GraphMutationTest.java index 82ff96756723..3d8d51d04466 100644 --- a/android/guava-tests/test/com/google/common/graph/GraphMutationTest.java +++ b/android/guava-tests/test/com/google/common/graph/GraphMutationTest.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Random; import java.util.RandomAccess; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -30,6 +31,7 @@ /** Tests for repeated node and edge addition and removal in a {@link Graph}. */ @RunWith(JUnit4.class) +@NullUnmarked public final class GraphMutationTest { private static final int NUM_TRIALS = 50; private static final int NUM_NODES = 100; diff --git a/android/guava-tests/test/com/google/common/graph/GraphPropertiesTest.java b/android/guava-tests/test/com/google/common/graph/GraphPropertiesTest.java index d81edaf6678c..bb1d25e77f99 100644 --- a/android/guava-tests/test/com/google/common/graph/GraphPropertiesTest.java +++ b/android/guava-tests/test/com/google/common/graph/GraphPropertiesTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; +import org.jspecify.annotations.NullUnmarked; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,6 +29,7 @@ /** Tests for {@link Graphs#hasCycle(Graph)} and {@link Graphs#hasCycle(Network)}. */ // TODO(user): Consider moving this to GraphsTest. @RunWith(JUnit4.class) +@NullUnmarked public class GraphPropertiesTest { ImmutableList> graphsToTest; Graph directedGraph; @@ -155,6 +157,17 @@ public void hasCycle_multipleCycles() { assertThat(hasCycle(undirectedGraph)).isTrue(); } + @Test + public void hasCycle_deepPathGraph() { + for (MutableGraph graph : graphsToTest) { + for (int i = 0; i < 100000; i++) { + graph.putEdge(i, i + 1); + } + } + assertThat(hasCycle(directedNetwork)).isFalse(); + assertThat(hasCycle(undirectedNetwork)).isFalse(); + } + @Test public void hasCycle_twoParallelEdges() { for (MutableNetwork network : networksToTest) { @@ -176,4 +189,15 @@ public void hasCycle_cyclicMultigraph() { assertThat(hasCycle(directedNetwork)).isTrue(); assertThat(hasCycle(undirectedNetwork)).isTrue(); } + + @Test + public void hasCycle_deepPathNetwork() { + for (MutableNetwork network : networksToTest) { + for (int i = 0; i < 100000; i++) { + network.addEdge(i, i + 1, Integer.toString(i)); + } + } + assertThat(hasCycle(directedNetwork)).isFalse(); + assertThat(hasCycle(undirectedNetwork)).isFalse(); + } } diff --git a/android/guava-tests/test/com/google/common/graph/GraphsTest.java b/android/guava-tests/test/com/google/common/graph/GraphsTest.java index a07def019c68..293ac4529273 100644 --- a/android/guava-tests/test/com/google/common/graph/GraphsTest.java +++ b/android/guava-tests/test/com/google/common/graph/GraphsTest.java @@ -16,16 +16,17 @@ package com.google.common.graph; +import static com.google.common.graph.Graphs.TransitiveClosureSelfLoopStrategy.ADD_SELF_LOOPS_ALWAYS; +import static com.google.common.graph.Graphs.TransitiveClosureSelfLoopStrategy.ADD_SELF_LOOPS_FOR_CYCLES; import static com.google.common.graph.Graphs.copyOf; import static com.google.common.graph.Graphs.inducedSubgraph; -import static com.google.common.graph.Graphs.reachableNodes; import static com.google.common.graph.Graphs.transitiveClosure; import static com.google.common.graph.Graphs.transpose; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableSet; -import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -35,6 +36,7 @@ * the missing nodes to the graph, then adds the edge between them. */ @RunWith(JUnit4.class) +@NullUnmarked public class GraphsTest { private static final Integer N1 = 1; private static final Integer N2 = 2; @@ -56,14 +58,10 @@ public class GraphsTest { // in one class (may be a utility class for error messages). private static final String ERROR_PARALLEL_EDGE = "connected by a different edge"; private static final String ERROR_NEGATIVE_COUNT = "is non-negative"; - private static final String ERROR_ADDED_PARALLEL_EDGE = - "Should not be allowed to add a parallel edge."; - private static final String ERROR_ADDED_SELF_LOOP = - "Should not be allowed to add a self-loop edge."; static final String ERROR_SELF_LOOP = "self-loops are not allowed"; @Test - public void transitiveClosure_directedGraph() { + public void transitiveClosure_directedGraph_addSelfLoopsAlways() { MutableGraph directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build(); directedGraph.putEdge(N1, N2); directedGraph.putEdge(N1, N3); @@ -79,11 +77,30 @@ public void transitiveClosure_directedGraph() { expectedClosure.putEdge(N3, N3); expectedClosure.putEdge(N4, N4); - checkTransitiveClosure(directedGraph, expectedClosure); + assertThat(transitiveClosure(directedGraph, ADD_SELF_LOOPS_ALWAYS)).isEqualTo(expectedClosure); } @Test - public void transitiveClosure_undirectedGraph() { + public void transitiveClosure_directedGraph_addSelfLoopsForCycles() { + MutableGraph directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build(); + directedGraph.putEdge(N1, N2); + directedGraph.putEdge(N1, N3); + directedGraph.putEdge(N2, N3); + directedGraph.addNode(N4); + + // the above graph is its own transitive closure + MutableGraph expectedClosure = GraphBuilder.directed().allowsSelfLoops(true).build(); + expectedClosure.putEdge(N1, N2); + expectedClosure.putEdge(N1, N3); + expectedClosure.putEdge(N2, N3); + expectedClosure.addNode(N4); + + assertThat( + transitiveClosure(directedGraph, ADD_SELF_LOOPS_FOR_CYCLES)).isEqualTo(expectedClosure); + } + + @Test + public void transitiveClosure_undirectedGraph_addSelfLoopsAlways() { MutableGraph undirectedGraph = GraphBuilder.undirected().allowsSelfLoops(false).build(); undirectedGraph.putEdge(N1, N2); @@ -100,11 +117,34 @@ public void transitiveClosure_undirectedGraph() { expectedClosure.putEdge(N3, N3); expectedClosure.putEdge(N4, N4); - checkTransitiveClosure(undirectedGraph, expectedClosure); + assertThat( + transitiveClosure(undirectedGraph, ADD_SELF_LOOPS_ALWAYS)).isEqualTo(expectedClosure); } @Test - public void transitiveClosure_directedPathGraph() { + public void transitiveClosure_undirectedGraph_addSelfLoopsForCycles() { + MutableGraph undirectedGraph = + GraphBuilder.undirected().allowsSelfLoops(false).build(); + undirectedGraph.putEdge(N1, N2); + undirectedGraph.putEdge(N1, N3); + undirectedGraph.putEdge(N2, N3); + undirectedGraph.addNode(N4); + + MutableGraph expectedClosure = GraphBuilder.undirected().allowsSelfLoops(true).build(); + expectedClosure.putEdge(N1, N1); + expectedClosure.putEdge(N1, N2); + expectedClosure.putEdge(N1, N3); + expectedClosure.putEdge(N2, N2); + expectedClosure.putEdge(N2, N3); + expectedClosure.putEdge(N3, N3); + expectedClosure.addNode(N4); // N4 is isolated => no incident edges in this transitive closure + + assertThat( + transitiveClosure(undirectedGraph, ADD_SELF_LOOPS_FOR_CYCLES)).isEqualTo(expectedClosure); + } + + @Test + public void transitiveClosure_directedPathGraph_addSelfLoopsAlways() { MutableGraph directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build(); directedGraph.putEdge(N1, N2); directedGraph.putEdge(N2, N3); @@ -122,7 +162,26 @@ public void transitiveClosure_directedPathGraph() { expectedClosure.putEdge(N3, N4); expectedClosure.putEdge(N4, N4); - checkTransitiveClosure(directedGraph, expectedClosure); + assertThat(transitiveClosure(directedGraph, ADD_SELF_LOOPS_ALWAYS)).isEqualTo(expectedClosure); + } + + @Test + public void transitiveClosure_directedPathGraph_addSelfLoopsForCycles() { + MutableGraph directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build(); + directedGraph.putEdge(N1, N2); + directedGraph.putEdge(N2, N3); + directedGraph.putEdge(N3, N4); + + MutableGraph expectedClosure = GraphBuilder.directed().allowsSelfLoops(true).build(); + expectedClosure.putEdge(N1, N2); + expectedClosure.putEdge(N1, N3); + expectedClosure.putEdge(N1, N4); + expectedClosure.putEdge(N2, N3); + expectedClosure.putEdge(N2, N4); + expectedClosure.putEdge(N3, N4); + + assertThat( + transitiveClosure(directedGraph, ADD_SELF_LOOPS_FOR_CYCLES)).isEqualTo(expectedClosure); } @Test @@ -145,11 +204,36 @@ public void transitiveClosure_undirectedPathGraph() { expectedClosure.putEdge(N3, N4); expectedClosure.putEdge(N4, N4); - checkTransitiveClosure(undirectedGraph, expectedClosure); + assertThat( + transitiveClosure(undirectedGraph, ADD_SELF_LOOPS_ALWAYS)).isEqualTo(expectedClosure); + } + + @Test + public void transitiveClosure_undirectedPathGraph_addSelfLoopsForCycles() { + MutableGraph undirectedGraph = + GraphBuilder.undirected().allowsSelfLoops(false).build(); + undirectedGraph.putEdge(N1, N2); + undirectedGraph.putEdge(N2, N3); + undirectedGraph.putEdge(N3, N4); + + MutableGraph expectedClosure = GraphBuilder.undirected().allowsSelfLoops(true).build(); + expectedClosure.putEdge(N1, N1); + expectedClosure.putEdge(N1, N2); + expectedClosure.putEdge(N1, N3); + expectedClosure.putEdge(N1, N4); + expectedClosure.putEdge(N2, N2); + expectedClosure.putEdge(N2, N3); + expectedClosure.putEdge(N2, N4); + expectedClosure.putEdge(N3, N3); + expectedClosure.putEdge(N3, N4); + expectedClosure.putEdge(N4, N4); + + assertThat( + transitiveClosure(undirectedGraph, ADD_SELF_LOOPS_FOR_CYCLES)).isEqualTo(expectedClosure); } @Test - public void transitiveClosure_directedCycleGraph() { + public void transitiveClosure_directedCycleGraph_addSelfLoopsAlways() { MutableGraph directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build(); directedGraph.putEdge(N1, N2); directedGraph.putEdge(N2, N3); @@ -174,11 +258,41 @@ public void transitiveClosure_directedCycleGraph() { expectedClosure.putEdge(N4, N3); expectedClosure.putEdge(N4, N4); - checkTransitiveClosure(directedGraph, expectedClosure); + assertThat(transitiveClosure(directedGraph, ADD_SELF_LOOPS_ALWAYS)).isEqualTo(expectedClosure); } @Test - public void transitiveClosure_undirectedCycleGraph() { + public void transitiveClosure_directedCycleGraph_addSelfLoopsForCycles() { + MutableGraph directedGraph = GraphBuilder.directed().allowsSelfLoops(false).build(); + directedGraph.putEdge(N1, N2); + directedGraph.putEdge(N2, N3); + directedGraph.putEdge(N3, N4); + directedGraph.putEdge(N4, N1); + + MutableGraph expectedClosure = GraphBuilder.directed().allowsSelfLoops(true).build(); + expectedClosure.putEdge(N1, N1); + expectedClosure.putEdge(N1, N2); + expectedClosure.putEdge(N1, N3); + expectedClosure.putEdge(N1, N4); + expectedClosure.putEdge(N2, N1); + expectedClosure.putEdge(N2, N2); + expectedClosure.putEdge(N2, N3); + expectedClosure.putEdge(N2, N4); + expectedClosure.putEdge(N3, N1); + expectedClosure.putEdge(N3, N2); + expectedClosure.putEdge(N3, N3); + expectedClosure.putEdge(N3, N4); + expectedClosure.putEdge(N4, N1); + expectedClosure.putEdge(N4, N2); + expectedClosure.putEdge(N4, N3); + expectedClosure.putEdge(N4, N4); + + assertThat( + transitiveClosure(directedGraph, ADD_SELF_LOOPS_FOR_CYCLES)).isEqualTo(expectedClosure); + } + + @Test + public void transitiveClosure_undirectedCycleGraph_addSelfLoopsAlways() { MutableGraph undirectedGraph = GraphBuilder.undirected().allowsSelfLoops(false).build(); undirectedGraph.putEdge(N1, N2); @@ -198,7 +312,33 @@ public void transitiveClosure_undirectedCycleGraph() { expectedClosure.putEdge(N3, N4); expectedClosure.putEdge(N4, N4); - checkTransitiveClosure(undirectedGraph, expectedClosure); + assertThat( + transitiveClosure(undirectedGraph, ADD_SELF_LOOPS_ALWAYS)).isEqualTo(expectedClosure); + } + + @Test + public void transitiveClosure_undirectedCycleGraph_addSelfLoopsForCycles() { + MutableGraph undirectedGraph = + GraphBuilder.undirected().allowsSelfLoops(false).build(); + undirectedGraph.putEdge(N1, N2); + undirectedGraph.putEdge(N2, N3); + undirectedGraph.putEdge(N3, N4); + undirectedGraph.putEdge(N4, N1); + + MutableGraph expectedClosure = GraphBuilder.undirected().allowsSelfLoops(true).build(); + expectedClosure.putEdge(N1, N1); + expectedClosure.putEdge(N1, N2); + expectedClosure.putEdge(N1, N3); + expectedClosure.putEdge(N1, N4); + expectedClosure.putEdge(N2, N2); + expectedClosure.putEdge(N2, N3); + expectedClosure.putEdge(N2, N4); + expectedClosure.putEdge(N3, N3); + expectedClosure.putEdge(N3, N4); + expectedClosure.putEdge(N4, N4); + + assertThat( + transitiveClosure(undirectedGraph, ADD_SELF_LOOPS_FOR_CYCLES)).isEqualTo(expectedClosure); } @Test @@ -336,7 +476,7 @@ public void transpose_directedNetwork() { @Test public void inducedSubgraph_graph() { - Set nodeSubset = ImmutableSet.of(N1, N2, N4); + ImmutableSet nodeSubset = ImmutableSet.of(N1, N2, N4); MutableGraph directedGraph = GraphBuilder.directed().allowsSelfLoops(true).build(); directedGraph.putEdge(N1, N2); @@ -355,7 +495,7 @@ public void inducedSubgraph_graph() { @Test public void inducedSubgraph_valueGraph() { - Set nodeSubset = ImmutableSet.of(N1, N2, N4); + ImmutableSet nodeSubset = ImmutableSet.of(N1, N2, N4); MutableValueGraph directedGraph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); @@ -376,7 +516,7 @@ public void inducedSubgraph_valueGraph() { @Test public void inducedSubgraph_network() { - Set nodeSubset = ImmutableSet.of(N1, N2, N4); + ImmutableSet nodeSubset = ImmutableSet.of(N1, N2, N4); MutableNetwork directedGraph = NetworkBuilder.directed().allowsSelfLoops(true).build(); @@ -399,20 +539,14 @@ public void inducedSubgraph_network() { public void inducedSubgraph_nodeNotInGraph() { MutableNetwork undirectedGraph = NetworkBuilder.undirected().build(); - try { - inducedSubgraph(undirectedGraph, ImmutableSet.of(N1)); - fail("Should have rejected getting induced subgraph with node not in original graph."); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> inducedSubgraph(undirectedGraph, ImmutableSet.of(N1))); } @Test public void copyOf_nullArgument() { - try { - copyOf((Graph) null); - fail("Should have rejected a null graph."); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> copyOf((Graph) null)); } @Test @@ -475,20 +609,13 @@ public void createDirected() { assertThat(directedGraph.edgesConnecting(N2, N1)).isEmpty(); // By default, parallel edges are not allowed. - try { - directedGraph.addEdge(N1, N2, E12_A); - fail(ERROR_ADDED_PARALLEL_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_PARALLEL_EDGE); - } + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> directedGraph.addEdge(N1, N2, E12_A)); + assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); // By default, self-loop edges are not allowed. - try { - directedGraph.addEdge(N1, N1, E11); - fail(ERROR_ADDED_SELF_LOOP); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); - } + e = assertThrows(IllegalArgumentException.class, () -> directedGraph.addEdge(N1, N1, E11)); + assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); } @Test @@ -501,26 +628,15 @@ public void createUndirected() { assertThat(undirectedGraph.edgesConnecting(N2, N1)).isEqualTo(ImmutableSet.of(E12)); // By default, parallel edges are not allowed. - try { - undirectedGraph.addEdge(N1, N2, E12_A); - fail(ERROR_ADDED_PARALLEL_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_PARALLEL_EDGE); - } - try { - undirectedGraph.addEdge(N2, N1, E21); - fail(ERROR_ADDED_PARALLEL_EDGE); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_PARALLEL_EDGE); - } + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> undirectedGraph.addEdge(N1, N2, E12_A)); + assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); + e = assertThrows(IllegalArgumentException.class, () -> undirectedGraph.addEdge(N2, N1, E21)); + assertThat(e).hasMessageThat().contains(ERROR_PARALLEL_EDGE); // By default, self-loop edges are not allowed. - try { - undirectedGraph.addEdge(N1, N1, E11); - fail(ERROR_ADDED_SELF_LOOP); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); - } + e = assertThrows(IllegalArgumentException.class, () -> undirectedGraph.addEdge(N1, N1, E11)); + assertThat(e).hasMessageThat().contains(ERROR_SELF_LOOP); } @Test @@ -564,12 +680,10 @@ public void createUndirected_expectedNodeCount() { @Test public void builder_expectedNodeCount_negative() { - try { - NetworkBuilder.directed().expectedNodeCount(-1); - fail("Should have rejected negative expected node count."); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_NEGATIVE_COUNT); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> NetworkBuilder.directed().expectedNodeCount(-1)); + assertThat(e).hasMessageThat().contains(ERROR_NEGATIVE_COUNT); } @Test @@ -592,19 +706,10 @@ public void createUndirected_expectedEdgeCount() { @Test public void builder_expectedEdgeCount_negative() { - try { - NetworkBuilder.directed().expectedEdgeCount(-1); - fail("Should have rejected negative expected edge count."); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).contains(ERROR_NEGATIVE_COUNT); - } - } - - private static void checkTransitiveClosure(Graph originalGraph, Graph expectedClosure) { - for (N node : originalGraph.nodes()) { - assertThat(reachableNodes(originalGraph, node)).isEqualTo(expectedClosure.successors(node)); - } - assertThat(transitiveClosure(originalGraph)).isEqualTo(expectedClosure); + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> NetworkBuilder.directed().expectedEdgeCount(-1)); + assertThat(e).hasMessageThat().contains(ERROR_NEGATIVE_COUNT); } private static MutableGraph buildDirectedGraph() { diff --git a/android/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java b/android/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java index 5de3cf751f8e..646424e1c341 100644 --- a/android/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java @@ -18,12 +18,14 @@ import static com.google.common.truth.Truth.assertThat; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link ImmutableNetwork}. */ @RunWith(JUnit4.class) +@NullUnmarked public class ImmutableNetworkTest { @Test @@ -32,7 +34,6 @@ public void immutableNetwork() { mutableNetwork.addNode("A"); ImmutableNetwork immutableNetwork = ImmutableNetwork.copyOf(mutableNetwork); - assertThat(immutableNetwork.asGraph()).isInstanceOf(ImmutableGraph.class); assertThat(immutableNetwork).isNotInstanceOf(MutableNetwork.class); assertThat(immutableNetwork).isEqualTo(mutableNetwork); diff --git a/android/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java b/android/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java index 8e5e67f3046e..6bec95ca5853 100644 --- a/android/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java @@ -18,12 +18,14 @@ import static com.google.common.truth.Truth.assertThat; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link ImmutableValueGraph} . */ @RunWith(JUnit4.class) +@NullUnmarked public class ImmutableValueGraphTest { @Test @@ -33,7 +35,6 @@ public void immutableValueGraph() { ImmutableValueGraph immutableValueGraph = ImmutableValueGraph.copyOf(mutableValueGraph); - assertThat(immutableValueGraph.asGraph()).isInstanceOf(ImmutableGraph.class); assertThat(immutableValueGraph).isNotInstanceOf(MutableValueGraph.class); assertThat(immutableValueGraph).isEqualTo(mutableValueGraph); diff --git a/android/guava-tests/test/com/google/common/graph/InvalidatableSetTest.java b/android/guava-tests/test/com/google/common/graph/InvalidatableSetTest.java new file mode 100644 index 000000000000..1f393acd25ce --- /dev/null +++ b/android/guava-tests/test/com/google/common/graph/InvalidatableSetTest.java @@ -0,0 +1,62 @@ +package com.google.common.graph; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableSet; +import java.util.HashSet; +import java.util.Set; +import org.jspecify.annotations.NullUnmarked; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@NullUnmarked +public final class InvalidatableSetTest { + Set wrappedSet; + Set copyOfWrappedSet; + InvalidatableSet setToTest; + + @Before + public void createSets() { + wrappedSet = new HashSet<>(); + wrappedSet.add(1); + wrappedSet.add(2); + wrappedSet.add(3); + + copyOfWrappedSet = ImmutableSet.copyOf(wrappedSet); + setToTest = + InvalidatableSet.of(wrappedSet, () -> wrappedSet.contains(1), () -> 1 + "is not present"); + } + + @Test + @SuppressWarnings("TruthSelfEquals") + public void testEquals() { + // sanity check on construction of copyOfWrappedSet + assertThat(wrappedSet).isEqualTo(copyOfWrappedSet); + + // test that setToTest is still valid + assertThat(setToTest).isEqualTo(wrappedSet); + assertThat(setToTest).isEqualTo(copyOfWrappedSet); + + // invalidate setToTest + wrappedSet.remove(1); + // sanity check on update of wrappedSet + assertThat(wrappedSet).isNotEqualTo(copyOfWrappedSet); + + ImmutableSet copyOfModifiedSet = ImmutableSet.copyOf(wrappedSet); // {2,3} + // sanity check on construction of copyOfModifiedSet + assertThat(wrappedSet).isEqualTo(copyOfModifiedSet); + + // setToTest should throw when it calls equals(), or equals is called on it, except for itself + assertThat(setToTest).isEqualTo(setToTest); + assertThrows(IllegalStateException.class, () -> setToTest.equals(wrappedSet)); + assertThrows(IllegalStateException.class, () -> setToTest.equals(copyOfWrappedSet)); + assertThrows(IllegalStateException.class, () -> setToTest.equals(copyOfModifiedSet)); + assertThrows(IllegalStateException.class, () -> wrappedSet.equals(setToTest)); + assertThrows(IllegalStateException.class, () -> copyOfWrappedSet.equals(setToTest)); + assertThrows(IllegalStateException.class, () -> copyOfModifiedSet.equals(setToTest)); + } +} diff --git a/android/guava-tests/test/com/google/common/graph/MapCacheTest.java b/android/guava-tests/test/com/google/common/graph/MapCacheTest.java index f66b19b7d985..e129443530f8 100644 --- a/android/guava-tests/test/com/google/common/graph/MapCacheTest.java +++ b/android/guava-tests/test/com/google/common/graph/MapCacheTest.java @@ -24,6 +24,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.TreeMap; +import org.jspecify.annotations.NullUnmarked; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,6 +35,7 @@ @AndroidIncompatible // TODO(cpovirk): Figure out Android JUnit 4 support. Does it work with Gingerbread? @RunWith? @RunWith(Parameterized.class) +@NullUnmarked public final class MapCacheTest { private final MapIteratorCache mapCache; @@ -90,25 +92,4 @@ public void testRemoveEqualKeyWithDifferentReference() { assertThat(mapCache.remove(fooReference2)).isEqualTo("bar"); assertThat(mapCache.get(fooReference1)).isNull(); } - - @Test - public void testHandleNulls() { - mapCache.put("foo", "bar"); - mapCache.put("non-null key", null); - mapCache.put(null, "non-null value"); - - assertThat(mapCache.get("foo")).isEqualTo("bar"); - assertThat(mapCache.get("non-null key")).isNull(); - assertThat(mapCache.get(null)).isEqualTo("non-null value"); - - assertThat(mapCache.containsKey("foo")).isTrue(); - assertThat(mapCache.containsKey("bar")).isFalse(); - assertThat(mapCache.containsKey("non-null key")).isTrue(); - assertThat(mapCache.containsKey(null)).isTrue(); - - // Test again - in reverse order. - assertThat(mapCache.get(null)).isEqualTo("non-null value"); - assertThat(mapCache.get("non-null key")).isNull(); - assertThat(mapCache.get("foo")).isEqualTo("bar"); - } } diff --git a/android/guava-tests/test/com/google/common/graph/NetworkEquivalenceTest.java b/android/guava-tests/test/com/google/common/graph/NetworkEquivalenceTest.java index 4aab1271b5f9..de8ff654c9fb 100644 --- a/android/guava-tests/test/com/google/common/graph/NetworkEquivalenceTest.java +++ b/android/guava-tests/test/com/google/common/graph/NetworkEquivalenceTest.java @@ -23,6 +23,7 @@ import com.google.common.graph.TestUtil.EdgeType; import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -31,6 +32,7 @@ @AndroidIncompatible // TODO(cpovirk): Figure out Android JUnit 4 support. Does it work with Gingerbread? @RunWith? @RunWith(Parameterized.class) +@NullUnmarked public final class NetworkEquivalenceTest { private static final Integer N1 = 1; private static final Integer N2 = 2; @@ -61,9 +63,8 @@ private static MutableNetwork createNetwork(EdgeType edgeType) return NetworkBuilder.undirected().allowsSelfLoops(true).build(); case DIRECTED: return NetworkBuilder.directed().allowsSelfLoops(true).build(); - default: - throw new IllegalStateException("Unexpected edge type: " + edgeType); } + throw new IllegalStateException("Unexpected edge type: " + edgeType); } private static EdgeType oppositeType(EdgeType edgeType) { @@ -72,9 +73,8 @@ private static EdgeType oppositeType(EdgeType edgeType) { return EdgeType.DIRECTED; case DIRECTED: return EdgeType.UNDIRECTED; - default: - throw new IllegalStateException("Unexpected edge type: " + edgeType); } + throw new IllegalStateException("Unexpected edge type: " + edgeType); } @Test @@ -184,8 +184,6 @@ public void equivalent_edgeDirectionsDiffer() { case DIRECTED: assertThat(network).isNotEqualTo(g2); break; - default: - throw new IllegalStateException("Unexpected edge type: " + edgeType); } } } diff --git a/android/guava-tests/test/com/google/common/graph/NetworkMutationTest.java b/android/guava-tests/test/com/google/common/graph/NetworkMutationTest.java index fd232dcdad17..f3629370c9e5 100644 --- a/android/guava-tests/test/com/google/common/graph/NetworkMutationTest.java +++ b/android/guava-tests/test/com/google/common/graph/NetworkMutationTest.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Random; import java.util.RandomAccess; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -30,10 +31,11 @@ /** Tests for repeated node and edge addition and removal in a {@link Network}. */ @RunWith(JUnit4.class) +@NullUnmarked public final class NetworkMutationTest { - private static final int NUM_TRIALS = 25; - private static final int NUM_NODES = 100; - private static final int NUM_EDGES = 1000; + private static final int NUM_TRIALS = 5; + private static final int NUM_NODES = 20; + private static final int NUM_EDGES = 100; private static final int NODE_POOL_SIZE = 1000; // must be >> NUM_NODES @Test diff --git a/android/guava-tests/test/com/google/common/graph/PackageSanityTests.java b/android/guava-tests/test/com/google/common/graph/PackageSanityTests.java index f1526c235f1c..2ddcbc1d5d8d 100644 --- a/android/guava-tests/test/com/google/common/graph/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/graph/PackageSanityTests.java @@ -20,7 +20,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import com.google.common.testing.AbstractPackageSanityTests; -import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullUnmarked; /** * Covers basic sanity checks for the entire package. @@ -28,11 +28,12 @@ * @author Kurt Alfred Kluever */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { - private static final AbstractGraphBuilder GRAPH_BUILDER_A = + private static final AbstractGraphBuilder graphBuilderA = GraphBuilder.directed().expectedNodeCount(10); - private static final AbstractGraphBuilder GRAPH_BUILDER_B = + private static final AbstractGraphBuilder graphBuilderB = ValueGraphBuilder.directed().allowsSelfLoops(true).expectedNodeCount(16); private static final ImmutableGraph IMMUTABLE_GRAPH_A = @@ -40,9 +41,9 @@ public class PackageSanityTests extends AbstractPackageSanityTests { private static final ImmutableGraph IMMUTABLE_GRAPH_B = GraphBuilder.directed().immutable().addNode("B").build(); - private static final NetworkBuilder NETWORK_BUILDER_A = + private static final NetworkBuilder networkBuilderA = NetworkBuilder.directed().allowsParallelEdges(true).expectedNodeCount(10); - private static final NetworkBuilder NETWORK_BUILDER_B = + private static final NetworkBuilder networkBuilderB = NetworkBuilder.directed().allowsSelfLoops(true).expectedNodeCount(16); private static final ImmutableNetwork IMMUTABLE_NETWORK_A = @@ -51,9 +52,15 @@ public class PackageSanityTests extends AbstractPackageSanityTests { NetworkBuilder.directed().immutable().addNode("B").build(); public PackageSanityTests() { - setDistinctValues(AbstractGraphBuilder.class, GRAPH_BUILDER_A, GRAPH_BUILDER_B); + MutableNetwork mutableNetworkA = NetworkBuilder.directed().build(); + mutableNetworkA.addNode("a"); + MutableNetwork mutableNetworkB = NetworkBuilder.directed().build(); + mutableNetworkB.addNode("b"); + + setDistinctValues(AbstractGraphBuilder.class, graphBuilderA, graphBuilderB); setDistinctValues(Graph.class, IMMUTABLE_GRAPH_A, IMMUTABLE_GRAPH_B); - setDistinctValues(NetworkBuilder.class, NETWORK_BUILDER_A, NETWORK_BUILDER_B); + setDistinctValues(MutableNetwork.class, mutableNetworkA, mutableNetworkB); + setDistinctValues(NetworkBuilder.class, networkBuilderA, networkBuilderB); setDistinctValues(Network.class, IMMUTABLE_NETWORK_A, IMMUTABLE_NETWORK_B); setDefault(EndpointPair.class, EndpointPair.ordered("A", "B")); } @@ -62,7 +69,7 @@ public PackageSanityTests() { public void testNulls() throws Exception { try { super.testNulls(); - } catch (AssertionFailedError e) { + } catch (AssertionError e) { assertWithMessage("Method did not throw null pointer OR element not in graph exception.") .that(e) .hasCauseThat() diff --git a/android/guava-tests/test/com/google/common/graph/StandardImmutableDirectedGraphTest.java b/android/guava-tests/test/com/google/common/graph/StandardImmutableDirectedGraphTest.java index d89baa0634bc..710f7890526b 100644 --- a/android/guava-tests/test/com/google/common/graph/StandardImmutableDirectedGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/StandardImmutableDirectedGraphTest.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -25,6 +26,7 @@ /** Tests for a directed {@link StandardMutableGraph}. */ @AndroidIncompatible @RunWith(Parameterized.class) +@NullUnmarked public final class StandardImmutableDirectedGraphTest extends AbstractStandardDirectedGraphTest { @Parameters(name = "allowsSelfLoops={0}") diff --git a/android/guava-tests/test/com/google/common/graph/StandardImmutableDirectedNetworkTest.java b/android/guava-tests/test/com/google/common/graph/StandardImmutableDirectedNetworkTest.java index b237ff91f07c..bc3e194d969b 100644 --- a/android/guava-tests/test/com/google/common/graph/StandardImmutableDirectedNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/StandardImmutableDirectedNetworkTest.java @@ -19,6 +19,7 @@ import com.google.common.collect.Ordering; import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -26,6 +27,7 @@ /** Tests for a directed {@link ImmutableNetwork}. */ @AndroidIncompatible @RunWith(Parameterized.class) +@NullUnmarked public class StandardImmutableDirectedNetworkTest extends AbstractStandardDirectedNetworkTest { @Parameters(name = "allowsSelfLoops={0}, allowsParallelEdges={1}, nodeOrder={2}, edgeOrder={3}") diff --git a/android/guava-tests/test/com/google/common/graph/StandardImmutableGraphAdditionalTest.java b/android/guava-tests/test/com/google/common/graph/StandardImmutableGraphAdditionalTest.java index 47cd6a037426..1a709ac6a227 100644 --- a/android/guava-tests/test/com/google/common/graph/StandardImmutableGraphAdditionalTest.java +++ b/android/guava-tests/test/com/google/common/graph/StandardImmutableGraphAdditionalTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -27,6 +28,7 @@ * {@link StandardImmutableDirectedGraphTest}. */ @RunWith(JUnit4.class) +@NullUnmarked public class StandardImmutableGraphAdditionalTest { @Test diff --git a/android/guava-tests/test/com/google/common/graph/StandardImmutableUndirectedGraphTest.java b/android/guava-tests/test/com/google/common/graph/StandardImmutableUndirectedGraphTest.java index 7f580a6db80d..290306d4a09d 100644 --- a/android/guava-tests/test/com/google/common/graph/StandardImmutableUndirectedGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/StandardImmutableUndirectedGraphTest.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -25,6 +26,7 @@ /** Tests for an undirected {@link StandardMutableGraph}. */ @AndroidIncompatible @RunWith(Parameterized.class) +@NullUnmarked public final class StandardImmutableUndirectedGraphTest extends AbstractStandardUndirectedGraphTest { diff --git a/android/guava-tests/test/com/google/common/graph/StandardMutableDirectedGraphTest.java b/android/guava-tests/test/com/google/common/graph/StandardMutableDirectedGraphTest.java index 191010e48f40..8b849a8cd2c6 100644 --- a/android/guava-tests/test/com/google/common/graph/StandardMutableDirectedGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/StandardMutableDirectedGraphTest.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -25,6 +26,7 @@ /** Tests for a directed {@link StandardMutableGraph}. */ @AndroidIncompatible @RunWith(Parameterized.class) +@NullUnmarked public final class StandardMutableDirectedGraphTest extends AbstractStandardDirectedGraphTest { @Parameters(name = "allowsSelfLoops={0}, incidentEdgeOrder={1}") diff --git a/android/guava-tests/test/com/google/common/graph/StandardMutableDirectedNetworkTest.java b/android/guava-tests/test/com/google/common/graph/StandardMutableDirectedNetworkTest.java index fb8be1d34d7a..1e8960a39c86 100644 --- a/android/guava-tests/test/com/google/common/graph/StandardMutableDirectedNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/StandardMutableDirectedNetworkTest.java @@ -19,6 +19,7 @@ import com.google.common.collect.Ordering; import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -26,6 +27,7 @@ /** Tests for a directed {@link StandardMutableNetwork} allowing self-loops. */ @AndroidIncompatible @RunWith(Parameterized.class) +@NullUnmarked public class StandardMutableDirectedNetworkTest extends AbstractStandardDirectedNetworkTest { @Parameters(name = "allowsSelfLoops={0}, allowsParallelEdges={1}, nodeOrder={2}, edgeOrder={3}") diff --git a/android/guava-tests/test/com/google/common/graph/StandardMutableUndirectedGraphTest.java b/android/guava-tests/test/com/google/common/graph/StandardMutableUndirectedGraphTest.java index aa0372aa8aae..fdb3bef224e6 100644 --- a/android/guava-tests/test/com/google/common/graph/StandardMutableUndirectedGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/StandardMutableUndirectedGraphTest.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -25,6 +26,7 @@ /** Tests for an undirected {@link StandardMutableGraph}. */ @AndroidIncompatible @RunWith(Parameterized.class) +@NullUnmarked public class StandardMutableUndirectedGraphTest extends AbstractStandardUndirectedGraphTest { @Parameters(name = "allowsSelfLoops={0}, incidentEdgeOrder={1}") diff --git a/android/guava-tests/test/com/google/common/graph/StandardMutableUndirectedNetworkTest.java b/android/guava-tests/test/com/google/common/graph/StandardMutableUndirectedNetworkTest.java index c021b5d17354..f5e12b33f7ef 100644 --- a/android/guava-tests/test/com/google/common/graph/StandardMutableUndirectedNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/StandardMutableUndirectedNetworkTest.java @@ -19,6 +19,7 @@ import com.google.common.collect.Ordering; import java.util.Arrays; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -26,6 +27,7 @@ /** Tests for an undirected {@link StandardMutableNetwork}. */ @AndroidIncompatible @RunWith(Parameterized.class) +@NullUnmarked public final class StandardMutableUndirectedNetworkTest extends AbstractStandardUndirectedNetworkTest { diff --git a/android/guava-tests/test/com/google/common/graph/TestUtil.java b/android/guava-tests/test/com/google/common/graph/TestUtil.java index 68a2503e223f..e1aea12d76cc 100644 --- a/android/guava-tests/test/com/google/common/graph/TestUtil.java +++ b/android/guava-tests/test/com/google/common/graph/TestUtil.java @@ -22,12 +22,15 @@ import com.google.common.collect.Iterators; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; /** Utility methods used in various common.graph tests. */ +@NullUnmarked final class TestUtil { static final String ERROR_ELEMENT_NOT_IN_GRAPH = "not an element of this graph"; static final String ERROR_NODE_NOT_IN_GRAPH = "Should not be allowed to pass a node that is not an element of the graph."; + static final String ERROR_ELEMENT_REMOVED = "used to generate this set"; private static final String NODE_STRING = "Node"; private static final String EDGE_STRING = "Edge"; @@ -48,6 +51,16 @@ static void assertEdgeNotInGraphErrorMessage(Throwable throwable) { assertThat(throwable).hasMessageThat().contains(ERROR_ELEMENT_NOT_IN_GRAPH); } + static void assertNodeRemovedFromGraphErrorMessage(Throwable throwable) { + assertThat(throwable).hasMessageThat().startsWith(NODE_STRING); + assertThat(throwable).hasMessageThat().contains(ERROR_ELEMENT_REMOVED); + } + + static void assertEdgeRemovedFromGraphErrorMessage(Throwable throwable) { + assertThat(throwable).hasMessageThat().startsWith(EDGE_STRING); + assertThat(throwable).hasMessageThat().contains(ERROR_ELEMENT_REMOVED); + } + static void assertStronglyEquivalent(Graph graphA, Graph graphB) { // Properties not covered by equals() assertThat(graphA.allowsSelfLoops()).isEqualTo(graphB.allowsSelfLoops()); diff --git a/android/guava-tests/test/com/google/common/graph/TraverserTest.java b/android/guava-tests/test/com/google/common/graph/TraverserTest.java index d4c8cf7521c3..a078de009405 100644 --- a/android/guava-tests/test/com/google/common/graph/TraverserTest.java +++ b/android/guava-tests/test/com/google/common/graph/TraverserTest.java @@ -21,31 +21,32 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.charactersOf; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.collect.HashMultiset; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Multiset; import com.google.common.collect.Ordering; import com.google.common.primitives.Chars; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) +@NullUnmarked public class TraverserTest { /** * The undirected graph in the {@link Traverser#breadthFirst(Object)} javadoc: * - *

    {@code
    +   * {@snippet :
        * b ---- a ---- d
        * |      |
        * |      |
        * e ---- c ---- f
    -   * }
    + * } */ private static final SuccessorsFunction JAVADOC_GRAPH = createUndirectedGraph("ba", "ad", "be", "ac", "ec", "cf"); @@ -53,13 +54,13 @@ public class TraverserTest { /** * A diamond shaped directed graph (arrows going down): * - *
    {@code
    +   * {@snippet :
        *   a
        *  / \
        * b   c
        *  \ /
        *   d
    -   * }
    + * } */ private static final SuccessorsFunction DIAMOND_GRAPH = createDirectedGraph("ab", "ac", "bd", "cd"); @@ -67,13 +68,13 @@ public class TraverserTest { /** * Same as {@link #DIAMOND_GRAPH}, but with an extra c->a edge and some self edges: * - *
    {@code
    +   * {@snippet :
        *   a<>
        *  / \\
        * b   c
        *  \ /
        *   d<>
    -   * }
    + * } * * {@code <>} indicates a self-loop */ @@ -87,13 +88,13 @@ public class TraverserTest { /** * Same as {@link #CYCLE_GRAPH}, but with an extra a->c edge. * - *
    {@code
    +   * {@snippet :
        * |--------------|
        * v              |
        * a -> b -> c -> d
        * |         ^
        * |---------|
    -   * }
    + * } */ private static final SuccessorsFunction TWO_CYCLES_GRAPH = createDirectedGraph("ab", "ac", "bc", "cd", "da"); @@ -101,7 +102,7 @@ public class TraverserTest { /** * A tree-shaped graph that looks as follows (all edges are directed facing downwards): * - *
    {@code
    +   * {@snippet :
        *        h
        *       /|\
        *      / | \
    @@ -110,7 +111,7 @@ public class TraverserTest {
        *   /|\      |
        *  / | \     |
        * a  b  c    f
    -   * }
    + * } */ private static final SuccessorsFunction TREE = createDirectedGraph("hd", "he", "hg", "da", "db", "dc", "gf"); @@ -118,21 +119,21 @@ public class TraverserTest { /** * Two disjoint tree-shaped graphs that look as follows (all edges are directed facing downwards): * - *
    {@code
    +   * {@snippet :
        * a   c
        * |   |
        * |   |
        * b   d
    -   * }
    + * } */ private static final SuccessorsFunction TWO_TREES = createDirectedGraph("ab", "cd"); /** * A graph consisting of a single root {@code a}: * - *
    {@code
    +   * {@snippet :
        * a
    -   * }
    + * } */ private static final SuccessorsFunction SINGLE_ROOT = createSingleRootGraph(); @@ -141,13 +142,13 @@ public class TraverserTest { * {@code f} and thus has a cycle) but is a valid input to {@link Traverser#forTree} when starting * e.g. at node {@code a} (all edges without an arrow are directed facing downwards): * - *
    {@code
    +   * {@snippet :
        *     a
        *    /
        *   b   e <----> f
        *  / \ /
        * c   d
    -   * }
    + * } */ private static final SuccessorsFunction CYCLIC_GRAPH_CONTAINING_TREE = createDirectedGraph("ab", "bc", "bd", "ed", "ef", "fe"); @@ -157,13 +158,13 @@ public class TraverserTest { * e} and {@code g}) but is a valid input to {@link Traverser#forTree} when starting e.g. at node * {@code a} (all edges are directed facing downwards): * - *
    {@code
    +   * {@snippet :
        *     a   f
        *    /   / \
        *   b   e   g
        *  / \ / \ /
        * c   d   h
    -   * }
    + * } */ private static final SuccessorsFunction GRAPH_CONTAINING_TREE_AND_DIAMOND = createDirectedGraph("ab", "fe", "fg", "bc", "bd", "ed", "eh", "gh"); @@ -311,11 +312,9 @@ public void forGraph_breadthFirstIterable_singleRoot() { @Test public void forGraph_breadthFirst_emptyGraph() { - try { - Traverser.forGraph(createDirectedGraph()).breadthFirst('a'); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forGraph(createDirectedGraph()).breadthFirst('a')); } /** @@ -326,11 +325,9 @@ public void forGraph_breadthFirst_emptyGraph() { public void forGraph_breadthFirstIterable_emptyGraph() { assertEqualCharNodes( Traverser.forGraph(createDirectedGraph()).breadthFirst(charactersOf("")), ""); - try { - Traverser.forGraph(createDirectedGraph()).breadthFirst(charactersOf("a")); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forGraph(createDirectedGraph()).breadthFirst(charactersOf("a"))); } /** @@ -509,22 +506,18 @@ public void forGraph_depthFirstPreOrderIterable_singleRoot() { @Test public void forGraph_depthFirstPreOrder_emptyGraph() { - try { - Traverser.forGraph(createDirectedGraph()).depthFirstPreOrder('a'); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forGraph(createDirectedGraph()).depthFirstPreOrder('a')); } @Test public void forGraph_depthFirstPreOrderIterable_emptyGraph() { assertEqualCharNodes( Traverser.forGraph(createDirectedGraph()).depthFirstPreOrder(charactersOf("")), ""); - try { - Traverser.forGraph(createDirectedGraph()).depthFirstPreOrder(charactersOf("a")); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forGraph(createDirectedGraph()).depthFirstPreOrder(charactersOf("a"))); } @Test @@ -691,22 +684,18 @@ public void forGraph_depthFirstPostOrderIterable_singleRoot() { @Test public void forGraph_depthFirstPostOrder_emptyGraph() { - try { - Traverser.forGraph(createDirectedGraph()).depthFirstPostOrder('a'); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forGraph(createDirectedGraph()).depthFirstPostOrder('a')); } @Test public void forGraph_depthFirstPostOrderIterable_emptyGraph() { assertEqualCharNodes( Traverser.forGraph(createDirectedGraph()).depthFirstPostOrder(charactersOf("")), ""); - try { - Traverser.forGraph(createDirectedGraph()).depthFirstPostOrder(charactersOf("a")); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forGraph(createDirectedGraph()).depthFirstPostOrder(charactersOf("a"))); } @Test @@ -749,11 +738,7 @@ public void forTree_withUndirectedGraph_throws() throws Exception { MutableGraph graph = GraphBuilder.undirected().build(); graph.putEdge("a", "b"); - try { - Traverser.forTree(graph); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Traverser.forTree(graph)); } @Test @@ -770,11 +755,7 @@ public void forTree_withUndirectedValueGraph_throws() throws Exception { MutableValueGraph valueGraph = ValueGraphBuilder.undirected().build(); valueGraph.putEdgeValue("a", "b", 11); - try { - Traverser.forTree(valueGraph); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Traverser.forTree(valueGraph)); } @Test @@ -791,11 +772,7 @@ public void forTree_withUndirectedNetwork_throws() throws Exception { MutableNetwork network = NetworkBuilder.undirected().build(); network.addEdge("a", "b", 11); - try { - Traverser.forTree(network); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Traverser.forTree(network)); } @Test @@ -889,22 +866,18 @@ public void forTree_breadthFirstIterable_singleRoot() { @Test public void forTree_breadthFirst_emptyGraph() { - try { - Traverser.forTree(createDirectedGraph()).breadthFirst('a'); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forTree(createDirectedGraph()).breadthFirst('a')); } @Test public void forTree_breadthFirstIterable_emptyGraph() { assertEqualCharNodes( Traverser.forTree(createDirectedGraph()).breadthFirst(charactersOf("")), ""); - try { - Traverser.forTree(createDirectedGraph()).breadthFirst(charactersOf("a")); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forTree(createDirectedGraph()).breadthFirst(charactersOf("a"))); } @Test @@ -1026,22 +999,18 @@ public void forTree_depthFirstPreOrderIterable_singleRoot() { @Test public void forTree_depthFirstPreOrder_emptyGraph() { - try { - Traverser.forTree(createDirectedGraph()).depthFirstPreOrder('a'); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forTree(createDirectedGraph()).depthFirstPreOrder('a')); } @Test public void forTree_depthFirstPreOrderIterable_emptyGraph() { assertEqualCharNodes( Traverser.forTree(createDirectedGraph()).depthFirstPreOrder(charactersOf("")), ""); - try { - Traverser.forTree(createDirectedGraph()).depthFirstPreOrder(charactersOf("a")); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forTree(createDirectedGraph()).depthFirstPreOrder(charactersOf("a"))); } @Test @@ -1156,22 +1125,18 @@ public void forTree_depthFirstPostOrderIterable_singleRoot() { @Test public void forTree_depthFirstPostOrder_emptyGraph() { - try { - Traverser.forTree(createDirectedGraph()).depthFirstPostOrder('a'); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forTree(createDirectedGraph()).depthFirstPostOrder('a')); } @Test public void forTree_depthFirstPostOrderIterable_emptyGraph() { assertEqualCharNodes( Traverser.forTree(createDirectedGraph()).depthFirstPostOrder(charactersOf("")), ""); - try { - Traverser.forTree(createDirectedGraph()).depthFirstPostOrder(charactersOf("a")); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Traverser.forTree(createDirectedGraph()).depthFirstPostOrder(charactersOf("a"))); } @Test @@ -1201,11 +1166,11 @@ public void forTree_depthFirstPostOrderIterable_iterableIsLazy() { } private static SuccessorsFunction createDirectedGraph(String... edges) { - return createGraph(/* directed = */ true, edges); + return createGraph(/* directed= */ true, edges); } private static SuccessorsFunction createUndirectedGraph(String... edges) { - return createGraph(/* directed = */ false, edges); + return createGraph(/* directed= */ false, edges); } /** @@ -1226,7 +1191,7 @@ private static SuccessorsFunction createGraph(boolean directed, Strin graphMapBuilder.put(node2, node1); } } - final ImmutableMultimap graphMap = graphMapBuilder.build(); + ImmutableMultimap graphMap = graphMapBuilder.build(); return new SuccessorsFunction() { @Override @@ -1247,7 +1212,7 @@ private static ImmutableGraph createSingleRootGraph() { } private static void assertEqualCharNodes(Iterable result, String expectedCharacters) { - assertThat(ImmutableList.copyOf(result)) + assertThat(result) .containsExactlyElementsIn(Chars.asList(expectedCharacters.toCharArray())) .inOrder(); } @@ -1267,7 +1232,7 @@ public Iterable successors(Character node) { } } - private static SuccessorsFunction fixedSuccessors(final Iterable successors) { + private static SuccessorsFunction fixedSuccessors(Iterable successors) { return new SuccessorsFunction() { @Override public Iterable successors(N n) { diff --git a/android/guava-tests/test/com/google/common/graph/ValueGraphTest.java b/android/guava-tests/test/com/google/common/graph/ValueGraphTest.java index b17c91dfae36..94dbafd74215 100644 --- a/android/guava-tests/test/com/google/common/graph/ValueGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/ValueGraphTest.java @@ -20,7 +20,7 @@ import static com.google.common.graph.TestUtil.assertStronglyEquivalent; import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.Executors.newFixedThreadPool; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import java.util.Set; @@ -28,6 +28,8 @@ import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,6 +38,7 @@ /** Tests for {@link StandardMutableValueGraph} and related functionality. */ // TODO(user): Expand coverage and move to proper test suite. @RunWith(JUnit4.class) +@NullUnmarked public final class ValueGraphTest { private static final String DEFAULT = "default"; @@ -55,6 +58,17 @@ public void validateGraphState() { assertThat(graph.isDirected()).isEqualTo(asGraph.isDirected()); assertThat(graph.allowsSelfLoops()).isEqualTo(asGraph.allowsSelfLoops()); + Network> asNetwork = graph.asNetwork(); + AbstractNetworkTest.validateNetwork(asNetwork); + assertThat(graph.nodes()).isEqualTo(asNetwork.nodes()); + assertThat(graph.edges()).hasSize(asNetwork.edges().size()); + assertThat(graph.nodeOrder()).isEqualTo(asNetwork.nodeOrder()); + assertThat(graph.isDirected()).isEqualTo(asNetwork.isDirected()); + assertThat(graph.allowsSelfLoops()).isEqualTo(asNetwork.allowsSelfLoops()); + assertThat(asNetwork.edgeOrder()).isEqualTo(ElementOrder.unordered()); + assertThat(asNetwork.allowsParallelEdges()).isFalse(); + assertThat(asNetwork.asGraph()).isEqualTo(graph.asGraph()); + for (Integer node : graph.nodes()) { assertThat(graph.adjacentNodes(node)).isEqualTo(asGraph.adjacentNodes(node)); assertThat(graph.predecessors(node)).isEqualTo(asGraph.predecessors(node)); @@ -173,8 +187,8 @@ public void hasEdgeConnecting_undirected_backwards() { public void hasEdgeConnecting_undirected_mismatch() { graph = ValueGraphBuilder.undirected().build(); graph.putEdgeValue(1, 2, "A"); - assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(1, 2))).isTrue(); - assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(2, 1))).isTrue(); + assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(1, 2))).isFalse(); + assertThat(graph.hasEdgeConnecting(EndpointPair.ordered(2, 1))).isFalse(); } @Test @@ -196,13 +210,16 @@ public void edgeValueOrDefault_directed_backwards() { public void edgeValueOrDefault_directed_mismatch() { graph = ValueGraphBuilder.directed().build(); graph.putEdgeValue(1, 2, "A"); - try { - String unused = graph.edgeValueOrDefault(EndpointPair.unordered(1, 2), "default"); - unused = graph.edgeValueOrDefault(EndpointPair.unordered(2, 1), "default"); - fail("Expected IllegalArgumentException: " + ENDPOINTS_MISMATCH); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> graph.edgeValueOrDefault(EndpointPair.unordered(1, 2), "default")); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); + e = + assertThrows( + IllegalArgumentException.class, + () -> graph.edgeValueOrDefault(EndpointPair.unordered(2, 1), "default")); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test @@ -223,8 +240,17 @@ public void edgeValueOrDefault_undirected_backwards() { public void edgeValueOrDefault_undirected_mismatch() { graph = ValueGraphBuilder.undirected().build(); graph.putEdgeValue(1, 2, "A"); - assertThat(graph.edgeValueOrDefault(EndpointPair.ordered(2, 1), "default")).isEqualTo("A"); - assertThat(graph.edgeValueOrDefault(EndpointPair.ordered(2, 1), "default")).isEqualTo("A"); + // Check that edgeValueOrDefault() throws on each possible ordering of an ordered EndpointPair + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> graph.edgeValueOrDefault(EndpointPair.ordered(1, 2), "default")); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); + e = + assertThrows( + IllegalArgumentException.class, + () -> graph.edgeValueOrDefault(EndpointPair.ordered(2, 1), "default")); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test @@ -240,18 +266,21 @@ public void putEdgeValue_directed() { @Test public void putEdgeValue_directed_orderMismatch() { graph = ValueGraphBuilder.directed().build(); - try { - graph.putEdgeValue(EndpointPair.unordered(1, 2), "irrelevant"); - fail("Expected IllegalArgumentException: " + ENDPOINTS_MISMATCH); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> graph.putEdgeValue(EndpointPair.unordered(1, 2), "irrelevant")); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test public void putEdgeValue_undirected_orderMismatch() { graph = ValueGraphBuilder.undirected().build(); - assertThat(graph.putEdgeValue(EndpointPair.ordered(1, 2), "irrelevant")).isNull(); + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> graph.putEdgeValue(EndpointPair.ordered(1, 2), "irrelevant")); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test @@ -298,20 +327,29 @@ public void removeEdge_directed_orderMismatch() { graph = ValueGraphBuilder.directed().build(); graph.putEdgeValue(1, 2, "1->2"); graph.putEdgeValue(2, 1, "2->1"); - try { - graph.removeEdge(EndpointPair.unordered(1, 2)); - graph.removeEdge(EndpointPair.unordered(2, 1)); - fail("Expected IllegalArgumentException: " + ENDPOINTS_MISMATCH); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> graph.removeEdge(EndpointPair.unordered(1, 2))); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); + e = + assertThrows( + IllegalArgumentException.class, () -> graph.removeEdge(EndpointPair.unordered(2, 1))); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test public void removeEdge_undirected_orderMismatch() { graph = ValueGraphBuilder.undirected().build(); graph.putEdgeValue(1, 2, "1-2"); - assertThat(graph.removeEdge(EndpointPair.ordered(1, 2))).isEqualTo("1-2"); + // Check that removeEdge() throws on each possible ordering of an ordered EndpointPair + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> graph.removeEdge(EndpointPair.ordered(1, 2))); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); + e = + assertThrows( + IllegalArgumentException.class, () -> graph.removeEdge(EndpointPair.ordered(2, 1))); + assertThat(e).hasMessageThat().contains(ENDPOINTS_MISMATCH); } @Test @@ -380,7 +418,6 @@ public void incidentEdges_stableIncidentEdgeOrder_preservesIncidentEdgesOrder_un .inOrder(); } - @Test public void concurrentIteration() throws Exception { graph = ValueGraphBuilder.directed().build(); @@ -390,14 +427,14 @@ public void concurrentIteration() throws Exception { int threadCount = 20; ExecutorService executor = newFixedThreadPool(threadCount); - final CyclicBarrier barrier = new CyclicBarrier(threadCount); + CyclicBarrier barrier = new CyclicBarrier(threadCount); ImmutableList.Builder> futures = ImmutableList.builder(); for (int i = 0; i < threadCount; i++) { futures.add( executor.submit( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Object call() throws Exception { + public @Nullable Void call() throws Exception { barrier.await(); Integer first = graph.nodes().iterator().next(); for (Integer node : graph.nodes()) { diff --git a/android/guava-tests/test/com/google/common/hash/AbstractByteHasherTest.java b/android/guava-tests/test/com/google/common/hash/AbstractByteHasherTest.java index e5c359aaf123..fe35bf3d9820 100644 --- a/android/guava-tests/test/com/google/common/hash/AbstractByteHasherTest.java +++ b/android/guava-tests/test/com/google/common/hash/AbstractByteHasherTest.java @@ -14,19 +14,21 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.UTF_16LE; +import static java.nio.charset.StandardCharsets.UTF_16LE; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertThrows; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.ByteArrayOutputStream; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for AbstractByteHasher. * * @author Colin Decker */ +@NullUnmarked public class AbstractByteHasherTest extends TestCase { public void testBytes() { @@ -93,24 +95,11 @@ public void testDouble() { public void testCorrectExceptions() { TestHasher hasher = new TestHasher(); - try { - hasher.putBytes(new byte[8], -1, 4); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - hasher.putBytes(new byte[8], 0, 16); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - hasher.putBytes(new byte[8], 0, -1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> hasher.putBytes(new byte[8], -1, 4)); + assertThrows(IndexOutOfBoundsException.class, () -> hasher.putBytes(new byte[8], 0, 16)); + assertThrows(IndexOutOfBoundsException.class, () -> hasher.putBytes(new byte[8], 0, -1)); } - @CanIgnoreReturnValue private class TestHasher extends AbstractByteHasher { private final ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/android/guava-tests/test/com/google/common/hash/AbstractNonStreamingHashFunctionTest.java b/android/guava-tests/test/com/google/common/hash/AbstractNonStreamingHashFunctionTest.java index 754147152fd9..281ec4fef5a4 100644 --- a/android/guava-tests/test/com/google/common/hash/AbstractNonStreamingHashFunctionTest.java +++ b/android/guava-tests/test/com/google/common/hash/AbstractNonStreamingHashFunctionTest.java @@ -24,8 +24,10 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Tests for AbstractNonStreamingHashFunction. */ +@NullUnmarked public class AbstractNonStreamingHashFunctionTest extends TestCase { /** * Constructs two trivial HashFunctions (output := input), one streaming and one non-streaming, diff --git a/android/guava-tests/test/com/google/common/hash/AbstractStreamingHasherTest.java b/android/guava-tests/test/com/google/common/hash/AbstractStreamingHasherTest.java index 99b2c71a07d5..773f1fa4a498 100644 --- a/android/guava-tests/test/com/google/common/hash/AbstractStreamingHasherTest.java +++ b/android/guava-tests/test/com/google/common/hash/AbstractStreamingHasherTest.java @@ -16,25 +16,29 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.UTF_16LE; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_16LE; +import static org.junit.Assert.assertThrows; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.hash.HashTestUtils.RandomHasherAction; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for AbstractStreamingHasher. * * @author Dimitris Andreou */ +@NullUnmarked public class AbstractStreamingHasherTest extends TestCase { public void testBytes() { Sink sink = new Sink(4); // byte order insignificant here @@ -113,21 +117,9 @@ public void testDouble() { public void testCorrectExceptions() { Sink sink = new Sink(4); - try { - sink.putBytes(new byte[8], -1, 4); - fail(); - } catch (IndexOutOfBoundsException ok) { - } - try { - sink.putBytes(new byte[8], 0, 16); - fail(); - } catch (IndexOutOfBoundsException ok) { - } - try { - sink.putBytes(new byte[8], 0, -1); - fail(); - } catch (IndexOutOfBoundsException ok) { - } + assertThrows(IndexOutOfBoundsException.class, () -> sink.putBytes(new byte[8], -1, 4)); + assertThrows(IndexOutOfBoundsException.class, () -> sink.putBytes(new byte[8], 0, 16)); + assertThrows(IndexOutOfBoundsException.class, () -> sink.putBytes(new byte[8], 0, -1)); } /** @@ -140,7 +132,7 @@ public void testExhaustive() throws Exception { Random random = new Random(0); // will iteratively make more debuggable, each time it breaks for (int totalInsertions = 0; totalInsertions < 200; totalInsertions++) { - List sinks = Lists.newArrayList(); + List sinks = new ArrayList<>(); for (int chunkSize = 4; chunkSize <= 32; chunkSize++) { for (int bufferSize = chunkSize; bufferSize <= chunkSize * 4; bufferSize += chunkSize) { // yes, that's a lot of sinks! @@ -206,7 +198,7 @@ protected HashCode makeHash() { protected void process(ByteBuffer bb) { processCalled++; assertEquals(ByteOrder.LITTLE_ENDIAN, bb.order()); - assertTrue(bb.remaining() >= chunkSize); + assertThat(bb.remaining()).isAtLeast(chunkSize); for (int i = 0; i < chunkSize; i++) { out.write(bb.get()); } @@ -217,8 +209,8 @@ protected void processRemaining(ByteBuffer bb) { assertFalse(remainingCalled); remainingCalled = true; assertEquals(ByteOrder.LITTLE_ENDIAN, bb.order()); - assertTrue(bb.remaining() > 0); - assertTrue(bb.remaining() < bufferSize); + assertThat(bb.remaining()).isGreaterThan(0); + assertThat(bb.remaining()).isLessThan(bufferSize); int before = processCalled; super.processRemaining(bb); int after = processCalled; diff --git a/android/guava-tests/test/com/google/common/hash/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/hash/AndroidIncompatible.java index f807acb8868a..1139e4336f3d 100644 --- a/android/guava-tests/test/com/google/common/hash/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/hash/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/hash/BloomFilterTest.java b/android/guava-tests/test/com/google/common/hash/BloomFilterTest.java index f69b57853374..80e80e7860b0 100644 --- a/android/guava-tests/test/com/google/common/hash/BloomFilterTest.java +++ b/android/guava-tests/test/com/google/common/hash/BloomFilterTest.java @@ -16,8 +16,12 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.hash.BloomFilter.toBloomFilter; +import static com.google.common.hash.Funnels.unencodedCharsFunnel; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; @@ -35,15 +39,17 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; -import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for SimpleGenericBloomFilter and derived BloomFilter views. * * @author Dimitris Andreou */ +@NullUnmarked public class BloomFilterTest extends TestCase { private static final int NUM_PUTS = 100_000; private static final ThreadLocal random = @@ -121,7 +127,7 @@ public void testCreateAndCheckMitz32BloomFilterWithKnownFalsePositives() { assertEquals(knownNumberOfFalsePositives, numFpp); double expectedReportedFpp = (double) knownNumberOfFalsePositives / numInsertions; double actualReportedFpp = bf.expectedFpp(); - assertEquals(expectedReportedFpp, actualReportedFpp, 0.00015); + assertThat(actualReportedFpp).isWithin(0.00015).of(expectedReportedFpp); } public void testCreateAndCheckBloomFilterWithKnownFalsePositives64() { @@ -165,7 +171,7 @@ public void testCreateAndCheckBloomFilterWithKnownFalsePositives64() { assertEquals(knownNumberOfFalsePositives, numFpp); double expectedReportedFpp = (double) knownNumberOfFalsePositives / numInsertions; double actualReportedFpp = bf.expectedFpp(); - assertEquals(expectedReportedFpp, actualReportedFpp, 0.00033); + assertThat(actualReportedFpp).isWithin(0.00033).of(expectedReportedFpp); } public void testCreateAndCheckBloomFilterWithKnownUtf8FalsePositives64() { @@ -208,7 +214,7 @@ public void testCreateAndCheckBloomFilterWithKnownUtf8FalsePositives64() { assertEquals(knownNumberOfFalsePositives, numFpp); double expectedReportedFpp = (double) knownNumberOfFalsePositives / numInsertions; double actualReportedFpp = bf.expectedFpp(); - assertEquals(expectedReportedFpp, actualReportedFpp, 0.00033); + assertThat(actualReportedFpp).isWithin(0.00033).of(expectedReportedFpp); } /** Sanity checking with many combinations of false positive rates and expected insertions */ @@ -221,36 +227,28 @@ public void testBasic() { } public void testPreconditions() { - try { - BloomFilter.create(Funnels.unencodedCharsFunnel(), -1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - BloomFilter.create(Funnels.unencodedCharsFunnel(), -1, 0.03); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - BloomFilter.create(Funnels.unencodedCharsFunnel(), 1, 0.0); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - BloomFilter.create(Funnels.unencodedCharsFunnel(), 1, 1.0); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> BloomFilter.create(Funnels.unencodedCharsFunnel(), -1)); + assertThrows( + IllegalArgumentException.class, + () -> BloomFilter.create(Funnels.unencodedCharsFunnel(), -1, 0.03)); + assertThrows( + IllegalArgumentException.class, + () -> BloomFilter.create(Funnels.unencodedCharsFunnel(), 1, 0.0)); + assertThrows( + IllegalArgumentException.class, + () -> BloomFilter.create(Funnels.unencodedCharsFunnel(), 1, 1.0)); } public void testFailureWhenMoreThan255HashFunctionsAreNeeded() { - try { - int n = 1000; - double p = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000001; - BloomFilter.create(Funnels.unencodedCharsFunnel(), n, p); - fail(); - } catch (IllegalArgumentException expected) { - } + int n = 1000; + double p = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000001; + assertThrows( + IllegalArgumentException.class, + () -> { + BloomFilter.create(Funnels.unencodedCharsFunnel(), n, p); + }); } public void testNullPointers() { @@ -262,42 +260,44 @@ public void testNullPointers() { /** Tests that we never get an optimal hashes number of zero. */ public void testOptimalHashes() { for (int n = 1; n < 1000; n++) { - for (int m = 0; m < 1000; m++) { - assertTrue(BloomFilter.optimalNumOfHashFunctions(n, m) > 0); + for (double p = 0.1; p > 1e-10; p /= 10) { + assertThat(BloomFilter.optimalNumOfHashFunctions(p)).isGreaterThan(0); } } } - // https://code.google.com/p/guava-libraries/issues/detail?id=1781 + // https://github.com/google/guava/issues/1781 public void testOptimalNumOfHashFunctionsRounding() { - assertEquals(7, BloomFilter.optimalNumOfHashFunctions(319, 3072)); + assertEquals(5, BloomFilter.optimalNumOfHashFunctions(0.03)); } /** Tests that we always get a non-negative optimal size. */ public void testOptimalSize() { for (int n = 1; n < 1000; n++) { for (double fpp = Double.MIN_VALUE; fpp < 1.0; fpp += 0.001) { - assertTrue(BloomFilter.optimalNumOfBits(n, fpp) >= 0); + assertThat(BloomFilter.optimalNumOfBits(n, fpp)).isAtLeast(0); } } // some random values Random random = new Random(0); for (int repeats = 0; repeats < 10000; repeats++) { - assertTrue(BloomFilter.optimalNumOfBits(random.nextInt(1 << 16), random.nextDouble()) >= 0); + assertThat(BloomFilter.optimalNumOfBits(random.nextInt(1 << 16), random.nextDouble())) + .isAtLeast(0); } // and some crazy values (this used to be capped to Integer.MAX_VALUE, now it can go bigger assertEquals(3327428144502L, BloomFilter.optimalNumOfBits(Integer.MAX_VALUE, Double.MIN_VALUE)); - try { - BloomFilter unused = - BloomFilter.create(HashTestUtils.BAD_FUNNEL, Integer.MAX_VALUE, Double.MIN_VALUE); - fail("we can't represent such a large BF!"); - } catch (IllegalArgumentException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("Could not create BloomFilter of 3327428144502 bits"); - } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> { + BloomFilter unused = + BloomFilter.create(HashTestUtils.BAD_FUNNEL, Integer.MAX_VALUE, Double.MIN_VALUE); + }); + assertThat(expected) + .hasMessageThat() + .isEqualTo("Could not create BloomFilter of 3327428144502 bits"); } @AndroidIncompatible // OutOfMemoryError @@ -308,6 +308,7 @@ public void testLargeNumberOfInsertions() { unused = BloomFilter.create(Funnels.unencodedCharsFunnel(), 45L * Integer.MAX_VALUE, 0.99); } + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method private static void checkSanity(BloomFilter bf) { assertFalse(bf.mightContain(new Object())); assertFalse(bf.apply(new Object())); @@ -329,7 +330,7 @@ public void testCopy() { public void testExpectedFpp() { BloomFilter bf = BloomFilter.create(HashTestUtils.BAD_FUNNEL, 10, 0.03); double fpp = bf.expectedFpp(); - assertEquals(0.0, fpp); + assertThat(fpp).isEqualTo(0.0); // usually completed in less than 200 iterations while (fpp != 1.0) { boolean changed = bf.put(new Object()); @@ -408,8 +409,8 @@ public void funnel(Long value, PrimitiveSink into) { } @Override - public boolean equals(@NullableDecl Object object) { - return (object instanceof CustomFunnel); + public boolean equals(@Nullable Object object) { + return object instanceof CustomFunnel; } @Override @@ -456,29 +457,29 @@ public void testPutAllDifferentSizes() { BloomFilter bf1 = BloomFilter.create(Funnels.integerFunnel(), 1); BloomFilter bf2 = BloomFilter.create(Funnels.integerFunnel(), 10); - try { - assertFalse(bf1.isCompatible(bf2)); - bf1.putAll(bf2); - fail(); - } catch (IllegalArgumentException expected) { - } + assertFalse(bf1.isCompatible(bf2)); + assertThrows( + IllegalArgumentException.class, + () -> { + bf1.putAll(bf2); + }); - try { - assertFalse(bf2.isCompatible(bf1)); - bf2.putAll(bf1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertFalse(bf2.isCompatible(bf1)); + assertThrows( + IllegalArgumentException.class, + () -> { + bf2.putAll(bf1); + }); } public void testPutAllWithSelf() { BloomFilter bf1 = BloomFilter.create(Funnels.integerFunnel(), 1); - try { - assertFalse(bf1.isCompatible(bf1)); - bf1.putAll(bf1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertFalse(bf1.isCompatible(bf1)); + assertThrows( + IllegalArgumentException.class, + () -> { + bf1.putAll(bf1); + }); } public void testJavaSerialization() { @@ -491,7 +492,7 @@ public void testJavaSerialization() { for (int i = 0; i < 10; i++) { assertTrue(copy.mightContain(Ints.toByteArray(i))); } - assertEquals(bf.expectedFpp(), copy.expectedFpp()); + assertThat(copy.expectedFpp()).isEqualTo(bf.expectedFpp()); SerializableTester.reserializeAndAssert(bf); } @@ -506,13 +507,18 @@ public void testCustomSerialization() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); bf.writeTo(out); - assertEquals(bf, BloomFilter.readFrom(new ByteArrayInputStream(out.toByteArray()), funnel)); + BloomFilter read = + BloomFilter.readFrom(new ByteArrayInputStream(out.toByteArray()), funnel); + assertThat(read).isEqualTo(bf); + assertThat(read.expectedFpp()).isGreaterThan(0); } /** * This test will fail whenever someone updates/reorders the BloomFilterStrategies constants. Only * appending a new constant is allowed. */ + // This test ensures that our reliance on the ordering elsewhere is safe. + @SuppressWarnings("EnumOrdinal") public void testBloomFilterStrategies() { assertThat(BloomFilterStrategies.values()).hasLength(2); assertEquals(BloomFilterStrategies.MURMUR128_MITZ_32, BloomFilterStrategies.values()[0]); @@ -521,7 +527,7 @@ public void testBloomFilterStrategies() { public void testNoRaceConditions() throws Exception { - final BloomFilter bloomFilter = + BloomFilter bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 15_000_000, 0.01); // This check has to be BEFORE the loop because the random insertions can @@ -534,8 +540,8 @@ public void testNoRaceConditions() throws Exception { bloomFilter.put(GOLDEN_PRESENT_KEY); int numThreads = 12; - final double safetyFalsePositiveRate = 0.1; - final Stopwatch stopwatch = Stopwatch.createStarted(); + double safetyFalsePositiveRate = 0.1; + Stopwatch stopwatch = Stopwatch.createStarted(); Runnable task = new Runnable() { @@ -560,7 +566,7 @@ public void run() { // Don't forget, the bloom filter slowly saturates over time and the // expected false positive probability goes up! assertThat(bloomFilter.expectedFpp()).isLessThan(safetyFalsePositiveRate); - } while (stopwatch.elapsed(TimeUnit.SECONDS) < 1); + } while (stopwatch.elapsed(SECONDS) < 1); } }; @@ -571,7 +577,7 @@ public void run() { private static List runThreadsAndReturnExceptions(int numThreads, Runnable task) { List threads = new ArrayList<>(numThreads); - final List exceptions = new ArrayList<>(numThreads); + List exceptions = new ArrayList<>(numThreads); for (int i = 0; i < numThreads; i++) { Thread thread = new Thread(task); thread.setUncaughtExceptionHandler( @@ -599,4 +605,13 @@ private static int getNonGoldenRandomKey() { } while (key == GOLDEN_PRESENT_KEY); return key; } + + public void testToBloomFilter() { + BloomFilter bf1 = BloomFilter.create(unencodedCharsFunnel(), 2); + bf1.put("1"); + bf1.put("2"); + + assertEquals(bf1, Stream.of("1", "2").collect(toBloomFilter(unencodedCharsFunnel(), 2))); + assertEquals(bf1, Stream.of("2", "1").collect(toBloomFilter(unencodedCharsFunnel(), 2))); + } } diff --git a/android/guava-tests/test/com/google/common/hash/ChecksumHashFunctionTest.java b/android/guava-tests/test/com/google/common/hash/ChecksumHashFunctionTest.java index 14a106a62825..1cdc8cdfcf8c 100644 --- a/android/guava-tests/test/com/google/common/hash/ChecksumHashFunctionTest.java +++ b/android/guava-tests/test/com/google/common/hash/ChecksumHashFunctionTest.java @@ -19,12 +19,14 @@ import java.util.zip.Checksum; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for ChecksumHashFunction. * * @author Colin Decker */ +@NullUnmarked public class ChecksumHashFunctionTest extends TestCase { public void testCrc32_equalsChecksumValue() throws Exception { diff --git a/android/guava-tests/test/com/google/common/hash/Crc32cHashFunctionTest.java b/android/guava-tests/test/com/google/common/hash/Crc32cHashFunctionTest.java index 3bea975e1427..9f19b70a6da5 100644 --- a/android/guava-tests/test/com/google/common/hash/Crc32cHashFunctionTest.java +++ b/android/guava-tests/test/com/google/common/hash/Crc32cHashFunctionTest.java @@ -14,11 +14,12 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import java.util.Arrays; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Crc32c}. Known test values are from RFC 3720, Section B.4. @@ -26,6 +27,7 @@ * @author Patrick Costello * @author Kurt Alfred Kluever */ +@NullUnmarked public class Crc32cHashFunctionTest extends TestCase { public void testEmpty() { assertCrc(0, new byte[0]); @@ -124,7 +126,7 @@ public void testAgainstSimplerImplementation() { private static int referenceCrc(byte[] bytes) { int crc = ~0; for (byte b : bytes) { - crc = (crc >>> 8) ^ Crc32cHashFunction.Crc32cHasher.BYTE_TABLE[(crc ^ b) & 0xFF]; + crc = (crc >>> 8) ^ Crc32cHashFunction.Crc32cHasher.byteTable[(crc ^ b) & 0xFF]; } return ~crc; } @@ -162,12 +164,12 @@ public void testCrc32cByteTable() { int crc = i; for (int j = 7; j >= 0; j--) { int mask = -(crc & 1); - crc = ((crc >>> 1) ^ (CRC32C_GENERATOR_FLIPPED & mask)); + crc = (crc >>> 1) ^ (CRC32C_GENERATOR_FLIPPED & mask); } expected[i] = crc; } - int[] actual = Crc32cHashFunction.Crc32cHasher.BYTE_TABLE; + int[] actual = Crc32cHashFunction.Crc32cHasher.byteTable; assertTrue( "Expected: \n" + Arrays.toString(expected) + "\nActual:\n" + Arrays.toString(actual), Arrays.equals(expected, actual)); @@ -184,7 +186,7 @@ static int advanceOneBit(int next) { public void testCrc32cStrideTable() { int next = CRC32C_GENERATOR_FLIPPED; for (int i = 0; i < 12; i++) { // for 3 ints = 12 bytes in between each stride window - next = (next >>> 8) ^ Crc32cHashFunction.Crc32cHasher.BYTE_TABLE[next & 0xFF]; + next = (next >>> 8) ^ Crc32cHashFunction.Crc32cHasher.byteTable[next & 0xFF]; } int[][] expected = new int[4][256]; for (int b = 0; b < 4; ++b) { @@ -202,7 +204,7 @@ public void testCrc32cStrideTable() { } } - int[][] actual = Crc32cHashFunction.Crc32cHasher.STRIDE_TABLE; + int[][] actual = Crc32cHashFunction.Crc32cHasher.strideTable; assertTrue( "Expected: \n" + Arrays.deepToString(expected) diff --git a/android/guava-tests/test/com/google/common/hash/FarmHashFingerprint64Test.java b/android/guava-tests/test/com/google/common/hash/FarmHashFingerprint64Test.java index 82a5750ba14e..0404cbfaa5e0 100644 --- a/android/guava-tests/test/com/google/common/hash/FarmHashFingerprint64Test.java +++ b/android/guava-tests/test/com/google/common/hash/FarmHashFingerprint64Test.java @@ -16,13 +16,14 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.ISO_8859_1; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Strings; import java.util.Arrays; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for FarmHashFingerprint64. @@ -30,11 +31,13 @@ * @author Kyle Maddison * @author Geoff Pike */ +@NullUnmarked public class FarmHashFingerprint64Test extends TestCase { private static final HashFunction HASH_FN = Hashing.farmHashFingerprint64(); // If this test fails, all bets are off + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 public void testReallySimpleFingerprints() { assertEquals(8581389452482819506L, fingerprint("test".getBytes(UTF_8))); // 32 characters long @@ -91,7 +94,7 @@ public void testPutNonChars() { .putBoolean(false) .putBoolean(false) .putBoolean(false); - final long hashCode = hasher.hash().asLong(); + long hashCode = hasher.hash().asLong(); hasher = HASH_FN.newHasher(); hasher diff --git a/android/guava-tests/test/com/google/common/hash/Fingerprint2011Test.java b/android/guava-tests/test/com/google/common/hash/Fingerprint2011Test.java new file mode 100644 index 000000000000..05ec65d1c888 --- /dev/null +++ b/android/guava-tests/test/com/google/common/hash/Fingerprint2011Test.java @@ -0,0 +1,236 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package com.google.common.hash; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Ordering; +import com.google.common.primitives.UnsignedLong; +import java.util.Arrays; +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +/** + * Unit test for Fingerprint2011. + * + * @author kylemaddison@google.com (Kyle Maddison) + */ +@NullUnmarked +public class Fingerprint2011Test extends TestCase { + + // Length of the sample string to produce + private static final int MAX_BYTES = 1000; + + // Map from sample string lengths to the fingerprint + private static final ImmutableSortedMap LENGTH_FINGERPRINTS = + new ImmutableSortedMap.Builder(Ordering.natural()) + .put(1000, 0x433109b33e13e6edL) + .put(800, 0x5f2f123bfc815f81L) + .put(640, 0x6396fc6a67293cf4L) + .put(512, 0x45c01b4934ddbbbeL) + .put(409, 0xfcd19b617551db45L) + .put(327, 0x4eee69e12854871eL) + .put(261, 0xab753446a3bbd532L) + .put(208, 0x54242fe06a291c3fL) + .put(166, 0x4f7acff7703a635bL) + .put(132, 0xa784bd0a1f22cc7fL) + .put(105, 0xf19118e187456638L) + .put(84, 0x3e2e58f9196abfe5L) + .put(67, 0xd38ae3dec0107aeaL) + .put(53, 0xea3033885868e10eL) + .put(42, 0x1394a146d0d7e04bL) + .put(33, 0x9962499315d2e8daL) + .put(26, 0x0849f5cfa85489b5L) + .put(20, 0x83b395ff19bf2171L) + .put(16, 0x9d33dd141bd55d9aL) + .put(12, 0x196248eb0b02466aL) + .put(9, 0x1cf73a50ff120336L) + .put(7, 0xb451c339457dbf51L) + .put(5, 0x681982c5e7b74064L) + .put(4, 0xc5ce47450ca6c021L) + .put(3, 0x9fcc3c3fde4d5ff7L) + .put(2, 0x090966a836e5fa4bL) + .put(1, 0x8199675ecaa6fe64L) + .put(0, 0x23ad7c904aa665e3L) + .build(); + private static final HashFunction HASH_FN = Hashing.fingerprint2011(); + + // If this test fails, all bets are off + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 + public void testReallySimpleFingerprints() { + assertEquals(8473225671271759044L, fingerprint("test".getBytes(UTF_8))); + // 32 characters long + assertEquals(7345148637025587076L, fingerprint(Strings.repeat("test", 8).getBytes(UTF_8))); + // 256 characters long + assertEquals(4904844928629814570L, fingerprint(Strings.repeat("test", 64).getBytes(UTF_8))); + } + + public void testStringsConsistency() { + for (String s : Arrays.asList("", "some", "test", "strings", "to", "try")) { + assertEquals(HASH_FN.newHasher().putUnencodedChars(s).hash(), HASH_FN.hashUnencodedChars(s)); + } + } + + public void testUtf8() { + char[] charsA = new char[128]; + char[] charsB = new char[128]; + + for (int i = 0; i < charsA.length; i++) { + if (i < 100) { + charsA[i] = 'a'; + charsB[i] = 'a'; + } else { + // Both two-byte characters, but must be different + charsA[i] = (char) (0x0180 + i); + charsB[i] = (char) (0x0280 + i); + } + } + + String stringA = new String(charsA); + String stringB = new String(charsB); + assertThat(stringA).isNotEqualTo(stringB); + assertThat(HASH_FN.hashUnencodedChars(stringA)) + .isNotEqualTo(HASH_FN.hashUnencodedChars(stringB)); + assertThat(fingerprint(stringA.getBytes(UTF_8))) + .isNotEqualTo(fingerprint(stringB.getBytes(UTF_8))); + + // ISO 8859-1 only has 0-255 (ubyte) representation so throws away UTF-8 characters + // greater than 127 (ie with their top bit set). + // Don't attempt to do this in real code. + assertEquals( + fingerprint(stringA.getBytes(ISO_8859_1)), fingerprint(stringB.getBytes(ISO_8859_1))); + } + + public void testMumurHash64() { + byte[] bytes = "test".getBytes(UTF_8); + assertEquals( + 1618900948208871284L, Fingerprint2011.murmurHash64WithSeed(bytes, 0, bytes.length, 1)); + + bytes = "test test test".getBytes(UTF_8); + assertEquals( + UnsignedLong.valueOf("12313169684067793560").longValue(), + Fingerprint2011.murmurHash64WithSeed(bytes, 0, bytes.length, 1)); + } + + public void testPutNonChars() { + Hasher hasher = HASH_FN.newHasher(); + // Expected data is 0x0100010100000000 + hasher + .putBoolean(true) + .putBoolean(true) + .putBoolean(false) + .putBoolean(true) + .putBoolean(false) + .putBoolean(false) + .putBoolean(false) + .putBoolean(false); + long hashCode = hasher.hash().asLong(); + + hasher = HASH_FN.newHasher(); + hasher + .putByte((byte) 0x01) + .putByte((byte) 0x01) + .putByte((byte) 0x00) + .putByte((byte) 0x01) + .putByte((byte) 0x00) + .putByte((byte) 0x00) + .putByte((byte) 0x00) + .putByte((byte) 0x00); + assertEquals(hashCode, hasher.hash().asLong()); + + hasher = HASH_FN.newHasher(); + hasher + .putChar((char) 0x0101) + .putChar((char) 0x0100) + .putChar((char) 0x0000) + .putChar((char) 0x0000); + assertEquals(hashCode, hasher.hash().asLong()); + + hasher = HASH_FN.newHasher(); + hasher.putBytes(new byte[] {0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}); + assertEquals(hashCode, hasher.hash().asLong()); + + hasher = HASH_FN.newHasher(); + hasher.putLong(0x0000000001000101L); + assertEquals(hashCode, hasher.hash().asLong()); + + hasher = HASH_FN.newHasher(); + hasher + .putShort((short) 0x0101) + .putShort((short) 0x0100) + .putShort((short) 0x0000) + .putShort((short) 0x0000); + assertEquals(hashCode, hasher.hash().asLong()); + } + + public void testHashFloatIsStable() { + // This is about the best we can do for floating-point + Hasher hasher = HASH_FN.newHasher(); + hasher.putFloat(0x01000101f).putFloat(0f); + assertEquals(0x96a4f8cc6ecbf16L, hasher.hash().asLong()); + + hasher = HASH_FN.newHasher(); + hasher.putDouble(0x0000000001000101d); + assertEquals(0xcf54171253fdc198L, hasher.hash().asLong()); + } + + /** Convenience method to compute a fingerprint on a full bytes array. */ + private static long fingerprint(byte[] bytes) { + return fingerprint(bytes, bytes.length); + } + + /** Convenience method to compute a fingerprint on a subset of a byte array. */ + private static long fingerprint(byte[] bytes, int length) { + return HASH_FN.hashBytes(bytes, 0, length).asLong(); + } + + /** + * Tests that the Java port of Fingerprint2011 provides the same results on buffers up to 800 + * bytes long as the original implementation in C++. See http://cl/106539598 + */ + public void testMultipleLengths() { + int iterations = 800; + byte[] buf = new byte[iterations * 4]; + int bufLen = 0; + long h = 0; + for (int i = 0; i < iterations; ++i) { + h ^= fingerprint(buf, i); + h = remix(h); + buf[bufLen++] = getChar(h); + + h ^= fingerprint(buf, i * i % bufLen); + h = remix(h); + buf[bufLen++] = getChar(h); + + h ^= fingerprint(buf, i * i * i % bufLen); + h = remix(h); + buf[bufLen++] = getChar(h); + + h ^= fingerprint(buf, bufLen); + h = remix(h); + buf[bufLen++] = getChar(h); + + int x0 = buf[bufLen - 1] & 0xff; + int x1 = buf[bufLen - 2] & 0xff; + int x2 = buf[bufLen - 3] & 0xff; + int x3 = buf[bufLen / 2] & 0xff; + buf[((x0 << 16) + (x1 << 8) + x2) % bufLen] ^= x3; + buf[((x1 << 16) + (x2 << 8) + x3) % bufLen] ^= i % 256; + } + assertEquals(0xeaa3b1c985261632L, h); + } + + private static long remix(long h) { + h ^= h >>> 41; + h *= 949921979; + return h; + } + + private static byte getChar(long h) { + return (byte) ('a' + ((h & 0xfffff) % 26)); + } +} diff --git a/android/guava-tests/test/com/google/common/hash/FunnelsTest.java b/android/guava-tests/test/com/google/common/hash/FunnelsTest.java index 6b0c75862cac..862ddcb6d153 100644 --- a/android/guava-tests/test/com/google/common/hash/FunnelsTest.java +++ b/android/guava-tests/test/com/google/common/hash/FunnelsTest.java @@ -16,11 +16,12 @@ package com.google.common.hash; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import com.google.common.base.Charsets; import com.google.common.testing.EqualsTester; import com.google.common.testing.SerializableTester; import java.io.OutputStream; @@ -28,6 +29,7 @@ import java.nio.charset.Charset; import java.util.Arrays; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; import org.mockito.InOrder; /** @@ -35,6 +37,7 @@ * * @author Dimitris Andreou */ +@NullUnmarked public class FunnelsTest extends TestCase { public void testForBytes() { PrimitiveSink primitiveSink = mock(PrimitiveSink.class); @@ -151,8 +154,8 @@ public void testSerialization() { Funnels.sequentialFunnel(Funnels.integerFunnel()), SerializableTester.reserialize(Funnels.sequentialFunnel(Funnels.integerFunnel()))); assertEquals( - Funnels.stringFunnel(Charsets.US_ASCII), - SerializableTester.reserialize(Funnels.stringFunnel(Charsets.US_ASCII))); + Funnels.stringFunnel(US_ASCII), + SerializableTester.reserialize(Funnels.stringFunnel(US_ASCII))); } public void testEquals() { @@ -161,8 +164,8 @@ public void testEquals() { .addEqualityGroup(Funnels.integerFunnel()) .addEqualityGroup(Funnels.longFunnel()) .addEqualityGroup(Funnels.unencodedCharsFunnel()) - .addEqualityGroup(Funnels.stringFunnel(Charsets.UTF_8)) - .addEqualityGroup(Funnels.stringFunnel(Charsets.US_ASCII)) + .addEqualityGroup(Funnels.stringFunnel(UTF_8)) + .addEqualityGroup(Funnels.stringFunnel(US_ASCII)) .addEqualityGroup( Funnels.sequentialFunnel(Funnels.integerFunnel()), SerializableTester.reserialize(Funnels.sequentialFunnel(Funnels.integerFunnel()))) diff --git a/android/guava-tests/test/com/google/common/hash/HashCodeTest.java b/android/guava-tests/test/com/google/common/hash/HashCodeTest.java index 4cccefe7e36d..786c3651fd50 100644 --- a/android/guava-tests/test/com/google/common/hash/HashCodeTest.java +++ b/android/guava-tests/test/com/google/common/hash/HashCodeTest.java @@ -17,13 +17,17 @@ package com.google.common.hash; import static com.google.common.io.BaseEncoding.base16; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.Assert.assertThrows; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; import com.google.common.testing.ClassSanityTester; import java.util.Arrays; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit tests for {@link HashCode}. @@ -31,6 +35,7 @@ * @author Dimitris Andreou * @author Kurt Alfred Kluever */ +@NullUnmarked public class HashCodeTest extends TestCase { // note: asInt(), asLong() are in little endian private static final ImmutableList expectedHashCodes = @@ -181,7 +186,7 @@ public void testHashCode_equalsAndSerializable() throws Exception { } public void testRoundTripHashCodeUsingBaseEncoding() { - HashCode hash1 = Hashing.sha1().hashString("foo", Charsets.US_ASCII); + HashCode hash1 = Hashing.sha1().hashString("foo", US_ASCII); HashCode hash2 = HashCode.fromBytes(BaseEncoding.base16().lowerCase().decode(hash1.toString())); assertEquals(hash1, hash2); } @@ -191,7 +196,7 @@ public void testObjectHashCode() { assertEquals(42, hashCode42.hashCode()); } - // See https://code.google.com/p/guava-libraries/issues/detail?id=1494 + // See https://github.com/google/guava/issues/1494 public void testObjectHashCodeWithSameLowOrderBytes() { // These will have the same first 4 bytes (all 0). byte[] bytesA = new byte[5]; @@ -213,7 +218,7 @@ public void testObjectHashCodeWithSameLowOrderBytes() { } public void testRoundTripHashCodeUsingFromString() { - HashCode hash1 = Hashing.sha1().hashString("foo", Charsets.US_ASCII); + HashCode hash1 = Hashing.sha1().hashString("foo", US_ASCII); HashCode hash2 = HashCode.fromString(hash1.toString()); assertEquals(hash1, hash2); } @@ -229,54 +234,34 @@ public void testRoundTrip() { } public void testFromStringFailsWithInvalidHexChar() { - try { - HashCode.fromString("7f8005ff0z"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HashCode.fromString("7f8005ff0z")); } public void testFromStringFailsWithUpperCaseString() { - String string = Hashing.sha1().hashString("foo", Charsets.US_ASCII).toString().toUpperCase(); - try { - HashCode.fromString(string); - fail(); - } catch (IllegalArgumentException expected) { - } + String string = Hashing.sha1().hashString("foo", US_ASCII).toString().toUpperCase(); + assertThrows(IllegalArgumentException.class, () -> HashCode.fromString(string)); } public void testFromStringFailsWithShortInputs() { - try { - HashCode.fromString(""); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - HashCode.fromString("7"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HashCode.fromString("")); + assertThrows(IllegalArgumentException.class, () -> HashCode.fromString("7")); HashCode unused = HashCode.fromString("7f"); } public void testFromStringFailsWithOddLengthInput() { - try { - HashCode.fromString("7f8"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HashCode.fromString("7f8")); } public void testIntWriteBytesTo() { byte[] dest = new byte[4]; HashCode.fromInt(42).writeBytesTo(dest, 0, 4); - assertTrue(Arrays.equals(HashCode.fromInt(42).asBytes(), dest)); + assertThat(dest).isEqualTo(HashCode.fromInt(42).asBytes()); } public void testLongWriteBytesTo() { byte[] dest = new byte[8]; HashCode.fromLong(42).writeBytesTo(dest, 0, 8); - assertTrue(Arrays.equals(HashCode.fromLong(42).asBytes(), dest)); + assertThat(dest).isEqualTo(HashCode.fromLong(42).asBytes()); } private static final HashCode HASH_ABCD = @@ -285,56 +270,44 @@ public void testLongWriteBytesTo() { public void testWriteBytesTo() { byte[] dest = new byte[4]; HASH_ABCD.writeBytesTo(dest, 0, 4); - assertTrue( - Arrays.equals(new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd}, dest)); + assertThat(dest).isEqualTo(new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd}); } public void testWriteBytesToOversizedArray() { byte[] dest = new byte[5]; HASH_ABCD.writeBytesTo(dest, 0, 4); - assertTrue( - Arrays.equals( - new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0x00}, dest)); + assertThat(dest) + .isEqualTo(new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0x00}); } public void testWriteBytesToOversizedArrayLongMaxLength() { byte[] dest = new byte[5]; HASH_ABCD.writeBytesTo(dest, 0, 5); - assertTrue( - Arrays.equals( - new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0x00}, dest)); + assertThat(dest) + .isEqualTo(new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0x00}); } public void testWriteBytesToOversizedArrayShortMaxLength() { byte[] dest = new byte[5]; HASH_ABCD.writeBytesTo(dest, 0, 3); - assertTrue( - Arrays.equals( - new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0x00, (byte) 0x00}, dest)); + assertThat(dest) + .isEqualTo(new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0x00, (byte) 0x00}); } public void testWriteBytesToUndersizedArray() { byte[] dest = new byte[3]; - try { - HASH_ABCD.writeBytesTo(dest, 0, 4); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> HASH_ABCD.writeBytesTo(dest, 0, 4)); } public void testWriteBytesToUndersizedArrayLongMaxLength() { byte[] dest = new byte[3]; - try { - HASH_ABCD.writeBytesTo(dest, 0, 5); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> HASH_ABCD.writeBytesTo(dest, 0, 5)); } public void testWriteBytesToUndersizedArrayShortMaxLength() { byte[] dest = new byte[3]; HASH_ABCD.writeBytesTo(dest, 0, 2); - assertTrue(Arrays.equals(new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0x00}, dest)); + assertThat(dest).isEqualTo(new byte[] {(byte) 0xaa, (byte) 0xbb, (byte) 0x00}); } private static ClassSanityTester.FactoryMethodReturnValueTester sanityTester() { @@ -346,10 +319,10 @@ private static ClassSanityTester.FactoryMethodReturnValueTester sanityTester() { } private static void assertExpectedHashCode(ExpectedHashCode expectedHashCode, HashCode hash) { - assertTrue(Arrays.equals(expectedHashCode.bytes, hash.asBytes())); + assertThat(hash.asBytes()).isEqualTo(expectedHashCode.bytes); byte[] bb = new byte[hash.bits() / 8]; hash.writeBytesTo(bb, 0, bb.length); - assertTrue(Arrays.equals(expectedHashCode.bytes, bb)); + assertThat(bb).isEqualTo(expectedHashCode.bytes); assertEquals(expectedHashCode.asInt, hash.asInt()); if (expectedHashCode.asLong == null) { try { @@ -369,11 +342,11 @@ private static void assertSideEffectFree(HashCode hash) { byte[] original = hash.asBytes(); byte[] mutated = hash.asBytes(); mutated[0]++; - assertTrue(Arrays.equals(original, hash.asBytes())); + assertThat(hash.asBytes()).isEqualTo(original); } private static void assertReadableBytes(HashCode hashCode) { - assertTrue(hashCode.bits() >= 32); // sanity + assertThat(hashCode.bits()).isAtLeast(32); // sanity byte[] hashBytes = hashCode.asBytes(); int totalBytes = hashCode.bits() / 8; @@ -381,7 +354,7 @@ private static void assertReadableBytes(HashCode hashCode) { byte[] bb = new byte[bytes]; hashCode.writeBytesTo(bb, 0, bb.length); - assertTrue(Arrays.equals(Arrays.copyOf(hashBytes, bytes), bb)); + assertThat(bb).isEqualTo(Arrays.copyOf(hashBytes, bytes)); } } @@ -391,7 +364,7 @@ private static class ExpectedHashCode { final Long asLong; // null means that asLong should throw an exception final String toString; - ExpectedHashCode(byte[] bytes, int asInt, Long asLong, String toString) { + ExpectedHashCode(byte[] bytes, int asInt, @Nullable Long asLong, String toString) { this.bytes = bytes; this.asInt = asInt; this.asLong = asLong; diff --git a/android/guava-tests/test/com/google/common/hash/HashFunctionEnum.java b/android/guava-tests/test/com/google/common/hash/HashFunctionEnum.java index 3a98fede9540..2533ce270629 100644 --- a/android/guava-tests/test/com/google/common/hash/HashFunctionEnum.java +++ b/android/guava-tests/test/com/google/common/hash/HashFunctionEnum.java @@ -16,12 +16,14 @@ package com.google.common.hash; +import org.jspecify.annotations.NullUnmarked; /** * An enum that contains all of the known hash functions. * * @author Kurt Alfred Kluever */ +@NullUnmarked enum HashFunctionEnum { ADLER32(Hashing.adler32()), CRC32(Hashing.crc32()), @@ -32,6 +34,7 @@ enum HashFunctionEnum { MD5(Hashing.md5()), MURMUR3_128(Hashing.murmur3_128()), MURMUR3_32(Hashing.murmur3_32()), + MURMUR3_32_FIXED(Hashing.murmur3_32_fixed()), SHA1(Hashing.sha1()), SHA256(Hashing.sha256()), SHA384(Hashing.sha384()), @@ -45,7 +48,7 @@ enum HashFunctionEnum { private final HashFunction hashFunction; - private HashFunctionEnum(HashFunction hashFunction) { + HashFunctionEnum(HashFunction hashFunction) { this.hashFunction = hashFunction; } diff --git a/android/guava-tests/test/com/google/common/hash/HashTestUtils.java b/android/guava-tests/test/com/google/common/hash/HashTestUtils.java index 8dfbdb0cdf03..9208b8b1acd0 100644 --- a/android/guava-tests/test/com/google/common/hash/HashTestUtils.java +++ b/android/guava-tests/test/com/google/common/hash/HashTestUtils.java @@ -16,10 +16,16 @@ package com.google.common.hash; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_16; +import static java.nio.charset.StandardCharsets.UTF_16BE; +import static java.nio.charset.StandardCharsets.UTF_16LE; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; @@ -30,6 +36,7 @@ import java.util.Arrays; import java.util.Random; import java.util.Set; +import org.jspecify.annotations.NullUnmarked; import org.junit.Assert; /** @@ -38,6 +45,7 @@ * @author Dimitris Andreou * @author Kurt Alfred Kluever */ +@NullUnmarked final class HashTestUtils { private HashTestUtils() {} @@ -195,8 +203,8 @@ void performAction(Random random, Iterable sinks) { int limit = pos + random.nextInt(value.length - pos + 1); for (PrimitiveSink sink : sinks) { ByteBuffer buffer = ByteBuffer.wrap(value); - buffer.position(pos); - buffer.limit(limit); + Java8Compatibility.position(buffer, pos); + Java8Compatibility.limit(buffer, limit); sink.putBytes(buffer); assertEquals(limit, buffer.limit()); assertEquals(limit, buffer.position()); @@ -298,7 +306,7 @@ static void checkNoFunnels(HashFunction function) { // test whether the hash values have same output bits same |= ~(hash1 ^ hash2); // test whether the hash values have different output bits - diff |= (hash1 ^ hash2); + diff |= hash1 ^ hash2; count++; // check whether we've exceeded the probabilistically @@ -353,7 +361,7 @@ static void checkAvalanche(HashFunction function, int trials, double epsilon) { // measure probability and assert it's within margin of error for (int j = 0; j < hashBits; j++) { double prob = (double) diff[j] / (double) (diff[j] + same[j]); - Assert.assertEquals(0.50d, prob, epsilon); + assertThat(prob).isWithin(epsilon).of(0.50d); } } } @@ -376,7 +384,7 @@ static void checkNo2BitCharacteristics(HashFunction function) { for (int j = 0; j < keyBits; j++) { if (j <= i) continue; int count = 0; - int maxCount = 20; // the probability of error here is miniscule + int maxCount = 20; // the probability of error here is minuscule boolean diff = false; while (!diff) { @@ -450,7 +458,7 @@ static void check2BitAvalanche(HashFunction function, int trials, double epsilon // measure probability and assert it's within margin of error for (int j = 0; j < hashBits; j++) { double prob = (double) diff[j] / (double) (diff[j] + same[j]); - Assert.assertEquals(0.50d, prob, epsilon); + assertThat(prob).isWithin(epsilon).of(0.50d); } } } @@ -473,7 +481,7 @@ static void assertInvariants(HashFunction hashFunction) { Assert.assertEquals(hashFunction.bits(), hashcode1.asBytes().length * 8); hashcodes.add(hashcode1); } - Assert.assertTrue(hashcodes.size() > objects * 0.95); // quite relaxed test + assertThat((double) hashcodes.size()).isGreaterThan(objects * 0.95); // quite relaxed test assertHashBytesThrowsCorrectExceptions(hashFunction); assertIndependentHashers(hashFunction); @@ -627,13 +635,7 @@ private static void assertHashLongEquivalence(HashFunction hashFunction, Random } private static final ImmutableSet CHARSETS = - ImmutableSet.of( - Charsets.ISO_8859_1, - Charsets.US_ASCII, - Charsets.UTF_16, - Charsets.UTF_16BE, - Charsets.UTF_16LE, - Charsets.UTF_8); + ImmutableSet.of(ISO_8859_1, US_ASCII, UTF_16, UTF_16BE, UTF_16LE, UTF_8); private static void assertHashStringEquivalence(HashFunction hashFunction, Random random) { // Test that only data and data-order is important, not the individual operations. @@ -657,7 +659,7 @@ private static void assertHashStringEquivalence(HashFunction hashFunction, Rando int size = random.nextInt(2048); byte[] bytes = new byte[size]; random.nextBytes(bytes); - String string = new String(bytes, Charsets.US_ASCII); + String string = new String(bytes, US_ASCII); assertEquals( hashFunction.hashUnencodedChars(string), hashFunction.newHasher().putUnencodedChars(string).hash()); diff --git a/android/guava-tests/test/com/google/common/hash/HashingInputStreamTest.java b/android/guava-tests/test/com/google/common/hash/HashingInputStreamTest.java index 05351d94cdde..51c65cb5f78e 100644 --- a/android/guava-tests/test/com/google/common/hash/HashingInputStreamTest.java +++ b/android/guava-tests/test/com/google/common/hash/HashingInputStreamTest.java @@ -23,12 +23,14 @@ import java.io.ByteArrayInputStream; import java.util.Arrays; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link HashingInputStream}. * * @author Qian Huang */ +@NullUnmarked public class HashingInputStreamTest extends TestCase { private Hasher hasher; private HashFunction hashFunction; diff --git a/android/guava-tests/test/com/google/common/hash/HashingOutputStreamTest.java b/android/guava-tests/test/com/google/common/hash/HashingOutputStreamTest.java index 55e8fbf4a5b6..9f4bf00f848d 100644 --- a/android/guava-tests/test/com/google/common/hash/HashingOutputStreamTest.java +++ b/android/guava-tests/test/com/google/common/hash/HashingOutputStreamTest.java @@ -22,12 +22,14 @@ import com.google.common.testing.NullPointerTester; import java.io.ByteArrayOutputStream; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link HashingOutputStream}. * - * @author Nick Piepmeier + * @author Zoe Piepmeier */ +@NullUnmarked public class HashingOutputStreamTest extends TestCase { private Hasher hasher; private HashFunction hashFunction; diff --git a/android/guava-tests/test/com/google/common/hash/HashingTest.java b/android/guava-tests/test/com/google/common/hash/HashingTest.java index dc50299ea00f..bad0ac5eaeb7 100644 --- a/android/guava-tests/test/com/google/common/hash/HashingTest.java +++ b/android/guava-tests/test/com/google/common/hash/HashingTest.java @@ -16,12 +16,14 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Lists; import com.google.common.collect.Table.Cell; import com.google.common.primitives.Ints; import com.google.common.testing.EqualsTester; @@ -30,21 +32,24 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Hashing}. * - *

    TODO(b/33919189): Migrate repeated testing methods to {@link #HashTestUtils} and tweak unit + *

    TODO(b/33919189): Migrate repeated testing methods to {@link HashTestUtils} and tweak unit * tests to reference them from there. * * @author Dimitris Andreou * @author Kurt Alfred Kluever */ +@NullUnmarked public class HashingTest extends TestCase { public void testMd5() { HashTestUtils.checkAvalanche(Hashing.md5(), 100, 0.4); @@ -125,11 +130,20 @@ public void testSipHash24() { Hashing.sipHash24().toString()); } + public void testFingerprint2011() { + HashTestUtils.check2BitAvalanche(Hashing.fingerprint2011(), 100, 0.4); + HashTestUtils.checkAvalanche(Hashing.fingerprint2011(), 100, 0.4); + HashTestUtils.checkNo2BitCharacteristics(Hashing.fingerprint2011()); + HashTestUtils.checkNoFunnels(Hashing.fingerprint2011()); + HashTestUtils.assertInvariants(Hashing.fingerprint2011()); + assertEquals("Hashing.fingerprint2011()", Hashing.fingerprint2011().toString()); + } + @AndroidIncompatible // slow TODO(cpovirk): Maybe just reduce iterations under Android. public void testGoodFastHash() { for (int i = 1; i < 200; i += 17) { HashFunction hasher = Hashing.goodFastHash(i); - assertTrue(hasher.bits() >= i); + assertThat(hasher.bits()).isAtLeast(i); HashTestUtils.assertInvariants(hasher); } } @@ -146,7 +160,7 @@ public void testGoodFastHash32() { // goodFastHash(128) uses Murmur3_128. Use the same epsilon bounds. public void testGoodFastHash128() { HashTestUtils.check2BitAvalanche(Hashing.goodFastHash(128), 250, 0.20); - HashTestUtils.checkAvalanche(Hashing.goodFastHash(128), 250, 0.17); + HashTestUtils.checkAvalanche(Hashing.goodFastHash(128), 500, 0.17); HashTestUtils.checkNo2BitCharacteristics(Hashing.goodFastHash(128)); HashTestUtils.checkNoFunnels(Hashing.goodFastHash(128)); HashTestUtils.assertInvariants(Hashing.goodFastHash(128)); @@ -155,7 +169,7 @@ public void testGoodFastHash128() { // goodFastHash(256) uses Murmur3_128. Use the same epsilon bounds. public void testGoodFastHash256() { HashTestUtils.check2BitAvalanche(Hashing.goodFastHash(256), 250, 0.20); - HashTestUtils.checkAvalanche(Hashing.goodFastHash(256), 250, 0.17); + HashTestUtils.checkAvalanche(Hashing.goodFastHash(256), 500, 0.17); HashTestUtils.checkNo2BitCharacteristics(Hashing.goodFastHash(256)); HashTestUtils.checkNoFunnels(Hashing.goodFastHash(256)); HashTestUtils.assertInvariants(Hashing.goodFastHash(256)); @@ -191,7 +205,7 @@ public void testConsistentHash_probabilities() { } for (int shard = 2; shard <= MAX_SHARDS; shard++) { // Rough: don't exceed 1.2x the expected number of remaps by more than 20 - assertTrue(map.get(shard) <= 1.2 * ITERS / shard + 20); + assertThat((double) map.get(shard)).isAtMost(1.2 * ITERS / shard + 20); } } @@ -210,11 +224,7 @@ private void countRemaps(long h, AtomicLongMap map) { private static final int MAX_SHARDS = 500; public void testConsistentHash_outOfRange() { - try { - Hashing.consistentHash(5L, 0); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Hashing.consistentHash(5L, 0)); } public void testConsistentHash_ofHashCode() { @@ -251,20 +261,19 @@ public void testConsistentHash_linearCongruentialGeneratorCompatibility() { private static final long RANDOM_SEED = 177L; public void testCombineOrdered_empty() { - try { - Hashing.combineOrdered(Collections.emptySet()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Hashing.combineOrdered(Collections.emptySet())); } public void testCombineOrdered_differentBitLengths() { - try { - HashCode unused = - Hashing.combineOrdered(ImmutableList.of(HashCode.fromInt(32), HashCode.fromLong(32L))); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> { + HashCode unused = + Hashing.combineOrdered( + ImmutableList.of(HashCode.fromInt(32), HashCode.fromLong(32L))); + }); } public void testCombineOrdered() { @@ -284,7 +293,7 @@ public void testCombineOrdered() { public void testCombineOrdered_randomHashCodes() { Random random = new Random(7); - List hashCodes = Lists.newArrayList(); + List hashCodes = new ArrayList<>(); for (int i = 0; i < 10; i++) { hashCodes.add(HashCode.fromLong(random.nextLong())); } @@ -296,20 +305,19 @@ public void testCombineOrdered_randomHashCodes() { } public void testCombineUnordered_empty() { - try { - Hashing.combineUnordered(Collections.emptySet()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Hashing.combineUnordered(Collections.emptySet())); } public void testCombineUnordered_differentBitLengths() { - try { - HashCode unused = - Hashing.combineUnordered(ImmutableList.of(HashCode.fromInt(32), HashCode.fromLong(32L))); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> { + HashCode unused = + Hashing.combineUnordered( + ImmutableList.of(HashCode.fromInt(32), HashCode.fromLong(32L))); + }); } public void testCombineUnordered() { @@ -326,7 +334,7 @@ public void testCombineUnordered() { public void testCombineUnordered_randomHashCodes() { Random random = new Random(RANDOM_SEED); - List hashCodes = Lists.newArrayList(); + List hashCodes = new ArrayList<>(); for (int i = 0; i < 10; i++) { hashCodes.add(HashCode.fromLong(random.nextLong())); } @@ -414,30 +422,32 @@ public void testHashIntVsForLoop() { assertEquals(expected, actual); } - private static final String EMPTY_STRING = ""; private static final String TQBFJOTLD = "The quick brown fox jumps over the lazy dog"; private static final String TQBFJOTLDP = "The quick brown fox jumps over the lazy dog."; private static final ImmutableTable KNOWN_HASHES = ImmutableTable.builder() - .put(Hashing.adler32(), EMPTY_STRING, "01000000") + .put(Hashing.adler32(), "", "01000000") .put(Hashing.adler32(), TQBFJOTLD, "da0fdc5b") .put(Hashing.adler32(), TQBFJOTLDP, "0810e46b") - .put(Hashing.md5(), EMPTY_STRING, "d41d8cd98f00b204e9800998ecf8427e") + .put(Hashing.md5(), "", "d41d8cd98f00b204e9800998ecf8427e") .put(Hashing.md5(), TQBFJOTLD, "9e107d9d372bb6826bd81d3542a419d6") .put(Hashing.md5(), TQBFJOTLDP, "e4d909c290d0fb1ca068ffaddf22cbd0") - .put(Hashing.murmur3_128(), EMPTY_STRING, "00000000000000000000000000000000") + .put(Hashing.murmur3_128(), "", "00000000000000000000000000000000") .put(Hashing.murmur3_128(), TQBFJOTLD, "6c1b07bc7bbc4be347939ac4a93c437a") .put(Hashing.murmur3_128(), TQBFJOTLDP, "c902e99e1f4899cde7b68789a3a15d69") - .put(Hashing.murmur3_32(), EMPTY_STRING, "00000000") + .put(Hashing.murmur3_32(), "", "00000000") .put(Hashing.murmur3_32(), TQBFJOTLD, "23f74f2e") .put(Hashing.murmur3_32(), TQBFJOTLDP, "fc8bc4d5") - .put(Hashing.sha1(), EMPTY_STRING, "da39a3ee5e6b4b0d3255bfef95601890afd80709") + .put(Hashing.murmur3_32_fixed(), "", "00000000") + .put(Hashing.murmur3_32_fixed(), TQBFJOTLD, "23f74f2e") + .put(Hashing.murmur3_32_fixed(), TQBFJOTLDP, "fc8bc4d5") + .put(Hashing.sha1(), "", "da39a3ee5e6b4b0d3255bfef95601890afd80709") .put(Hashing.sha1(), TQBFJOTLD, "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12") .put(Hashing.sha1(), TQBFJOTLDP, "408d94384216f890ff7a0c3528e8bed1e0b01621") .put( Hashing.sha256(), - EMPTY_STRING, + "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") .put( Hashing.sha256(), @@ -449,7 +459,7 @@ public void testHashIntVsForLoop() { "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c") .put( Hashing.sha384(), - EMPTY_STRING, + "", "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da2" + "74edebfe76f65fbd51ad2f14898b95b") .put( @@ -464,7 +474,7 @@ public void testHashIntVsForLoop() { + "a7af2819a021c2fc34e91bdb63409d7") .put( Hashing.sha512(), - EMPTY_STRING, + "", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce" + "47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e") .put( @@ -477,28 +487,26 @@ public void testHashIntVsForLoop() { TQBFJOTLDP, "91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bb" + "c6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed") - .put(Hashing.crc32(), EMPTY_STRING, "00000000") + .put(Hashing.crc32(), "", "00000000") .put(Hashing.crc32(), TQBFJOTLD, "39a34f41") .put(Hashing.crc32(), TQBFJOTLDP, "e9259051") - .put(Hashing.sipHash24(), EMPTY_STRING, "310e0edd47db6f72") + .put(Hashing.sipHash24(), "", "310e0edd47db6f72") .put(Hashing.sipHash24(), TQBFJOTLD, "e46f1fdc05612752") .put(Hashing.sipHash24(), TQBFJOTLDP, "9b602581fce4d4f8") - .put(Hashing.crc32c(), EMPTY_STRING, "00000000") + .put(Hashing.crc32c(), "", "00000000") .put(Hashing.crc32c(), TQBFJOTLD, "04046222") .put(Hashing.crc32c(), TQBFJOTLDP, "b3970019") - .put(Hashing.farmHashFingerprint64(), EMPTY_STRING, "4f40902f3b6ae19a") + .put(Hashing.farmHashFingerprint64(), "", "4f40902f3b6ae19a") .put(Hashing.farmHashFingerprint64(), TQBFJOTLD, "34511b3bf383beab") .put(Hashing.farmHashFingerprint64(), TQBFJOTLDP, "737d7e5f8660653e") + .put(Hashing.fingerprint2011(), "", "e365a64a907cad23") + .put(Hashing.fingerprint2011(), TQBFJOTLD, "c9688c84e813b089") + .put(Hashing.fingerprint2011(), TQBFJOTLDP, "a714d70f1d569cd0") .build(); public void testAllHashFunctionsHaveKnownHashes() throws Exception { - // The following legacy hashing function methods have been covered by unit testing already. - List legacyHashingMethodNames = ImmutableList.of("murmur2_64", "fprint96"); for (Method method : Hashing.class.getDeclaredMethods()) { - if (method.getReturnType().equals(HashFunction.class) // must return HashFunction - && Modifier.isPublic(method.getModifiers()) // only the public methods - && method.getParameterTypes().length == 0 // only the seed-less grapes^W hash functions - && !legacyHashingMethodNames.contains(method.getName())) { + if (shouldHaveKnownHashes(method)) { HashFunction hashFunction = (HashFunction) method.invoke(Hashing.class); assertTrue( "There should be at least 3 entries in KNOWN_HASHES for " + hashFunction, @@ -567,9 +575,7 @@ public void testGoodFastHashEquals() throws Exception { static void assertSeedlessHashFunctionEquals(Class clazz) throws Exception { for (Method method : clazz.getDeclaredMethods()) { - if (method.getReturnType().equals(HashFunction.class) // must return HashFunction - && Modifier.isPublic(method.getModifiers()) // only the public methods - && method.getParameterTypes().length == 0) { // only the seed-less hash functions + if (shouldHaveKnownHashes(method)) { HashFunction hashFunction1a = (HashFunction) method.invoke(clazz); HashFunction hashFunction1b = (HashFunction) method.invoke(clazz); @@ -583,6 +589,16 @@ static void assertSeedlessHashFunctionEquals(Class clazz) throws Exception { } } + private static boolean shouldHaveKnownHashes(Method method) { + // The following legacy hashing function methods have been covered by unit testing already. + ImmutableSet legacyHashingMethodNames = + ImmutableSet.of("murmur2_64", "fprint96", "highwayFingerprint64", "highwayFingerprint128"); + return method.getReturnType().equals(HashFunction.class) // must return HashFunction + && Modifier.isPublic(method.getModifiers()) // only the public methods + && method.getParameterTypes().length == 0 // only the seedless hash functions + && !legacyHashingMethodNames.contains(method.getName()); + } + static void assertSeededHashFunctionEquals(Class clazz) throws Exception { Random random = new Random(RANDOM_SEED); for (Method method : clazz.getDeclaredMethods()) { diff --git a/android/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java b/android/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java index 4dbb4241e0b6..9bfe7af85a99 100644 --- a/android/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java +++ b/android/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java @@ -16,8 +16,9 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.io.BaseEncoding.base16; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableTable; @@ -29,6 +30,8 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import sun.security.jca.ProviderList; import sun.security.jca.Providers; @@ -37,6 +40,7 @@ * * @author Kurt Alfred Kluever */ +@NullUnmarked public class MacHashFunctionTest extends TestCase { private static final ImmutableSet INPUTS = ImmutableSet.of("", "Z", "foobar"); @@ -57,7 +61,7 @@ public class MacHashFunctionTest extends TestCase { .put("HmacSHA1", SHA1_KEY, Hashing.hmacSha1(SHA1_KEY)) .put("HmacSHA256", SHA256_KEY, Hashing.hmacSha256(SHA256_KEY)) .put("HmacSHA512", SHA512_KEY, Hashing.hmacSha512(SHA512_KEY)) - .build(); + .buildOrThrow(); public void testNulls() { NullPointerTester tester = @@ -155,7 +159,7 @@ public String getAlgorithm() { } @Override - public byte[] getEncoded() { + public byte @Nullable [] getEncoded() { return null; } @@ -224,11 +228,7 @@ public void testPutAfterHash() { assertEquals( "9753980fe94daa8ecaa82216519393a9", hasher.putString("The quick brown fox jumps over the lazy dog", UTF_8).hash().toString()); - try { - hasher.putInt(42); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> hasher.putInt(42)); } public void testHashTwice() { @@ -237,11 +237,7 @@ public void testHashTwice() { assertEquals( "9753980fe94daa8ecaa82216519393a9", hasher.putString("The quick brown fox jumps over the lazy dog", UTF_8).hash().toString()); - try { - hasher.hash(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> hasher.hash()); } public void testToString() { diff --git a/android/guava-tests/test/com/google/common/hash/MessageDigestHashFunctionTest.java b/android/guava-tests/test/com/google/common/hash/MessageDigestHashFunctionTest.java index 535d455212d4..127b0e7eeac7 100644 --- a/android/guava-tests/test/com/google/common/hash/MessageDigestHashFunctionTest.java +++ b/android/guava-tests/test/com/google/common/hash/MessageDigestHashFunctionTest.java @@ -16,19 +16,23 @@ package com.google.common.hash; -import com.google.common.base.Charsets; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; + import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the MessageDigestHashFunction. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class MessageDigestHashFunctionTest extends TestCase { private static final ImmutableSet INPUTS = ImmutableSet.of("", "Z", "foobar"); @@ -62,14 +66,8 @@ public void testPutAfterHash() { assertEquals( "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", - sha1.putString("The quick brown fox jumps over the lazy dog", Charsets.UTF_8) - .hash() - .toString()); - try { - sha1.putInt(42); - fail(); - } catch (IllegalStateException expected) { - } + sha1.putString("The quick brown fox jumps over the lazy dog", UTF_8).hash().toString()); + assertThrows(IllegalStateException.class, () -> sha1.putInt(42)); } public void testHashTwice() { @@ -77,14 +75,8 @@ public void testHashTwice() { assertEquals( "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", - sha1.putString("The quick brown fox jumps over the lazy dog", Charsets.UTF_8) - .hash() - .toString()); - try { - sha1.hash(); - fail(); - } catch (IllegalStateException expected) { - } + sha1.putString("The quick brown fox jumps over the lazy dog", UTF_8).hash().toString()); + assertThrows(IllegalStateException.class, () -> sha1.hash()); } public void testToString() { diff --git a/android/guava-tests/test/com/google/common/hash/Murmur3Hash128Test.java b/android/guava-tests/test/com/google/common/hash/Murmur3Hash128Test.java index 89b072cf5eee..be1d5fb548f9 100644 --- a/android/guava-tests/test/com/google/common/hash/Murmur3Hash128Test.java +++ b/android/guava-tests/test/com/google/common/hash/Murmur3Hash128Test.java @@ -17,14 +17,16 @@ package com.google.common.hash; import static com.google.common.hash.Hashing.murmur3_128; +import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Charsets; import com.google.common.hash.HashTestUtils.HashFn; import java.nio.ByteBuffer; import java.nio.ByteOrder; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Tests for {@link Murmur3_128HashFunction}. */ +@NullUnmarked public class Murmur3Hash128Test extends TestCase { public void testKnownValues() { assertHash(0, 0x629942693e10f867L, 0x92db0b82baeb5347L, "hell"); @@ -40,7 +42,7 @@ public void testKnownValues() { // Known output from Python smhasher HashCode foxHash = - murmur3_128(0).hashString("The quick brown fox jumps over the lazy dog", Charsets.UTF_8); + murmur3_128(0).hashString("The quick brown fox jumps over the lazy dog", UTF_8); assertEquals("6c1b07bc7bbc4be347939ac4a93c437a", foxHash.toString()); } diff --git a/android/guava-tests/test/com/google/common/hash/Murmur3Hash32Test.java b/android/guava-tests/test/com/google/common/hash/Murmur3Hash32Test.java index de86e4bbd86b..3728b44b5bf7 100644 --- a/android/guava-tests/test/com/google/common/hash/Murmur3Hash32Test.java +++ b/android/guava-tests/test/com/google/common/hash/Murmur3Hash32Test.java @@ -17,13 +17,19 @@ package com.google.common.hash; import static com.google.common.hash.Hashing.murmur3_32; +import static com.google.common.hash.Hashing.murmur3_32_fixed; +import static java.nio.charset.StandardCharsets.UTF_16; +import static java.nio.charset.StandardCharsets.UTF_16LE; +import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Charsets; import com.google.common.hash.HashTestUtils.HashFn; +import java.nio.charset.Charset; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Tests for {@link Murmur3_32HashFunction}. */ +@NullUnmarked public class Murmur3Hash32Test extends TestCase { public void testKnownIntegerInputs() { assertHash(593689054, murmur3_32().hashInt(0)); @@ -51,30 +57,66 @@ public void testKnownStringInputs() { -528633700, murmur3_32().hashUnencodedChars("The quick brown fox jumps over the lazy dog")); } - public void testKnownUtf8StringInputs() { - assertHash(0, murmur3_32().hashString("", Charsets.UTF_8)); - assertHash(0xcfbda5d1, murmur3_32().hashString("k", Charsets.UTF_8)); - assertHash(0xa167dbf3, murmur3_32().hashString("hell", Charsets.UTF_8)); - assertHash(0x248bfa47, murmur3_32().hashString("hello", Charsets.UTF_8)); - assertHash(0x3d41b97c, murmur3_32().hashString("http://www.google.com/", Charsets.UTF_8)); - assertHash( - 0x2e4ff723, - murmur3_32().hashString("The quick brown fox jumps over the lazy dog", Charsets.UTF_8)); - assertHash(0xfc5ba834, murmur3_32().hashString("毎月1日,毎週月曜日", Charsets.UTF_8)); + @SuppressWarnings("deprecation") + public void testKnownEncodedStringInputs() { + assertStringHash(0, "", UTF_8); + assertStringHash(0xcfbda5d1, "k", UTF_8); + assertStringHash(0xa167dbf3, "hell", UTF_8); + assertStringHash(0x248bfa47, "hello", UTF_8); + assertStringHash(0x3d41b97c, "http://www.google.com/", UTF_8); + assertStringHash(0x2e4ff723, "The quick brown fox jumps over the lazy dog", UTF_8); + assertStringHash(0xb5a4be05, "ABCDefGHI\u0799", UTF_8); + assertStringHash(0xfc5ba834, "毎月1日,毎週月曜日", UTF_8); + assertStringHash(0x8a5c3699, "surrogate pair: \uD83D\uDCB0", UTF_8); + + assertStringHash(0, "", UTF_16LE); + assertStringHash(0x288418e4, "k", UTF_16LE); + assertStringHash(0x5a0cb7c3, "hell", UTF_16LE); + assertStringHash(0xd7c31989, "hello", UTF_16LE); + assertStringHash(0x73564d8c, "http://www.google.com/", UTF_16LE); + assertStringHash(0xe07db09c, "The quick brown fox jumps over the lazy dog", UTF_16LE); + assertStringHash(0xfefa3e76, "ABCDefGHI\u0799", UTF_16LE); + assertStringHash(0x6a7be132, "毎月1日,毎週月曜日", UTF_16LE); + assertStringHash(0x5a2d41c7, "surrogate pair: \uD83D\uDCB0", UTF_16LE); + } + + @SuppressWarnings("deprecation") + private void assertStringHash(int expected, String string, Charset charset) { + if (allBmp(string)) { + assertHash(expected, murmur3_32().hashString(string, charset)); + } + assertHash(expected, murmur3_32_fixed().hashString(string, charset)); + assertHash(expected, murmur3_32().newHasher().putString(string, charset).hash()); + assertHash(expected, murmur3_32_fixed().newHasher().putString(string, charset).hash()); + assertHash(expected, murmur3_32().hashBytes(string.getBytes(charset))); + assertHash(expected, murmur3_32_fixed().hashBytes(string.getBytes(charset))); + assertHash(expected, murmur3_32().newHasher().putBytes(string.getBytes(charset)).hash()); + assertHash(expected, murmur3_32_fixed().newHasher().putBytes(string.getBytes(charset)).hash()); + } + + private boolean allBmp(String string) { + // Ordinarily we'd use something like i += Character.charCount(string.codePointAt(i)) here. But + // we can get away with i++ because the whole point of this method is to return false if we find + // a code point that doesn't fit in a char. + for (int i = 0; i < string.length(); i++) { + if (string.codePointAt(i) > 0xffff) { + return false; + } + } + return true; } @SuppressWarnings("deprecation") public void testSimpleStringUtf8() { assertEquals( - murmur3_32().hashBytes("ABCDefGHI\u0799".getBytes(Charsets.UTF_8)), - murmur3_32().hashString("ABCDefGHI\u0799", Charsets.UTF_8)); + murmur3_32().hashBytes("ABCDefGHI\u0799".getBytes(UTF_8)), + murmur3_32().hashString("ABCDefGHI\u0799", UTF_8)); } @SuppressWarnings("deprecation") - public void testStringInputsUtf8() { + public void testEncodedStringInputs() { Random rng = new Random(0); for (int z = 0; z < 100; z++) { - String str; int[] codePoints = new int[rng.nextInt(8)]; for (int i = 0; i < codePoints.length; i++) { do { @@ -87,10 +129,15 @@ public void testStringInputsUtf8() { for (int i = 0; i < codePoints.length; i++) { builder.appendCodePoint(codePoints[i]); } - str = builder.toString(); - assertEquals( - murmur3_32().hashBytes(str.getBytes(Charsets.UTF_8)), - murmur3_32().hashString(str, Charsets.UTF_8)); + String str = builder.toString(); + HashCode hashUtf8 = murmur3_32().hashBytes(str.getBytes(UTF_8)); + assertEquals(hashUtf8, murmur3_32().newHasher().putBytes(str.getBytes(UTF_8)).hash()); + assertEquals(hashUtf8, murmur3_32().hashString(str, UTF_8)); + assertEquals(hashUtf8, murmur3_32().newHasher().putString(str, UTF_8).hash()); + HashCode hashUtf16 = murmur3_32().hashBytes(str.getBytes(UTF_16)); + assertEquals(hashUtf16, murmur3_32().newHasher().putBytes(str.getBytes(UTF_16)).hash()); + assertEquals(hashUtf16, murmur3_32().hashString(str, UTF_16)); + assertEquals(hashUtf16, murmur3_32().newHasher().putString(str, UTF_16).hash()); } } @@ -135,17 +182,21 @@ public void testInvalidUnicodeHashString() { String str = new String( new char[] {'a', Character.MIN_HIGH_SURROGATE, Character.MIN_HIGH_SURROGATE, 'z'}); + assertEquals(murmur3_32().hashBytes(str.getBytes(UTF_8)), murmur3_32().hashString(str, UTF_8)); assertEquals( - murmur3_32().hashBytes(str.getBytes(Charsets.UTF_8)), - murmur3_32().hashString(str, Charsets.UTF_8)); + murmur3_32_fixed().hashBytes(str.getBytes(UTF_8)), murmur3_32().hashString(str, UTF_8)); } + @SuppressWarnings("deprecation") public void testInvalidUnicodeHasherPutString() { String str = new String( new char[] {'a', Character.MIN_HIGH_SURROGATE, Character.MIN_HIGH_SURROGATE, 'z'}); assertEquals( - murmur3_32().hashBytes(str.getBytes(Charsets.UTF_8)), - murmur3_32().newHasher().putString(str, Charsets.UTF_8).hash()); + murmur3_32().hashBytes(str.getBytes(UTF_8)), + murmur3_32().newHasher().putString(str, UTF_8).hash()); + assertEquals( + murmur3_32_fixed().hashBytes(str.getBytes(UTF_8)), + murmur3_32_fixed().newHasher().putString(str, UTF_8).hash()); } } diff --git a/android/guava-tests/test/com/google/common/hash/PackageSanityTests.java b/android/guava-tests/test/com/google/common/hash/PackageSanityTests.java index d2b0ef5261bb..e81d60515839 100644 --- a/android/guava-tests/test/com/google/common/hash/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/hash/PackageSanityTests.java @@ -18,6 +18,7 @@ import com.google.common.hash.BloomFilterStrategies.LockFreeBitArray; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** * Basic sanity tests for the entire package. @@ -25,6 +26,7 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() { setDefault(LockFreeBitArray.class, new LockFreeBitArray(1)); diff --git a/android/guava-tests/test/com/google/common/hash/SipHashFunctionTest.java b/android/guava-tests/test/com/google/common/hash/SipHashFunctionTest.java index ded444752d27..64ff4e20c99d 100644 --- a/android/guava-tests/test/com/google/common/hash/SipHashFunctionTest.java +++ b/android/guava-tests/test/com/google/common/hash/SipHashFunctionTest.java @@ -14,16 +14,18 @@ package com.google.common.hash; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableSet; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link SipHashFunction}. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class SipHashFunctionTest extends TestCase { // From https://131002.net/siphash/siphash24.c diff --git a/android/guava-tests/test/com/google/common/html/HtmlEscapersTest.java b/android/guava-tests/test/com/google/common/html/HtmlEscapersTest.java index 776aa4c75eff..3f7b4a5b0805 100644 --- a/android/guava-tests/test/com/google/common/html/HtmlEscapersTest.java +++ b/android/guava-tests/test/com/google/common/html/HtmlEscapersTest.java @@ -18,6 +18,7 @@ import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the {@link HtmlEscapers} class. @@ -25,6 +26,7 @@ * @author David Beaumont */ @GwtCompatible +@NullUnmarked public class HtmlEscapersTest extends TestCase { public void testHtmlEscaper() throws Exception { diff --git a/android/guava-tests/test/com/google/common/io/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/io/AndroidIncompatible.java index b0d80aec30d7..afd626ec9636 100644 --- a/android/guava-tests/test/com/google/common/io/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/io/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/io/AppendableWriterTest.java b/android/guava-tests/test/com/google/common/io/AppendableWriterTest.java index dd0408362bcd..adce6dbffcc2 100644 --- a/android/guava-tests/test/com/google/common/io/AppendableWriterTest.java +++ b/android/guava-tests/test/com/google/common/io/AppendableWriterTest.java @@ -16,23 +16,27 @@ package com.google.common.io; +import static org.junit.Assert.assertThrows; + import java.io.Closeable; import java.io.Flushable; import java.io.IOException; import java.io.Writer; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link AppendableWriter}. * * @author Alan Green */ +@NullUnmarked public class AppendableWriterTest extends IoTestCase { /** Helper class for testing behavior with Flushable and Closeable targets. */ private static class SpyAppendable implements Appendable, Flushable, Closeable { boolean flushed; boolean closed; - StringBuilder result = new StringBuilder(); + final StringBuilder result = new StringBuilder(); @Override public Appendable append(CharSequence csq) { @@ -113,17 +117,9 @@ public void testCloseIsFinal() throws IOException { writer.write("Hi"); writer.close(); - try { - writer.write(" Greg"); - fail("Should have thrown IOException due to writer already closed"); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> writer.write(" Greg")); - try { - writer.flush(); - fail("Should have thrown IOException due to writer already closed"); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> writer.flush()); // close()ing already closed writer is allowed writer.close(); diff --git a/android/guava-tests/test/com/google/common/io/BaseEncodingTest.java b/android/guava-tests/test/com/google/common/io/BaseEncodingTest.java index 832fb0713fa9..82c1b4e1994c 100644 --- a/android/guava-tests/test/com/google/common/io/BaseEncodingTest.java +++ b/android/guava-tests/test/com/google/common/io/BaseEncodingTest.java @@ -14,13 +14,14 @@ package com.google.common.io; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.io.BaseEncoding.base16; import static com.google.common.io.BaseEncoding.base32; import static com.google.common.io.BaseEncoding.base32Hex; import static com.google.common.io.BaseEncoding.base64; import static com.google.common.io.BaseEncoding.base64Url; +import static com.google.common.io.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; @@ -36,14 +37,16 @@ import java.io.StringReader; import java.io.StringWriter; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@code BaseEncoding}. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class BaseEncodingTest extends TestCase { public void testSeparatorsExplicitly() { @@ -53,26 +56,15 @@ public void testSeparatorsExplicitly() { } public void testSeparatorSameAsPadChar() { - try { - base64().withSeparator("=", 3); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> base64().withSeparator("=", 3)); - try { - base64().withPadChar('#').withSeparator("!#!", 3); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> base64().withPadChar('#').withSeparator("!#!", 3)); } public void testAtMostOneSeparator() { BaseEncoding separated = base64().withSeparator("\n", 3); - try { - separated.withSeparator("$", 4); - fail("Expected UnsupportedOperationException"); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> separated.withSeparator("$", 4)); } public void testBase64() { @@ -121,21 +113,15 @@ public void testBase64InvalidDecodings() { } public void testBase64CannotUpperCase() { - try { - base64().upperCase(); - fail(); - } catch (IllegalStateException expected) { - // success - } + assertThrows(IllegalStateException.class, () -> base64().upperCase()); } public void testBase64CannotLowerCase() { - try { - base64().lowerCase(); - fail(); - } catch (IllegalStateException expected) { - // success - } + assertThrows(IllegalStateException.class, () -> base64().lowerCase()); + } + + public void testBase64CannotIgnoreCase() { + assertThrows(IllegalStateException.class, () -> base64().ignoreCase()); } public void testBase64AlternatePadding() { @@ -262,7 +248,19 @@ public void testBase32InvalidDecodings() { } public void testBase32UpperCaseIsNoOp() { - assertSame(base32(), base32().upperCase()); + assertThat(base32().upperCase()).isSameInstanceAs(base32()); + } + + public void testBase32LowerCase() { + testEncodingWithCasing(base32().lowerCase(), "foobar", "mzxw6ytboi======"); + } + + public void testBase32IgnoreCase() { + BaseEncoding ignoreCase = base32().ignoreCase(); + assertThat(ignoreCase).isNotSameInstanceAs(base32()); + assertThat(ignoreCase).isSameInstanceAs(base32().ignoreCase()); + testDecodes(ignoreCase, "MZXW6YTBOI======", "foobar"); + testDecodes(ignoreCase, "mzxw6ytboi======", "foobar"); } public void testBase32Offset() { @@ -318,7 +316,7 @@ public void testBase32HexInvalidDecodings() { } public void testBase32HexUpperCaseIsNoOp() { - assertSame(base32Hex(), base32Hex().upperCase()); + assertThat(base32Hex().upperCase()).isSameInstanceAs(base32Hex()); } public void testBase16() { @@ -332,7 +330,44 @@ public void testBase16() { } public void testBase16UpperCaseIsNoOp() { - assertSame(base16(), base16().upperCase()); + assertThat(base16().upperCase()).isSameInstanceAs(base16()); + } + + public void testBase16LowerCase() { + BaseEncoding lowerCase = base16().lowerCase(); + assertThat(lowerCase).isNotSameInstanceAs(base16()); + assertThat(lowerCase).isSameInstanceAs(base16().lowerCase()); + testEncodingWithCasing(lowerCase, "foobar", "666f6f626172"); + } + + public void testBase16IgnoreCase() { + BaseEncoding ignoreCase = base16().ignoreCase(); + assertThat(ignoreCase).isNotSameInstanceAs(base16()); + assertThat(ignoreCase).isSameInstanceAs(base16().ignoreCase()); + testEncodingWithCasing(ignoreCase, "foobar", "666F6F626172"); + testDecodes(ignoreCase, "666F6F626172", "foobar"); + testDecodes(ignoreCase, "666f6f626172", "foobar"); + testDecodes(ignoreCase, "666F6f626172", "foobar"); + } + + public void testBase16LowerCaseIgnoreCase() { + BaseEncoding ignoreCase = base16().lowerCase().ignoreCase(); + assertThat(ignoreCase).isNotSameInstanceAs(base16()); + assertThat(ignoreCase).isSameInstanceAs(base16().lowerCase().ignoreCase()); + testEncodingWithCasing(ignoreCase, "foobar", "666f6f626172"); + testDecodes(ignoreCase, "666F6F626172", "foobar"); + testDecodes(ignoreCase, "666f6f626172", "foobar"); + testDecodes(ignoreCase, "666F6f626172", "foobar"); + } + + // order the methods are called should not matter + public void testBase16IgnoreCaseLowerCase() { + BaseEncoding ignoreCase = base16().ignoreCase().lowerCase(); + assertThat(ignoreCase).isNotSameInstanceAs(base16()); + testEncodingWithCasing(ignoreCase, "foobar", "666f6f626172"); + testDecodes(ignoreCase, "666F6F626172", "foobar"); + testDecodes(ignoreCase, "666f6f626172", "foobar"); + testDecodes(ignoreCase, "666F6f626172", "foobar"); } public void testBase16InvalidDecodings() { @@ -344,6 +379,8 @@ public void testBase16InvalidDecodings() { assertFailsToDecode(base16(), "ABC"); // These have a combination of invalid length and unrecognized characters. assertFailsToDecode(base16(), "?", "Invalid input length 1"); + assertFailsToDecode(base16(), "ab"); + assertFailsToDecode(base16().lowerCase(), "AB"); } public void testBase16Offset() { @@ -391,12 +428,12 @@ private static void testEncodesWithOffset( } private static void testDecodes(BaseEncoding encoding, String encoded, String decoded) { - assertTrue(encoding.canDecode(encoded)); + assertThat(encoding.canDecode(encoded)).isTrue(); assertThat(encoding.decode(encoded)).isEqualTo(decoded.getBytes(UTF_8)); } private static void testDecodesByBytes(BaseEncoding encoding, String encoded, byte[] decoded) { - assertTrue(encoding.canDecode(encoded)); + assertThat(encoding.canDecode(encoded)).isTrue(); assertThat(encoding.decode(encoded)).isEqualTo(decoded); } @@ -405,7 +442,7 @@ private static void assertFailsToDecode(BaseEncoding encoding, String cannotDeco } private static void assertFailsToDecode( - BaseEncoding encoding, String cannotDecode, @NullableDecl String expectedMessage) { + BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) { // We use this somewhat weird pattern with an enum for each assertion we want to make as a way // of dealing with the fact that one of the assertions is @GwtIncompatible but we don't want to // have to have duplicate @GwtIncompatible test methods just to make that assertion. @@ -415,39 +452,17 @@ private static void assertFailsToDecode( } enum AssertFailsToDecodeStrategy { - @GwtIncompatible // decodingStream(Reader) - DECODING_STREAM { - @Override - void assertFailsToDecode( - BaseEncoding encoding, String cannotDecode, @NullableDecl String expectedMessage) { - // Regression test for case where DecodingException was swallowed by default implementation - // of - // InputStream.read(byte[], int, int) - // See https://github.com/google/guava/issues/3542 - Reader reader = new StringReader(cannotDecode); - InputStream decodingStream = encoding.decodingStream(reader); - try { - ByteStreams.exhaust(decodingStream); - fail("Expected DecodingException"); - } catch (DecodingException expected) { - // Don't assert on the expectedMessage; the messages for exceptions thrown from the - // decoding stream may differ from the messages for the decode methods. - } catch (IOException e) { - fail("Expected DecodingException but got: " + e); - } - } - }, CAN_DECODE { @Override void assertFailsToDecode( - BaseEncoding encoding, String cannotDecode, @NullableDecl String expectedMessage) { - assertFalse(encoding.canDecode(cannotDecode)); + BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) { + assertThat(encoding.canDecode(cannotDecode)).isFalse(); } }, DECODE { @Override void assertFailsToDecode( - BaseEncoding encoding, String cannotDecode, @NullableDecl String expectedMessage) { + BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) { try { encoding.decode(cannotDecode); fail("Expected IllegalArgumentException"); @@ -461,7 +476,7 @@ void assertFailsToDecode( DECODE_CHECKED { @Override void assertFailsToDecode( - BaseEncoding encoding, String cannotDecode, @NullableDecl String expectedMessage) { + BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) { try { encoding.decodeChecked(cannotDecode); fail("Expected DecodingException"); @@ -471,10 +486,37 @@ void assertFailsToDecode( } } } + }, + /* + * This one comes last to work around b/367716565. (One *possible* alternative would be to not + * declare any methods in this enum, converting assertFailsToDecode into a static method that is + * implemented with a `switch`. I haven't tested that.) + */ + @GwtIncompatible // decodingStream(Reader) + DECODING_STREAM { + @Override + void assertFailsToDecode( + BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage) { + // Regression test for case where DecodingException was swallowed by default implementation + // of + // InputStream.read(byte[], int, int) + // See https://github.com/google/guava/issues/3542 + Reader reader = new StringReader(cannotDecode); + InputStream decodingStream = encoding.decodingStream(reader); + try { + ByteStreams.exhaust(decodingStream); + fail("Expected DecodingException"); + } catch (DecodingException expected) { + // Don't assert on the expectedMessage; the messages for exceptions thrown from the + // decoding stream may differ from the messages for the decode methods. + } catch (IOException e) { + fail("Expected DecodingException but got: " + e); + } + } }; abstract void assertFailsToDecode( - BaseEncoding encoding, String cannotDecode, @NullableDecl String expectedMessage); + BaseEncoding encoding, String cannotDecode, @Nullable String expectedMessage); } @GwtIncompatible // Reader/Writer @@ -512,9 +554,9 @@ private static void testStreamingEncoding(BaseEncoding encoding, String decoded, private static void testStreamingEncodes(BaseEncoding encoding, String decoded, String encoded) throws IOException { StringWriter writer = new StringWriter(); - OutputStream encodingStream = encoding.encodingStream(writer); - encodingStream.write(decoded.getBytes(UTF_8)); - encodingStream.close(); + try (OutputStream encodingStream = encoding.encodingStream(writer)) { + encodingStream.write(decoded.getBytes(UTF_8)); + } assertThat(writer.toString()).isEqualTo(encoded); } @@ -522,22 +564,21 @@ private static void testStreamingEncodes(BaseEncoding encoding, String decoded, private static void testStreamingDecodes(BaseEncoding encoding, String encoded, String decoded) throws IOException { byte[] bytes = decoded.getBytes(UTF_8); - InputStream decodingStream = encoding.decodingStream(new StringReader(encoded)); - for (int i = 0; i < bytes.length; i++) { - assertThat(decodingStream.read()).isEqualTo(bytes[i] & 0xFF); + try (InputStream decodingStream = encoding.decodingStream(new StringReader(encoded))) { + for (int i = 0; i < bytes.length; i++) { + assertThat(decodingStream.read()).isEqualTo(bytes[i] & 0xFF); + } + assertThat(decodingStream.read()).isEqualTo(-1); } - assertThat(decodingStream.read()).isEqualTo(-1); - decodingStream.close(); } public void testToString() { - assertEquals("BaseEncoding.base64().withPadChar('=')", base64().toString()); - assertEquals("BaseEncoding.base32Hex().omitPadding()", base32Hex().omitPadding().toString()); - assertEquals( - "BaseEncoding.base32().lowerCase().withPadChar('$')", - base32().lowerCase().withPadChar('$').toString()); - assertEquals( - "BaseEncoding.base16().withSeparator(\"\n\", 10)", - base16().withSeparator("\n", 10).toString()); + assertThat(base64().toString()).isEqualTo("BaseEncoding.base64().withPadChar('=')"); + assertThat(base32Hex().omitPadding().toString()) + .isEqualTo("BaseEncoding.base32Hex().omitPadding()"); + assertThat(base32().lowerCase().withPadChar('$').toString()) + .isEqualTo("BaseEncoding.base32().lowerCase().withPadChar('$')"); + assertThat(base16().withSeparator("\n", 10).toString()) + .isEqualTo("BaseEncoding.base16().withSeparator(\"\n\", 10)"); } } diff --git a/android/guava-tests/test/com/google/common/io/ByteSinkTest.java b/android/guava-tests/test/com/google/common/io/ByteSinkTest.java index 208a8fc5e5ee..6fd8b634750a 100644 --- a/android/guava-tests/test/com/google/common/io/ByteSinkTest.java +++ b/android/guava-tests/test/com/google/common/io/ByteSinkTest.java @@ -21,17 +21,20 @@ import static com.google.common.io.TestOption.READ_THROWS; import static com.google.common.io.TestOption.WRITE_THROWS; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertThrows; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; import java.util.EnumSet; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the default implementations of {@code ByteSink} methods. * * @author Colin Decker */ +@NullUnmarked public class ByteSinkTest extends IoTestCase { private final byte[] bytes = newPreFilledByteArray(10000); @@ -82,11 +85,7 @@ public void testClosesOnErrors_copyingFromByteSourceThatThrows() { for (TestOption option : EnumSet.of(OPEN_THROWS, READ_THROWS, CLOSE_THROWS)) { TestByteSource failSource = new TestByteSource(new byte[10], option); TestByteSink okSink = new TestByteSink(); - try { - failSource.copyTo(okSink); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> failSource.copyTo(okSink)); // ensure stream was closed IF it was opened (depends on implementation whether or not it's // opened at all if source.newInputStream() throws). assertTrue( @@ -97,22 +96,14 @@ public void testClosesOnErrors_copyingFromByteSourceThatThrows() { public void testClosesOnErrors_whenWriteThrows() { TestByteSink failSink = new TestByteSink(WRITE_THROWS); - try { - new TestByteSource(new byte[10]).copyTo(failSink); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> new TestByteSource(new byte[10]).copyTo(failSink)); assertTrue(failSink.wasStreamClosed()); } - public void testClosesOnErrors_writingFromInputStreamThatThrows() { + public void testClosesOnErrors_writingFromInputStreamThatThrows() throws IOException { TestByteSink okSink = new TestByteSink(); - try { - TestInputStream in = new TestInputStream(new ByteArrayInputStream(new byte[10]), READ_THROWS); - okSink.writeFrom(in); - fail(); - } catch (IOException expected) { - } + TestInputStream in = new TestInputStream(new ByteArrayInputStream(new byte[10]), READ_THROWS); + assertThrows(IOException.class, () -> okSink.writeFrom(in)); assertTrue(okSink.wasStreamClosed()); } } diff --git a/android/guava-tests/test/com/google/common/io/ByteSinkTester.java b/android/guava-tests/test/com/google/common/io/ByteSinkTester.java index a8ed36f8118b..049323d4175f 100644 --- a/android/guava-tests/test/com/google/common/io/ByteSinkTester.java +++ b/android/guava-tests/test/com/google/common/io/ByteSinkTester.java @@ -18,9 +18,9 @@ import static com.google.common.io.SourceSinkFactory.ByteSinkFactory; import static com.google.common.io.SourceSinkFactory.CharSinkFactory; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -28,15 +28,17 @@ import java.lang.reflect.Method; import java.util.Map.Entry; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * A generator of {@code TestSuite} instances for testing {@code ByteSink} implementations. - * Generates tests of a all methods on a {@code ByteSink} given various inputs written to it as well + * Generates tests of all methods on a {@code ByteSink} given various inputs written to it as well * as sub-suites for testing the {@code CharSink} view in the same way. * * @author Colin Decker */ -@AndroidIncompatible // Android doesn't understand tests that lack default constructors. +@AndroidIncompatible // TODO(b/230620681): Make this available (even though we won't run it). +@NullUnmarked public class ByteSinkTester extends SourceSinkTester { private static final ImmutableList testMethods = getTestMethods(ByteSinkTester.class); @@ -53,7 +55,7 @@ static TestSuite tests(String name, ByteSinkFactory factory) { private static TestSuite suiteForString( String name, ByteSinkFactory factory, String string, String desc) { - byte[] bytes = string.getBytes(Charsets.UTF_8); + byte[] bytes = string.getBytes(UTF_8); TestSuite suite = suiteForBytes(name, factory, desc, bytes); CharSinkFactory charSinkFactory = SourceSinkFactories.asCharSinkFactory(factory); suite.addTest( @@ -65,7 +67,7 @@ private static TestSuite suiteForString( private static TestSuite suiteForBytes( String name, ByteSinkFactory factory, String desc, byte[] bytes) { TestSuite suite = new TestSuite(name + " [" + desc + "]"); - for (final Method method : testMethods) { + for (Method method : testMethods) { suite.addTest(new ByteSinkTester(factory, bytes, name, desc, method)); } return suite; diff --git a/android/guava-tests/test/com/google/common/io/ByteSourceTest.java b/android/guava-tests/test/com/google/common/io/ByteSourceTest.java index f0ba829b22eb..903f0c170181 100644 --- a/android/guava-tests/test/com/google/common/io/ByteSourceTest.java +++ b/android/guava-tests/test/com/google/common/io/ByteSourceTest.java @@ -23,15 +23,18 @@ import static com.google.common.io.TestOption.READ_THROWS; import static com.google.common.io.TestOption.SKIP_THROWS; import static com.google.common.io.TestOption.WRITE_THROWS; +import static com.google.common.truth.Truth.assertThat; +import static java.lang.Byte.toUnsignedInt; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.nio.charset.StandardCharsets.US_ASCII; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertThrows; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.hash.Hashing; -import com.google.common.primitives.UnsignedBytes; -import com.google.common.testing.TestLogHandler; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -39,12 +42,15 @@ import java.util.Arrays; import java.util.EnumSet; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for the default implementations of {@code ByteSource} methods. * * @author Colin Decker */ +@NullUnmarked public class ByteSourceTest extends IoTestCase { @AndroidIncompatible // Android doesn't understand suites whose tests lack default constructors. @@ -125,7 +131,7 @@ public void testRead_toArray() throws IOException { } public void testRead_withProcessor() throws IOException { - final byte[] processedBytes = new byte[bytes.length]; + byte[] processedBytes = new byte[bytes.length]; ByteProcessor processor = new ByteProcessor() { int pos; @@ -150,8 +156,8 @@ public byte[] getResult() { } public void testRead_withProcessor_stopsOnFalse() throws IOException { - ByteProcessor processor = - new ByteProcessor() { + ByteProcessor<@Nullable Void> processor = + new ByteProcessor<@Nullable Void>() { boolean firstCall = true; @Override @@ -162,7 +168,7 @@ public boolean processBytes(byte[] buf, int off, int len) throws IOException { } @Override - public Void getResult() { + public @Nullable Void getResult() { return null; } }; @@ -172,7 +178,7 @@ public Void getResult() { } public void testHash() throws IOException { - ByteSource byteSource = new TestByteSource("hamburger\n".getBytes(Charsets.US_ASCII)); + ByteSource byteSource = new TestByteSource("hamburger\n".getBytes(US_ASCII)); // Pasted this expected string from `echo hamburger | md5sum` assertEquals("cfa0c5002275c90508338a5cdb2a9781", byteSource.hash(Hashing.md5()).toString()); @@ -197,17 +203,9 @@ public void testContentEquals() throws IOException { public void testSlice() throws IOException { // Test preconditions - try { - source.slice(-1, 10); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> source.slice(-1, 10)); - try { - source.slice(0, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> source.slice(0, -1)); assertCorrectSlice(0, 0, 0, 0); assertCorrectSlice(0, 0, 1, 0); @@ -252,7 +250,7 @@ public void testSlice_appendingAfterSlicing() throws IOException { private static class AppendableByteSource extends ByteSource { private byte[] bytes; - public AppendableByteSource(byte[] initialBytes) { + AppendableByteSource(byte[] initialBytes) { this.bytes = initialBytes.clone(); } @@ -261,7 +259,7 @@ public InputStream openStream() { return new In(); } - public void append(byte[] b) { + void append(byte[] b) { byte[] newBytes = Arrays.copyOf(bytes, bytes.length + b.length); System.arraycopy(b, 0, newBytes, bytes.length, b.length); bytes = newBytes; @@ -273,7 +271,7 @@ private class In extends InputStream { @Override public int read() throws IOException { byte[] b = new byte[1]; - return read(b) == -1 ? -1 : UnsignedBytes.toInt(b[0]); + return read(b) == -1 ? -1 : toUnsignedInt(b[0]); } @Override @@ -282,7 +280,7 @@ public int read(byte[] b, int off, int len) { return -1; } - int lenToRead = Math.min(len, bytes.length - pos); + int lenToRead = min(len, bytes.length - pos); System.arraycopy(bytes, pos, b, off, lenToRead); pos += lenToRead; return lenToRead; @@ -298,7 +296,7 @@ public int read(byte[] b, int off, int len) { */ private static void assertCorrectSlice(int input, int offset, long length, int expectRead) throws IOException { - checkArgument(expectRead == (int) Math.max(0, Math.min(input, offset + length) - offset)); + checkArgument(expectRead == (int) max(0, min(input, offset + length) - offset)); byte[] expected = newPreFilledByteArray(offset, expectRead); @@ -318,11 +316,7 @@ public void testCopyToStream_doesNotCloseThatStream() throws IOException { public void testClosesOnErrors_copyingToByteSinkThatThrows() { for (TestOption option : EnumSet.of(OPEN_THROWS, WRITE_THROWS, CLOSE_THROWS)) { TestByteSource okSource = new TestByteSource(bytes); - try { - okSource.copyTo(new TestByteSink(option)); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> okSource.copyTo(new TestByteSink(option))); // ensure stream was closed IF it was opened (depends on implementation whether or not it's // opened at all if sink.newOutputStream() throws). assertTrue( @@ -333,22 +327,14 @@ public void testClosesOnErrors_copyingToByteSinkThatThrows() { public void testClosesOnErrors_whenReadThrows() { TestByteSource failSource = new TestByteSource(bytes, READ_THROWS); - try { - failSource.copyTo(new TestByteSink()); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> failSource.copyTo(new TestByteSink())); assertTrue(failSource.wasStreamClosed()); } - public void testClosesOnErrors_copyingToOutputStreamThatThrows() { + public void testClosesOnErrors_copyingToOutputStreamThatThrows() throws IOException { TestByteSource okSource = new TestByteSource(bytes); - try { - OutputStream out = new TestOutputStream(ByteStreams.nullOutputStream(), WRITE_THROWS); - okSource.copyTo(out); - fail(); - } catch (IOException expected) { - } + OutputStream out = new TestOutputStream(ByteStreams.nullOutputStream(), WRITE_THROWS); + assertThrows(IOException.class, () -> okSource.copyTo(out)); assertTrue(okSource.wasStreamClosed()); } @@ -395,61 +381,28 @@ public void testConcat_infiniteIterable() throws IOException { ImmutableSet.of(BROKEN_CLOSE_SINK, BROKEN_OPEN_SINK, BROKEN_WRITE_SINK); public void testCopyExceptions() { - if (!Closer.SuppressingSuppressor.isAvailable()) { - // test that exceptions are logged - - TestLogHandler logHandler = new TestLogHandler(); - Closeables.logger.addHandler(logHandler); - try { - for (ByteSource in : BROKEN_SOURCES) { - runFailureTest(in, newNormalByteSink()); - assertTrue(logHandler.getStoredLogRecords().isEmpty()); - - runFailureTest(in, BROKEN_CLOSE_SINK); - assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, getAndResetRecords(logHandler)); - } - - for (ByteSink out : BROKEN_SINKS) { - runFailureTest(newNormalByteSource(), out); - assertTrue(logHandler.getStoredLogRecords().isEmpty()); + // test that exceptions are suppressed - runFailureTest(BROKEN_CLOSE_SOURCE, out); - assertEquals(1, getAndResetRecords(logHandler)); - } + for (ByteSource in : BROKEN_SOURCES) { + int suppressed = runSuppressionFailureTest(in, newNormalByteSink()); + assertEquals(0, suppressed); - for (ByteSource in : BROKEN_SOURCES) { - for (ByteSink out : BROKEN_SINKS) { - runFailureTest(in, out); - assertTrue(getAndResetRecords(logHandler) <= 1); - } - } - } finally { - Closeables.logger.removeHandler(logHandler); - } - } else { - // test that exceptions are suppressed + suppressed = runSuppressionFailureTest(in, BROKEN_CLOSE_SINK); + assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, suppressed); + } - for (ByteSource in : BROKEN_SOURCES) { - int suppressed = runSuppressionFailureTest(in, newNormalByteSink()); - assertEquals(0, suppressed); + for (ByteSink out : BROKEN_SINKS) { + int suppressed = runSuppressionFailureTest(newNormalByteSource(), out); + assertEquals(0, suppressed); - suppressed = runSuppressionFailureTest(in, BROKEN_CLOSE_SINK); - assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, suppressed); - } + suppressed = runSuppressionFailureTest(BROKEN_CLOSE_SOURCE, out); + assertEquals(1, suppressed); + } + for (ByteSource in : BROKEN_SOURCES) { for (ByteSink out : BROKEN_SINKS) { - int suppressed = runSuppressionFailureTest(newNormalByteSource(), out); - assertEquals(0, suppressed); - - suppressed = runSuppressionFailureTest(BROKEN_CLOSE_SOURCE, out); - assertEquals(1, suppressed); - } - - for (ByteSource in : BROKEN_SOURCES) { - for (ByteSink out : BROKEN_SINKS) { - int suppressed = runSuppressionFailureTest(in, out); - assertTrue(suppressed <= 1); - } + int suppressed = runSuppressionFailureTest(in, out); + assertThat(suppressed).isAtMost(1); } } } @@ -458,27 +411,15 @@ public void testSlice_returnEmptySource() { assertEquals(ByteSource.empty(), source.slice(0, 3).slice(4, 3)); } - private static int getAndResetRecords(TestLogHandler logHandler) { - int records = logHandler.getStoredLogRecords().size(); - logHandler.clear(); - return records; - } - - private static void runFailureTest(ByteSource in, ByteSink out) { - try { - in.copyTo(out); - fail(); - } catch (IOException expected) { - } - } - - /** @return the number of exceptions that were suppressed on the expected thrown exception */ + /** + * @return the number of exceptions that were suppressed on the expected thrown exception + */ private static int runSuppressionFailureTest(ByteSource in, ByteSink out) { try { in.copyTo(out); fail(); } catch (IOException expected) { - return CloserTest.getSuppressed(expected).length; + return expected.getSuppressed().length; } throw new AssertionError(); // can't happen } diff --git a/android/guava-tests/test/com/google/common/io/ByteSourceTester.java b/android/guava-tests/test/com/google/common/io/ByteSourceTester.java index 75854125606a..be7aaedc17cb 100644 --- a/android/guava-tests/test/com/google/common/io/ByteSourceTester.java +++ b/android/guava-tests/test/com/google/common/io/ByteSourceTester.java @@ -18,9 +18,10 @@ import static com.google.common.io.SourceSinkFactory.ByteSourceFactory; import static com.google.common.io.SourceSinkFactory.CharSourceFactory; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertThrows; -import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.hash.HashCode; @@ -34,16 +35,18 @@ import java.util.Map.Entry; import java.util.Random; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * A generator of {@code TestSuite} instances for testing {@code ByteSource} implementations. - * Generates tests of a all methods on a {@code ByteSource} given various inputs the source is + * Generates tests of all methods on a {@code ByteSource} given various inputs the source is * expected to contain as well as sub-suites for testing the {@code CharSource} view and {@code * slice()} views in the same way. * * @author Colin Decker */ -@AndroidIncompatible // Android doesn't understand tests that lack default constructors. +@AndroidIncompatible // TODO(b/230620681): Make this available (even though we won't run it). +@NullUnmarked public class ByteSourceTester extends SourceSinkTester { private static final ImmutableList testMethods = getTestMethods(ByteSourceTester.class); @@ -55,8 +58,7 @@ static TestSuite tests(String name, ByteSourceFactory factory, boolean testAsCha suite.addTest(suiteForString(factory, entry.getValue(), name, entry.getKey())); } else { suite.addTest( - suiteForBytes( - factory, entry.getValue().getBytes(Charsets.UTF_8), name, entry.getKey(), true)); + suiteForBytes(factory, entry.getValue().getBytes(UTF_8), name, entry.getKey(), true)); } } return suite; @@ -64,7 +66,7 @@ static TestSuite tests(String name, ByteSourceFactory factory, boolean testAsCha static TestSuite suiteForString( ByteSourceFactory factory, String string, String name, String desc) { - TestSuite suite = suiteForBytes(factory, string.getBytes(Charsets.UTF_8), name, desc, true); + TestSuite suite = suiteForBytes(factory, string.getBytes(UTF_8), name, desc, true); CharSourceFactory charSourceFactory = SourceSinkFactories.asCharSourceFactory(factory); suite.addTest( CharSourceTester.suiteForString( @@ -153,7 +155,7 @@ public void testCopyTo_outputStream() throws IOException { } public void testCopyTo_byteSink() throws IOException { - final ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); // HERESY! but it's ok just for this I guess source.copyTo( new ByteSink() { @@ -219,17 +221,15 @@ public void testHash() throws IOException { } public void testSlice_illegalArguments() { - try { - source.slice(-1, 0); - fail("expected IllegalArgumentException for call to slice with offset -1: " + source); - } catch (IllegalArgumentException expected) { - } - - try { - source.slice(0, -1); - fail("expected IllegalArgumentException for call to slice with length -1: " + source); - } catch (IllegalArgumentException expected) { - } + assertThrows( + "expected IllegalArgumentException for call to slice with offset -1: " + source, + IllegalArgumentException.class, + () -> source.slice(-1, 0)); + + assertThrows( + "expected IllegalArgumentException for call to slice with length -1: " + source, + IllegalArgumentException.class, + () -> source.slice(0, -1)); } // Test that you can not expand the readable data in a previously sliced ByteSource. diff --git a/android/guava-tests/test/com/google/common/io/ByteStreamsTest.java b/android/guava-tests/test/com/google/common/io/ByteStreamsTest.java index 3ae2c2559827..859bedd5b341 100644 --- a/android/guava-tests/test/com/google/common/io/ByteStreamsTest.java +++ b/android/guava-tests/test/com/google/common/io/ByteStreamsTest.java @@ -17,8 +17,12 @@ package com.google.common.io; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_16; +import static java.nio.charset.StandardCharsets.UTF_16BE; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; -import com.google.common.base.Charsets; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; @@ -33,12 +37,14 @@ import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.Arrays; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link ByteStreams}. * * @author Chris Nokleberg */ +@NullUnmarked public class ByteStreamsTest extends IoTestCase { public void testCopyChannel() throws IOException { @@ -53,7 +59,7 @@ public void testCopyChannel() throws IOException { public void testCopyFileChannel() throws IOException { - final int chunkSize = 14407; // Random prime, unlikely to match any internal chunk size + int chunkSize = 14407; // Random prime, unlikely to match any internal chunk size ByteArrayOutputStream out = new ByteArrayOutputStream(); WritableByteChannel outChannel = Channels.newChannel(out); @@ -76,47 +82,24 @@ public void testCopyFileChannel() throws IOException { public void testReadFully() throws IOException { byte[] b = new byte[10]; - try { - ByteStreams.readFully(newTestStream(10), null, 0, 10); - fail("expected exception"); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, () -> ByteStreams.readFully(newTestStream(10), null, 0, 10)); - try { - ByteStreams.readFully(null, b, 0, 10); - fail("expected exception"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> ByteStreams.readFully(null, b, 0, 10)); - try { - ByteStreams.readFully(newTestStream(10), b, -1, 10); - fail("expected exception"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, () -> ByteStreams.readFully(newTestStream(10), b, -1, 10)); - try { - ByteStreams.readFully(newTestStream(10), b, 0, -1); - fail("expected exception"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, () -> ByteStreams.readFully(newTestStream(10), b, 0, -1)); - try { - ByteStreams.readFully(newTestStream(10), b, 0, -1); - fail("expected exception"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, () -> ByteStreams.readFully(newTestStream(10), b, 0, -1)); - try { - ByteStreams.readFully(newTestStream(10), b, 2, 10); - fail("expected exception"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows( + IndexOutOfBoundsException.class, () -> ByteStreams.readFully(newTestStream(10), b, 2, 10)); - try { - ByteStreams.readFully(newTestStream(5), b, 0, 10); - fail("expected exception"); - } catch (EOFException expected) { - } + assertThrows(EOFException.class, () -> ByteStreams.readFully(newTestStream(5), b, 0, 10)); Arrays.fill(b, (byte) 0); ByteStreams.readFully(newTestStream(10), b, 0, 0); @@ -138,11 +121,7 @@ public void testSkipFully() throws IOException { skipHelper(50, 50, new SlowSkipper(new ByteArrayInputStream(bytes), 1)); skipHelper(50, 50, new SlowSkipper(new ByteArrayInputStream(bytes), 0)); skipHelper(100, -1, new ByteArrayInputStream(bytes)); - try { - skipHelper(101, 0, new ByteArrayInputStream(bytes)); - fail("expected exception"); - } catch (EOFException expected) { - } + assertThrows(EOFException.class, () -> skipHelper(101, 0, new ByteArrayInputStream(bytes))); } private static void skipHelper(long n, int expect, InputStream in) throws IOException { @@ -156,22 +135,14 @@ private static void skipHelper(long n, int expect, InputStream in) throws IOExce public void testNewDataInput_empty() { byte[] b = new byte[0]; ByteArrayDataInput in = ByteStreams.newDataInput(b); - try { - in.readInt(); - fail("expected exception"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> in.readInt()); } public void testNewDataInput_normal() { ByteArrayDataInput in = ByteStreams.newDataInput(bytes); assertEquals(0x12345678, in.readInt()); assertEquals(0x76543210, in.readInt()); - try { - in.readInt(); - fail("expected exception"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> in.readInt()); } public void testNewDataInput_readFully() { @@ -184,12 +155,9 @@ public void testNewDataInput_readFully() { public void testNewDataInput_readFullyAndThenSome() { ByteArrayDataInput in = ByteStreams.newDataInput(bytes); byte[] actual = new byte[bytes.length * 2]; - try { - in.readFully(actual); - fail("expected exception"); - } catch (IllegalStateException ex) { - assertThat(ex).hasCauseThat().isInstanceOf(EOFException.class); - } + IllegalStateException ex = + assertThrows(IllegalStateException.class, () -> in.readFully(actual)); + assertThat(ex).hasCauseThat().isInstanceOf(EOFException.class); } public void testNewDataInput_readFullyWithOffset() { @@ -205,7 +173,7 @@ public void testNewDataInput_readFullyWithOffset() { public void testNewDataInput_readLine() { ByteArrayDataInput in = ByteStreams.newDataInput( - "This is a line\r\nThis too\rand this\nand also this".getBytes(Charsets.UTF_8)); + "This is a line\r\nThis too\rand this\nand also this".getBytes(UTF_8)); assertEquals("This is a line", in.readLine()); assertEquals("This too", in.readLine()); assertEquals("and this", in.readLine()); @@ -215,26 +183,26 @@ public void testNewDataInput_readLine() { public void testNewDataInput_readFloat() { byte[] data = {0x12, 0x34, 0x56, 0x78, 0x76, 0x54, 0x32, 0x10}; ByteArrayDataInput in = ByteStreams.newDataInput(data); - assertEquals(Float.intBitsToFloat(0x12345678), in.readFloat(), 0.0); - assertEquals(Float.intBitsToFloat(0x76543210), in.readFloat(), 0.0); + assertThat(in.readFloat()).isEqualTo(Float.intBitsToFloat(0x12345678)); + assertThat(in.readFloat()).isEqualTo(Float.intBitsToFloat(0x76543210)); } public void testNewDataInput_readDouble() { byte[] data = {0x12, 0x34, 0x56, 0x78, 0x76, 0x54, 0x32, 0x10}; ByteArrayDataInput in = ByteStreams.newDataInput(data); - assertEquals(Double.longBitsToDouble(0x1234567876543210L), in.readDouble(), 0.0); + assertThat(in.readDouble()).isEqualTo(Double.longBitsToDouble(0x1234567876543210L)); } public void testNewDataInput_readUTF() { byte[] data = new byte[17]; data[1] = 15; - System.arraycopy("Kilroy was here".getBytes(Charsets.UTF_8), 0, data, 2, 15); + System.arraycopy("Kilroy was here".getBytes(UTF_8), 0, data, 2, 15); ByteArrayDataInput in = ByteStreams.newDataInput(data); assertEquals("Kilroy was here", in.readUTF()); } public void testNewDataInput_readChar() { - byte[] data = "qed".getBytes(Charsets.UTF_16BE); + byte[] data = "qed".getBytes(UTF_16BE); ByteArrayDataInput in = ByteStreams.newDataInput(data); assertEquals('q', in.readChar()); assertEquals('e', in.readChar()); @@ -266,12 +234,8 @@ public void testNewDataInput_readByte() { for (byte aByte : bytes) { assertEquals(aByte, in.readByte()); } - try { - in.readByte(); - fail("expected exception"); - } catch (IllegalStateException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(EOFException.class); - } + IllegalStateException expected = assertThrows(IllegalStateException.class, () -> in.readByte()); + assertThat(expected).hasCauseThat().isInstanceOf(EOFException.class); } public void testNewDataInput_readUnsignedByte() { @@ -279,22 +243,15 @@ public void testNewDataInput_readUnsignedByte() { for (byte aByte : bytes) { assertEquals(aByte, in.readUnsignedByte()); } - try { - in.readUnsignedByte(); - fail("expected exception"); - } catch (IllegalStateException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(EOFException.class); - } + IllegalStateException expected = + assertThrows(IllegalStateException.class, () -> in.readUnsignedByte()); + assertThat(expected).hasCauseThat().isInstanceOf(EOFException.class); } public void testNewDataInput_offset() { ByteArrayDataInput in = ByteStreams.newDataInput(bytes, 2); assertEquals(0x56787654, in.readInt()); - try { - in.readInt(); - fail("expected exception"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> in.readInt()); } public void testNewDataInput_skip() { @@ -303,7 +260,7 @@ public void testNewDataInput_skip() { assertEquals(0, in.skipBytes(1)); } - public void testNewDataInput_BAIS() { + public void testNewDataInput_bais() { ByteArrayInputStream bais = new ByteArrayInputStream(new byte[] {0x12, 0x34, 0x56, 0x78}); ByteArrayDataInput in = ByteStreams.newDataInput(bais); assertEquals(0x12345678, in.readInt()); @@ -311,7 +268,7 @@ public void testNewDataInput_BAIS() { public void testNewDataOutput_empty() { ByteArrayDataOutput out = ByteStreams.newDataOutput(); - assertEquals(0, out.toByteArray().length); + assertThat(out.toByteArray()).isEmpty(); } public void testNewDataOutput_writeInt() { @@ -380,17 +337,17 @@ public void testNewDataOutput_writeChars() { assertThat(out.toByteArray()).isEqualTo(expected); } - @AndroidIncompatible // https://code.google.com/p/android/issues/detail?id=196848 + @AndroidIncompatible // https://issuetracker.google.com/issues/37074504 public void testUtf16Expected() { byte[] hardcodedExpected = utf16ExpectedWithBom; - byte[] computedExpected = "r\u00C9sum\u00C9".getBytes(Charsets.UTF_16); + byte[] computedExpected = "r\u00C9sum\u00C9".getBytes(UTF_16); assertThat(computedExpected).isEqualTo(hardcodedExpected); } public void testNewDataOutput_writeUTF() { ByteArrayDataOutput out = ByteStreams.newDataOutput(); out.writeUTF("r\u00C9sum\u00C9"); - byte[] expected = "r\u00C9sum\u00C9".getBytes(Charsets.UTF_8); + byte[] expected = "r\u00C9sum\u00C9".getBytes(UTF_8); byte[] actual = out.toByteArray(); // writeUTF writes the length of the string in 2 bytes assertEquals(0, actual[0]); @@ -417,7 +374,7 @@ public void testNewDataOutput_writeFloat() { assertThat(out.toByteArray()).isEqualTo(bytes); } - public void testNewDataOutput_BAOS() { + public void testNewDataOutput_baos() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayDataOutput out = ByteStreams.newDataOutput(baos); out.writeInt(0x12345678); @@ -436,7 +393,7 @@ public void testToByteArray() throws IOException { public void testToByteArray_emptyStream() throws IOException { InputStream in = newTestStream(0); byte[] b = ByteStreams.toByteArray(in); - assertThat(b).isEqualTo(new byte[0]); + assertThat(b).isEmpty(); } public void testToByteArray_largeStream() throws IOException { @@ -515,7 +472,7 @@ public long skip(long n) throws IOException { } public void testReadBytes() throws IOException { - final byte[] array = newPreFilledByteArray(1000); + byte[] array = newPreFilledByteArray(1000); assertThat(ByteStreams.readBytes(new ByteArrayInputStream(array), new TestByteProcessor())) .isEqualTo(array); } @@ -562,12 +519,25 @@ public void testNullOutputStream() throws Exception { // write to the output stream nos.write('n'); String test = "Test string for NullOutputStream"; - nos.write(test.getBytes()); - nos.write(test.getBytes(), 2, 10); + byte[] bytes = test.getBytes(US_ASCII); + nos.write(bytes); + nos.write(bytes, 2, 10); + nos.write(bytes, bytes.length - 5, 5); // nothing really to assert? assertSame(ByteStreams.nullOutputStream(), ByteStreams.nullOutputStream()); } + public void testNullOutputStream_exceptions() throws Exception { + OutputStream nos = ByteStreams.nullOutputStream(); + assertThrows(NullPointerException.class, () -> nos.write(null)); + assertThrows(NullPointerException.class, () -> nos.write(null, 0, 1)); + byte[] tenBytes = new byte[10]; + assertThrows(IndexOutOfBoundsException.class, () -> nos.write(tenBytes, -1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> nos.write(tenBytes, 1, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> nos.write(tenBytes, 9, 2)); + assertThrows(IndexOutOfBoundsException.class, () -> nos.write(tenBytes, 9, 100)); + } + public void testLimit() throws Exception { byte[] big = newPreFilledByteArray(5); InputStream bin = new ByteArrayInputStream(big); @@ -642,23 +612,15 @@ public void testLimit_markNotSet() { InputStream bin = new ByteArrayInputStream(big); InputStream lin = ByteStreams.limit(bin, 2); - try { - lin.reset(); - fail(); - } catch (IOException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Mark not set"); - } + IOException expected = assertThrows(IOException.class, () -> lin.reset()); + assertThat(expected).hasMessageThat().isEqualTo("Mark not set"); } public void testLimit_markNotSupported() { InputStream lin = ByteStreams.limit(new UnmarkableInputStream(), 2); - try { - lin.reset(); - fail(); - } catch (IOException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Mark not supported"); - } + IOException expected = assertThrows(IOException.class, () -> lin.reset()); + assertThat(expected).hasMessageThat().isEqualTo("Mark not supported"); } private static class UnmarkableInputStream extends InputStream { diff --git a/android/guava-tests/test/com/google/common/io/CharSequenceReaderTest.java b/android/guava-tests/test/com/google/common/io/CharSequenceReaderTest.java index 12bc17e588e3..bb97b591ffd7 100644 --- a/android/guava-tests/test/com/google/common/io/CharSequenceReaderTest.java +++ b/android/guava-tests/test/com/google/common/io/CharSequenceReaderTest.java @@ -16,15 +16,19 @@ package com.google.common.io; +import static org.junit.Assert.assertThrows; + import java.io.IOException; import java.nio.CharBuffer; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link CharSequenceReader}. * * @author Colin Decker */ +@NullUnmarked public class CharSequenceReaderTest extends TestCase { public void testReadEmptyString() throws IOException { @@ -73,106 +77,42 @@ public void testIllegalArguments() throws IOException { CharSequenceReader reader = new CharSequenceReader("12345"); char[] buf = new char[10]; - try { - reader.read(buf, 0, 11); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(buf, 0, 11)); - try { - reader.read(buf, 10, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(buf, 10, 1)); - try { - reader.read(buf, 11, 0); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(buf, 11, 0)); - try { - reader.read(buf, -1, 5); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(buf, -1, 5)); - try { - reader.read(buf, 5, -1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(buf, 5, -1)); - try { - reader.read(buf, 0, 11); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> reader.read(buf, 0, 11)); - try { - reader.skip(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> reader.skip(-1)); - try { - reader.mark(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> reader.mark(-1)); } public void testMethodsThrowWhenClosed() throws IOException { CharSequenceReader reader = new CharSequenceReader(""); reader.close(); - try { - reader.read(); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> reader.read()); - try { - reader.read(new char[10]); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> reader.read(new char[10])); - try { - reader.read(new char[10], 0, 10); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> reader.read(new char[10], 0, 10)); - try { - reader.read(CharBuffer.allocate(10)); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> reader.read(CharBuffer.allocate(10))); - try { - reader.skip(10); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> reader.skip(10)); - try { - reader.ready(); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> reader.ready()); - try { - reader.mark(10); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> reader.mark(10)); - try { - reader.reset(); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> reader.reset()); } /** @@ -211,7 +151,7 @@ private static void assertReadsCorrectly(CharSequence charSequence) throws IOExc reader = new CharSequenceReader(charSequence); CharBuffer buf2 = CharBuffer.allocate(expected.length()); assertEquals(expected.length() == 0 ? -1 : expected.length(), reader.read(buf2)); - buf2.flip(); + Java8Compatibility.flip(buf2); assertEquals(expected, buf2.toString()); assertFullyRead(reader); @@ -220,9 +160,9 @@ private static void assertReadsCorrectly(CharSequence charSequence) throws IOExc buf2 = CharBuffer.allocate(5); builder = new StringBuilder(); while (reader.read(buf2) != -1) { - buf2.flip(); + Java8Compatibility.flip(buf2); builder.append(buf2); - buf2.clear(); + Java8Compatibility.clear(buf2); } assertEquals(expected, builder.toString()); assertFullyRead(reader); diff --git a/android/guava-tests/test/com/google/common/io/CharSinkTest.java b/android/guava-tests/test/com/google/common/io/CharSinkTest.java index ac96d906451f..d2d59d35be0f 100644 --- a/android/guava-tests/test/com/google/common/io/CharSinkTest.java +++ b/android/guava-tests/test/com/google/common/io/CharSinkTest.java @@ -16,22 +16,26 @@ package com.google.common.io; +import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR; import static com.google.common.io.TestOption.CLOSE_THROWS; import static com.google.common.io.TestOption.OPEN_THROWS; import static com.google.common.io.TestOption.READ_THROWS; import static com.google.common.io.TestOption.WRITE_THROWS; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.StringReader; import java.io.Writer; import java.util.EnumSet; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the default implementations of {@code CharSink} methods. * * @author Colin Decker */ +@NullUnmarked public class CharSinkTest extends IoTestCase { private static final String STRING = ASCII + I18N; @@ -89,15 +93,22 @@ public void testWriteLines_withDefaultSeparator() throws IOException { assertEquals("foo" + separator + "bar" + separator + "baz" + separator, sink.getString()); } + public void testWriteLines_stream() throws IOException { + sink.writeLines(ImmutableList.of("foo", "bar", "baz").stream()); + String separator = LINE_SEPARATOR.value(); + assertEquals("foo" + separator + "bar" + separator + "baz" + separator, sink.getString()); + } + + public void testWriteLines_stream_separator() throws IOException { + sink.writeLines(ImmutableList.of("foo", "bar", "baz").stream(), "!"); + assertEquals("foo!bar!baz!", sink.getString()); + } + public void testClosesOnErrors_copyingFromCharSourceThatThrows() { for (TestOption option : EnumSet.of(OPEN_THROWS, READ_THROWS, CLOSE_THROWS)) { TestCharSource failSource = new TestCharSource(STRING, option); TestCharSink okSink = new TestCharSink(); - try { - failSource.copyTo(okSink); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> failSource.copyTo(okSink)); // ensure writer was closed IF it was opened (depends on implementation whether or not it's // opened at all if source.newReader() throws). assertTrue( @@ -108,21 +119,13 @@ public void testClosesOnErrors_copyingFromCharSourceThatThrows() { public void testClosesOnErrors_whenWriteThrows() { TestCharSink failSink = new TestCharSink(WRITE_THROWS); - try { - new TestCharSource(STRING).copyTo(failSink); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> new TestCharSource(STRING).copyTo(failSink)); assertTrue(failSink.wasStreamClosed()); } public void testClosesOnErrors_whenWritingFromReaderThatThrows() { TestCharSink okSink = new TestCharSink(); - try { - okSink.writeFrom(new TestReader(READ_THROWS)); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> okSink.writeFrom(new TestReader(READ_THROWS))); assertTrue(okSink.wasStreamClosed()); } } diff --git a/android/guava-tests/test/com/google/common/io/CharSinkTester.java b/android/guava-tests/test/com/google/common/io/CharSinkTester.java index 788f9c02e1e8..cb835029d623 100644 --- a/android/guava-tests/test/com/google/common/io/CharSinkTester.java +++ b/android/guava-tests/test/com/google/common/io/CharSinkTester.java @@ -25,14 +25,16 @@ import java.lang.reflect.Method; import java.util.Map.Entry; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * A generator of {@code TestSuite} instances for testing {@code CharSink} implementations. - * Generates tests of a all methods on a {@code CharSink} given various inputs written to it. + * Generates tests of all methods on a {@code CharSink} given various inputs written to it. * * @author Colin Decker */ -@AndroidIncompatible // Android doesn't understand tests that lack default constructors. +@AndroidIncompatible // TODO(b/230620681): Make this available (even though we won't run it). +@NullUnmarked public class CharSinkTester extends SourceSinkTester { private static final ImmutableList testMethods = getTestMethods(CharSinkTester.class); @@ -50,7 +52,7 @@ static TestSuite tests(String name, CharSinkFactory factory) { static TestSuite suiteForString( String name, CharSinkFactory factory, String string, String desc) { TestSuite stringSuite = new TestSuite(name + " [" + desc + "]"); - for (final Method method : testMethods) { + for (Method method : testMethods) { stringSuite.addTest(new CharSinkTester(factory, string, name, desc, method)); } return stringSuite; diff --git a/android/guava-tests/test/com/google/common/io/CharSourceTest.java b/android/guava-tests/test/com/google/common/io/CharSourceTest.java index a1034904dd7c..018097c03602 100644 --- a/android/guava-tests/test/com/google/common/io/CharSourceTest.java +++ b/android/guava-tests/test/com/google/common/io/CharSourceTest.java @@ -16,30 +16,35 @@ package com.google.common.io; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.io.TestOption.CLOSE_THROWS; import static com.google.common.io.TestOption.OPEN_THROWS; import static com.google.common.io.TestOption.READ_THROWS; import static com.google.common.io.TestOption.WRITE_THROWS; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.testing.TestLogHandler; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import java.util.stream.Stream; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the default implementations of {@code CharSource} methods. * * @author Colin Decker */ +@NullUnmarked public class CharSourceTest extends IoTestCase { @AndroidIncompatible // Android doesn't understand suites whose tests lack default constructors. @@ -61,6 +66,8 @@ public static TestSuite suite() { private static final String STRING = ASCII + I18N; private static final String LINES = "foo\nbar\r\nbaz\rsomething"; + private static final ImmutableList SPLIT_LINES = + ImmutableList.of("foo", "bar", "baz", "something"); private TestCharSource source; @@ -87,6 +94,21 @@ public void testOpenBufferedStream() throws IOException { assertEquals(STRING, writer.toString()); } + public void testLines() throws IOException { + source = new TestCharSource(LINES); + + ImmutableList lines; + try (Stream linesStream = source.lines()) { + assertTrue(source.wasStreamOpened()); + assertFalse(source.wasStreamClosed()); + + lines = linesStream.collect(toImmutableList()); + } + + assertTrue(source.wasStreamClosed()); + assertEquals(SPLIT_LINES, lines); + } + public void testCopyTo_appendable() throws IOException { StringBuilder builder = new StringBuilder(); @@ -130,7 +152,7 @@ public void testReadLines_withProcessor() throws IOException { List list = lines.readLines( new LineProcessor>() { - List list = Lists.newArrayList(); + final List list = new ArrayList<>(); @Override public boolean processLine(String line) throws IOException { @@ -152,7 +174,7 @@ public void testReadLines_withProcessor_stopsOnFalse() throws IOException { List list = lines.readLines( new LineProcessor>() { - List list = Lists.newArrayList(); + final List list = new ArrayList<>(); @Override public boolean processLine(String line) throws IOException { @@ -169,6 +191,17 @@ public List getResult() { assertTrue(lines.wasStreamOpened() && lines.wasStreamClosed()); } + public void testForEachLine() throws IOException { + source = new TestCharSource(LINES); + + ImmutableList.Builder builder = ImmutableList.builder(); + source.forEachLine(builder::add); + + assertEquals(SPLIT_LINES, builder.build()); + assertTrue(source.wasStreamOpened()); + assertTrue(source.wasStreamClosed()); + } + public void testCopyToAppendable_doesNotCloseIfWriter() throws IOException { TestWriter writer = new TestWriter(); assertFalse(writer.closed()); @@ -179,11 +212,7 @@ public void testCopyToAppendable_doesNotCloseIfWriter() throws IOException { public void testClosesOnErrors_copyingToCharSinkThatThrows() { for (TestOption option : EnumSet.of(OPEN_THROWS, WRITE_THROWS, CLOSE_THROWS)) { TestCharSource okSource = new TestCharSource(STRING); - try { - okSource.copyTo(new TestCharSink(option)); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> okSource.copyTo(new TestCharSink(option))); // ensure reader was closed IF it was opened (depends on implementation whether or not it's // opened at all if sink.newWriter() throws). assertTrue( @@ -194,21 +223,13 @@ public void testClosesOnErrors_copyingToCharSinkThatThrows() { public void testClosesOnErrors_whenReadThrows() { TestCharSource failSource = new TestCharSource(STRING, READ_THROWS); - try { - failSource.copyTo(new TestCharSink()); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> failSource.copyTo(new TestCharSink())); assertTrue(failSource.wasStreamClosed()); } public void testClosesOnErrors_copyingToWriterThatThrows() { TestCharSource okSource = new TestCharSource(STRING); - try { - okSource.copyTo(new TestWriter(WRITE_THROWS)); - fail(); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> okSource.copyTo(new TestWriter(WRITE_THROWS))); assertTrue(okSource.wasStreamClosed()); } @@ -258,86 +279,41 @@ public void testConcat_infiniteIterable() throws IOException { ImmutableSet.of(BROKEN_CLOSE_SINK, BROKEN_OPEN_SINK, BROKEN_WRITE_SINK); public void testCopyExceptions() { - if (!Closer.SuppressingSuppressor.isAvailable()) { - // test that exceptions are logged - - TestLogHandler logHandler = new TestLogHandler(); - Closeables.logger.addHandler(logHandler); - try { - for (CharSource in : BROKEN_SOURCES) { - runFailureTest(in, newNormalCharSink()); - assertTrue(logHandler.getStoredLogRecords().isEmpty()); - - runFailureTest(in, BROKEN_CLOSE_SINK); - assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, getAndResetRecords(logHandler)); - } - - for (CharSink out : BROKEN_SINKS) { - runFailureTest(newNormalCharSource(), out); - assertTrue(logHandler.getStoredLogRecords().isEmpty()); - - runFailureTest(BROKEN_CLOSE_SOURCE, out); - assertEquals(1, getAndResetRecords(logHandler)); - } - - for (CharSource in : BROKEN_SOURCES) { - for (CharSink out : BROKEN_SINKS) { - runFailureTest(in, out); - assertTrue(getAndResetRecords(logHandler) <= 1); - } - } - } finally { - Closeables.logger.removeHandler(logHandler); - } - } else { - // test that exceptions are suppressed - - for (CharSource in : BROKEN_SOURCES) { - int suppressed = runSuppressionFailureTest(in, newNormalCharSink()); - assertEquals(0, suppressed); + // test that exceptions are suppressed - suppressed = runSuppressionFailureTest(in, BROKEN_CLOSE_SINK); - assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, suppressed); - } + for (CharSource in : BROKEN_SOURCES) { + int suppressed = runSuppressionFailureTest(in, newNormalCharSink()); + assertEquals(0, suppressed); - for (CharSink out : BROKEN_SINKS) { - int suppressed = runSuppressionFailureTest(newNormalCharSource(), out); - assertEquals(0, suppressed); + suppressed = runSuppressionFailureTest(in, BROKEN_CLOSE_SINK); + assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, suppressed); + } - suppressed = runSuppressionFailureTest(BROKEN_CLOSE_SOURCE, out); - assertEquals(1, suppressed); - } + for (CharSink out : BROKEN_SINKS) { + int suppressed = runSuppressionFailureTest(newNormalCharSource(), out); + assertEquals(0, suppressed); - for (CharSource in : BROKEN_SOURCES) { - for (CharSink out : BROKEN_SINKS) { - int suppressed = runSuppressionFailureTest(in, out); - assertTrue(suppressed <= 1); - } - } + suppressed = runSuppressionFailureTest(BROKEN_CLOSE_SOURCE, out); + assertEquals(1, suppressed); } - } - - private static int getAndResetRecords(TestLogHandler logHandler) { - int records = logHandler.getStoredLogRecords().size(); - logHandler.clear(); - return records; - } - private static void runFailureTest(CharSource in, CharSink out) { - try { - in.copyTo(out); - fail(); - } catch (IOException expected) { + for (CharSource in : BROKEN_SOURCES) { + for (CharSink out : BROKEN_SINKS) { + int suppressed = runSuppressionFailureTest(in, out); + assertThat(suppressed).isAtMost(1); + } } } - /** @return the number of exceptions that were suppressed on the expected thrown exception */ + /** + * @return the number of exceptions that were suppressed on the expected thrown exception + */ private static int runSuppressionFailureTest(CharSource in, CharSink out) { try { in.copyTo(out); fail(); } catch (IOException expected) { - return CloserTest.getSuppressed(expected).length; + return expected.getSuppressed().length; } throw new AssertionError(); // can't happen } diff --git a/android/guava-tests/test/com/google/common/io/CharSourceTester.java b/android/guava-tests/test/com/google/common/io/CharSourceTester.java index e73fcdc17649..d71a41255a9e 100644 --- a/android/guava-tests/test/com/google/common/io/CharSourceTester.java +++ b/android/guava-tests/test/com/google/common/io/CharSourceTester.java @@ -16,10 +16,11 @@ package com.google.common.io; -import com.google.common.base.Charsets; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import com.google.common.io.SourceSinkFactory.ByteSourceFactory; import com.google.common.io.SourceSinkFactory.CharSourceFactory; import java.io.BufferedReader; @@ -27,18 +28,21 @@ import java.io.Reader; import java.io.StringWriter; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * A generator of {@code TestSuite} instances for testing {@code CharSource} implementations. - * Generates tests of a all methods on a {@code CharSource} given various inputs the source is + * Generates tests of all methods on a {@code CharSource} given various inputs the source is * expected to contain. * * @author Colin Decker */ -@AndroidIncompatible // Android doesn't understand tests that lack default constructors. +@AndroidIncompatible // TODO(b/230620681): Make this available (even though we won't run it). +@NullUnmarked public class CharSourceTester extends SourceSinkTester { private static final ImmutableList testMethods = getTestMethods(CharSourceTester.class); @@ -48,8 +52,7 @@ static TestSuite tests(String name, CharSourceFactory factory, boolean testAsByt for (Entry entry : TEST_STRINGS.entrySet()) { if (testAsByteSource) { suite.addTest( - suiteForBytes( - factory, entry.getValue().getBytes(Charsets.UTF_8), name, entry.getKey(), true)); + suiteForBytes(factory, entry.getValue().getBytes(UTF_8), name, entry.getKey(), true)); } else { suite.addTest(suiteForString(factory, entry.getValue(), name, entry.getKey())); } @@ -59,7 +62,7 @@ static TestSuite tests(String name, CharSourceFactory factory, boolean testAsByt static TestSuite suiteForBytes( CharSourceFactory factory, byte[] bytes, String name, String desc, boolean slice) { - TestSuite suite = suiteForString(factory, new String(bytes, Charsets.UTF_8), name, desc); + TestSuite suite = suiteForString(factory, new String(bytes, UTF_8), name, desc); ByteSourceFactory byteSourceFactory = SourceSinkFactories.asByteSourceFactory(factory); suite.addTest( ByteSourceTester.suiteForBytes( @@ -144,7 +147,7 @@ public void testRead_toString() throws IOException { public void testReadFirstLine() throws IOException { if (expectedLines.isEmpty()) { - assertNull(source.readFirstLine()); + assertThat(source.readFirstLine()).isNull(); } else { assertEquals(expectedLines.get(0), source.readFirstLine()); } @@ -173,7 +176,7 @@ public void testReadLines_withProcessor() throws IOException { List list = source.readLines( new LineProcessor>() { - List list = Lists.newArrayList(); + final List list = new ArrayList<>(); @Override public boolean processLine(String line) throws IOException { @@ -194,7 +197,7 @@ public void testReadLines_withProcessor_stopsOnFalse() throws IOException { List list = source.readLines( new LineProcessor>() { - List list = Lists.newArrayList(); + final List list = new ArrayList<>(); @Override public boolean processLine(String line) throws IOException { diff --git a/android/guava-tests/test/com/google/common/io/CharStreamsTest.java b/android/guava-tests/test/com/google/common/io/CharStreamsTest.java index 9b2c24e28549..10e12b3ade62 100644 --- a/android/guava-tests/test/com/google/common/io/CharStreamsTest.java +++ b/android/guava-tests/test/com/google/common/io/CharStreamsTest.java @@ -16,6 +16,9 @@ package com.google.common.io; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import java.io.EOFException; @@ -27,12 +30,14 @@ import java.io.Writer; import java.nio.CharBuffer; import java.util.List; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link CharStreams}. * * @author Chris Nokleberg */ +@NullUnmarked public class CharStreamsTest extends IoTestCase { private static final String TEXT = "The quick brown fox jumped over the lazy dog."; @@ -95,7 +100,7 @@ public Integer getResult() { // Test a LineProcessor that is conditional. r = new StringReader(text); - final StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); LineProcessor conditional = new LineProcessor() { int seen; @@ -116,13 +121,9 @@ public Integer getResult() { assertEquals("ab", sb.toString()); } - public void testSkipFully_EOF() throws IOException { + public void testSkipFully_eof() throws IOException { Reader reader = new StringReader("abcde"); - try { - CharStreams.skipFully(reader, 6); - fail("expected EOFException"); - } catch (EOFException expected) { - } + assertThrows(EOFException.class, () -> CharStreams.skipFully(reader, 6)); } public void testSkipFully() throws IOException { @@ -143,7 +144,7 @@ public void testAsWriter() { Appendable plainAppendable = new StringBuilder(); Writer result = CharStreams.asWriter(plainAppendable); assertNotSame(plainAppendable, result); - assertNotNull(result); + assertThat(result).isNotNull(); // A Writer should not be wrapped Appendable secretlyAWriter = new StringWriter(); @@ -218,7 +219,7 @@ public void testCopy_toWriter_fromReadable() throws IOException { } /** - * Test for Guava issue 1061: http://code.google.com/p/guava-libraries/issues/detail?id=1061 + * Test for Guava issue 1061: https://github.com/google/guava/issues/1061 * *

    CharStreams.copy was failing to clear its CharBuffer after each read call, which effectively * reduced the available size of the buffer each time a call to read didn't fill up the available @@ -226,6 +227,7 @@ public void testCopy_toWriter_fromReadable() throws IOException { * is permanently reduced, but with certain Reader implementations it could also cause the buffer * size to reach 0, causing an infinite loop. */ + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 public void testCopyWithReaderThatDoesNotFillBuffer() throws IOException { // need a long enough string for the buffer to hit 0 remaining before the copy completes String string = Strings.repeat("0123456789", 100); @@ -270,17 +272,9 @@ public void testNullWriter() throws Exception { nullWriter.append(null); nullWriter.append(null, 0, 4); - try { - nullWriter.append(null, -1, 4); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> nullWriter.append(null, -1, 4)); - try { - nullWriter.append(null, 0, 5); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> nullWriter.append(null, 0, 5)); // nothing really to assert? assertSame(CharStreams.nullWriter(), CharStreams.nullWriter()); @@ -307,7 +301,7 @@ public int read(char[] cbuf, int off, int len) throws IOException { } /** Wrap an appendable in an appendable to defeat any type specific optimizations. */ - private static Appendable wrapAsGenericAppendable(final Appendable a) { + private static Appendable wrapAsGenericAppendable(Appendable a) { return new Appendable() { @Override @@ -331,7 +325,7 @@ public Appendable append(char c) throws IOException { } /** Wrap a readable in a readable to defeat any type specific optimizations. */ - private static Readable wrapAsGenericReadable(final Readable a) { + private static Readable wrapAsGenericReadable(Readable a) { return new Readable() { @Override public int read(CharBuffer cb) throws IOException { diff --git a/android/guava-tests/test/com/google/common/io/CloseablesTest.java b/android/guava-tests/test/com/google/common/io/CloseablesTest.java index 34648b46e88d..2ac954c11f34 100644 --- a/android/guava-tests/test/com/google/common/io/CloseablesTest.java +++ b/android/guava-tests/test/com/google/common/io/CloseablesTest.java @@ -26,6 +26,7 @@ import java.io.InputStream; import java.io.Reader; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Closeables}. @@ -35,6 +36,7 @@ * * @author Michael Lancaster */ +@NullUnmarked public class CloseablesTest extends TestCase { private Closeable mockCloseable; diff --git a/android/guava-tests/test/com/google/common/io/CloserTest.java b/android/guava-tests/test/com/google/common/io/CloserTest.java index b97a3057698a..a36c74c07e51 100644 --- a/android/guava-tests/test/com/google/common/io/CloserTest.java +++ b/android/guava-tests/test/com/google/common/io/CloserTest.java @@ -16,28 +16,28 @@ package com.google.common.io; +import static com.google.common.base.Throwables.throwIfInstanceOf; +import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; -import com.google.common.testing.TestLogHandler; import java.io.Closeable; import java.io.IOException; -import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.List; -import java.util.logging.LogRecord; +import java.util.Objects; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Closer}. * * @author Colin Decker */ +@NullUnmarked public class CloserTest extends TestCase { private TestSuppressor suppressor; @@ -47,11 +47,6 @@ protected void setUp() throws Exception { suppressor = new TestSuppressor(); } - @AndroidIncompatible // TODO(cpovirk): Look up Build.VERSION.SDK_INT reflectively. - public void testCreate() { - assertThat(Closer.create().suppressor).isInstanceOf(Closer.SuppressingSuppressor.class); - } - public void testNoExceptionsThrown() throws IOException { Closer closer = new Closer(suppressor); @@ -120,7 +115,7 @@ public void testExceptionThrown_whenCreatingCloseables() throws IOException { assertTrue(c1.isClosed()); assertTrue(c2.isClosed()); - assertNull(c3); + assertThat(c3).isNull(); assertTrue(suppressor.suppressions.isEmpty()); } @@ -280,43 +275,8 @@ public void testErrors() throws IOException { new Suppression(c1, c3Exception, c1Exception)); } - public static void testLoggingSuppressor() throws IOException { - TestLogHandler logHandler = new TestLogHandler(); - - Closeables.logger.addHandler(logHandler); - try { - Closer closer = new Closer(new Closer.LoggingSuppressor()); - - TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(new IOException())); - TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(new RuntimeException())); - try { - throw closer.rethrow(new IOException("thrown"), IOException.class); - } catch (IOException expected) { - } - - assertTrue(logHandler.getStoredLogRecords().isEmpty()); - - closer.close(); - - assertEquals(2, logHandler.getStoredLogRecords().size()); - - LogRecord record = logHandler.getStoredLogRecords().get(0); - assertEquals("Suppressing exception thrown when closing " + c2, record.getMessage()); - - record = logHandler.getStoredLogRecords().get(1); - assertEquals("Suppressing exception thrown when closing " + c1, record.getMessage()); - } finally { - Closeables.logger.removeHandler(logHandler); - } - } - - public static void testSuppressingSuppressorIfPossible() throws IOException { - // can't test the JDK7 suppressor when not running on JDK7 - if (!Closer.SuppressingSuppressor.isAvailable()) { - return; - } - - Closer closer = new Closer(new Closer.SuppressingSuppressor()); + public static void testSuppressingSuppressor() throws IOException { + Closer closer = Closer.create(); IOException thrownException = new IOException(); IOException c1Exception = new IOException(); @@ -330,7 +290,7 @@ public static void testSuppressingSuppressorIfPossible() throws IOException { } catch (Throwable e) { throw closer.rethrow(thrownException, IOException.class); } finally { - assertThat(getSuppressed(thrownException)).isEmpty(); + assertThat(thrownException.getSuppressed()).isEmpty(); closer.close(); } } catch (IOException expected) { @@ -340,7 +300,7 @@ public static void testSuppressingSuppressorIfPossible() throws IOException { assertTrue(c1.isClosed()); assertTrue(c2.isClosed()); - ImmutableSet suppressed = ImmutableSet.copyOf(getSuppressed(thrownException)); + ImmutableSet suppressed = ImmutableSet.copyOf(thrownException.getSuppressed()); assertEquals(2, suppressed.size()); assertEquals(ImmutableSet.of(c1Exception, c2Exception), suppressed); @@ -352,15 +312,6 @@ public void testNullCloseable() throws IOException { closer.close(); } - static Throwable[] getSuppressed(Throwable throwable) { - try { - Method getSuppressed = Throwable.class.getDeclaredMethod("getSuppressed"); - return (Throwable[]) getSuppressed.invoke(throwable); - } catch (Exception e) { - throw new AssertionError(e); // only called if running on JDK7 - } - } - /** * Asserts that an exception was thrown when trying to close each of the given throwables and that * each such exception was suppressed because of the given thrown exception. @@ -369,10 +320,11 @@ private void assertSuppressed(Suppression... expected) { assertEquals(ImmutableList.copyOf(expected), suppressor.suppressions); } + // TODO(cpovirk): Just use addSuppressed+getSuppressed now that we can rely on it. /** Suppressor that records suppressions. */ private static class TestSuppressor implements Closer.Suppressor { - private final List suppressions = Lists.newArrayList(); + private final List suppressions = new ArrayList<>(); @Override public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { @@ -393,7 +345,7 @@ private Suppression(Closeable closeable, Throwable thrown, Throwable suppressed) } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof Suppression) { Suppression other = (Suppression) obj; return closeable.equals(other.closeable) @@ -405,7 +357,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hashCode(closeable, thrown, suppressed); + return Objects.hash(closeable, thrown, suppressed); } @Override @@ -435,11 +387,11 @@ static TestCloseable throwsOnCreate() throws IOException { throw new IOException(); } - private TestCloseable(@NullableDecl Throwable throwOnClose) { + private TestCloseable(@Nullable Throwable throwOnClose) { this.throwOnClose = throwOnClose; } - public boolean isClosed() { + boolean isClosed() { return closed; } @@ -447,7 +399,8 @@ public boolean isClosed() { public void close() throws IOException { closed = true; if (throwOnClose != null) { - Throwables.propagateIfPossible(throwOnClose, IOException.class); + throwIfInstanceOf(throwOnClose, IOException.class); + throwIfUnchecked(throwOnClose); throw new AssertionError(throwOnClose); } } diff --git a/android/guava-tests/test/com/google/common/io/CountingInputStreamTest.java b/android/guava-tests/test/com/google/common/io/CountingInputStreamTest.java index 163027b49f6c..20212ae40955 100644 --- a/android/guava-tests/test/com/google/common/io/CountingInputStreamTest.java +++ b/android/guava-tests/test/com/google/common/io/CountingInputStreamTest.java @@ -17,16 +17,19 @@ package com.google.common.io; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link CountingInputStream}. * * @author Chris Nokleberg */ +@NullUnmarked public class CountingInputStreamTest extends IoTestCase { private CountingInputStream counter; @@ -90,23 +93,15 @@ public void testMark() throws Exception { } public void testMarkNotSet() { - try { - counter.reset(); - fail(); - } catch (IOException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Mark not set"); - } + IOException expected = assertThrows(IOException.class, () -> counter.reset()); + assertThat(expected).hasMessageThat().isEqualTo("Mark not set"); } public void testMarkNotSupported() { counter = new CountingInputStream(new UnmarkableInputStream()); - try { - counter.reset(); - fail(); - } catch (IOException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Mark not supported"); - } + IOException expected = assertThrows(IOException.class, () -> counter.reset()); + assertThat(expected).hasMessageThat().isEqualTo("Mark not supported"); } private static class UnmarkableInputStream extends InputStream { diff --git a/android/guava-tests/test/com/google/common/io/CountingOutputStreamTest.java b/android/guava-tests/test/com/google/common/io/CountingOutputStreamTest.java index 870692b327f4..6a1a1e70180d 100644 --- a/android/guava-tests/test/com/google/common/io/CountingOutputStreamTest.java +++ b/android/guava-tests/test/com/google/common/io/CountingOutputStreamTest.java @@ -16,13 +16,17 @@ package com.google.common.io; +import static org.junit.Assert.assertThrows; + import java.io.ByteArrayOutputStream; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link CountingOutputStream}. * * @author Chris Nokleberg */ +@NullUnmarked public class CountingOutputStreamTest extends IoTestCase { public void testCount() throws Exception { @@ -54,11 +58,7 @@ public void testCount() throws Exception { assertEquals(written, counter.getCount()); // Test that illegal arguments do not affect count - try { - counter.write(data, 0, data.length + 1); - fail("expected exception"); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> counter.write(data, 0, data.length + 1)); assertEquals(written, out.size()); assertEquals(written, counter.getCount()); } diff --git a/android/guava-tests/test/com/google/common/io/FileBackedOutputStreamAndroidIncompatibleTest.java b/android/guava-tests/test/com/google/common/io/FileBackedOutputStreamAndroidIncompatibleTest.java new file mode 100644 index 000000000000..122c115c04cf --- /dev/null +++ b/android/guava-tests/test/com/google/common/io/FileBackedOutputStreamAndroidIncompatibleTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import static com.google.common.io.FileBackedOutputStreamTest.write; + +import com.google.common.testing.GcFinalization; +import java.io.File; +import org.jspecify.annotations.NullUnmarked; + +/** + * Android-incompatible tests for {@link FileBackedOutputStream}. + * + * @author Chris Nokleberg + */ +@AndroidIncompatible // Finalization probably just doesn't happen fast enough? +@NullUnmarked +public class FileBackedOutputStreamAndroidIncompatibleTest extends IoTestCase { + + public void testFinalizeDeletesFile() throws Exception { + byte[] data = newPreFilledByteArray(100); + FileBackedOutputStream out = new FileBackedOutputStream(0, true); + + write(out, data, 0, 100, true); + File file = out.getFile(); + assertEquals(100, file.length()); + assertTrue(file.exists()); + out.close(); + + // Make sure that finalize deletes the file + out = null; + + // times out and throws RuntimeException on failure + GcFinalization.awaitDone( + new GcFinalization.FinalizationPredicate() { + @Override + public boolean isDone() { + return !file.exists(); + } + }); + } +} diff --git a/android/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java b/android/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java index 6841a41a9742..86caf6d6582e 100644 --- a/android/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java +++ b/android/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java @@ -16,18 +16,29 @@ package com.google.common.io; +import static com.google.common.base.StandardSystemProperty.OS_NAME; +import static com.google.common.primitives.Bytes.concat; +import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.min; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import static org.junit.Assert.assertThrows; -import com.google.common.testing.GcFinalization; import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.util.Arrays; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link FileBackedOutputStream}. * + *

    For a tiny bit more testing, see {@link FileBackedOutputStreamAndroidIncompatibleTest}. + * * @author Chris Nokleberg */ +@NullUnmarked public class FileBackedOutputStreamTest extends IoTestCase { @@ -48,7 +59,7 @@ private void testThreshold( byte[] data = newPreFilledByteArray(dataSize); FileBackedOutputStream out = new FileBackedOutputStream(fileThreshold, resetOnFinalize); ByteSource source = out.asByteSource(); - int chunk1 = Math.min(dataSize, fileThreshold); + int chunk1 = min(dataSize, fileThreshold); int chunk2 = dataSize - chunk1; // Write just enough to not trip the threshold @@ -57,7 +68,7 @@ private void testThreshold( assertTrue(ByteSource.wrap(data).slice(0, chunk1).contentEquals(source)); } File file = out.getFile(); - assertNull(file); + assertThat(file).isNull(); // Write data to go over the threshold if (chunk2 > 0) { @@ -65,11 +76,18 @@ private void testThreshold( file = out.getFile(); assertEquals(dataSize, file.length()); assertTrue(file.exists()); + assertThat(file.getName()).contains("FileBackedOutputStream"); + if (!isAndroid() && !isWindows()) { + PosixFileAttributes attributes = + java.nio.file.Files.getFileAttributeView(file.toPath(), PosixFileAttributeView.class) + .readAttributes(); + assertThat(attributes.permissions()).containsExactly(OWNER_READ, OWNER_WRITE); + } } out.close(); // Check that source returns the right data - assertTrue(Arrays.equals(data, source.read())); + assertThat(source.read()).isEqualTo(data); // Make sure that reset deleted the file out.reset(); @@ -79,30 +97,6 @@ private void testThreshold( } - public void testFinalizeDeletesFile() throws Exception { - byte[] data = newPreFilledByteArray(100); - FileBackedOutputStream out = new FileBackedOutputStream(0, true); - - write(out, data, 0, 100, true); - final File file = out.getFile(); - assertEquals(100, file.length()); - assertTrue(file.exists()); - out.close(); - - // Make sure that finalize deletes the file - out = null; - - // times out and throws RuntimeException on failure - GcFinalization.awaitDone( - new GcFinalization.FinalizationPredicate() { - @Override - public boolean isDone() { - return !file.exists(); - } - }); - } - - public void testThreshold_resetOnFinalize() throws Exception { testThreshold(0, 100, true, true); testThreshold(10, 100, true, true); @@ -114,7 +108,7 @@ public void testThreshold_resetOnFinalize() throws Exception { testThreshold(1000, 100, false, true); } - private static void write(OutputStream out, byte[] b, int off, int len, boolean singleByte) + static void write(OutputStream out, byte[] b, int off, int len, boolean singleByte) throws IOException { if (singleByte) { for (int i = off; i < off + len; i++) { @@ -134,17 +128,13 @@ public void testWriteErrorAfterClose() throws Exception { ByteSource source = out.asByteSource(); out.write(data); - assertTrue(Arrays.equals(data, source.read())); + assertThat(source.read()).isEqualTo(data); out.close(); - try { - out.write(42); - fail("expected exception"); - } catch (IOException expected) { - } + assertThrows(IOException.class, () -> out.write(42)); // Verify that write had no effect - assertTrue(Arrays.equals(data, source.read())); + assertThat(source.read()).isEqualTo(data); out.reset(); } @@ -154,14 +144,57 @@ public void testReset() throws Exception { ByteSource source = out.asByteSource(); out.write(data); - assertTrue(Arrays.equals(data, source.read())); + assertThat(source.read()).isEqualTo(data); out.reset(); - assertTrue(Arrays.equals(new byte[0], source.read())); + assertThat(source.read()).isEmpty(); out.write(data); - assertTrue(Arrays.equals(data, source.read())); + assertThat(source.read()).isEqualTo(data); out.close(); } + + private static boolean isAndroid() { + return System.getProperty("java.runtime.name", "").contains("Android"); + } + + private static boolean isWindows() { + return OS_NAME.value().startsWith("Windows"); + } + + /** + * Test that verifies the resource leak fix for Issue #5756. + * + *

    This test covers a scenario where we write a smaller amount of data first, then write a + * large amount that crosses the threshold (transitioning from "not at threshold" to "over the + * threshold"). (We then write some more afterward.) This differs from the existing + * testThreshold() which writes exactly enough bytes to fill the buffer, then immediately writes + * more bytes. + * + *

    Note: Direct testing of the {@link IOException} scenario during write/flush is challenging + * without mocking. This test verifies that normal operation with threshold crossing still works + * correctly with the fix in place. + */ + public void testThresholdCrossing_resourceManagement() throws Exception { + FileBackedOutputStream out = new FileBackedOutputStream(/* fileThreshold= */ 10); + ByteSource source = out.asByteSource(); + + byte[] chunk1 = newPreFilledByteArray(8); // Below threshold + byte[] chunk2 = newPreFilledByteArray(5); // Crosses threshold + byte[] chunk3 = newPreFilledByteArray(20); // More data to file + + out.write(chunk1); + assertThat(out.getFile()).isNull(); + + out.write(chunk2); + assertThat(out.getFile()).isNotNull(); + assertThat(source.read()).isEqualTo(concat(chunk1, chunk2)); + + out.write(chunk3); + assertThat(source.read()).isEqualTo(concat(chunk1, chunk2, chunk3)); + + out.reset(); + } } diff --git a/android/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java b/android/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java new file mode 100644 index 000000000000..90c91aa280d6 --- /dev/null +++ b/android/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static com.google.common.base.StandardSystemProperty.OS_NAME; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; + +import java.io.File; +import java.io.IOException; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +/** + * Unit test for {@link Files#createTempDir}. + * + * @author Chris Nokleberg + */ + +@SuppressWarnings("deprecation") // tests of a deprecated method +@NullUnmarked +public class FilesCreateTempDirTest extends TestCase { + public void testCreateTempDir() throws IOException { + File temp = Files.createTempDir(); + try { + assertThat(temp.exists()).isTrue(); + assertThat(temp.isDirectory()).isTrue(); + assertThat(temp.listFiles()).isEmpty(); + File child = new File(temp, "child"); + assertThat(child.createNewFile()).isTrue(); + assertThat(child.delete()).isTrue(); + + if (!isAndroid() && !isWindows()) { + PosixFileAttributes attributes = + java.nio.file.Files.getFileAttributeView(temp.toPath(), PosixFileAttributeView.class) + .readAttributes(); + assertThat(attributes.permissions()) + .containsExactly(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE); + } + } finally { + assertThat(temp.delete()).isTrue(); + } + } + + public void testBogusSystemPropertiesUsername() { + if (isAndroid()) { + /* + * The test calls directly into the "ACL-based filesystem" code, which isn't available under + * old versions of Android. Since Android doesn't use that code path, anyway, there's no need + * to test it. + */ + return; + } + + /* + * Only under Windows (or hypothetically when running with some other non-POSIX, ACL-based + * filesystem) does our prod code look up the username. Thus, this test doesn't necessarily test + * anything interesting under most environments. Still, we can run it (except for Android, at + * least old versions), so we mostly do. This is useful because we don't actually run our CI on + * Windows under Java 8, at least as of this writing. + * + * Under Windows in particular, we want to test that: + * + * - Under Java 9+, createTempDir() succeeds because it can look up the *real* username, rather + * than relying on the one from the system property. + * + * - Under Java 8, createTempDir() fails because it falls back to the bogus username from the + * system property. + */ + + String save = System.getProperty("user.name"); + System.setProperty("user.name", "-this-is-definitely-not-the-username-we-are-running-as//?"); + try { + TempFileCreator.testMakingUserPermissionsFromScratch(); + assertThat(isJava8()).isFalse(); + } catch (IOException expectedIfJava8) { + assertThat(isJava8()).isTrue(); + } finally { + System.setProperty("user.name", save); + } + } + + private static boolean isAndroid() { + return System.getProperty("java.runtime.name", "").contains("Android"); + } + + private static boolean isWindows() { + return OS_NAME.value().startsWith("Windows"); + } + + private static boolean isJava8() { + return JAVA_SPECIFICATION_VERSION.value().equals("1.8"); + } +} diff --git a/android/guava-tests/test/com/google/common/io/FilesFileTraverserTest.java b/android/guava-tests/test/com/google/common/io/FilesFileTraverserTest.java index c13240685b82..5353bd709a7b 100644 --- a/android/guava-tests/test/com/google/common/io/FilesFileTraverserTest.java +++ b/android/guava-tests/test/com/google/common/io/FilesFileTraverserTest.java @@ -22,11 +22,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.File; import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Files#fileTraverser()}. @@ -34,37 +30,14 @@ * @author Jens Nyman */ -public class FilesFileTraverserTest extends TestCase { +@NullUnmarked +public class FilesFileTraverserTest extends IoTestCase { private File rootDir; @Override public void setUp() throws IOException { - rootDir = Files.createTempDir(); - } - - @Override - public void tearDown() throws IOException { - // delete rootDir and its contents - java.nio.file.Files.walkFileTree( - rootDir.toPath(), - new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) - throws IOException { - java.nio.file.Files.deleteIfExists(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - if (exc != null) { - return FileVisitResult.TERMINATE; - } - java.nio.file.Files.deleteIfExists(dir); - return FileVisitResult.CONTINUE; - } - }); + rootDir = createTempDir(); } public void testFileTraverser_emptyDirectory() throws Exception { diff --git a/android/guava-tests/test/com/google/common/io/FilesSimplifyPathTest.java b/android/guava-tests/test/com/google/common/io/FilesSimplifyPathTest.java index 69eb934de8d5..eb87be285ead 100644 --- a/android/guava-tests/test/com/google/common/io/FilesSimplifyPathTest.java +++ b/android/guava-tests/test/com/google/common/io/FilesSimplifyPathTest.java @@ -16,8 +16,8 @@ package com.google.common.io; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.io.Files.simplifyPath; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; @@ -25,12 +25,14 @@ import java.net.URL; import java.util.Iterator; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Files#simplifyPath}. * * @author Pablo Bellver */ +@NullUnmarked public class FilesSimplifyPathTest extends TestCase { public void testSimplifyEmptyString() { @@ -120,13 +122,13 @@ public void testMadbotsBug() { assertEquals("../ok", simplifyPath("../this/../ok")); } - // https://code.google.com/p/guava-libraries/issues/detail?id=705 + // https://github.com/google/guava/issues/705 public void test705() { assertEquals("../b", simplifyPath("x/../../b")); assertEquals("b", simplifyPath("x/../b")); } - // https://code.google.com/p/guava-libraries/issues/detail?id=716 + // https://github.com/google/guava/issues/716 public void test716() { assertEquals("b", simplifyPath("./b")); assertEquals("b", simplifyPath("./b/.")); @@ -142,7 +144,7 @@ public void testHiddenFiles() { assertEquals(".metadata/b", simplifyPath("./.metadata/b")); } - // https://code.google.com/p/guava-libraries/issues/detail?id=716 + // https://github.com/google/guava/issues/716 public void testMultipleDotFilenames() { assertEquals("..a", simplifyPath("..a")); assertEquals("/..a", simplifyPath("/..a")); @@ -156,18 +158,18 @@ public void testSlashDot() { assertEquals("/", simplifyPath("/.")); } - // http://code.google.com/p/guava-libraries/issues/detail?id=722 + // https://github.com/google/guava/issues/722 public void testInitialSlashDotDot() { assertEquals("/c", simplifyPath("/../c")); } - // http://code.google.com/p/guava-libraries/issues/detail?id=722 + // https://github.com/google/guava/issues/722 public void testInitialSlashDot() { assertEquals("/a", simplifyPath("/./a")); assertEquals("/.a", simplifyPath("/.a/a/..")); } - // http://code.google.com/p/guava-libraries/issues/detail?id=722 + // https://github.com/google/guava/issues/722 public void testConsecutiveParentsAfterPresent() { assertEquals("../..", simplifyPath("./../../")); assertEquals("../..", simplifyPath("./.././../")); diff --git a/android/guava-tests/test/com/google/common/io/FilesTest.java b/android/guava-tests/test/com/google/common/io/FilesTest.java index 8446da1d55d2..9d6b6260afde 100644 --- a/android/guava-tests/test/com/google/common/io/FilesTest.java +++ b/android/guava-tests/test/com/google/common/io/FilesTest.java @@ -17,8 +17,11 @@ package com.google.common.io; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_16LE; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.hash.Hashing; import com.google.common.primitives.Bytes; @@ -34,21 +37,29 @@ import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel.MapMode; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Random; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Files}. * - *

    Note: {@link Files#fileTraverser()} is tested in {@link FilesFileTraverserTest}. + *

    Some methods are tested in separate files: + * + *

      + *
    • {@link Files#fileTraverser()} is tested in {@link FilesFileTraverserTest}. + *
    • {@link Files#createTempDir()} is tested in {@link FilesCreateTempDirTest}. + *
    * * @author Chris Nokleberg */ +@SuppressWarnings("InlineMeInliner") // many tests of deprecated methods +@NullUnmarked public class FilesTest extends IoTestCase { + @AndroidIncompatible // suites, ByteSourceTester (b/230620681) public static TestSuite suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -78,15 +89,15 @@ public static TestSuite suite() { public void testRoundTripSources() throws Exception { File asciiFile = getTestFile("ascii.txt"); ByteSource byteSource = Files.asByteSource(asciiFile); - assertSame(byteSource, byteSource.asCharSource(Charsets.UTF_8).asByteSource(Charsets.UTF_8)); + assertSame(byteSource, byteSource.asCharSource(UTF_8).asByteSource(UTF_8)); } public void testToByteArray() throws IOException { File asciiFile = getTestFile("ascii.txt"); File i18nFile = getTestFile("i18n.txt"); - assertTrue(Arrays.equals(ASCII.getBytes(Charsets.US_ASCII), Files.toByteArray(asciiFile))); - assertTrue(Arrays.equals(I18N.getBytes(Charsets.UTF_8), Files.toByteArray(i18nFile))); - assertTrue(Arrays.equals(I18N.getBytes(Charsets.UTF_8), Files.asByteSource(i18nFile).read())); + assertThat(Files.toByteArray(asciiFile)).isEqualTo(ASCII.getBytes(US_ASCII)); + assertThat(Files.toByteArray(i18nFile)).isEqualTo(I18N.getBytes(UTF_8)); + assertThat(Files.asByteSource(i18nFile).read()).isEqualTo(I18N.getBytes(UTF_8)); } /** A {@link File} that provides a specialized value for {@link File#length()}. */ @@ -94,7 +105,7 @@ private static class BadLengthFile extends File { private final long badLength; - public BadLengthFile(File delegate, long badLength) { + BadLengthFile(File delegate, long badLength) { super(delegate.getPath()); this.badLength = badLength; } @@ -110,38 +121,34 @@ public long length() { public void testToString() throws IOException { File asciiFile = getTestFile("ascii.txt"); File i18nFile = getTestFile("i18n.txt"); - assertEquals(ASCII, Files.toString(asciiFile, Charsets.US_ASCII)); - assertEquals(I18N, Files.toString(i18nFile, Charsets.UTF_8)); - assertThat(Files.toString(i18nFile, Charsets.US_ASCII)).isNotEqualTo(I18N); + assertEquals(ASCII, Files.toString(asciiFile, US_ASCII)); + assertEquals(I18N, Files.toString(i18nFile, UTF_8)); + assertThat(Files.toString(i18nFile, US_ASCII)).isNotEqualTo(I18N); } public void testWriteString() throws IOException { File temp = createTempFile(); - Files.write(I18N, temp, Charsets.UTF_16LE); - assertEquals(I18N, Files.toString(temp, Charsets.UTF_16LE)); + Files.write(I18N, temp, UTF_16LE); + assertEquals(I18N, Files.toString(temp, UTF_16LE)); } public void testWriteBytes() throws IOException { File temp = createTempFile(); byte[] data = newPreFilledByteArray(2000); Files.write(data, temp); - assertTrue(Arrays.equals(data, Files.toByteArray(temp))); + assertThat(Files.toByteArray(temp)).isEqualTo(data); - try { - Files.write(null, temp); - fail("expected exception"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Files.write(null, temp)); } public void testAppendString() throws IOException { File temp = createTempFile(); - Files.append(I18N, temp, Charsets.UTF_16LE); - assertEquals(I18N, Files.toString(temp, Charsets.UTF_16LE)); - Files.append(I18N, temp, Charsets.UTF_16LE); - assertEquals(I18N + I18N, Files.toString(temp, Charsets.UTF_16LE)); - Files.append(I18N, temp, Charsets.UTF_16LE); - assertEquals(I18N + I18N + I18N, Files.toString(temp, Charsets.UTF_16LE)); + Files.append(I18N, temp, UTF_16LE); + assertEquals(I18N, Files.toString(temp, UTF_16LE)); + Files.append(I18N, temp, UTF_16LE); + assertEquals(I18N + I18N, Files.toString(temp, UTF_16LE)); + Files.append(I18N, temp, UTF_16LE); + assertEquals(I18N + I18N + I18N, Files.toString(temp, UTF_16LE)); } public void testCopyToOutputStream() throws IOException { @@ -154,7 +161,7 @@ public void testCopyToOutputStream() throws IOException { public void testCopyToAppendable() throws IOException { File i18nFile = getTestFile("i18n.txt"); StringBuilder sb = new StringBuilder(); - Files.copy(i18nFile, Charsets.UTF_8, sb); + Files.copy(i18nFile, UTF_8, sb); assertEquals(I18N, sb.toString()); } @@ -162,40 +169,32 @@ public void testCopyFile() throws IOException { File i18nFile = getTestFile("i18n.txt"); File temp = createTempFile(); Files.copy(i18nFile, temp); - assertEquals(I18N, Files.toString(temp, Charsets.UTF_8)); + assertEquals(I18N, Files.toString(temp, UTF_8)); } public void testCopyEqualFiles() throws IOException { File temp1 = createTempFile(); File temp2 = file(temp1.getPath()); assertEquals(temp1, temp2); - Files.write(ASCII, temp1, Charsets.UTF_8); - try { - Files.copy(temp1, temp2); - fail("Expected an IAE to be thrown but wasn't"); - } catch (IllegalArgumentException expected) { - } - assertEquals(ASCII, Files.toString(temp1, Charsets.UTF_8)); + Files.write(ASCII, temp1, UTF_8); + assertThrows(IllegalArgumentException.class, () -> Files.copy(temp1, temp2)); + assertEquals(ASCII, Files.toString(temp1, UTF_8)); } public void testCopySameFile() throws IOException { File temp = createTempFile(); - Files.write(ASCII, temp, Charsets.UTF_8); - try { - Files.copy(temp, temp); - fail("Expected an IAE to be thrown but wasn't"); - } catch (IllegalArgumentException expected) { - } - assertEquals(ASCII, Files.toString(temp, Charsets.UTF_8)); + Files.write(ASCII, temp, UTF_8); + assertThrows(IllegalArgumentException.class, () -> Files.copy(temp, temp)); + assertEquals(ASCII, Files.toString(temp, UTF_8)); } public void testCopyIdenticalFiles() throws IOException { File temp1 = createTempFile(); - Files.write(ASCII, temp1, Charsets.UTF_8); + Files.write(ASCII, temp1, UTF_8); File temp2 = createTempFile(); - Files.write(ASCII, temp2, Charsets.UTF_8); + Files.write(ASCII, temp2, UTF_8); Files.copy(temp1, temp2); - assertEquals(ASCII, Files.toString(temp2, Charsets.UTF_8)); + assertEquals(ASCII, Files.toString(temp2, UTF_8)); } public void testEqual() throws IOException { @@ -226,19 +225,11 @@ public void testEqual() throws IOException { public void testNewReader() throws IOException { File asciiFile = getTestFile("ascii.txt"); - try { - Files.newReader(asciiFile, null); - fail("expected exception"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Files.newReader(asciiFile, null)); - try { - Files.newReader(null, Charsets.UTF_8); - fail("expected exception"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Files.newReader(null, UTF_8)); - BufferedReader r = Files.newReader(asciiFile, Charsets.US_ASCII); + BufferedReader r = Files.newReader(asciiFile, US_ASCII); try { assertEquals(ASCII, r.readLine()); } finally { @@ -248,19 +239,11 @@ public void testNewReader() throws IOException { public void testNewWriter() throws IOException { File temp = createTempFile(); - try { - Files.newWriter(temp, null); - fail("expected exception"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Files.newWriter(temp, null)); - try { - Files.newWriter(null, Charsets.UTF_8); - fail("expected exception"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Files.newWriter(null, UTF_8)); - BufferedWriter w = Files.newWriter(temp, Charsets.UTF_8); + BufferedWriter w = Files.newWriter(temp, UTF_8); try { w.write(I18N); } finally { @@ -281,19 +264,18 @@ public void testTouch() throws IOException { Files.touch(temp); assertTrue(temp.exists()); - try { - Files.touch( - new File(temp.getPath()) { - @Override - public boolean setLastModified(long t) { - return false; - } + assertThrows( + IOException.class, + () -> + Files.touch( + new File(temp.getPath()) { + @Override + public boolean setLastModified(long t) { + return false; + } - private static final long serialVersionUID = 0; - }); - fail("expected exception"); - } catch (IOException expected) { - } + private static final long serialVersionUID = 0; + })); } public void testTouchTime() throws IOException { @@ -307,15 +289,15 @@ public void testTouchTime() throws IOException { public void testCreateParentDirs_root() throws IOException { File file = root(); - assertNull(file.getParentFile()); - assertNull(file.getCanonicalFile().getParentFile()); + assertThat(file.getParentFile()).isNull(); + assertThat(file.getCanonicalFile().getParentFile()).isNull(); Files.createParentDirs(file); } public void testCreateParentDirs_relativePath() throws IOException { File file = file("nonexistent.file"); - assertNull(file.getParentFile()); - assertNotNull(file.getCanonicalFile().getParentFile()); + assertThat(file.getParentFile()).isNull(); + assertThat(file.getCanonicalFile().getParentFile()).isNotNull(); Files.createParentDirs(file); } @@ -350,19 +332,7 @@ public void testCreateParentDirs_nonDirectoryParentExists() throws IOException { File parent = getTestFile("ascii.txt"); assertTrue(parent.isFile()); File file = file(parent, "foo"); - try { - Files.createParentDirs(file); - fail(); - } catch (IOException expected) { - } - } - - public void testCreateTempDir() { - File temp = Files.createTempDir(); - assertTrue(temp.exists()); - assertTrue(temp.isDirectory()); - assertThat(temp.listFiles()).isEmpty(); - assertTrue(temp.delete()); + assertThrows(IOException.class, () -> Files.createParentDirs(file)); } public void testMove() throws IOException { @@ -393,12 +363,8 @@ public void testMoveFailures() throws IOException { moveHelper( false, new UnmovableFile(temp1, false, false), new UnmovableFile(temp2, true, false)); - try { - File asciiFile = getTestFile("ascii.txt"); - moveHelper(false, asciiFile, asciiFile); - fail("expected exception"); - } catch (IllegalArgumentException expected) { - } + File asciiFile = getTestFile("ascii.txt"); + assertThrows(IllegalArgumentException.class, () -> moveHelper(false, asciiFile, asciiFile)); } private void moveHelper(boolean success, File from, File to) throws IOException { @@ -422,7 +388,7 @@ private static class UnmovableFile extends File { private final boolean canRename; private final boolean canDelete; - public UnmovableFile(File file, boolean canRename, boolean canDelete) { + UnmovableFile(File file, boolean canRename, boolean canDelete) { super(file.getPath()); this.canRename = canRename; this.canDelete = canDelete; @@ -443,19 +409,18 @@ public boolean delete() { public void testLineReading() throws IOException { File temp = createTempFile(); - assertNull(Files.readFirstLine(temp, Charsets.UTF_8)); - assertTrue(Files.readLines(temp, Charsets.UTF_8).isEmpty()); + assertThat(Files.readFirstLine(temp, UTF_8)).isNull(); + assertTrue(Files.readLines(temp, UTF_8).isEmpty()); - PrintWriter w = new PrintWriter(Files.newWriter(temp, Charsets.UTF_8)); + PrintWriter w = new PrintWriter(Files.newWriter(temp, UTF_8)); w.println("hello"); w.println(""); w.println(" world "); w.println(""); w.close(); - assertEquals("hello", Files.readFirstLine(temp, Charsets.UTF_8)); - assertEquals( - ImmutableList.of("hello", "", " world ", ""), Files.readLines(temp, Charsets.UTF_8)); + assertEquals("hello", Files.readFirstLine(temp, UTF_8)); + assertEquals(ImmutableList.of("hello", "", " world ", ""), Files.readLines(temp, UTF_8)); assertTrue(temp.delete()); } @@ -464,7 +429,7 @@ public void testReadLines_withLineProcessor() throws IOException { File temp = createTempFile(); LineProcessor> collect = new LineProcessor>() { - List collector = new ArrayList<>(); + final List collector = new ArrayList<>(); @Override public boolean processLine(String line) { @@ -477,20 +442,20 @@ public List getResult() { return collector; } }; - assertThat(Files.readLines(temp, Charsets.UTF_8, collect)).isEmpty(); + assertThat(Files.readLines(temp, UTF_8, collect)).isEmpty(); - PrintWriter w = new PrintWriter(Files.newWriter(temp, Charsets.UTF_8)); + PrintWriter w = new PrintWriter(Files.newWriter(temp, UTF_8)); w.println("hello"); w.println(""); w.println(" world "); w.println(""); w.close(); - Files.readLines(temp, Charsets.UTF_8, collect); + Files.readLines(temp, UTF_8, collect); assertThat(collect.getResult()).containsExactly("hello", "", " world ", "").inOrder(); LineProcessor> collectNonEmptyLines = new LineProcessor>() { - List collector = new ArrayList<>(); + final List collector = new ArrayList<>(); @Override public boolean processLine(String line) { @@ -505,7 +470,7 @@ public List getResult() { return collector; } }; - Files.readLines(temp, Charsets.UTF_8, collectNonEmptyLines); + Files.readLines(temp, UTF_8, collectNonEmptyLines); assertThat(collectNonEmptyLines.getResult()).containsExactly("hello", " world ").inOrder(); assertTrue(temp.delete()); @@ -549,11 +514,7 @@ public void testMap_noSuchFile() throws IOException { assertTrue(deleted); // Test - try { - Files.map(file); - fail("Should have thrown FileNotFoundException."); - } catch (FileNotFoundException expected) { - } + assertThrows(FileNotFoundException.class, () -> Files.map(file)); } public void testMap_readWrite() throws IOException { @@ -575,7 +536,7 @@ public void testMap_readWrite() throws IOException { // Verify byte[] actualBytes = Files.toByteArray(file); - assertTrue(Arrays.equals(expectedBytes, actualBytes)); + assertThat(actualBytes).isEqualTo(expectedBytes); } public void testMap_readWrite_creates() throws IOException { @@ -598,18 +559,16 @@ public void testMap_readWrite_creates() throws IOException { assertTrue(file.isFile()); assertEquals(size, file.length()); byte[] actualBytes = Files.toByteArray(file); - assertTrue(Arrays.equals(expectedBytes, actualBytes)); + assertThat(actualBytes).isEqualTo(expectedBytes); } public void testMap_readWrite_max_value_plus_1() throws IOException { // Setup File file = createTempFile(); // Test - try { - Files.map(file, MapMode.READ_WRITE, (long) Integer.MAX_VALUE + 1); - fail("Should throw when size exceeds Integer.MAX_VALUE"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Files.map(file, MapMode.READ_WRITE, (long) Integer.MAX_VALUE + 1)); } public void testGetFileExtension() { @@ -695,7 +654,7 @@ public byte[] getResult() { File asciiFile = getTestFile("ascii.txt"); byte[] result = Files.readBytes(asciiFile, processor); - assertEquals(1, result.length); + assertThat(result).hasLength(1); } public void testPredicates() throws IOException { diff --git a/android/guava-tests/test/com/google/common/io/FlushablesTest.java b/android/guava-tests/test/com/google/common/io/FlushablesTest.java index b45150a43d44..5ee9c2f70504 100644 --- a/android/guava-tests/test/com/google/common/io/FlushablesTest.java +++ b/android/guava-tests/test/com/google/common/io/FlushablesTest.java @@ -23,6 +23,7 @@ import java.io.Flushable; import java.io.IOException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Flushables}. @@ -32,6 +33,7 @@ * * @author Michael Lancaster */ +@NullUnmarked public class FlushablesTest extends TestCase { private Flushable mockFlushable; @@ -71,9 +73,7 @@ public void testFlushQuietly_flushableWithEatenException() throws IOException { private void setupFlushable(boolean shouldThrowOnFlush) throws IOException { mockFlushable = mock(Flushable.class); if (shouldThrowOnFlush) { - doThrow( - new IOException( - "This should only appear in the " + "logs. It should not be rethrown.")) + doThrow(new IOException("This should only appear in the logs. It should not be rethrown.")) .when(mockFlushable) .flush(); } diff --git a/android/guava-tests/test/com/google/common/io/IoTestCase.java b/android/guava-tests/test/com/google/common/io/IoTestCase.java index fa8961905931..a8c462734d42 100644 --- a/android/guava-tests/test/com/google/common/io/IoTestCase.java +++ b/android/guava-tests/test/com/google/common/io/IoTestCase.java @@ -16,7 +16,6 @@ package com.google.common.io; -import com.google.common.collect.Sets; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.File; import java.io.FileOutputStream; @@ -24,10 +23,13 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URL; +import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Base test case class for I/O tests. @@ -35,6 +37,7 @@ * @author Chris Nokleberg * @author Colin Decker */ +@NullUnmarked public abstract class IoTestCase extends TestCase { private static final Logger logger = Logger.getLogger(IoTestCase.class.getName()); @@ -50,7 +53,7 @@ public abstract class IoTestCase extends TestCase { private File testDir; private File tempDir; - private final Set filesToDelete = Sets.newHashSet(); + private final Set filesToDelete = new HashSet<>(); @Override protected void tearDown() { @@ -92,7 +95,7 @@ private File getTestDir() throws IOException { } /** Returns the file with the given name under the testdata directory. */ - protected final File getTestFile(String name) throws IOException { + protected final @Nullable File getTestFile(String name) throws IOException { File file = new File(getTestDir(), name); if (!file.exists()) { URL resourceUrl = IoTestCase.class.getResource("testdata/" + name); diff --git a/android/guava-tests/test/com/google/common/io/LineBufferTest.java b/android/guava-tests/test/com/google/common/io/LineBufferTest.java index efe476669916..71ab3e775148 100644 --- a/android/guava-tests/test/com/google/common/io/LineBufferTest.java +++ b/android/guava-tests/test/com/google/common/io/LineBufferTest.java @@ -16,7 +16,11 @@ package com.google.common.io; +import static java.lang.Math.max; +import static java.lang.Math.min; + import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.io.BufferedReader; import java.io.FilterReader; @@ -24,8 +28,10 @@ import java.io.Reader; import java.io.StringReader; import java.nio.CharBuffer; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link LineBuffer} and {@link LineReader}. @@ -33,6 +39,7 @@ * @author Chris Nokleberg */ @AndroidIncompatible // occasionally very slow +@NullUnmarked public class LineBufferTest extends IoTestCase { public void testProcess() throws IOException { @@ -53,7 +60,8 @@ public void testProcess() throws IOException { bufferHelper("mixed\nline\rendings\r\n", "mixed\n", "line\r", "endings\r\n"); } - private static final int[] CHUNK_SIZES = {1, 2, 3, Integer.MAX_VALUE}; + private static final ImmutableSet CHUNK_SIZES = + ImmutableSet.of(1, 2, 3, Integer.MAX_VALUE); private static void bufferHelper(String input, String... expect) throws IOException { @@ -69,7 +77,7 @@ public String apply(String value) { }); for (int chunk : CHUNK_SIZES) { - chunk = Math.max(1, Math.min(chunk, input.length())); + chunk = max(1, min(chunk, input.length())); assertEquals(expectProcess, bufferHelper(input, chunk)); assertEquals(expectRead, readUsingJava(input, chunk)); assertEquals(expectRead, readUsingReader(input, chunk, true)); @@ -78,7 +86,7 @@ public String apply(String value) { } private static List bufferHelper(String input, int chunk) throws IOException { - final List lines = Lists.newArrayList(); + List lines = new ArrayList<>(); LineBuffer lineBuf = new LineBuffer() { @Override @@ -89,7 +97,7 @@ protected void handleLine(String line, String end) { char[] chars = input.toCharArray(); int off = 0; while (off < chars.length) { - int len = Math.min(chars.length, off + chunk) - off; + int len = min(chars.length, off + chunk) - off; lineBuf.add(chars, off, len); off += len; } @@ -99,7 +107,7 @@ protected void handleLine(String line, String end) { private static List readUsingJava(String input, int chunk) throws IOException { BufferedReader r = new BufferedReader(getChunkedReader(input, chunk)); - List lines = Lists.newArrayList(); + List lines = new ArrayList<>(); String line; while ((line = r.readLine()) != null) { lines.add(line); @@ -113,7 +121,7 @@ private static List readUsingReader(String input, int chunk, boolean asR Readable readable = asReader ? getChunkedReader(input, chunk) : getChunkedReadable(input, chunk); LineReader r = new LineReader(readable); - List lines = Lists.newArrayList(); + List lines = new ArrayList<>(); String line; while ((line = r.readLine()) != null) { lines.add(line); @@ -123,7 +131,7 @@ private static List readUsingReader(String input, int chunk, boolean asR // Returns a Readable that is *not* a Reader. private static Readable getChunkedReadable(String input, int chunk) { - final Reader reader = getChunkedReader(input, chunk); + Reader reader = getChunkedReader(input, chunk); return new Readable() { @Override public int read(CharBuffer cbuf) throws IOException { @@ -132,11 +140,11 @@ public int read(CharBuffer cbuf) throws IOException { }; } - private static Reader getChunkedReader(String input, final int chunk) { + private static Reader getChunkedReader(String input, int chunk) { return new FilterReader(new StringReader(input)) { @Override public int read(char[] cbuf, int off, int len) throws IOException { - return super.read(cbuf, off, Math.min(chunk, len)); + return super.read(cbuf, off, min(chunk, len)); } }; } diff --git a/android/guava-tests/test/com/google/common/io/LittleEndianDataInputStreamTest.java b/android/guava-tests/test/com/google/common/io/LittleEndianDataInputStreamTest.java index f8e40df25a98..e5280bc90aee 100644 --- a/android/guava-tests/test/com/google/common/io/LittleEndianDataInputStreamTest.java +++ b/android/guava-tests/test/com/google/common/io/LittleEndianDataInputStreamTest.java @@ -17,6 +17,7 @@ package com.google.common.io; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.primitives.Bytes; import java.io.ByteArrayInputStream; @@ -26,12 +27,14 @@ import java.io.EOFException; import java.io.IOException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test class for {@link LittleEndianDataInputStream}. * * @author Chris Nokleberg */ +@NullUnmarked public class LittleEndianDataInputStreamTest extends TestCase { private byte[] data; @@ -75,31 +78,21 @@ public void testReadFully() throws IOException { public void testReadUnsignedByte_eof() throws IOException { DataInput in = new LittleEndianDataInputStream(new ByteArrayInputStream(new byte[0])); - try { - in.readUnsignedByte(); - fail(); - } catch (EOFException expected) { - } + assertThrows(EOFException.class, () -> in.readUnsignedByte()); } public void testReadUnsignedShort_eof() throws IOException { byte[] buf = {23}; DataInput in = new LittleEndianDataInputStream(new ByteArrayInputStream(buf)); - try { - in.readUnsignedShort(); - fail(); - } catch (EOFException expected) { - } + assertThrows(EOFException.class, () -> in.readUnsignedShort()); } + @SuppressWarnings("DoNotCall") public void testReadLine() throws IOException { DataInput in = new LittleEndianDataInputStream(new ByteArrayInputStream(data)); - try { - in.readLine(); - fail(); - } catch (UnsupportedOperationException expected) { - assertThat(expected).hasMessageThat().isEqualTo("readLine is not supported"); - } + UnsupportedOperationException expected = + assertThrows(UnsupportedOperationException.class, () -> in.readLine()); + assertThat(expected).hasMessageThat().isEqualTo("readLine is not supported"); } public void testReadLittleEndian() throws IOException { diff --git a/android/guava-tests/test/com/google/common/io/LittleEndianDataOutputStreamTest.java b/android/guava-tests/test/com/google/common/io/LittleEndianDataOutputStreamTest.java index 2568aae1de7d..bf0faf227b47 100644 --- a/android/guava-tests/test/com/google/common/io/LittleEndianDataOutputStreamTest.java +++ b/android/guava-tests/test/com/google/common/io/LittleEndianDataOutputStreamTest.java @@ -16,7 +16,8 @@ package com.google.common.io; -import com.google.common.base.Charsets; +import static java.nio.charset.StandardCharsets.ISO_8859_1; + import com.google.common.primitives.Bytes; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -24,16 +25,18 @@ import java.io.DataInputStream; import java.io.IOException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test class for {@link LittleEndianDataOutputStream}. * * @author Keith Bottner */ +@NullUnmarked public class LittleEndianDataOutputStreamTest extends TestCase { - private ByteArrayOutputStream baos = new ByteArrayOutputStream(); - private LittleEndianDataOutputStream out = new LittleEndianDataOutputStream(baos); + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + private final LittleEndianDataOutputStream out = new LittleEndianDataOutputStream(baos); public void testWriteLittleEndian() throws IOException { @@ -92,7 +95,7 @@ public void testWriteBytes() throws IOException { /* Read in various values NORMALLY */ byte[] b = new byte[6]; in.readFully(b); - assertEquals("r\u00C9sum\u00C9".getBytes(Charsets.ISO_8859_1), b); + assertEquals("r\u00C9sum\u00C9".getBytes(ISO_8859_1), b); } @SuppressWarnings("deprecation") // testing a deprecated method diff --git a/android/guava-tests/test/com/google/common/io/MoreFilesTest.java b/android/guava-tests/test/com/google/common/io/MoreFilesTest.java new file mode 100644 index 000000000000..510836b43d34 --- /dev/null +++ b/android/guava-tests/test/com/google/common/io/MoreFilesTest.java @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import static com.google.common.base.StandardSystemProperty.OS_NAME; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static com.google.common.jimfs.Feature.SECURE_DIRECTORY_STREAM; +import static com.google.common.jimfs.Feature.SYMBOLIC_LINKS; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.LinkOption.NOFOLLOW_LINKS; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ObjectArrays; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Feature; +import com.google.common.jimfs.Jimfs; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.EnumSet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +/** + * Tests for {@link MoreFiles}. + * + *

    Note: {@link MoreFiles#fileTraverser()} is tested in {@link MoreFilesFileTraverserTest}. + * + * @author Colin Decker + */ + +@NullUnmarked +public class MoreFilesTest extends TestCase { + + /* + * Note: We don't include suite() in the backport. I've lost track of whether the Android test + * runner would run it even if we did, but part of the problem is b/230620681. + */ + + private static final FileSystem FS = FileSystems.getDefault(); + + private static Path root() { + return FS.getRootDirectories().iterator().next(); + } + + private Path tempDir; + + @Override + protected void setUp() throws Exception { + tempDir = Files.createTempDirectory("MoreFilesTest"); + } + + @Override + protected void tearDown() throws Exception { + if (tempDir != null) { + // delete tempDir and its contents + Files.walkFileTree( + tempDir, + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.deleteIfExists(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + if (exc != null) { + return FileVisitResult.TERMINATE; + } + Files.deleteIfExists(dir); + return FileVisitResult.CONTINUE; + } + }); + } + } + + private Path createTempFile() throws IOException { + return Files.createTempFile(tempDir, "test", ".test"); + } + + public void testByteSource_size_ofDirectory() throws IOException { + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path dir = fs.getPath("dir"); + Files.createDirectory(dir); + + ByteSource source = MoreFiles.asByteSource(dir); + + assertThat(source.sizeIfKnown()).isAbsent(); + + assertThrows(IOException.class, () -> source.size()); + } + } + + public void testByteSource_size_ofSymlinkToDirectory() throws IOException { + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path dir = fs.getPath("dir"); + Files.createDirectory(dir); + Path link = fs.getPath("link"); + Files.createSymbolicLink(link, dir); + + ByteSource source = MoreFiles.asByteSource(link); + + assertThat(source.sizeIfKnown()).isAbsent(); + + assertThrows(IOException.class, () -> source.size()); + } + } + + public void testByteSource_size_ofSymlinkToRegularFile() throws IOException { + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path file = fs.getPath("file"); + Files.write(file, new byte[10]); + Path link = fs.getPath("link"); + Files.createSymbolicLink(link, file); + + ByteSource source = MoreFiles.asByteSource(link); + + assertEquals(10L, (long) source.sizeIfKnown().get()); + assertEquals(10L, source.size()); + } + } + + public void testByteSource_size_ofSymlinkToRegularFile_nofollowLinks() throws IOException { + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path file = fs.getPath("file"); + Files.write(file, new byte[10]); + Path link = fs.getPath("link"); + Files.createSymbolicLink(link, file); + + ByteSource source = MoreFiles.asByteSource(link, NOFOLLOW_LINKS); + + assertThat(source.sizeIfKnown()).isAbsent(); + + assertThrows(IOException.class, () -> source.size()); + } + } + + public void testEqual() throws IOException { + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path fooPath = fs.getPath("foo"); + Path barPath = fs.getPath("bar"); + MoreFiles.asCharSink(fooPath, UTF_8).write("foo"); + MoreFiles.asCharSink(barPath, UTF_8).write("barbar"); + + assertThat(MoreFiles.equal(fooPath, barPath)).isFalse(); + assertThat(MoreFiles.equal(fooPath, fooPath)).isTrue(); + assertThat(MoreFiles.asByteSource(fooPath).contentEquals(MoreFiles.asByteSource(fooPath))) + .isTrue(); + + Path fooCopy = Files.copy(fooPath, fs.getPath("fooCopy")); + assertThat(Files.isSameFile(fooPath, fooCopy)).isFalse(); + assertThat(MoreFiles.equal(fooPath, fooCopy)).isTrue(); + + MoreFiles.asCharSink(fooCopy, UTF_8).write("boo"); + assertThat(MoreFiles.asByteSource(fooPath).size()) + .isEqualTo(MoreFiles.asByteSource(fooCopy).size()); + assertThat(MoreFiles.equal(fooPath, fooCopy)).isFalse(); + + // should also assert that a Path that erroneously reports a size 0 can still be compared, + // not sure how to do that with the Path API + } + } + + public void testEqual_links() throws IOException { + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path fooPath = fs.getPath("foo"); + MoreFiles.asCharSink(fooPath, UTF_8).write("foo"); + + Path fooSymlink = fs.getPath("symlink"); + Files.createSymbolicLink(fooSymlink, fooPath); + + Path fooHardlink = fs.getPath("hardlink"); + Files.createLink(fooHardlink, fooPath); + + assertThat(MoreFiles.equal(fooPath, fooSymlink)).isTrue(); + assertThat(MoreFiles.equal(fooPath, fooHardlink)).isTrue(); + assertThat(MoreFiles.equal(fooSymlink, fooHardlink)).isTrue(); + } + } + + public void testTouch() throws IOException { + Path temp = createTempFile(); + assertTrue(Files.exists(temp)); + Files.delete(temp); + assertFalse(Files.exists(temp)); + + MoreFiles.touch(temp); + assertTrue(Files.exists(temp)); + MoreFiles.touch(temp); + assertTrue(Files.exists(temp)); + } + + public void testTouchTime() throws IOException { + Path temp = createTempFile(); + assertTrue(Files.exists(temp)); + Files.setLastModifiedTime(temp, FileTime.fromMillis(0)); + assertEquals(0, Files.getLastModifiedTime(temp).toMillis()); + MoreFiles.touch(temp); + assertThat(Files.getLastModifiedTime(temp).toMillis()).isNotEqualTo(0); + } + + public void testCreateParentDirectories_root() throws IOException { + // We use a fake filesystem to sidestep flaky problems with Windows (b/136041958). + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path root = fs.getRootDirectories().iterator().next(); + assertThat(root.getParent()).isNull(); + assertThat(root.toRealPath().getParent()).isNull(); + MoreFiles.createParentDirectories(root); // test that there's no exception + } + } + + public void testCreateParentDirectories_relativePath() throws IOException { + Path path = FS.getPath("nonexistent.file"); + assertThat(path.getParent()).isNull(); + assertThat(path.toAbsolutePath().getParent()).isNotNull(); + MoreFiles.createParentDirectories(path); // test that there's no exception + } + + public void testCreateParentDirectories_noParentsNeeded() throws IOException { + Path path = tempDir.resolve("nonexistent.file"); + assertTrue(Files.exists(path.getParent())); + MoreFiles.createParentDirectories(path); // test that there's no exception + } + + public void testCreateParentDirectories_oneParentNeeded() throws IOException { + Path path = tempDir.resolve("parent/nonexistent.file"); + Path parent = path.getParent(); + assertFalse(Files.exists(parent)); + MoreFiles.createParentDirectories(path); + assertTrue(Files.exists(parent)); + } + + public void testCreateParentDirectories_multipleParentsNeeded() throws IOException { + Path path = tempDir.resolve("grandparent/parent/nonexistent.file"); + Path parent = path.getParent(); + Path grandparent = parent.getParent(); + assertFalse(Files.exists(grandparent)); + assertFalse(Files.exists(parent)); + + MoreFiles.createParentDirectories(path); + assertTrue(Files.exists(parent)); + assertTrue(Files.exists(grandparent)); + } + + public void testCreateParentDirectories_noPermission() { + if (isWindows()) { + return; // TODO: b/136041958 - Create/find a directory that we don't have permissions on? + } + Path file = root().resolve("parent/nonexistent.file"); + Path parent = file.getParent(); + assertFalse(Files.exists(parent)); + assertThrows(IOException.class, () -> MoreFiles.createParentDirectories(file)); + } + + public void testCreateParentDirectories_nonDirectoryParentExists() throws IOException { + Path parent = createTempFile(); + assertTrue(Files.isRegularFile(parent)); + Path file = parent.resolve("foo"); + assertThrows(IOException.class, () -> MoreFiles.createParentDirectories(file)); + } + + public void testCreateParentDirectories_symlinkParentExists() throws IOException { + /* + * We use a fake filesystem to sidestep: + * + * - flaky problems with Windows (b/136041958) + * + * - the lack of support for symlinks in the default filesystem under Android's desugared + * java.nio.file + */ + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path symlink = fs.getPath("linkToDir"); + Files.createSymbolicLink(symlink, fs.getRootDirectories().iterator().next()); + Path file = symlink.resolve("foo"); + MoreFiles.createParentDirectories(file); + } + } + + public void testGetFileExtension() { + assertEquals("txt", MoreFiles.getFileExtension(FS.getPath(".txt"))); + assertEquals("txt", MoreFiles.getFileExtension(FS.getPath("blah.txt"))); + assertEquals("txt", MoreFiles.getFileExtension(FS.getPath("blah..txt"))); + assertEquals("txt", MoreFiles.getFileExtension(FS.getPath(".blah.txt"))); + assertEquals("txt", MoreFiles.getFileExtension(root().resolve("tmp/blah.txt"))); + assertEquals("gz", MoreFiles.getFileExtension(FS.getPath("blah.tar.gz"))); + assertEquals("", MoreFiles.getFileExtension(root())); + assertEquals("", MoreFiles.getFileExtension(FS.getPath("."))); + assertEquals("", MoreFiles.getFileExtension(FS.getPath(".."))); + assertEquals("", MoreFiles.getFileExtension(FS.getPath("..."))); + assertEquals("", MoreFiles.getFileExtension(FS.getPath("blah"))); + assertEquals("", MoreFiles.getFileExtension(FS.getPath("blah."))); + assertEquals("", MoreFiles.getFileExtension(FS.getPath(".blah."))); + assertEquals("", MoreFiles.getFileExtension(root().resolve("foo.bar/blah"))); + assertEquals("", MoreFiles.getFileExtension(root().resolve("foo/.bar/blah"))); + } + + public void testGetNameWithoutExtension() { + assertEquals("", MoreFiles.getNameWithoutExtension(FS.getPath(".txt"))); + assertEquals("blah", MoreFiles.getNameWithoutExtension(FS.getPath("blah.txt"))); + assertEquals("blah.", MoreFiles.getNameWithoutExtension(FS.getPath("blah..txt"))); + assertEquals(".blah", MoreFiles.getNameWithoutExtension(FS.getPath(".blah.txt"))); + assertEquals("blah", MoreFiles.getNameWithoutExtension(root().resolve("tmp/blah.txt"))); + assertEquals("blah.tar", MoreFiles.getNameWithoutExtension(FS.getPath("blah.tar.gz"))); + assertEquals("", MoreFiles.getNameWithoutExtension(root())); + assertEquals("", MoreFiles.getNameWithoutExtension(FS.getPath("."))); + assertEquals(".", MoreFiles.getNameWithoutExtension(FS.getPath(".."))); + assertEquals("..", MoreFiles.getNameWithoutExtension(FS.getPath("..."))); + assertEquals("blah", MoreFiles.getNameWithoutExtension(FS.getPath("blah"))); + assertEquals("blah", MoreFiles.getNameWithoutExtension(FS.getPath("blah."))); + assertEquals(".blah", MoreFiles.getNameWithoutExtension(FS.getPath(".blah."))); + assertEquals("blah", MoreFiles.getNameWithoutExtension(root().resolve("foo.bar/blah"))); + assertEquals("blah", MoreFiles.getNameWithoutExtension(root().resolve("foo/.bar/blah"))); + } + + public void testPredicates() throws IOException { + /* + * We use a fake filesystem to sidestep the lack of support for symlinks in the default + * filesystem under Android's desugared java.nio.file. + */ + try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) { + Path file = fs.getPath("file"); + Files.createFile(file); + Path dir = fs.getPath("dir"); + Files.createDirectory(dir); + + assertTrue(MoreFiles.isDirectory().apply(dir)); + assertFalse(MoreFiles.isRegularFile().apply(dir)); + + assertFalse(MoreFiles.isDirectory().apply(file)); + assertTrue(MoreFiles.isRegularFile().apply(file)); + + Path symlinkToDir = fs.getPath("symlinkToDir"); + Path symlinkToFile = fs.getPath("symlinkToFile"); + + Files.createSymbolicLink(symlinkToDir, dir); + Files.createSymbolicLink(symlinkToFile, file); + + assertTrue(MoreFiles.isDirectory().apply(symlinkToDir)); + assertFalse(MoreFiles.isRegularFile().apply(symlinkToDir)); + + assertFalse(MoreFiles.isDirectory().apply(symlinkToFile)); + assertTrue(MoreFiles.isRegularFile().apply(symlinkToFile)); + + assertFalse(MoreFiles.isDirectory(NOFOLLOW_LINKS).apply(symlinkToDir)); + assertFalse(MoreFiles.isRegularFile(NOFOLLOW_LINKS).apply(symlinkToFile)); + } + } + + /** + * Creates a new file system for testing that supports the given features in addition to + * supporting symbolic links. The file system is created initially having the following file + * structure: + * + *

    +   *   /
    +   *      work/
    +   *         dir/
    +   *            a
    +   *            b/
    +   *               g
    +   *               h -> ../a
    +   *               i/
    +   *                  j/
    +   *                     k
    +   *                     l/
    +   *            c
    +   *            d -> b/i
    +   *            e/
    +   *            f -> /dontdelete
    +   *      dontdelete/
    +   *         a
    +   *         b/
    +   *         c
    +   *      symlinktodir -> work/dir
    +   * 
    + */ + static FileSystem newTestFileSystem(Feature... supportedFeatures) throws IOException { + FileSystem fs = + Jimfs.newFileSystem( + Configuration.unix().toBuilder() + .setSupportedFeatures(ObjectArrays.concat(SYMBOLIC_LINKS, supportedFeatures)) + .build()); + Files.createDirectories(fs.getPath("dir/b/i/j/l")); + Files.createFile(fs.getPath("dir/a")); + Files.createFile(fs.getPath("dir/c")); + Files.createSymbolicLink(fs.getPath("dir/d"), fs.getPath("b/i")); + Files.createDirectory(fs.getPath("dir/e")); + Files.createSymbolicLink(fs.getPath("dir/f"), fs.getPath("/dontdelete")); + Files.createFile(fs.getPath("dir/b/g")); + Files.createSymbolicLink(fs.getPath("dir/b/h"), fs.getPath("../a")); + Files.createFile(fs.getPath("dir/b/i/j/k")); + Files.createDirectory(fs.getPath("/dontdelete")); + Files.createFile(fs.getPath("/dontdelete/a")); + Files.createDirectory(fs.getPath("/dontdelete/b")); + Files.createFile(fs.getPath("/dontdelete/c")); + Files.createSymbolicLink(fs.getPath("/symlinktodir"), fs.getPath("work/dir")); + return fs; + } + + public void testDirectoryDeletion_basic() throws IOException { + for (DirectoryDeleteMethod method : EnumSet.allOf(DirectoryDeleteMethod.class)) { + try (FileSystem fs = newTestFileSystem(SECURE_DIRECTORY_STREAM)) { + Path dir = fs.getPath("dir"); + assertEquals(6, MoreFiles.listFiles(dir).size()); + + method.delete(dir); + method.assertDeleteSucceeded(dir); + + assertEquals( + "contents of /dontdelete deleted by delete method " + method, + 3, + MoreFiles.listFiles(fs.getPath("/dontdelete")).size()); + } + } + } + + public void testDirectoryDeletion_emptyDir() throws IOException { + for (DirectoryDeleteMethod method : EnumSet.allOf(DirectoryDeleteMethod.class)) { + try (FileSystem fs = newTestFileSystem(SECURE_DIRECTORY_STREAM)) { + Path emptyDir = fs.getPath("dir/e"); + assertEquals(0, MoreFiles.listFiles(emptyDir).size()); + + method.delete(emptyDir); + method.assertDeleteSucceeded(emptyDir); + } + } + } + + public void testDeleteRecursively_symlinkToDir() throws IOException { + try (FileSystem fs = newTestFileSystem(SECURE_DIRECTORY_STREAM)) { + Path symlink = fs.getPath("/symlinktodir"); + Path dir = fs.getPath("dir"); + + assertEquals(6, MoreFiles.listFiles(dir).size()); + + MoreFiles.deleteRecursively(symlink); + + assertFalse(Files.exists(symlink)); + assertTrue(Files.exists(dir)); + assertEquals(6, MoreFiles.listFiles(dir).size()); + } + } + + public void testDeleteDirectoryContents_symlinkToDir() throws IOException { + try (FileSystem fs = newTestFileSystem(SECURE_DIRECTORY_STREAM)) { + Path symlink = fs.getPath("/symlinktodir"); + Path dir = fs.getPath("dir"); + + assertEquals(6, MoreFiles.listFiles(symlink).size()); + + MoreFiles.deleteDirectoryContents(symlink); + + assertTrue(Files.exists(symlink, NOFOLLOW_LINKS)); + assertTrue(Files.exists(symlink)); + assertTrue(Files.exists(dir)); + assertEquals(0, MoreFiles.listFiles(symlink).size()); + } + } + + public void testDirectoryDeletion_sdsNotSupported_fails() throws IOException { + for (DirectoryDeleteMethod method : EnumSet.allOf(DirectoryDeleteMethod.class)) { + try (FileSystem fs = newTestFileSystem()) { + Path dir = fs.getPath("dir"); + assertEquals(6, MoreFiles.listFiles(dir).size()); + + assertThrows(InsecureRecursiveDeleteException.class, () -> method.delete(dir)); + + assertTrue(Files.exists(dir)); + assertEquals(6, MoreFiles.listFiles(dir).size()); + } + } + } + + public void testDirectoryDeletion_sdsNotSupported_allowInsecure() throws IOException { + for (DirectoryDeleteMethod method : EnumSet.allOf(DirectoryDeleteMethod.class)) { + try (FileSystem fs = newTestFileSystem()) { + Path dir = fs.getPath("dir"); + assertEquals(6, MoreFiles.listFiles(dir).size()); + + method.delete(dir, ALLOW_INSECURE); + method.assertDeleteSucceeded(dir); + + assertEquals( + "contents of /dontdelete deleted by delete method " + method, + 3, + MoreFiles.listFiles(fs.getPath("/dontdelete")).size()); + } + } + } + + public void testDeleteRecursively_symlinkToDir_sdsNotSupported_allowInsecure() + throws IOException { + try (FileSystem fs = newTestFileSystem()) { + Path symlink = fs.getPath("/symlinktodir"); + Path dir = fs.getPath("dir"); + + assertEquals(6, MoreFiles.listFiles(dir).size()); + + MoreFiles.deleteRecursively(symlink, ALLOW_INSECURE); + + assertFalse(Files.exists(symlink)); + assertTrue(Files.exists(dir)); + assertEquals(6, MoreFiles.listFiles(dir).size()); + } + } + + public void testDeleteRecursively_nonexistingFile_throwsNoSuchFileException() throws IOException { + try (FileSystem fs = newTestFileSystem()) { + NoSuchFileException expected = + assertThrows( + NoSuchFileException.class, + () -> MoreFiles.deleteRecursively(fs.getPath("/work/nothere"), ALLOW_INSECURE)); + assertThat(expected.getFile()).isEqualTo("/work/nothere"); + } + } + + public void testDeleteDirectoryContents_symlinkToDir_sdsNotSupported_allowInsecure() + throws IOException { + try (FileSystem fs = newTestFileSystem()) { + Path symlink = fs.getPath("/symlinktodir"); + Path dir = fs.getPath("dir"); + + assertEquals(6, MoreFiles.listFiles(dir).size()); + + MoreFiles.deleteDirectoryContents(symlink, ALLOW_INSECURE); + assertEquals(0, MoreFiles.listFiles(dir).size()); + } + } + + /** + * This test attempts to create a situation in which one thread is constantly changing a file from + * being a real directory to being a symlink to another directory. It then calls + * deleteDirectoryContents thousands of times on a directory whose subtree contains the file + * that's switching between directory and symlink to try to ensure that under no circumstance does + * deleteDirectoryContents follow the symlink to the other directory and delete that directory's + * contents. + * + *

    We can only test this with a file system that supports SecureDirectoryStream, because it's + * not possible to protect against this if the file system doesn't. + */ + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. + public void testDirectoryDeletion_directorySymlinkRace() throws IOException { + int iterations = isAndroid() ? 100 : 5000; + for (DirectoryDeleteMethod method : EnumSet.allOf(DirectoryDeleteMethod.class)) { + try (FileSystem fs = newTestFileSystem(SECURE_DIRECTORY_STREAM)) { + Path dirToDelete = fs.getPath("dir/b/i"); + Path changingFile = dirToDelete.resolve("j/l"); + Path symlinkTarget = fs.getPath("/dontdelete"); + + ExecutorService executor = newSingleThreadExecutor(); + startDirectorySymlinkSwitching(changingFile, symlinkTarget, executor); + + try { + for (int i = 0; i < iterations; i++) { + try { + Files.createDirectories(changingFile); + Files.createFile(dirToDelete.resolve("j/k")); + } catch (FileAlreadyExistsException expected) { + // if a file already exists, that's fine... just continue + } + + try { + method.delete(dirToDelete); + } catch (FileSystemException expected) { + // the delete method may or may not throw an exception, but if it does that's fine + // and expected + } + + // this test is mainly checking that the contents of /dontdelete aren't deleted under + // any circumstances + assertEquals(3, MoreFiles.listFiles(symlinkTarget).size()); + + Thread.yield(); + } + } finally { + executor.shutdownNow(); + } + } + } + } + + public void testDeleteRecursively_nonDirectoryFile() throws IOException { + try (FileSystem fs = newTestFileSystem(SECURE_DIRECTORY_STREAM)) { + Path file = fs.getPath("dir/a"); + assertTrue(Files.isRegularFile(file, NOFOLLOW_LINKS)); + + MoreFiles.deleteRecursively(file); + + assertFalse(Files.exists(file, NOFOLLOW_LINKS)); + + Path symlink = fs.getPath("/symlinktodir"); + assertTrue(Files.isSymbolicLink(symlink)); + + Path realSymlinkTarget = symlink.toRealPath(); + assertTrue(Files.isDirectory(realSymlinkTarget, NOFOLLOW_LINKS)); + + MoreFiles.deleteRecursively(symlink); + + assertFalse(Files.exists(symlink, NOFOLLOW_LINKS)); + assertTrue(Files.isDirectory(realSymlinkTarget, NOFOLLOW_LINKS)); + } + } + + /** + * Starts a new task on the given executor that switches (deletes and replaces) a file between + * being a directory and being a symlink. The given {@code file} is the file that should switch + * between being a directory and being a symlink, while the given {@code target} is the target the + * symlink should have. + */ + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. + private static void startDirectorySymlinkSwitching( + Path file, Path target, ExecutorService executor) { + @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored + Future possiblyIgnoredError = + executor.submit( + new Runnable() { + @Override + public void run() { + boolean createSymlink = false; + while (!Thread.interrupted()) { + try { + // trying to switch between a real directory and a symlink (dir -> /a) + if (Files.deleteIfExists(file)) { + if (createSymlink) { + Files.createSymbolicLink(file, target); + } else { + Files.createDirectory(file); + } + createSymlink = !createSymlink; + } + } catch (IOException tolerated) { + // it's expected that some of these will fail + } + + Thread.yield(); + } + } + }); + } + + /** Enum defining the two MoreFiles methods that delete directory contents. */ + private enum DirectoryDeleteMethod { + DELETE_DIRECTORY_CONTENTS { + @Override + public void delete(Path path, RecursiveDeleteOption... options) throws IOException { + MoreFiles.deleteDirectoryContents(path, options); + } + + @Override + public void assertDeleteSucceeded(Path path) throws IOException { + assertEquals( + "contents of directory " + path + " not deleted with delete method " + this, + 0, + MoreFiles.listFiles(path).size()); + } + }, + DELETE_RECURSIVELY { + @Override + public void delete(Path path, RecursiveDeleteOption... options) throws IOException { + MoreFiles.deleteRecursively(path, options); + } + + @Override + public void assertDeleteSucceeded(Path path) throws IOException { + assertFalse("file " + path + " not deleted with delete method " + this, Files.exists(path)); + } + }; + + abstract void delete(Path path, RecursiveDeleteOption... options) throws IOException; + + abstract void assertDeleteSucceeded(Path path) throws IOException; + } + + private static boolean isWindows() { + return OS_NAME.value().startsWith("Windows"); + } + + private static boolean isAndroid() { + return System.getProperty("java.runtime.name", "").contains("Android"); + } +} diff --git a/android/guava-tests/test/com/google/common/io/MultiInputStreamTest.java b/android/guava-tests/test/com/google/common/io/MultiInputStreamTest.java index 2b68595201af..079ea01f1a9b 100644 --- a/android/guava-tests/test/com/google/common/io/MultiInputStreamTest.java +++ b/android/guava-tests/test/com/google/common/io/MultiInputStreamTest.java @@ -16,19 +16,23 @@ package com.google.common.io; -import com.google.common.collect.Lists; +import static com.google.common.truth.Truth.assertThat; + import java.io.ByteArrayInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.NullUnmarked; /** * Test class for {@link MultiInputStream}. * * @author Chris Nokleberg */ +@NullUnmarked public class MultiInputStreamTest extends IoTestCase { public void testJoin() throws Exception { @@ -45,8 +49,8 @@ public void testJoin() throws Exception { } public void testOnlyOneOpen() throws Exception { - final ByteSource source = newByteSource(0, 50); - final int[] counter = new int[1]; + ByteSource source = newByteSource(0, 50); + int[] counter = new int[1]; ByteSource checker = new ByteSource() { @Override @@ -64,11 +68,11 @@ public void close() throws IOException { } }; byte[] result = ByteSource.concat(checker, checker, checker).read(); - assertEquals(150, result.length); + assertThat(result).hasLength(150); } private void joinHelper(Integer... spans) throws Exception { - List sources = Lists.newArrayList(); + List sources = new ArrayList<>(); int start = 0; for (Integer span : spans) { sources.add(newByteSource(start, span)); @@ -133,7 +137,7 @@ private static MultiInputStream tenMillionEmptySources() throws IOException { return new MultiInputStream(Collections.nCopies(10_000_000, ByteSource.empty()).iterator()); } - private static ByteSource newByteSource(final int start, final int size) { + private static ByteSource newByteSource(int start, int size) { return new ByteSource() { @Override public InputStream openStream() { diff --git a/android/guava-tests/test/com/google/common/io/MultiReaderTest.java b/android/guava-tests/test/com/google/common/io/MultiReaderTest.java index 20b4042b6c24..27d42cd9621d 100644 --- a/android/guava-tests/test/com/google/common/io/MultiReaderTest.java +++ b/android/guava-tests/test/com/google/common/io/MultiReaderTest.java @@ -22,14 +22,18 @@ import java.io.Reader; import java.io.StringReader; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author ricebin */ +/** + * @author ricebin + */ +@NullUnmarked public class MultiReaderTest extends TestCase { public void testOnlyOneOpen() throws Exception { String testString = "abcdefgh"; - final CharSource source = newCharSource(testString); - final int[] counter = new int[1]; + CharSource source = newCharSource(testString); + int[] counter = new int[1]; CharSource reader = new CharSource() { @Override @@ -72,7 +76,7 @@ public void testSimple() throws Exception { assertEquals(expectedString, CharStreams.toString(joinedReader)); } - private static CharSource newCharSource(final String text) { + private static CharSource newCharSource(String text) { return new CharSource() { @Override public Reader openStream() { diff --git a/android/guava-tests/test/com/google/common/io/PackageSanityTests.java b/android/guava-tests/test/com/google/common/io/PackageSanityTests.java index 68aad1832019..3beef50c6bae 100644 --- a/android/guava-tests/test/com/google/common/io/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/io/PackageSanityTests.java @@ -16,11 +16,13 @@ package com.google.common.io; -import com.google.common.base.Charsets; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.common.testing.AbstractPackageSanityTests; import java.lang.reflect.Method; import java.nio.channels.FileChannel.MapMode; import java.nio.charset.CharsetEncoder; +import org.jspecify.annotations.NullUnmarked; /** * Basic sanity tests for the entire package. @@ -28,6 +30,7 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() { setDefault(BaseEncoding.class, BaseEncoding.base64()); @@ -35,6 +38,6 @@ public PackageSanityTests() { setDefault(String.class, "abcd"); setDefault(Method.class, AbstractPackageSanityTests.class.getDeclaredMethods()[0]); setDefault(MapMode.class, MapMode.READ_ONLY); - setDefault(CharsetEncoder.class, Charsets.UTF_8.newEncoder()); + setDefault(CharsetEncoder.class, UTF_8.newEncoder()); } } diff --git a/android/guava-tests/test/com/google/common/io/PatternFilenameFilterTest.java b/android/guava-tests/test/com/google/common/io/PatternFilenameFilterTest.java index 77ace52f2506..ecd914ae1821 100644 --- a/android/guava-tests/test/com/google/common/io/PatternFilenameFilterTest.java +++ b/android/guava-tests/test/com/google/common/io/PatternFilenameFilterTest.java @@ -16,24 +16,26 @@ package com.google.common.io; +import static org.junit.Assert.assertThrows; + +import com.google.common.testing.NullPointerTester; +import com.google.common.testing.NullPointerTester.Visibility; import java.io.File; import java.io.FilenameFilter; import java.util.regex.PatternSyntaxException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link PatternFilenameFilter}. * * @author Chris Nokleberg */ +@NullUnmarked public class PatternFilenameFilterTest extends TestCase { public void testSyntaxException() { - try { - new PatternFilenameFilter("("); - fail("expected exception"); - } catch (PatternSyntaxException expected) { - } + assertThrows(PatternSyntaxException.class, () -> new PatternFilenameFilter("(")); } public void testAccept() { @@ -46,4 +48,15 @@ public void testAccept() { // Show that dir is ignored assertTrue(filter.accept(null, "a")); } + + public void testNulls() throws Exception { + NullPointerTester tester = new NullPointerTester(); + + tester.testConstructors(PatternFilenameFilter.class, Visibility.PACKAGE); + tester.testStaticMethods(PatternFilenameFilter.class, Visibility.PACKAGE); // currently none + + // The reason that we skip this method is discussed in a comment on the method. + tester.ignore(PatternFilenameFilter.class.getMethod("accept", File.class, String.class)); + tester.testInstanceMethods(new PatternFilenameFilter(".*"), Visibility.PACKAGE); + } } diff --git a/android/guava-tests/test/com/google/common/io/RandomAmountInputStream.java b/android/guava-tests/test/com/google/common/io/RandomAmountInputStream.java index d457ec7dcf8c..468db514b4cd 100644 --- a/android/guava-tests/test/com/google/common/io/RandomAmountInputStream.java +++ b/android/guava-tests/test/com/google/common/io/RandomAmountInputStream.java @@ -22,8 +22,10 @@ import java.io.IOException; import java.io.InputStream; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** Returns a random portion of the requested bytes on each call. */ +@NullUnmarked class RandomAmountInputStream extends FilterInputStream { private final Random random; diff --git a/android/guava-tests/test/com/google/common/io/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/io/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..957bfb4902a1 --- /dev/null +++ b/android/guava-tests/test/com/google/common/io/ReflectionFreeAssertThrows.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/io/ResourcesTest.java b/android/guava-tests/test/com/google/common/io/ResourcesTest.java index af2abbbc627a..0e87a58f487a 100644 --- a/android/guava-tests/test/com/google/common/io/ResourcesTest.java +++ b/android/guava-tests/test/com/google/common/io/ResourcesTest.java @@ -18,13 +18,13 @@ import static com.google.common.base.CharMatcher.whitespace; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.testing.NullPointerTester; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.PrintWriter; @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.List; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Resources}. @@ -40,8 +41,10 @@ * @author Chris Nokleberg */ +@NullUnmarked public class ResourcesTest extends IoTestCase { + @AndroidIncompatible // wouldn't run anyway, but strip the source entirely because of b/230620681 public static TestSuite suite() { TestSuite suite = new TestSuite(); suite.addTest( @@ -58,26 +61,26 @@ public static TestSuite suite() { public void testToString() throws IOException { URL resource = getClass().getResource("testdata/i18n.txt"); - assertEquals(I18N, Resources.toString(resource, Charsets.UTF_8)); - assertThat(Resources.toString(resource, Charsets.US_ASCII)).isNotEqualTo(I18N); + assertEquals(I18N, Resources.toString(resource, UTF_8)); + assertThat(Resources.toString(resource, US_ASCII)).isNotEqualTo(I18N); } - public void testToToByteArray() throws IOException { - byte[] data = Resources.toByteArray(classfile(Resources.class)); - assertEquals(0xCAFEBABE, new DataInputStream(new ByteArrayInputStream(data)).readInt()); + public void testToByteArray() throws IOException { + URL resource = getClass().getResource("testdata/i18n.txt"); + assertThat(Resources.toByteArray(resource)).isEqualTo(I18N.getBytes(UTF_8)); } public void testReadLines() throws IOException { // TODO(chrisn): Check in a better resource URL resource = getClass().getResource("testdata/i18n.txt"); - assertEquals(ImmutableList.of(I18N), Resources.readLines(resource, Charsets.UTF_8)); + assertEquals(ImmutableList.of(I18N), Resources.readLines(resource, UTF_8)); } public void testReadLines_withLineProcessor() throws IOException { URL resource = getClass().getResource("testdata/alice_in_wonderland.txt"); LineProcessor> collectAndLowercaseAndTrim = new LineProcessor>() { - List collector = new ArrayList<>(); + final List collector = new ArrayList<>(); @Override public boolean processLine(String line) { @@ -90,8 +93,7 @@ public List getResult() { return collector; } }; - List result = - Resources.readLines(resource, Charsets.US_ASCII, collectAndLowercaseAndTrim); + List result = Resources.readLines(resource, US_ASCII, collectAndLowercaseAndTrim); assertEquals(3600, result.size()); assertEquals("ALICE'S ADVENTURES IN WONDERLAND", result.get(0)); assertEquals("THE END", result.get(result.size() - 1)); @@ -105,33 +107,30 @@ public void testCopyToOutputStream() throws IOException { } public void testGetResource_notFound() { - try { - Resources.getResource("no such resource"); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().isEqualTo("resource no such resource not found."); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, () -> Resources.getResource("no such resource")); + assertThat(e).hasMessageThat().isEqualTo("resource no such resource not found."); } public void testGetResource() { - assertNotNull(Resources.getResource("com/google/common/io/testdata/i18n.txt")); + assertThat(Resources.getResource("com/google/common/io/testdata/i18n.txt")).isNotNull(); } public void testGetResource_relativePath_notFound() { - try { - Resources.getResource(getClass(), "com/google/common/io/testdata/i18n.txt"); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - "resource com/google/common/io/testdata/i18n.txt" - + " relative to com.google.common.io.ResourcesTest not found."); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> Resources.getResource(getClass(), "com/google/common/io/testdata/i18n.txt")); + assertThat(e) + .hasMessageThat() + .isEqualTo( + "resource com/google/common/io/testdata/i18n.txt" + + " relative to com.google.common.io.ResourcesTest not found."); } public void testGetResource_relativePath() { - assertNotNull(Resources.getResource(getClass(), "testdata/i18n.txt")); + assertThat(Resources.getResource(getClass(), "testdata/i18n.txt")).isNotNull(); } public void testGetResource_contextClassLoader() throws IOException { @@ -146,11 +145,7 @@ public void testGetResource_contextClassLoader() throws IOException { // First check that we can't find it without setting the context loader. // This is a sanity check that the test doesn't spuriously pass because // the resource is visible to the system class loader. - try { - Resources.getResource(tempFile.getName()); - fail("Should get IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Resources.getResource(tempFile.getName())); // Now set the context loader to one that should find the resource. URL baseUrl = tempFile.getParentFile().toURI().toURL(); @@ -159,8 +154,8 @@ public void testGetResource_contextClassLoader() throws IOException { try { Thread.currentThread().setContextClassLoader(loader); URL url = Resources.getResource(tempFile.getName()); - String text = Resources.toString(url, Charsets.UTF_8); - assertEquals("rud a chur ar an méar fhada\n", text); + String text = Resources.toString(url, UTF_8); + assertEquals("rud a chur ar an méar fhada" + System.lineSeparator(), text); } finally { Thread.currentThread().setContextClassLoader(oldContextLoader); } @@ -170,17 +165,14 @@ public void testGetResource_contextClassLoaderNull() { ClassLoader oldContextLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(null); - assertNotNull(Resources.getResource("com/google/common/io/testdata/i18n.txt")); - try { - Resources.getResource("no such resource"); - fail("Should get IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThat(Resources.getResource("com/google/common/io/testdata/i18n.txt")).isNotNull(); + assertThrows(IllegalArgumentException.class, () -> Resources.getResource("no such resource")); } finally { Thread.currentThread().setContextClassLoader(oldContextLoader); } } + @AndroidIncompatible // .class files aren't available public void testNulls() { new NullPointerTester() .setDefault(URL.class, classfile(ResourcesTest.class)) diff --git a/android/guava-tests/test/com/google/common/io/SourceSinkFactories.java b/android/guava-tests/test/com/google/common/io/SourceSinkFactories.java index 8bfa6e1ba12a..19649f89d012 100644 --- a/android/guava-tests/test/com/google/common/io/SourceSinkFactories.java +++ b/android/guava-tests/test/com/google/common/io/SourceSinkFactories.java @@ -21,8 +21,9 @@ import static com.google.common.io.SourceSinkFactory.ByteSourceFactory; import static com.google.common.io.SourceSinkFactory.CharSinkFactory; import static com.google.common.io.SourceSinkFactory.CharSourceFactory; +import static java.lang.Math.min; +import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Charsets; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -37,13 +38,15 @@ import java.nio.CharBuffer; import java.util.Arrays; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * {@link SourceSinkFactory} implementations. * * @author Colin Decker */ +@NullUnmarked public class SourceSinkFactories { private SourceSinkFactories() {} @@ -74,7 +77,7 @@ public static ByteSinkFactory fileByteSinkFactory() { public static ByteSinkFactory appendingFileByteSinkFactory() { String initialString = IoTestCase.ASCII + IoTestCase.I18N; - return new FileByteSinkFactory(initialString.getBytes(Charsets.UTF_8)); + return new FileByteSinkFactory(initialString.getBytes(UTF_8)); } public static CharSourceFactory fileCharSourceFactory() { @@ -98,17 +101,17 @@ public static CharSourceFactory urlCharSourceFactory() { return new UrlCharSourceFactory(); } - public static ByteSourceFactory asByteSourceFactory(final CharSourceFactory factory) { + public static ByteSourceFactory asByteSourceFactory(CharSourceFactory factory) { checkNotNull(factory); return new ByteSourceFactory() { @Override public ByteSource createSource(byte[] data) throws IOException { - return factory.createSource(new String(data, Charsets.UTF_8)).asByteSource(Charsets.UTF_8); + return factory.createSource(new String(data, UTF_8)).asByteSource(UTF_8); } @Override public byte[] getExpected(byte[] data) { - return factory.getExpected(new String(data, Charsets.UTF_8)).getBytes(Charsets.UTF_8); + return factory.getExpected(new String(data, UTF_8)).getBytes(UTF_8); } @Override @@ -118,17 +121,17 @@ public void tearDown() throws IOException { }; } - public static CharSourceFactory asCharSourceFactory(final ByteSourceFactory factory) { + public static CharSourceFactory asCharSourceFactory(ByteSourceFactory factory) { checkNotNull(factory); return new CharSourceFactory() { @Override public CharSource createSource(String string) throws IOException { - return factory.createSource(string.getBytes(Charsets.UTF_8)).asCharSource(Charsets.UTF_8); + return factory.createSource(string.getBytes(UTF_8)).asCharSource(UTF_8); } @Override public String getExpected(String data) { - return new String(factory.getExpected(data.getBytes(Charsets.UTF_8)), Charsets.UTF_8); + return new String(factory.getExpected(data.getBytes(UTF_8)), UTF_8); } @Override @@ -138,17 +141,17 @@ public void tearDown() throws IOException { }; } - public static CharSinkFactory asCharSinkFactory(final ByteSinkFactory factory) { + public static CharSinkFactory asCharSinkFactory(ByteSinkFactory factory) { checkNotNull(factory); return new CharSinkFactory() { @Override public CharSink createSink() throws IOException { - return factory.createSink().asCharSink(Charsets.UTF_8); + return factory.createSink().asCharSink(UTF_8); } @Override public String getSinkContents() throws IOException { - return new String(factory.getSinkContents(), Charsets.UTF_8); + return new String(factory.getSinkContents(), UTF_8); } @Override @@ -158,7 +161,7 @@ public String getExpected(String data) { * string to that. */ byte[] factoryExpectedForNothing = factory.getExpected(new byte[0]); - return new String(factoryExpectedForNothing, Charsets.UTF_8) + checkNotNull(data); + return new String(factoryExpectedForNothing, UTF_8) + checkNotNull(data); } @Override @@ -169,7 +172,7 @@ public void tearDown() throws IOException { } public static ByteSourceFactory asSlicedByteSourceFactory( - final ByteSourceFactory factory, final long off, final long len) { + ByteSourceFactory factory, long off, long len) { checkNotNull(factory); return new ByteSourceFactory() { @Override @@ -180,8 +183,8 @@ public ByteSource createSource(byte[] bytes) throws IOException { @Override public byte[] getExpected(byte[] bytes) { byte[] baseExpected = factory.getExpected(bytes); - int startOffset = (int) Math.min(off, baseExpected.length); - int actualLen = (int) Math.min(len, baseExpected.length - startOffset); + int startOffset = (int) min(off, baseExpected.length); + int actualLen = (int) min(len, baseExpected.length - startOffset); return Arrays.copyOfRange(baseExpected, startOffset, startOffset + actualLen); } @@ -262,16 +265,18 @@ private abstract static class FileFactory { private final ThreadLocal fileThreadLocal = new ThreadLocal<>(); - protected File createFile() throws IOException { + File createFile() throws IOException { File file = File.createTempFile("SinkSourceFile", "txt"); fileThreadLocal.set(file); return file; } - protected File getFile() { + File getFile() { return fileThreadLocal.get(); } + // acts as an override in subclasses that implement SourceSinkFactory + @SuppressWarnings("EffectivelyPrivate") public final void tearDown() throws IOException { if (!fileThreadLocal.get().delete()) { logger.warning("Unable to delete file: " + fileThreadLocal.get()); @@ -305,7 +310,7 @@ private static class FileByteSinkFactory extends FileFactory implements ByteSink private final byte[] initialBytes; - private FileByteSinkFactory(@NullableDecl byte[] initialBytes) { + private FileByteSinkFactory(byte @Nullable [] initialBytes) { this.initialBytes = initialBytes; } @@ -356,13 +361,13 @@ private static class FileCharSourceFactory extends FileFactory implements CharSo public CharSource createSource(String string) throws IOException { checkNotNull(string); File file = createFile(); - Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8); + Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8); try { writer.write(string); } finally { writer.close(); } - return Files.asCharSource(file, Charsets.UTF_8); + return Files.asCharSource(file, UTF_8); } @Override @@ -375,7 +380,7 @@ private static class FileCharSinkFactory extends FileFactory implements CharSink private final String initialString; - private FileCharSinkFactory(@NullableDecl String initialString) { + private FileCharSinkFactory(@Nullable String initialString) { this.initialString = initialString; } @@ -383,15 +388,15 @@ private FileCharSinkFactory(@NullableDecl String initialString) { public CharSink createSink() throws IOException { File file = createFile(); if (initialString != null) { - Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8); + Writer writer = new OutputStreamWriter(new FileOutputStream(file), UTF_8); try { writer.write(initialString); } finally { writer.close(); } - return Files.asCharSink(file, Charsets.UTF_8, FileWriteMode.APPEND); + return Files.asCharSink(file, UTF_8, FileWriteMode.APPEND); } - return Files.asCharSink(file, Charsets.UTF_8); + return Files.asCharSink(file, UTF_8); } @Override @@ -403,13 +408,13 @@ public String getExpected(String string) { @Override public String getSinkContents() throws IOException { File file = getFile(); - Reader reader = new InputStreamReader(new FileInputStream(file), Charsets.UTF_8); + Reader reader = new InputStreamReader(new FileInputStream(file), UTF_8); StringBuilder builder = new StringBuilder(); CharBuffer buffer = CharBuffer.allocate(100); while (reader.read(buffer) != -1) { - buffer.flip(); + Java8Compatibility.flip(buffer); builder.append(buffer); - buffer.clear(); + Java8Compatibility.clear(buffer); } return builder.toString(); } @@ -431,7 +436,7 @@ private static class UrlCharSourceFactory extends FileCharSourceFactory { @Override public CharSource createSource(String string) throws IOException { super.createSource(string); // just ignore returned CharSource - return Resources.asCharSource(getFile().toURI().toURL(), Charsets.UTF_8); + return Resources.asCharSource(getFile().toURI().toURL(), UTF_8); } } } diff --git a/android/guava-tests/test/com/google/common/io/SourceSinkFactory.java b/android/guava-tests/test/com/google/common/io/SourceSinkFactory.java index b8cbc919ecf5..ca8c4ab61ffe 100644 --- a/android/guava-tests/test/com/google/common/io/SourceSinkFactory.java +++ b/android/guava-tests/test/com/google/common/io/SourceSinkFactory.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; +import org.jspecify.annotations.NullUnmarked; /** * A test factory for byte or char sources or sinks. In addition to creating sources or sinks, the @@ -32,6 +33,7 @@ * @param the data type (byte[] or String) * @author Colin Decker */ +@NullUnmarked public interface SourceSinkFactory { /** @@ -44,17 +46,17 @@ public interface SourceSinkFactory { T getExpected(T data); /** Cleans up anything created when creating the source or sink. */ - public abstract void tearDown() throws IOException; + void tearDown() throws IOException; /** Factory for byte or char sources. */ - public interface SourceFactory extends SourceSinkFactory { + interface SourceFactory extends SourceSinkFactory { /** Creates a new source containing some or all of the given data. */ S createSource(T data) throws IOException; } /** Factory for byte or char sinks. */ - public interface SinkFactory extends SourceSinkFactory { + interface SinkFactory extends SourceSinkFactory { /** Creates a new sink. */ S createSink() throws IOException; @@ -64,14 +66,14 @@ public interface SinkFactory extends SourceSinkFactory { } /** Factory for {@link ByteSource} instances. */ - public interface ByteSourceFactory extends SourceFactory {} + interface ByteSourceFactory extends SourceFactory {} /** Factory for {@link ByteSink} instances. */ - public interface ByteSinkFactory extends SinkFactory {} + interface ByteSinkFactory extends SinkFactory {} /** Factory for {@link CharSource} instances. */ - public interface CharSourceFactory extends SourceFactory {} + interface CharSourceFactory extends SourceFactory {} /** Factory for {@link CharSink} instances. */ - public interface CharSinkFactory extends SinkFactory {} + interface CharSinkFactory extends SinkFactory {} } diff --git a/android/guava-tests/test/com/google/common/io/SourceSinkTester.java b/android/guava-tests/test/com/google/common/io/SourceSinkTester.java index 9b07355a6939..e1550524ddcf 100644 --- a/android/guava-tests/test/com/google/common/io/SourceSinkTester.java +++ b/android/guava-tests/test/com/google/common/io/SourceSinkTester.java @@ -20,14 +20,15 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * @param the source or sink type @@ -35,7 +36,8 @@ * @param the factory type * @author Colin Decker */ -@AndroidIncompatible // Android doesn't understand tests that lack default constructors. +@AndroidIncompatible // TODO(b/230620681): Make this available (even though we won't run it). +@NullUnmarked public class SourceSinkTester> extends TestCase { static final String LOREM_IPSUM = @@ -69,7 +71,7 @@ public class SourceSinkTester> extends T .put("\\n at EOF", "hello\nworld\n") .put("\\r at EOF", "hello\nworld\r") .put("lorem ipsum", LOREM_IPSUM) - .build(); + .buildOrThrow(); protected final F factory; protected final T data; @@ -92,7 +94,7 @@ public String getName() { return super.getName() + " [" + suiteName + " [" + caseDesc + "]]"; } - protected static ImmutableList getLines(final String string) { + protected static ImmutableList getLines(String string) { try { return new CharSource() { @Override @@ -111,7 +113,7 @@ public void tearDown() throws IOException { } static ImmutableList getTestMethods(Class testClass) { - List result = Lists.newArrayList(); + List result = new ArrayList<>(); for (Method method : testClass.getDeclaredMethods()) { if (Modifier.isPublic(method.getModifiers()) && method.getReturnType() == void.class diff --git a/android/guava-tests/test/com/google/common/io/TestByteSink.java b/android/guava-tests/test/com/google/common/io/TestByteSink.java index b7eeef0d7ac8..0bbd315db503 100644 --- a/android/guava-tests/test/com/google/common/io/TestByteSink.java +++ b/android/guava-tests/test/com/google/common/io/TestByteSink.java @@ -20,12 +20,14 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import org.jspecify.annotations.NullUnmarked; /** * A byte sink for testing that has configurable behavior. * * @author Colin Decker */ +@NullUnmarked public class TestByteSink extends ByteSink implements TestStreamSupplier { private final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); @@ -61,7 +63,7 @@ public OutputStream openStream() throws IOException { private final class Out extends TestOutputStream { - public Out() throws IOException { + Out() throws IOException { super(bytes, options); } diff --git a/android/guava-tests/test/com/google/common/io/TestByteSource.java b/android/guava-tests/test/com/google/common/io/TestByteSource.java index 54ee982dad5e..5b3f32647c81 100644 --- a/android/guava-tests/test/com/google/common/io/TestByteSource.java +++ b/android/guava-tests/test/com/google/common/io/TestByteSource.java @@ -23,12 +23,14 @@ import java.io.IOException; import java.io.InputStream; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * A byte source for testing that has configurable behavior. * * @author Colin Decker */ +@NullUnmarked public final class TestByteSource extends ByteSource implements TestStreamSupplier { private final byte[] bytes; @@ -60,7 +62,7 @@ public InputStream openStream() throws IOException { private final class In extends TestInputStream { - public In() throws IOException { + In() throws IOException { super(new ByteArrayInputStream(bytes), options); } diff --git a/android/guava-tests/test/com/google/common/io/TestCharSink.java b/android/guava-tests/test/com/google/common/io/TestCharSink.java index 6f7686f7671e..e87642cab8a6 100644 --- a/android/guava-tests/test/com/google/common/io/TestCharSink.java +++ b/android/guava-tests/test/com/google/common/io/TestCharSink.java @@ -16,18 +16,20 @@ package com.google.common.io; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.FilterWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import org.jspecify.annotations.NullUnmarked; /** * A char sink for testing that has configurable behavior. * * @author Colin Decker */ +@NullUnmarked public class TestCharSink extends CharSink implements TestStreamSupplier { private final TestByteSink byteSink; diff --git a/android/guava-tests/test/com/google/common/io/TestCharSource.java b/android/guava-tests/test/com/google/common/io/TestCharSource.java index 37ee8dcd4e5a..f7c589a33624 100644 --- a/android/guava-tests/test/com/google/common/io/TestCharSource.java +++ b/android/guava-tests/test/com/google/common/io/TestCharSource.java @@ -16,17 +16,19 @@ package com.google.common.io; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import org.jspecify.annotations.NullUnmarked; /** * A char source for testing that has configurable options. * * @author Colin Decker */ +@NullUnmarked public class TestCharSource extends CharSource implements TestStreamSupplier { private final TestByteSource byteSource; diff --git a/android/guava-tests/test/com/google/common/io/TestInputStream.java b/android/guava-tests/test/com/google/common/io/TestInputStream.java index c885cf75f4cf..18fa18e8ba60 100644 --- a/android/guava-tests/test/com/google/common/io/TestInputStream.java +++ b/android/guava-tests/test/com/google/common/io/TestInputStream.java @@ -27,8 +27,12 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import org.jspecify.annotations.NullUnmarked; -/** @author Colin Decker */ +/** + * @author Colin Decker + */ +@NullUnmarked public class TestInputStream extends FilterInputStream { private final ImmutableSet options; diff --git a/android/guava-tests/test/com/google/common/io/TestOption.java b/android/guava-tests/test/com/google/common/io/TestOption.java index 5ebd1f1f6c6e..b370476161d4 100644 --- a/android/guava-tests/test/com/google/common/io/TestOption.java +++ b/android/guava-tests/test/com/google/common/io/TestOption.java @@ -16,11 +16,14 @@ package com.google.common.io; +import org.jspecify.annotations.NullUnmarked; + /** * Options controlling the behavior of sources/sinks/streams for testing. * * @author Colin Decker */ +@NullUnmarked public enum TestOption { OPEN_THROWS, SKIP_THROWS, diff --git a/android/guava-tests/test/com/google/common/io/TestOutputStream.java b/android/guava-tests/test/com/google/common/io/TestOutputStream.java index 1a40b837cfed..56847e360b55 100644 --- a/android/guava-tests/test/com/google/common/io/TestOutputStream.java +++ b/android/guava-tests/test/com/google/common/io/TestOutputStream.java @@ -26,8 +26,12 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; +import org.jspecify.annotations.NullUnmarked; -/** @author Colin Decker */ +/** + * @author Colin Decker + */ +@NullUnmarked public class TestOutputStream extends FilterOutputStream { private final ImmutableSet options; diff --git a/android/guava-tests/test/com/google/common/io/TestReader.java b/android/guava-tests/test/com/google/common/io/TestReader.java index d6bf01795069..9f41cbdc8312 100644 --- a/android/guava-tests/test/com/google/common/io/TestReader.java +++ b/android/guava-tests/test/com/google/common/io/TestReader.java @@ -16,15 +16,19 @@ package com.google.common.io; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.ByteArrayInputStream; import java.io.FilterReader; import java.io.IOException; import java.io.InputStreamReader; +import org.jspecify.annotations.NullUnmarked; -/** @author Colin Decker */ +/** + * @author Colin Decker + */ +@NullUnmarked public class TestReader extends FilterReader { private final TestInputStream in; diff --git a/android/guava-tests/test/com/google/common/io/TestStreamSupplier.java b/android/guava-tests/test/com/google/common/io/TestStreamSupplier.java index dcaa20b8944b..e5beeed6f8aa 100644 --- a/android/guava-tests/test/com/google/common/io/TestStreamSupplier.java +++ b/android/guava-tests/test/com/google/common/io/TestStreamSupplier.java @@ -16,6 +16,8 @@ package com.google.common.io; +import org.jspecify.annotations.NullUnmarked; + /** * Interface for a supplier of streams that can report whether a stream was opened and whether that * stream was closed. Intended for use in a test where only a single stream should be opened and @@ -23,6 +25,7 @@ * * @author Colin Decker */ +@NullUnmarked public interface TestStreamSupplier { /** Returns whether or not a new stream was opened. */ diff --git a/android/guava-tests/test/com/google/common/io/TestWriter.java b/android/guava-tests/test/com/google/common/io/TestWriter.java index 34c2690ddccd..50f5fe9608c5 100644 --- a/android/guava-tests/test/com/google/common/io/TestWriter.java +++ b/android/guava-tests/test/com/google/common/io/TestWriter.java @@ -16,14 +16,18 @@ package com.google.common.io; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.FilterWriter; import java.io.IOException; import java.io.OutputStreamWriter; +import org.jspecify.annotations.NullUnmarked; -/** @author Colin Decker */ +/** + * @author Colin Decker + */ +@NullUnmarked public class TestWriter extends FilterWriter { private final TestOutputStream out; diff --git a/android/guava-tests/test/com/google/common/math/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/math/AndroidIncompatible.java index b9d81c019087..26a8fb4e42c0 100644 --- a/android/guava-tests/test/com/google/common/math/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/math/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/math/BigDecimalMathTest.java b/android/guava-tests/test/com/google/common/math/BigDecimalMathTest.java index ff86fd52f5f2..9fc19b4958a1 100644 --- a/android/guava-tests/test/com/google/common/math/BigDecimalMathTest.java +++ b/android/guava-tests/test/com/google/common/math/BigDecimalMathTest.java @@ -25,8 +25,10 @@ import static java.math.RoundingMode.UNNECESSARY; import static java.math.RoundingMode.UP; import static java.math.RoundingMode.values; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; @@ -34,8 +36,10 @@ import java.util.EnumSet; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; @GwtIncompatible +@NullUnmarked public class BigDecimalMathTest extends TestCase { private static final class RoundToDoubleTester { private final BigDecimal input; @@ -46,6 +50,7 @@ private static final class RoundToDoubleTester { this.input = input; } + @CanIgnoreReturnValue RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) { for (RoundingMode mode : modes) { Double previous = expectedValues.put(mode, expectedValue); @@ -56,12 +61,13 @@ RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) return this; } - public RoundToDoubleTester roundUnnecessaryShouldThrow() { + @CanIgnoreReturnValue + RoundToDoubleTester roundUnnecessaryShouldThrow() { unnecessaryShouldThrow = true; return this; } - public void test() { + void test() { assertThat(expectedValues.keySet()) .containsAtLeastElementsIn(EnumSet.complementOf(EnumSet.of(UNNECESSARY))); for (Map.Entry entry : expectedValues.entrySet()) { @@ -76,12 +82,10 @@ public void test() { assertWithMessage("Expected roundUnnecessaryShouldThrow call") .that(unnecessaryShouldThrow) .isTrue(); - try { - BigDecimalMath.roundToDouble(input, UNNECESSARY); - fail("Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)"); - } catch (ArithmeticException expected) { - // expected - } + assertThrows( + "Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)", + ArithmeticException.class, + () -> BigDecimalMath.roundToDouble(input, UNNECESSARY)); } } } @@ -180,13 +184,15 @@ public void testRoundToDouble_twoToThe54PlusFour() { } public void testRoundToDouble_maxDouble() { - BigDecimal maxDoubleAsBD = new BigDecimal(Double.MAX_VALUE); - new RoundToDoubleTester(maxDoubleAsBD).setExpectation(Double.MAX_VALUE, values()).test(); + BigDecimal maxDoubleAsBigDecimal = new BigDecimal(Double.MAX_VALUE); + new RoundToDoubleTester(maxDoubleAsBigDecimal) + .setExpectation(Double.MAX_VALUE, values()) + .test(); } public void testRoundToDouble_maxDoublePlusOne() { - BigDecimal maxDoubleAsBD = new BigDecimal(Double.MAX_VALUE).add(BigDecimal.ONE); - new RoundToDoubleTester(maxDoubleAsBD) + BigDecimal maxDoubleAsBigDecimal = new BigDecimal(Double.MAX_VALUE).add(BigDecimal.ONE); + new RoundToDoubleTester(maxDoubleAsBigDecimal) .setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN) .setExpectation(Double.POSITIVE_INFINITY, UP, CEILING) .roundUnnecessaryShouldThrow() @@ -246,13 +252,15 @@ public void testRoundToDouble_negativeTwoToThe54MinusFour() { } public void testRoundToDouble_minDouble() { - BigDecimal minDoubleAsBD = new BigDecimal(-Double.MAX_VALUE); - new RoundToDoubleTester(minDoubleAsBD).setExpectation(-Double.MAX_VALUE, values()).test(); + BigDecimal minDoubleAsBigDecimal = new BigDecimal(-Double.MAX_VALUE); + new RoundToDoubleTester(minDoubleAsBigDecimal) + .setExpectation(-Double.MAX_VALUE, values()) + .test(); } public void testRoundToDouble_minDoubleMinusOne() { - BigDecimal minDoubleAsBD = new BigDecimal(-Double.MAX_VALUE).subtract(BigDecimal.ONE); - new RoundToDoubleTester(minDoubleAsBD) + BigDecimal minDoubleAsBigDecimal = new BigDecimal(-Double.MAX_VALUE).subtract(BigDecimal.ONE); + new RoundToDoubleTester(minDoubleAsBigDecimal) .setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN) .setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR) .roundUnnecessaryShouldThrow() diff --git a/android/guava-tests/test/com/google/common/math/BigIntegerMathTest.java b/android/guava-tests/test/com/google/common/math/BigIntegerMathTest.java index 170261bb547f..e3ea1bae76ff 100644 --- a/android/guava-tests/test/com/google/common/math/BigIntegerMathTest.java +++ b/android/guava-tests/test/com/google/common/math/BigIntegerMathTest.java @@ -22,6 +22,7 @@ import static com.google.common.math.MathTesting.NEGATIVE_BIGINTEGER_CANDIDATES; import static com.google.common.math.MathTesting.NONZERO_BIGINTEGER_CANDIDATES; import static com.google.common.math.MathTesting.POSITIVE_BIGINTEGER_CANDIDATES; +import static com.google.common.math.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.math.BigInteger.ONE; @@ -40,7 +41,10 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.FormatMethod; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; @@ -48,20 +52,22 @@ import java.util.EnumSet; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for BigIntegerMath. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class BigIntegerMathTest extends TestCase { public void testCeilingPowerOfTwo() { for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) { BigInteger result = BigIntegerMath.ceilingPowerOfTwo(x); assertTrue(BigIntegerMath.isPowerOfTwo(result)); - assertTrue(result.compareTo(x) >= 0); - assertTrue(result.compareTo(x.add(x)) < 0); + assertThat(result).isAtLeast(x); + assertThat(result).isLessThan(x.add(x)); } } @@ -69,45 +75,31 @@ public void testFloorPowerOfTwo() { for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) { BigInteger result = BigIntegerMath.floorPowerOfTwo(x); assertTrue(BigIntegerMath.isPowerOfTwo(result)); - assertTrue(result.compareTo(x) <= 0); - assertTrue(result.add(result).compareTo(x) > 0); + assertThat(result).isAtMost(x); + assertThat(result.add(result)).isGreaterThan(x); } } public void testCeilingPowerOfTwoNegative() { for (BigInteger x : NEGATIVE_BIGINTEGER_CANDIDATES) { - try { - BigIntegerMath.ceilingPowerOfTwo(x); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> BigIntegerMath.ceilingPowerOfTwo(x)); } } public void testFloorPowerOfTwoNegative() { for (BigInteger x : NEGATIVE_BIGINTEGER_CANDIDATES) { - try { - BigIntegerMath.floorPowerOfTwo(x); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> BigIntegerMath.floorPowerOfTwo(x)); } } public void testCeilingPowerOfTwoZero() { - try { - BigIntegerMath.ceilingPowerOfTwo(BigInteger.ZERO); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> BigIntegerMath.ceilingPowerOfTwo(BigInteger.ZERO)); } public void testFloorPowerOfTwoZero() { - try { - BigIntegerMath.floorPowerOfTwo(BigInteger.ZERO); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> BigIntegerMath.floorPowerOfTwo(BigInteger.ZERO)); } @GwtIncompatible // TODO @@ -128,21 +120,14 @@ public void testIsPowerOfTwo() { public void testLog2ZeroAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - BigIntegerMath.log2(ZERO, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> BigIntegerMath.log2(ZERO, mode)); } } public void testLog2NegativeAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - BigIntegerMath.log2(BigInteger.valueOf(-1), mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> BigIntegerMath.log2(BigInteger.valueOf(-1), mode)); } } @@ -150,8 +135,8 @@ public void testLog2Floor() { for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) { for (RoundingMode mode : asList(FLOOR, DOWN)) { int result = BigIntegerMath.log2(x, mode); - assertTrue(ZERO.setBit(result).compareTo(x) <= 0); - assertTrue(ZERO.setBit(result + 1).compareTo(x) > 0); + assertThat(ZERO.setBit(result)).isAtMost(x); + assertThat(ZERO.setBit(result + 1)).isGreaterThan(x); } } } @@ -160,7 +145,7 @@ public void testLog2Ceiling() { for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) { for (RoundingMode mode : asList(CEILING, UP)) { int result = BigIntegerMath.log2(x, mode); - assertTrue(ZERO.setBit(result).compareTo(x) >= 0); + assertThat(ZERO.setBit(result)).isAtLeast(x); assertTrue(result == 0 || ZERO.setBit(result - 1).compareTo(x) < 0); } } @@ -185,7 +170,7 @@ public void testLog2HalfUp() { int result = BigIntegerMath.log2(x, HALF_UP); BigInteger x2 = x.pow(2); // x^2 < 2^(2 * result + 1), or else we would have rounded up - assertTrue(ZERO.setBit(2 * result + 1).compareTo(x2) > 0); + assertThat(ZERO.setBit(2 * result + 1)).isGreaterThan(x2); // x^2 >= 2^(2 * result - 1), or else we would have rounded down assertTrue(result == 0 || ZERO.setBit(2 * result - 1).compareTo(x2) <= 0); } @@ -196,7 +181,7 @@ public void testLog2HalfDown() { int result = BigIntegerMath.log2(x, HALF_DOWN); BigInteger x2 = x.pow(2); // x^2 <= 2^(2 * result + 1), or else we would have rounded up - assertTrue(ZERO.setBit(2 * result + 1).compareTo(x2) >= 0); + assertThat(ZERO.setBit(2 * result + 1)).isAtLeast(x2); // x^2 > 2^(2 * result - 1), or else we would have rounded down assertTrue(result == 0 || ZERO.setBit(2 * result - 1).compareTo(x2) < 0); } @@ -216,22 +201,15 @@ public void testLog2HalfEven() { @GwtIncompatible // TODO public void testLog10ZeroAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - BigIntegerMath.log10(ZERO, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> BigIntegerMath.log10(ZERO, mode)); } } @GwtIncompatible // TODO public void testLog10NegativeAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - BigIntegerMath.log10(BigInteger.valueOf(-1), mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> BigIntegerMath.log10(BigInteger.valueOf(-1), mode)); } } @@ -240,8 +218,8 @@ public void testLog10Floor() { for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) { for (RoundingMode mode : asList(FLOOR, DOWN)) { int result = BigIntegerMath.log10(x, mode); - assertTrue(TEN.pow(result).compareTo(x) <= 0); - assertTrue(TEN.pow(result + 1).compareTo(x) > 0); + assertThat(TEN.pow(result)).isAtMost(x); + assertThat(TEN.pow(result + 1)).isGreaterThan(x); } } } @@ -251,7 +229,7 @@ public void testLog10Ceiling() { for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) { for (RoundingMode mode : asList(CEILING, UP)) { int result = BigIntegerMath.log10(x, mode); - assertTrue(TEN.pow(result).compareTo(x) >= 0); + assertThat(TEN.pow(result)).isAtLeast(x); assertTrue(result == 0 || TEN.pow(result - 1).compareTo(x) < 0); } } @@ -278,7 +256,7 @@ public void testLog10HalfUp() { int result = BigIntegerMath.log10(x, HALF_UP); BigInteger x2 = x.pow(2); // x^2 < 10^(2 * result + 1), or else we would have rounded up - assertTrue(TEN.pow(2 * result + 1).compareTo(x2) > 0); + assertThat(TEN.pow(2 * result + 1)).isGreaterThan(x2); // x^2 >= 10^(2 * result - 1), or else we would have rounded down assertTrue(result == 0 || TEN.pow(2 * result - 1).compareTo(x2) <= 0); } @@ -290,7 +268,7 @@ public void testLog10HalfDown() { int result = BigIntegerMath.log10(x, HALF_DOWN); BigInteger x2 = x.pow(2); // x^2 <= 10^(2 * result + 1), or else we would have rounded up - assertTrue(TEN.pow(2 * result + 1).compareTo(x2) >= 0); + assertThat(TEN.pow(2 * result + 1)).isAtLeast(x2); // x^2 > 10^(2 * result - 1), or else we would have rounded down assertTrue(result == 0 || TEN.pow(2 * result - 1).compareTo(x2) < 0); } @@ -326,11 +304,8 @@ public void testSqrtZeroAlwaysZero() { @GwtIncompatible // TODO public void testSqrtNegativeAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - BigIntegerMath.sqrt(BigInteger.valueOf(-1), mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> BigIntegerMath.sqrt(BigInteger.valueOf(-1), mode)); } } @@ -339,9 +314,9 @@ public void testSqrtFloor() { for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) { for (RoundingMode mode : asList(FLOOR, DOWN)) { BigInteger result = BigIntegerMath.sqrt(x, mode); - assertTrue(result.compareTo(ZERO) > 0); - assertTrue(result.pow(2).compareTo(x) <= 0); - assertTrue(result.add(ONE).pow(2).compareTo(x) > 0); + assertThat(result).isGreaterThan(ZERO); + assertThat(result.pow(2)).isAtMost(x); + assertThat(result.add(ONE).pow(2)).isGreaterThan(x); } } } @@ -351,8 +326,8 @@ public void testSqrtCeiling() { for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) { for (RoundingMode mode : asList(CEILING, UP)) { BigInteger result = BigIntegerMath.sqrt(x, mode); - assertTrue(result.compareTo(ZERO) > 0); - assertTrue(result.pow(2).compareTo(x) >= 0); + assertThat(result).isGreaterThan(ZERO); + assertThat(result.pow(2)).isAtLeast(x); assertTrue(result.signum() == 0 || result.subtract(ONE).pow(2).compareTo(x) < 0); } } @@ -382,7 +357,7 @@ public void testSqrtHalfUp() { BigInteger x4 = x.shiftLeft(2); // sqrt(x) < result + 0.5, so 4 * x < (result + 0.5)^2 * 4 // (result + 0.5)^2 * 4 = (result^2 + result)*4 + 1 - assertTrue(x4.compareTo(plusHalfSquared) < 0); + assertThat(plusHalfSquared).isGreaterThan(x4); BigInteger minusHalfSquared = result.pow(2).subtract(result).shiftLeft(2).add(ONE); // sqrt(x) > result - 0.5, so 4 * x > (result - 0.5)^2 * 4 // (result - 0.5)^2 * 4 = (result^2 - result)*4 + 1 @@ -398,7 +373,7 @@ public void testSqrtHalfDown() { BigInteger x4 = x.shiftLeft(2); // sqrt(x) <= result + 0.5, so 4 * x <= (result + 0.5)^2 * 4 // (result + 0.5)^2 * 4 = (result^2 + result)*4 + 1 - assertTrue(x4.compareTo(plusHalfSquared) <= 0); + assertThat(plusHalfSquared).isAtLeast(x4); BigInteger minusHalfSquared = result.pow(2).subtract(result).shiftLeft(2).add(ONE); // sqrt(x) > result - 0.5, so 4 * x > (result - 0.5)^2 * 4 // (result - 0.5)^2 * 4 = (result^2 - result)*4 + 1 @@ -435,21 +410,15 @@ public void testDivNonZero() { private static final BigInteger BAD_FOR_ANDROID_P = new BigInteger("-9223372036854775808"); private static final BigInteger BAD_FOR_ANDROID_Q = new BigInteger("-1"); - private static final BigInteger BAD_FOR_GINGERBREAD_P = new BigInteger("-9223372036854775808"); - private static final BigInteger BAD_FOR_GINGERBREAD_Q = new BigInteger("-4294967296"); - @GwtIncompatible // TODO @AndroidIncompatible // slow public void testDivNonZeroExact() { - boolean isAndroid = System.getProperty("java.runtime.name").contains("Android"); + String runtimeName = System.getProperty("java.runtime.name"); + boolean isAndroid = runtimeName != null && runtimeName.contains("Android"); for (BigInteger p : NONZERO_BIGINTEGER_CANDIDATES) { for (BigInteger q : NONZERO_BIGINTEGER_CANDIDATES) { if (isAndroid && p.equals(BAD_FOR_ANDROID_P) && q.equals(BAD_FOR_ANDROID_Q)) { - // https://code.google.com/p/android/issues/detail?id=196555 - continue; - } - if (isAndroid && p.equals(BAD_FOR_GINGERBREAD_P) && q.equals(BAD_FOR_GINGERBREAD_Q)) { - // Works fine under Marshmallow, so I haven't filed a bug. + // https://issuetracker.google.com/issues/37074172 continue; } @@ -482,11 +451,7 @@ public void testZeroDivIsAlwaysZero() { public void testDivByZeroAlwaysFails() { for (BigInteger p : ALL_BIGINTEGER_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - BigIntegerMath.divide(p, ZERO, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> BigIntegerMath.divide(p, ZERO, mode)); } } } @@ -504,11 +469,7 @@ public void testFactorial0() { } public void testFactorialNegative() { - try { - BigIntegerMath.factorial(-1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> BigIntegerMath.factorial(-1)); } public void testBinomialSmall() { @@ -534,21 +495,15 @@ private static void runBinomialTest(int firstN, int lastN) { } public void testBinomialOutside() { - for (int n = 0; n <= 50; n++) { - try { - BigIntegerMath.binomial(n, -1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - BigIntegerMath.binomial(n, n + 1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + for (int i = 0; i <= 50; i++) { + int n = i; + assertThrows(IllegalArgumentException.class, () -> BigIntegerMath.binomial(n, -1)); + assertThrows(IllegalArgumentException.class, () -> BigIntegerMath.binomial(n, n + 1)); } } - @GwtIncompatible + @J2ktIncompatible + @GwtIncompatible // EnumSet.complementOf private static final class RoundToDoubleTester { private final BigInteger input; private final Map expectedValues = new EnumMap<>(RoundingMode.class); @@ -558,6 +513,7 @@ private static final class RoundToDoubleTester { this.input = input; } + @CanIgnoreReturnValue RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) { for (RoundingMode mode : modes) { Double previous = expectedValues.put(mode, expectedValue); @@ -568,12 +524,13 @@ RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) return this; } - public RoundToDoubleTester roundUnnecessaryShouldThrow() { + @CanIgnoreReturnValue + RoundToDoubleTester roundUnnecessaryShouldThrow() { unnecessaryShouldThrow = true; return this; } - public void test() { + void test() { assertThat(expectedValues.keySet()) .containsAtLeastElementsIn(EnumSet.complementOf(EnumSet.of(UNNECESSARY))); for (Map.Entry entry : expectedValues.entrySet()) { @@ -588,26 +545,25 @@ public void test() { assertWithMessage("Expected roundUnnecessaryShouldThrow call") .that(unnecessaryShouldThrow) .isTrue(); - try { - BigIntegerMath.roundToDouble(input, UNNECESSARY); - fail("Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)"); - } catch (ArithmeticException expected) { - // expected - } + assertThrows( + ArithmeticException.class, () -> BigIntegerMath.roundToDouble(input, UNNECESSARY)); } } } + @J2ktIncompatible @GwtIncompatible - public void testRoundToDouble_Zero() { + public void testRoundToDouble_zero() { new RoundToDoubleTester(BigInteger.ZERO).setExpectation(0.0, values()).test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_smallPositive() { new RoundToDoubleTester(BigInteger.valueOf(16)).setExpectation(16.0, values()).test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_maxPreciselyRepresentable() { new RoundToDoubleTester(BigInteger.valueOf(1L << 53)) @@ -615,6 +571,7 @@ public void testRoundToDouble_maxPreciselyRepresentable() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_maxPreciselyRepresentablePlusOne() { double twoToThe53 = Math.pow(2, 53); @@ -627,6 +584,7 @@ public void testRoundToDouble_maxPreciselyRepresentablePlusOne() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_twoToThe54PlusOne() { double twoToThe54 = Math.pow(2, 54); @@ -639,6 +597,7 @@ public void testRoundToDouble_twoToThe54PlusOne() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_twoToThe54PlusThree() { double twoToThe54 = Math.pow(2, 54); @@ -651,6 +610,7 @@ public void testRoundToDouble_twoToThe54PlusThree() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_twoToThe54PlusFour() { new RoundToDoubleTester(BigInteger.valueOf((1L << 54) + 4)) @@ -658,23 +618,28 @@ public void testRoundToDouble_twoToThe54PlusFour() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_maxDouble() { - BigInteger maxDoubleAsBI = DoubleMath.roundToBigInteger(Double.MAX_VALUE, UNNECESSARY); - new RoundToDoubleTester(maxDoubleAsBI).setExpectation(Double.MAX_VALUE, values()).test(); + BigInteger maxDoubleAsBigInteger = DoubleMath.roundToBigInteger(Double.MAX_VALUE, UNNECESSARY); + new RoundToDoubleTester(maxDoubleAsBigInteger) + .setExpectation(Double.MAX_VALUE, values()) + .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_maxDoublePlusOne() { - BigInteger maxDoubleAsBI = + BigInteger maxDoubleAsBigInteger = DoubleMath.roundToBigInteger(Double.MAX_VALUE, UNNECESSARY).add(BigInteger.ONE); - new RoundToDoubleTester(maxDoubleAsBI) + new RoundToDoubleTester(maxDoubleAsBigInteger) .setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN) .setExpectation(Double.POSITIVE_INFINITY, UP, CEILING) .roundUnnecessaryShouldThrow() .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_wayTooBig() { BigInteger bi = BigInteger.ONE.shiftLeft(2 * Double.MAX_EXPONENT); @@ -685,11 +650,13 @@ public void testRoundToDouble_wayTooBig() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_smallNegative() { new RoundToDoubleTester(BigInteger.valueOf(-16)).setExpectation(-16.0, values()).test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_minPreciselyRepresentable() { new RoundToDoubleTester(BigInteger.valueOf(-1L << 53)) @@ -697,6 +664,7 @@ public void testRoundToDouble_minPreciselyRepresentable() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_minPreciselyRepresentableMinusOne() { // the representable doubles are -2^53 and -2^53 - 2. @@ -708,6 +676,7 @@ public void testRoundToDouble_minPreciselyRepresentableMinusOne() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_negativeTwoToThe54MinusOne() { new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 1)) @@ -717,6 +686,7 @@ public void testRoundToDouble_negativeTwoToThe54MinusOne() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_negativeTwoToThe54MinusThree() { new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 3)) @@ -727,6 +697,7 @@ public void testRoundToDouble_negativeTwoToThe54MinusThree() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_negativeTwoToThe54MinusFour() { new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 4)) @@ -734,23 +705,28 @@ public void testRoundToDouble_negativeTwoToThe54MinusFour() { .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_minDouble() { - BigInteger minDoubleAsBI = DoubleMath.roundToBigInteger(-Double.MAX_VALUE, UNNECESSARY); - new RoundToDoubleTester(minDoubleAsBI).setExpectation(-Double.MAX_VALUE, values()).test(); + BigInteger minDoubleAsBigInteger = DoubleMath.roundToBigInteger(-Double.MAX_VALUE, UNNECESSARY); + new RoundToDoubleTester(minDoubleAsBigInteger) + .setExpectation(-Double.MAX_VALUE, values()) + .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_minDoubleMinusOne() { - BigInteger minDoubleAsBI = + BigInteger minDoubleAsBigInteger = DoubleMath.roundToBigInteger(-Double.MAX_VALUE, UNNECESSARY).subtract(BigInteger.ONE); - new RoundToDoubleTester(minDoubleAsBI) + new RoundToDoubleTester(minDoubleAsBigInteger) .setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN) .setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR) .roundUnnecessaryShouldThrow() .test(); } + @J2ktIncompatible @GwtIncompatible public void testRoundToDouble_negativeWayTooBig() { BigInteger bi = BigInteger.ONE.shiftLeft(2 * Double.MAX_EXPONENT).negate(); @@ -761,6 +737,7 @@ public void testRoundToDouble_negativeWayTooBig() { .test(); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -771,6 +748,7 @@ public void testNullPointers() { } @GwtIncompatible // String.format + @FormatMethod private static void failFormat(String template, Object... args) { fail(String.format(template, args)); } diff --git a/android/guava-tests/test/com/google/common/math/DoubleMathTest.java b/android/guava-tests/test/com/google/common/math/DoubleMathTest.java index 724ae96d8f1f..1d8b30e90dc1 100644 --- a/android/guava-tests/test/com/google/common/math/DoubleMathTest.java +++ b/android/guava-tests/test/com/google/common/math/DoubleMathTest.java @@ -28,6 +28,8 @@ import static com.google.common.math.MathTesting.INTEGRAL_DOUBLE_CANDIDATES; import static com.google.common.math.MathTesting.NEGATIVE_INTEGER_CANDIDATES; import static com.google.common.math.MathTesting.POSITIVE_FINITE_DOUBLE_CANDIDATES; +import static com.google.common.math.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; import static java.math.RoundingMode.CEILING; import static java.math.RoundingMode.DOWN; import static java.math.RoundingMode.FLOOR; @@ -40,6 +42,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.primitives.Doubles; @@ -48,15 +51,16 @@ import java.math.BigInteger; import java.math.RoundingMode; import java.util.Arrays; -import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code DoubleMath}. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class DoubleMathTest extends TestCase { private static final BigDecimal MAX_INT_AS_BIG_DECIMAL = BigDecimal.valueOf(Integer.MAX_VALUE); @@ -67,15 +71,15 @@ public class DoubleMathTest extends TestCase { public void testConstantsMaxFactorial() { BigInteger maxDoubleValue = BigDecimal.valueOf(Double.MAX_VALUE).toBigInteger(); - assertTrue(BigIntegerMath.factorial(DoubleMath.MAX_FACTORIAL).compareTo(maxDoubleValue) <= 0); - assertTrue( - BigIntegerMath.factorial(DoubleMath.MAX_FACTORIAL + 1).compareTo(maxDoubleValue) > 0); + assertThat(BigIntegerMath.factorial(DoubleMath.MAX_FACTORIAL)).isAtMost(maxDoubleValue); + assertThat(BigIntegerMath.factorial(DoubleMath.MAX_FACTORIAL + 1)) + .isGreaterThan(maxDoubleValue); } public void testConstantsEverySixteenthFactorial() { for (int i = 0, n = 0; n <= DoubleMath.MAX_FACTORIAL; i++, n += 16) { - assertEquals( - BigIntegerMath.factorial(n).doubleValue(), DoubleMath.everySixteenthFactorial[i]); + assertThat(DoubleMath.everySixteenthFactorial[i]) + .isEqualTo(BigIntegerMath.factorial(n).doubleValue()); } } @@ -140,38 +144,24 @@ public void testRoundExactIntegralDoubleToInt() { @GwtIncompatible // DoubleMath.roundToInt(double, RoundingMode) public void testRoundExactFractionalDoubleToIntFails() { for (double d : FRACTIONAL_DOUBLE_CANDIDATES) { - try { - DoubleMath.roundToInt(d, UNNECESSARY); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> DoubleMath.roundToInt(d, UNNECESSARY)); } } @GwtIncompatible // DoubleMath.roundToInt(double, RoundingMode) public void testRoundNaNToIntAlwaysFails() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - DoubleMath.roundToInt(Double.NaN, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> DoubleMath.roundToInt(Double.NaN, mode)); } } @GwtIncompatible // DoubleMath.roundToInt(double, RoundingMode) public void testRoundInfiniteToIntAlwaysFails() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - DoubleMath.roundToInt(Double.POSITIVE_INFINITY, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } - try { - DoubleMath.roundToInt(Double.NEGATIVE_INFINITY, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, () -> DoubleMath.roundToInt(Double.POSITIVE_INFINITY, mode)); + assertThrows( + ArithmeticException.class, () -> DoubleMath.roundToInt(Double.NEGATIVE_INFINITY, mode)); } } @@ -234,38 +224,24 @@ public void testRoundExactIntegralDoubleToLong() { @GwtIncompatible // DoubleMath.roundToLong(double, RoundingMode) public void testRoundExactFractionalDoubleToLongFails() { for (double d : FRACTIONAL_DOUBLE_CANDIDATES) { - try { - DoubleMath.roundToLong(d, UNNECESSARY); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> DoubleMath.roundToLong(d, UNNECESSARY)); } } @GwtIncompatible // DoubleMath.roundToLong(double, RoundingMode) public void testRoundNaNToLongAlwaysFails() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - DoubleMath.roundToLong(Double.NaN, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> DoubleMath.roundToLong(Double.NaN, mode)); } } @GwtIncompatible // DoubleMath.roundToLong(double, RoundingMode) public void testRoundInfiniteToLongAlwaysFails() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - DoubleMath.roundToLong(Double.POSITIVE_INFINITY, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } - try { - DoubleMath.roundToLong(Double.NEGATIVE_INFINITY, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, () -> DoubleMath.roundToLong(Double.POSITIVE_INFINITY, mode)); + assertThrows( + ArithmeticException.class, () -> DoubleMath.roundToLong(Double.NEGATIVE_INFINITY, mode)); } } @@ -300,38 +276,26 @@ public void testRoundExactIntegralDoubleToBigInteger() { @GwtIncompatible // DoubleMath.roundToBigInteger(double, RoundingMode) public void testRoundExactFractionalDoubleToBigIntegerFails() { for (double d : FRACTIONAL_DOUBLE_CANDIDATES) { - try { - DoubleMath.roundToBigInteger(d, UNNECESSARY); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> DoubleMath.roundToBigInteger(d, UNNECESSARY)); } } @GwtIncompatible // DoubleMath.roundToBigInteger(double, RoundingMode) public void testRoundNaNToBigIntegerAlwaysFails() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - DoubleMath.roundToBigInteger(Double.NaN, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> DoubleMath.roundToBigInteger(Double.NaN, mode)); } } @GwtIncompatible // DoubleMath.roundToBigInteger(double, RoundingMode) public void testRoundInfiniteToBigIntegerAlwaysFails() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - DoubleMath.roundToBigInteger(Double.POSITIVE_INFINITY, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } - try { - DoubleMath.roundToBigInteger(Double.NEGATIVE_INFINITY, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, + () -> DoubleMath.roundToBigInteger(Double.POSITIVE_INFINITY, mode)); + assertThrows( + ArithmeticException.class, + () -> DoubleMath.roundToBigInteger(Double.NEGATIVE_INFINITY, mode)); } } @@ -393,13 +357,8 @@ public void testRoundLog2Half() { for (RoundingMode mode : asList(HALF_EVEN, HALF_UP, HALF_DOWN)) { double x = Math.scalb(Math.sqrt(2) + 0.001, exp); double y = Math.scalb(Math.sqrt(2) - 0.001, exp); - if (exp < 0) { - assertEquals(exp + 1, DoubleMath.log2(x, mode)); - assertEquals(exp, DoubleMath.log2(y, mode)); - } else { - assertEquals(exp + 1, DoubleMath.log2(x, mode)); - assertEquals(exp, DoubleMath.log2(y, mode)); - } + assertEquals(exp + 1, DoubleMath.log2(x, mode)); + assertEquals(exp, DoubleMath.log2(y, mode)); } } } @@ -410,7 +369,7 @@ public void testRoundLog2Exact() { boolean isPowerOfTwo = StrictMath.pow(2.0, DoubleMath.log2(x, FLOOR)) == x; try { int log2 = DoubleMath.log2(x, UNNECESSARY); - assertEquals(x, Math.scalb(1.0, log2)); + assertThat(Math.scalb(1.0, log2)).isEqualTo(x); assertTrue(isPowerOfTwo); } catch (ArithmeticException e) { assertFalse(isPowerOfTwo); @@ -423,11 +382,7 @@ public void testRoundLog2ThrowsOnZerosInfinitiesAndNaN() { for (RoundingMode mode : ALL_ROUNDING_MODES) { for (double d : asList(0.0, -0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN)) { - try { - DoubleMath.log2(d, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> DoubleMath.log2(d, mode)); } } } @@ -436,11 +391,7 @@ public void testRoundLog2ThrowsOnZerosInfinitiesAndNaN() { public void testRoundLog2ThrowsOnNegative() { for (RoundingMode mode : ALL_ROUNDING_MODES) { for (double d : POSITIVE_FINITE_DOUBLE_CANDIDATES) { - try { - DoubleMath.log2(-d, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> DoubleMath.log2(-d, mode)); } } } @@ -486,17 +437,18 @@ public void testLog2Negative() { } public void testLog2Zero() { - assertEquals(Double.NEGATIVE_INFINITY, DoubleMath.log2(0.0)); - assertEquals(Double.NEGATIVE_INFINITY, DoubleMath.log2(-0.0)); + assertThat(DoubleMath.log2(0.0)).isNegativeInfinity(); + assertThat(DoubleMath.log2(-0.0)).isNegativeInfinity(); } public void testLog2NaNInfinity() { - assertEquals(Double.POSITIVE_INFINITY, DoubleMath.log2(Double.POSITIVE_INFINITY)); + assertThat(DoubleMath.log2(Double.POSITIVE_INFINITY)).isPositiveInfinity(); assertTrue(Double.isNaN(DoubleMath.log2(Double.NEGATIVE_INFINITY))); assertTrue(Double.isNaN(DoubleMath.log2(Double.NaN))); } @GwtIncompatible // StrictMath + @SuppressWarnings("strictfp") // Guava still supports Java 8 private strictfp double trueLog2(double d) { double trueLog2 = StrictMath.log(d) / StrictMath.log(2); // increment until it's >= the true value @@ -540,22 +492,18 @@ public void testFactorial() { for (int i = 0; i <= DoubleMath.MAX_FACTORIAL; i++) { double actual = BigIntegerMath.factorial(i).doubleValue(); double result = DoubleMath.factorial(i); - assertEquals(actual, result, Math.ulp(actual)); + assertThat(result).isWithin(Math.ulp(actual)).of(actual); } } public void testFactorialTooHigh() { - assertEquals(Double.POSITIVE_INFINITY, DoubleMath.factorial(DoubleMath.MAX_FACTORIAL + 1)); - assertEquals(Double.POSITIVE_INFINITY, DoubleMath.factorial(DoubleMath.MAX_FACTORIAL + 20)); + assertThat(DoubleMath.factorial(DoubleMath.MAX_FACTORIAL + 1)).isPositiveInfinity(); + assertThat(DoubleMath.factorial(DoubleMath.MAX_FACTORIAL + 20)).isPositiveInfinity(); } public void testFactorialNegative() { for (int n : NEGATIVE_INTEGER_CANDIDATES) { - try { - DoubleMath.factorial(n); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> DoubleMath.factorial(n)); } } @@ -563,17 +511,20 @@ public void testFactorialNegative() { ImmutableList.of(-0.0, 0.0, 1.0, 100.0, 10000.0, Double.MAX_VALUE); private static final Iterable TOLERANCE_CANDIDATES = - Iterables.concat(FINITE_TOLERANCE_CANDIDATES, ImmutableList.of(Double.POSITIVE_INFINITY)); - - private static final List BAD_TOLERANCE_CANDIDATES = - Doubles.asList( - -Double.MIN_VALUE, - -Double.MIN_NORMAL, - -1, - -20, - Double.NaN, - Double.NEGATIVE_INFINITY, - -0.001); + ImmutableList.copyOf( + Iterables.concat( + FINITE_TOLERANCE_CANDIDATES, ImmutableList.of(Double.POSITIVE_INFINITY))); + + private static final ImmutableList BAD_TOLERANCE_CANDIDATES = + ImmutableList.copyOf( + Doubles.asList( + -Double.MIN_VALUE, + -Double.MIN_NORMAL, + -1, + -20, + Double.NaN, + Double.NEGATIVE_INFINITY, + -0.001)); public void testFuzzyEqualsFinite() { for (double a : FINITE_DOUBLE_CANDIDATES) { @@ -642,12 +593,7 @@ public void testFuzzyEqualsZeroTolerance() { public void testFuzzyEqualsBadTolerance() { for (double tolerance : BAD_TOLERANCE_CANDIDATES) { - try { - DoubleMath.fuzzyEquals(1, 2, tolerance); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // success - } + assertThrows(IllegalArgumentException.class, () -> DoubleMath.fuzzyEquals(1, 2, tolerance)); } } @@ -701,118 +647,97 @@ private static void runTestFuzzyCompare(int toleranceIndex) { public void testFuzzyCompareBadTolerance() { for (double tolerance : BAD_TOLERANCE_CANDIDATES) { - try { - DoubleMath.fuzzyCompare(1, 2, tolerance); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - // success - } + assertThrows(IllegalArgumentException.class, () -> DoubleMath.fuzzyCompare(1, 2, tolerance)); } } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_doubleVarargs() { - assertEquals(-1.375, DoubleMath.mean(1.1, -2.2, 4.4, -8.8), 1.0e-10); - assertEquals(1.1, DoubleMath.mean(1.1), 1.0e-10); - try { - DoubleMath.mean(Double.NaN); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - DoubleMath.mean(Double.POSITIVE_INFINITY); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThat(DoubleMath.mean(1.1, -2.2, 4.4, -8.8)).isWithin(1.0e-10).of(-1.375); + assertThat(DoubleMath.mean(1.1)).isWithin(1.0e-10).of(1.1); + assertThrows(IllegalArgumentException.class, () -> DoubleMath.mean(Double.NaN)); + assertThrows(IllegalArgumentException.class, () -> DoubleMath.mean(Double.POSITIVE_INFINITY)); } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_intVarargs() { - assertEquals(-13.75, DoubleMath.mean(11, -22, 44, -88), 1.0e-10); - assertEquals(11.0, DoubleMath.mean(11), 1.0e-10); + assertThat(DoubleMath.mean(11, -22, 44, -88)).isWithin(1.0e-10).of(-13.75); + assertThat(DoubleMath.mean(11)).isWithin(1.0e-10).of(11.0); } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_longVarargs() { - assertEquals(-13.75, DoubleMath.mean(11L, -22L, 44L, -88L), 1.0e-10); - assertEquals(11.0, DoubleMath.mean(11L), 1.0e-10); + assertThat(DoubleMath.mean(11L, -22L, 44L, -88L)).isWithin(1.0e-10).of(-13.75); + assertThat(DoubleMath.mean(11L)).isWithin(1.0e-10).of(11.0); } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_emptyVarargs() { - try { - DoubleMath.mean(); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> DoubleMath.mean()); } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_doubleIterable() { - assertEquals(-1.375, DoubleMath.mean(ImmutableList.of(1.1, -2.2, 4.4, -8.8)), 1.0e-10); - assertEquals(1.1, DoubleMath.mean(ImmutableList.of(1.1)), 1.0e-10); - try { - DoubleMath.mean(ImmutableList.of()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - DoubleMath.mean(ImmutableList.of(Double.NaN)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - DoubleMath.mean(ImmutableList.of(Double.POSITIVE_INFINITY)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThat(DoubleMath.mean(ImmutableList.of(1.1, -2.2, 4.4, -8.8))) + .isWithin(1.0e-10) + .of(-1.375); + assertThat(DoubleMath.mean(ImmutableList.of(1.1))).isWithin(1.0e-10).of(1.1); + assertThrows(IllegalArgumentException.class, () -> DoubleMath.mean(ImmutableList.of())); + assertThrows( + IllegalArgumentException.class, () -> DoubleMath.mean(ImmutableList.of(Double.NaN))); + assertThrows( + IllegalArgumentException.class, + () -> DoubleMath.mean(ImmutableList.of(Double.POSITIVE_INFINITY))); } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_intIterable() { - assertEquals(-13.75, DoubleMath.mean(ImmutableList.of(11, -22, 44, -88)), 1.0e-10); - assertEquals(11, DoubleMath.mean(ImmutableList.of(11)), 1.0e-10); - try { - DoubleMath.mean(ImmutableList.of()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThat(DoubleMath.mean(ImmutableList.of(11, -22, 44, -88))).isWithin(1.0e-10).of(-13.75); + assertThat(DoubleMath.mean(ImmutableList.of(11))).isWithin(1.0e-10).of(11); + assertThrows( + IllegalArgumentException.class, () -> DoubleMath.mean(ImmutableList.of())); } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_longIterable() { - assertEquals(-13.75, DoubleMath.mean(ImmutableList.of(11L, -22L, 44L, -88L)), 1.0e-10); - assertEquals(11, DoubleMath.mean(ImmutableList.of(11L)), 1.0e-10); - try { - DoubleMath.mean(ImmutableList.of()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThat(DoubleMath.mean(ImmutableList.of(11L, -22L, 44L, -88L))) + .isWithin(1.0e-10) + .of(-13.75); + assertThat(DoubleMath.mean(ImmutableList.of(11L))).isWithin(1.0e-10).of(11); + assertThrows(IllegalArgumentException.class, () -> DoubleMath.mean(ImmutableList.of())); } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_intIterator() { - assertEquals(-13.75, DoubleMath.mean(ImmutableList.of(11, -22, 44, -88).iterator()), 1.0e-10); - assertEquals(11, DoubleMath.mean(ImmutableList.of(11).iterator()), 1.0e-10); - try { - DoubleMath.mean(ImmutableList.of().iterator()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThat(DoubleMath.mean(ImmutableList.of(11, -22, 44, -88).iterator())) + .isWithin(1.0e-10) + .of(-13.75); + assertThat(DoubleMath.mean(ImmutableList.of(11).iterator())).isWithin(1.0e-10).of(11); + assertThrows( + IllegalArgumentException.class, + () -> DoubleMath.mean(ImmutableList.of().iterator())); } @GwtIncompatible // DoubleMath.mean + @SuppressWarnings("deprecation") // test of deprecated method public void testMean_longIterator() { - assertEquals( - -13.75, DoubleMath.mean(ImmutableList.of(11L, -22L, 44L, -88L).iterator()), 1.0e-10); - assertEquals(11, DoubleMath.mean(ImmutableList.of(11L).iterator()), 1.0e-10); - try { - DoubleMath.mean(ImmutableList.of().iterator()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThat(DoubleMath.mean(ImmutableList.of(11L, -22L, 44L, -88L).iterator())) + .isWithin(1.0e-10) + .of(-13.75); + assertThat(DoubleMath.mean(ImmutableList.of(11L).iterator())).isWithin(1.0e-10).of(11); + assertThrows( + IllegalArgumentException.class, () -> DoubleMath.mean(ImmutableList.of().iterator())); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/math/DoubleUtilsTest.java b/android/guava-tests/test/com/google/common/math/DoubleUtilsTest.java index 2f6263ea9602..94c6c5a625a5 100644 --- a/android/guava-tests/test/com/google/common/math/DoubleUtilsTest.java +++ b/android/guava-tests/test/com/google/common/math/DoubleUtilsTest.java @@ -19,16 +19,20 @@ import static com.google.common.math.MathTesting.ALL_BIGINTEGER_CANDIDATES; import static com.google.common.math.MathTesting.FINITE_DOUBLE_CANDIDATES; import static com.google.common.math.MathTesting.POSITIVE_FINITE_DOUBLE_CANDIDATES; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import java.lang.reflect.Method; import java.math.BigInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link DoubleUtils}. * * @author Louis Wasserman */ +@NullUnmarked public class DoubleUtilsTest extends TestCase { @AndroidIncompatible // no FpUtils and no Math.nextDown in old versions public void testNextDown() throws Exception { @@ -58,18 +62,14 @@ public void testBigToDouble() { } public void testEnsureNonNegative() { - assertEquals(0.0, DoubleUtils.ensureNonNegative(0.0)); + assertThat(DoubleUtils.ensureNonNegative(0.0)).isEqualTo(0.0); for (double positiveValue : POSITIVE_FINITE_DOUBLE_CANDIDATES) { - assertEquals(positiveValue, DoubleUtils.ensureNonNegative(positiveValue)); - assertEquals(0.0, DoubleUtils.ensureNonNegative(-positiveValue)); - } - assertEquals(Double.POSITIVE_INFINITY, DoubleUtils.ensureNonNegative(Double.POSITIVE_INFINITY)); - assertEquals(0.0, DoubleUtils.ensureNonNegative(Double.NEGATIVE_INFINITY)); - try { - DoubleUtils.ensureNonNegative(Double.NaN); - fail("Expected IllegalArgumentException from ensureNonNegative(Double.NaN)"); - } catch (IllegalArgumentException expected) { + assertThat(DoubleUtils.ensureNonNegative(positiveValue)).isEqualTo(positiveValue); + assertThat(DoubleUtils.ensureNonNegative(-positiveValue)).isEqualTo(0.0); } + assertThat(DoubleUtils.ensureNonNegative(Double.POSITIVE_INFINITY)).isPositiveInfinity(); + assertThat(DoubleUtils.ensureNonNegative(Double.NEGATIVE_INFINITY)).isEqualTo(0.0); + assertThrows(IllegalArgumentException.class, () -> DoubleUtils.ensureNonNegative(Double.NaN)); } public void testOneBits() { diff --git a/android/guava-tests/test/com/google/common/math/IntMathTest.java b/android/guava-tests/test/com/google/common/math/IntMathTest.java index 12b23e214728..e07bb0deae9c 100644 --- a/android/guava-tests/test/com/google/common/math/IntMathTest.java +++ b/android/guava-tests/test/com/google/common/math/IntMathTest.java @@ -23,26 +23,32 @@ import static com.google.common.math.MathTesting.NEGATIVE_INTEGER_CANDIDATES; import static com.google.common.math.MathTesting.NONZERO_INTEGER_CANDIDATES; import static com.google.common.math.MathTesting.POSITIVE_INTEGER_CANDIDATES; +import static com.google.common.math.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.math.TestPlatform.intsCanGoOutOfRange; +import static java.lang.Math.min; import static java.math.BigInteger.valueOf; +import static java.math.RoundingMode.DOWN; import static java.math.RoundingMode.FLOOR; import static java.math.RoundingMode.UNNECESSARY; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link IntMath}. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class IntMathTest extends TestCase { public void testMaxSignedPowerOfTwo() { assertTrue(IntMath.isPowerOfTwo(IntMath.MAX_SIGNED_POWER_OF_TWO)); @@ -58,11 +64,7 @@ public void testCeilingPowerOfTwo() { if (fitsInInt(expectedResult)) { assertEquals(expectedResult.intValue(), IntMath.ceilingPowerOfTwo(x)); } else { - try { - IntMath.ceilingPowerOfTwo(x); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> IntMath.ceilingPowerOfTwo(x)); } } } @@ -76,46 +78,30 @@ public void testFloorPowerOfTwo() { public void testCeilingPowerOfTwoNegative() { for (int x : NEGATIVE_INTEGER_CANDIDATES) { - try { - IntMath.ceilingPowerOfTwo(x); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.ceilingPowerOfTwo(x)); } } public void testFloorPowerOfTwoNegative() { for (int x : NEGATIVE_INTEGER_CANDIDATES) { - try { - IntMath.floorPowerOfTwo(x); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.floorPowerOfTwo(x)); } } public void testCeilingPowerOfTwoZero() { - try { - IntMath.ceilingPowerOfTwo(0); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.ceilingPowerOfTwo(0)); } public void testFloorPowerOfTwoZero() { - try { - IntMath.floorPowerOfTwo(0); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.floorPowerOfTwo(0)); } @GwtIncompatible // BigIntegerMath // TODO(cpovirk): GWT-enable BigIntegerMath public void testConstantMaxPowerOfSqrt2Unsigned() { assertEquals( - /*expected=*/ BigIntegerMath.sqrt(BigInteger.ZERO.setBit(2 * Integer.SIZE - 1), FLOOR) + /* expected= */ BigIntegerMath.sqrt(BigInteger.ZERO.setBit(2 * Integer.SIZE - 1), FLOOR) .intValue(), - /*actual=*/ IntMath.MAX_POWER_OF_SQRT2_UNSIGNED); + /* actual= */ IntMath.MAX_POWER_OF_SQRT2_UNSIGNED); } @GwtIncompatible // pow() @@ -137,10 +123,11 @@ public void testMaxLog10ForLeadingZeros() { @GwtIncompatible // BigIntegerMath // TODO(cpovirk): GWT-enable BigIntegerMath public void testConstantsHalfPowersOf10() { for (int i = 0; i < IntMath.halfPowersOf10.length; i++) { - assert IntMath.halfPowersOf10[i] - == Math.min( + assertEquals( + IntMath.halfPowersOf10[i], + min( Integer.MAX_VALUE, - BigIntegerMath.sqrt(BigInteger.TEN.pow(2 * i + 1), FLOOR).longValue()); + BigIntegerMath.sqrt(BigInteger.TEN.pow(2 * i + 1), FLOOR).longValue())); } } @@ -162,8 +149,8 @@ public void testConstantsBiggestBinomials() { @GwtIncompatible // sqrt public void testPowersSqrtMaxInt() { assertEquals( - /*expected=*/ IntMath.sqrt(Integer.MAX_VALUE, FLOOR), - /*actual=*/ IntMath.FLOOR_SQRT_MAX_INT); + /* expected= */ IntMath.sqrt(Integer.MAX_VALUE, FLOOR), + /* actual= */ IntMath.FLOOR_SQRT_MAX_INT); } @AndroidIncompatible // presumably slow @@ -191,27 +178,19 @@ public void testIsPowerOfTwo() { public void testLog2ZeroAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - IntMath.log2(0, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.log2(0, mode)); } } public void testLog2NegativeAlwaysThrows() { for (int x : NEGATIVE_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - IntMath.log2(x, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.log2(x, mode)); } } } - // Relies on the correctness of BigIntegrerMath.log2 for all modes except UNNECESSARY. + // Relies on the correctness of BigIntegerMath.log2 for all modes except UNNECESSARY. public void testLog2MatchesBigInteger() { for (int x : POSITIVE_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_SAFE_ROUNDING_MODES) { @@ -237,11 +216,7 @@ public void testLog2Exact() { @GwtIncompatible // log10 public void testLog10ZeroAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - IntMath.log10(0, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.log10(0, mode)); } } @@ -249,11 +224,7 @@ public void testLog10ZeroAlwaysThrows() { public void testLog10NegativeAlwaysThrows() { for (int x : NEGATIVE_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - IntMath.log10(x, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.log10(x, mode)); } } } @@ -304,11 +275,7 @@ public void testSqrtZeroAlwaysZero() { public void testSqrtNegativeAlwaysThrows() { for (int x : NEGATIVE_INTEGER_CANDIDATES) { for (RoundingMode mode : RoundingMode.values()) { - try { - IntMath.sqrt(x, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.sqrt(x, mode)); } } } @@ -332,7 +299,7 @@ public void testSqrtExactMatchesFloorOrThrows() { for (int x : POSITIVE_INTEGER_CANDIDATES) { int floor = IntMath.sqrt(x, FLOOR); // We only expect an exception if x was not a perfect square. - boolean isPerfectSquare = (floor * floor == x); + boolean isPerfectSquare = floor * floor == x; try { assertEquals(floor, IntMath.sqrt(x, UNNECESSARY)); assertTrue(isPerfectSquare); @@ -352,6 +319,7 @@ public void testPow() { } @AndroidIncompatible // slow + @GwtIncompatible // Math.floorDiv gets wrong answers for negative divisors public void testDivNonZero() { for (int p : NONZERO_INTEGER_CANDIDATES) { for (int q : NONZERO_INTEGER_CANDIDATES) { @@ -364,6 +332,12 @@ public void testDivNonZero() { int expected = new BigDecimal(valueOf(p)).divide(new BigDecimal(valueOf(q)), 0, mode).intValue(); assertEquals(p + "/" + q, force32(expected), IntMath.divide(p, q, mode)); + // Check the assertions we make in the javadoc. + if (mode == DOWN) { + assertEquals(p + "/" + q, p / q, IntMath.divide(p, q, mode)); + } else if (mode == FLOOR) { + assertEquals("⌊" + p + "/" + q + "⌋", Math.floorDiv(p, q), IntMath.divide(p, q, mode)); + } } } } @@ -399,11 +373,7 @@ public void testZeroDivIsAlwaysZero() { public void testDivByZeroAlwaysFails() { for (int p : ALL_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - IntMath.divide(p, 0, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> IntMath.divide(p, 0, mode)); } } } @@ -419,22 +389,14 @@ public void testMod() { public void testModNegativeModulusFails() { for (int x : POSITIVE_INTEGER_CANDIDATES) { for (int m : NEGATIVE_INTEGER_CANDIDATES) { - try { - IntMath.mod(x, m); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> IntMath.mod(x, m)); } } } public void testModZeroModulusFails() { for (int x : ALL_INTEGER_CANDIDATES) { - try { - IntMath.mod(x, 0); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> IntMath.mod(x, 0)); } } @@ -456,31 +418,15 @@ public void testGCDZero() { public void testGCDNegativePositiveThrows() { for (int a : NEGATIVE_INTEGER_CANDIDATES) { - try { - IntMath.gcd(a, 3); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - IntMath.gcd(3, a); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.gcd(a, 3)); + assertThrows(IllegalArgumentException.class, () -> IntMath.gcd(3, a)); } } public void testGCDNegativeZeroThrows() { for (int a : NEGATIVE_INTEGER_CANDIDATES) { - try { - IntMath.gcd(a, 0); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - IntMath.gcd(0, a); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.gcd(a, 0)); + assertThrows(IllegalArgumentException.class, () -> IntMath.gcd(0, a)); } } @@ -628,11 +574,7 @@ public void testFactorial() { public void testFactorialNegative() { for (int n : NEGATIVE_INTEGER_CANDIDATES) { - try { - IntMath.factorial(n); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.factorial(n)); } } @@ -648,27 +590,16 @@ public void testBinomial() { } public void testBinomialOutside() { - for (int n = 0; n <= 50; n++) { - try { - IntMath.binomial(n, -1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - IntMath.binomial(n, n + 1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + for (int i = 0; i <= 50; i++) { + int n = i; + assertThrows(IllegalArgumentException.class, () -> IntMath.binomial(n, -1)); + assertThrows(IllegalArgumentException.class, () -> IntMath.binomial(n, n + 1)); } } public void testBinomialNegative() { for (int n : NEGATIVE_INTEGER_CANDIDATES) { - try { - IntMath.binomial(n, 0); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> IntMath.binomial(n, 0)); } } @@ -734,16 +665,16 @@ private static void assertMean(int x, int y) { private static int computeMeanSafely(int x, int y) { BigInteger bigX = BigInteger.valueOf(x); BigInteger bigY = BigInteger.valueOf(y); - BigDecimal bigMean = - new BigDecimal(bigX.add(bigY)).divide(BigDecimal.valueOf(2), BigDecimal.ROUND_FLOOR); - // parseInt blows up on overflow as opposed to intValue() which does not. - return Integer.parseInt(bigMean.toString()); + BigDecimal two = BigDecimal.valueOf(2); // Android doesn't have BigDecimal.TWO yet + BigDecimal bigMean = new BigDecimal(bigX.add(bigY)).divide(two, RoundingMode.FLOOR); + return bigMean.intValueExact(); } private static boolean fitsInInt(BigInteger big) { return big.bitLength() <= 31; } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -768,6 +699,17 @@ public void testIsPrime() { } } + public void testSaturatedAbs() { + assertEquals(Integer.MAX_VALUE, IntMath.saturatedAbs(Integer.MIN_VALUE)); + assertEquals(Integer.MAX_VALUE, IntMath.saturatedAbs(Integer.MAX_VALUE)); + assertEquals(Integer.MAX_VALUE, IntMath.saturatedAbs(-Integer.MAX_VALUE)); + assertEquals(0, IntMath.saturatedAbs(0)); + assertEquals(1, IntMath.saturatedAbs(1)); + assertEquals(1, IntMath.saturatedAbs(-1)); + assertEquals(10, IntMath.saturatedAbs(10)); + assertEquals(10, IntMath.saturatedAbs(-10)); + } + private static int force32(int value) { // GWT doesn't consistently overflow values to make them 32-bit, so we need to force it. return value & 0xffffffff; diff --git a/android/guava-tests/test/com/google/common/math/LinearTransformationTest.java b/android/guava-tests/test/com/google/common/math/LinearTransformationTest.java index 36d5e84329e3..c18b7d6360a5 100644 --- a/android/guava-tests/test/com/google/common/math/LinearTransformationTest.java +++ b/android/guava-tests/test/com/google/common/math/LinearTransformationTest.java @@ -21,14 +21,17 @@ import static com.google.common.math.StatsTesting.assertLinearTransformationNaN; import static com.google.common.math.StatsTesting.assertVerticalLinearTransformation; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link LinearTransformation}. * * @author Pete Gillin */ +@NullUnmarked public class LinearTransformationTest extends TestCase { private static final double ALLOWED_ERROR = 1e-10; @@ -60,79 +63,59 @@ public void testMappingAnd_vertical() { } public void testMapping_infiniteX1() { - try { - LinearTransformation.mapping(Double.POSITIVE_INFINITY, 3.4); - fail("Expected IllegalArgumentException from mapping(x, y) with infinite x"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.mapping(Double.POSITIVE_INFINITY, 3.4)); } public void testMapping_infiniteY1() { - try { - LinearTransformation.mapping(1.2, Double.NEGATIVE_INFINITY); - fail("Expected IllegalArgumentException from mapping(x, y) with infinite y"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.mapping(1.2, Double.NEGATIVE_INFINITY)); } public void testMappingAnd_infiniteX2() { - try { - LinearTransformation.mapping(1.2, 3.4).and(Double.NEGATIVE_INFINITY, 7.8); - fail("Expected IllegalArgumentException from and(x, y) with infinite x"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.mapping(1.2, 3.4).and(Double.NEGATIVE_INFINITY, 7.8)); } public void testMappingAnd_infiniteY2() { - try { - LinearTransformation.mapping(1.2, 3.4).and(5.6, Double.POSITIVE_INFINITY); - fail("Expected IllegalArgumentException from and(x, y) with infinite y"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.mapping(1.2, 3.4).and(5.6, Double.POSITIVE_INFINITY)); } public void testMapping_nanX1() { - try { - LinearTransformation.mapping(Double.NaN, 3.4); - fail("Expected IllegalArgumentException from mapping(x, y) with NaN x"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> LinearTransformation.mapping(Double.NaN, 3.4)); } public void testMapping_nanY1() { - try { - LinearTransformation.mapping(1.2, Double.NaN); - fail("Expected IllegalArgumentException from mapping(x, y) with NaN y"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> LinearTransformation.mapping(1.2, Double.NaN)); } public void testMappingAnd_nanX2() { - try { - LinearTransformation.mapping(1.2, 3.4).and(Double.NaN, 7.8); - fail("Expected IllegalArgumentException from and(x, y) with NaN x"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.mapping(1.2, 3.4).and(Double.NaN, 7.8)); } public void testMappingAnd_nanY2() { - try { - LinearTransformation.mapping(1.2, 3.4).and(5.6, Double.NaN); - fail("Expected IllegalArgumentException from and(x, y) with NaN y"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.mapping(1.2, 3.4).and(5.6, Double.NaN)); } public void testMappingAnd_samePointTwice() { - try { - double x = 1.2; - double y = 3.4; - LinearTransformation.mapping(x, y).and(x, y); - fail( - "Expected IllegalArgumentException from mapping(x1, y1).and(x2, y2) with" - + " (x1 == x2) && (y1 == y2)"); - } catch (IllegalArgumentException expected) { - } + double x = 1.2; + double y = 3.4; + assertThrows( + IllegalArgumentException.class, + () -> { + LinearTransformation.mapping(x, y).and(x, y); + }); } public void testMappingWithSlope_regular() { @@ -184,11 +167,9 @@ public void testMappingWithSlope_maximalSlope() { } public void testMappingWithSlope_nanSlope() { - try { - LinearTransformation.mapping(1.2, 3.4).withSlope(Double.NaN); - fail("Expected IllegalArgumentException from withSlope(slope) with NaN slope"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.mapping(1.2, 3.4).withSlope(Double.NaN)); } public void testVertical_regular() { @@ -198,19 +179,13 @@ public void testVertical_regular() { } public void testVertical_infiniteX() { - try { - LinearTransformation.vertical(Double.NEGATIVE_INFINITY); - fail("Expected IllegalArgumentException from vertical(x) with infinite x"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.vertical(Double.NEGATIVE_INFINITY)); } public void testVertical_nanX() { - try { - LinearTransformation.vertical(Double.NaN); - fail("Expected IllegalArgumentException from vertical(x) with NaN x"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LinearTransformation.vertical(Double.NaN)); } public void testHorizontal_regular() { @@ -220,19 +195,13 @@ public void testHorizontal_regular() { } public void testHorizontal_infiniteY() { - try { - LinearTransformation.horizontal(Double.POSITIVE_INFINITY); - fail("Expected IllegalArgumentException from horizontal(y) with infinite y"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> LinearTransformation.horizontal(Double.POSITIVE_INFINITY)); } public void testHorizontal_nanY() { - try { - LinearTransformation.horizontal(Double.NaN); - fail("Expected IllegalArgumentException from horizontal(y) with NaN y"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LinearTransformation.horizontal(Double.NaN)); } public void testForNaN() { diff --git a/android/guava-tests/test/com/google/common/math/LongMathTest.java b/android/guava-tests/test/com/google/common/math/LongMathTest.java index 40c2ac9efa28..0d1b60955afd 100644 --- a/android/guava-tests/test/com/google/common/math/LongMathTest.java +++ b/android/guava-tests/test/com/google/common/math/LongMathTest.java @@ -25,14 +25,17 @@ import static com.google.common.math.MathTesting.NONZERO_LONG_CANDIDATES; import static com.google.common.math.MathTesting.POSITIVE_INTEGER_CANDIDATES; import static com.google.common.math.MathTesting.POSITIVE_LONG_CANDIDATES; +import static com.google.common.math.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.math.BigInteger.valueOf; +import static java.math.RoundingMode.DOWN; import static java.math.RoundingMode.FLOOR; import static java.math.RoundingMode.UNNECESSARY; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import java.math.BigDecimal; import java.math.BigInteger; @@ -40,13 +43,15 @@ import java.util.EnumSet; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for LongMath. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class LongMathTest extends TestCase { @SuppressWarnings("ConstantOverflow") public void testMaxSignedPowerOfTwo() { @@ -60,11 +65,7 @@ public void testCeilingPowerOfTwo() { if (fitsInLong(expectedResult)) { assertEquals(expectedResult.longValue(), LongMath.ceilingPowerOfTwo(x)); } else { - try { - LongMath.ceilingPowerOfTwo(x); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> LongMath.ceilingPowerOfTwo(x)); } } } @@ -78,46 +79,30 @@ public void testFloorPowerOfTwo() { public void testCeilingPowerOfTwoNegative() { for (long x : NEGATIVE_LONG_CANDIDATES) { - try { - LongMath.ceilingPowerOfTwo(x); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.ceilingPowerOfTwo(x)); } } public void testFloorPowerOfTwoNegative() { for (long x : NEGATIVE_LONG_CANDIDATES) { - try { - LongMath.floorPowerOfTwo(x); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.floorPowerOfTwo(x)); } } public void testCeilingPowerOfTwoZero() { - try { - LongMath.ceilingPowerOfTwo(0L); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.ceilingPowerOfTwo(0L)); } public void testFloorPowerOfTwoZero() { - try { - LongMath.floorPowerOfTwo(0L); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.floorPowerOfTwo(0L)); } @GwtIncompatible // TODO public void testConstantMaxPowerOfSqrt2Unsigned() { assertEquals( - /*expected=*/ BigIntegerMath.sqrt(BigInteger.ZERO.setBit(2 * Long.SIZE - 1), FLOOR) + /* expected= */ BigIntegerMath.sqrt(BigInteger.ZERO.setBit(2 * Long.SIZE - 1), FLOOR) .longValue(), - /*actual=*/ LongMath.MAX_POWER_OF_SQRT2_UNSIGNED); + /* actual= */ LongMath.MAX_POWER_OF_SQRT2_UNSIGNED); } @GwtIncompatible // BigIntegerMath // TODO(cpovirk): GWT-enable BigIntegerMath @@ -134,11 +119,8 @@ public void testConstantsPowersOf10() { for (int i = 0; i < LongMath.powersOf10.length; i++) { assertEquals(LongMath.checkedPow(10, i), LongMath.powersOf10[i]); } - try { - LongMath.checkedPow(10, LongMath.powersOf10.length); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, () -> LongMath.checkedPow(10, LongMath.powersOf10.length)); } @GwtIncompatible // TODO @@ -150,14 +132,14 @@ public void testConstantsHalfPowersOf10() { } BigInteger nextBigger = BigIntegerMath.sqrt(BigInteger.TEN.pow(2 * LongMath.halfPowersOf10.length + 1), FLOOR); - assertTrue(nextBigger.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0); + assertThat(nextBigger).isGreaterThan(BigInteger.valueOf(Long.MAX_VALUE)); } @GwtIncompatible // TODO public void testConstantsSqrtMaxLong() { assertEquals( - /*expected=*/ LongMath.sqrt(Long.MAX_VALUE, FLOOR), - /*actual=*/ LongMath.FLOOR_SQRT_MAX_LONG); + /* expected= */ LongMath.sqrt(Long.MAX_VALUE, FLOOR), + /* actual= */ LongMath.FLOOR_SQRT_MAX_LONG); } @GwtIncompatible // TODO @@ -166,12 +148,11 @@ public void testConstantsFactorials() { for (int i = 0; i < LongMath.factorials.length; i++, expected *= i) { assertEquals(expected, LongMath.factorials[i]); } - try { - LongMath.checkedMultiply( - LongMath.factorials[LongMath.factorials.length - 1], LongMath.factorials.length); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expect) { - } + assertThrows( + ArithmeticException.class, + () -> + LongMath.checkedMultiply( + LongMath.factorials[LongMath.factorials.length - 1], LongMath.factorials.length)); } @GwtIncompatible // TODO @@ -191,25 +172,19 @@ public void testConstantsBiggestBinomials() { @GwtIncompatible // TODO public void testConstantsBiggestSimpleBinomials() { - for (int k = 0; k < LongMath.biggestSimpleBinomials.length; k++) { + for (int i = 0; i < LongMath.biggestSimpleBinomials.length; i++) { + int k = i; assertTrue(LongMath.biggestSimpleBinomials[k] <= LongMath.biggestBinomials[k]); long unused = simpleBinomial(LongMath.biggestSimpleBinomials[k], k); // mustn't throw if (LongMath.biggestSimpleBinomials[k] < Integer.MAX_VALUE) { // unless all n are fair game with this k - try { - simpleBinomial(LongMath.biggestSimpleBinomials[k] + 1, k); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, + () -> simpleBinomial(LongMath.biggestSimpleBinomials[k] + 1, k)); } } - try { - int k = LongMath.biggestSimpleBinomials.length; - simpleBinomial(2 * k, k); - // 2 * k is the smallest value for which we don't replace k with (n-k). - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + int k = LongMath.biggestSimpleBinomials.length; + assertThrows(ArithmeticException.class, () -> simpleBinomial(2 * k, k)); } @AndroidIncompatible // slow @@ -249,22 +224,14 @@ public void testIsPowerOfTwo() { public void testLog2ZeroAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - LongMath.log2(0L, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.log2(0L, mode)); } } public void testLog2NegativeAlwaysThrows() { for (long x : NEGATIVE_LONG_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - LongMath.log2(x, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.log2(x, mode)); } } } @@ -296,11 +263,7 @@ public void testLog2Exact() { @GwtIncompatible // TODO public void testLog10ZeroAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - LongMath.log10(0L, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.log10(0L, mode)); } } @@ -308,11 +271,7 @@ public void testLog10ZeroAlwaysThrows() { public void testLog10NegativeAlwaysThrows() { for (long x : NEGATIVE_LONG_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - LongMath.log10(x, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.log10(x, mode)); } } } @@ -356,11 +315,7 @@ public void testLog10TrivialOnPowerOf10() { public void testSqrtNegativeAlwaysThrows() { for (long x : NEGATIVE_LONG_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - LongMath.sqrt(x, mode); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.sqrt(x, mode)); } } } @@ -383,7 +338,7 @@ public void testSqrtExactMatchesFloorOrThrows() { for (long x : POSITIVE_LONG_CANDIDATES) { long sqrtFloor = LongMath.sqrt(x, FLOOR); // We only expect an exception if x was not a perfect square. - boolean isPerfectSquare = (sqrtFloor * sqrtFloor == x); + boolean isPerfectSquare = sqrtFloor * sqrtFloor == x; try { assertEquals(sqrtFloor, LongMath.sqrt(x, UNNECESSARY)); assertTrue(isPerfectSquare); @@ -402,6 +357,7 @@ public void testPow() { } } + @J2ktIncompatible // J2kt BigDecimal.divide also has the rounding bug @GwtIncompatible // TODO @AndroidIncompatible // TODO(cpovirk): File BigDecimal.divide() rounding bug. public void testDivNonZero() { @@ -414,6 +370,12 @@ public void testDivNonZero() { if (expected != actual) { failFormat("expected divide(%s, %s, %s) = %s; got %s", p, q, mode, expected, actual); } + // Check the assertions we make in the javadoc. + if (mode == DOWN) { + assertEquals(p / q, LongMath.divide(p, q, mode)); + } else if (mode == FLOOR) { + assertEquals(Math.floorDiv(p, q), LongMath.divide(p, q, mode)); + } } } } @@ -452,11 +414,7 @@ public void testZeroDivIsAlwaysZero() { public void testDivByZeroAlwaysFails() { for (long p : ALL_LONG_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { - try { - LongMath.divide(p, 0L, mode); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> LongMath.divide(p, 0L, mode)); } } } @@ -474,11 +432,7 @@ public void testIntMod() { public void testIntModNegativeModulusFails() { for (long x : ALL_LONG_CANDIDATES) { for (int m : NEGATIVE_INTEGER_CANDIDATES) { - try { - LongMath.mod(x, m); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> LongMath.mod(x, m)); } } } @@ -486,11 +440,7 @@ public void testIntModNegativeModulusFails() { @GwtIncompatible // TODO public void testIntModZeroModulusFails() { for (long x : ALL_LONG_CANDIDATES) { - try { - LongMath.mod(x, 0); - fail("Expected AE"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> LongMath.mod(x, 0)); } } @@ -508,11 +458,7 @@ public void testMod() { public void testModNegativeModulusFails() { for (long x : ALL_LONG_CANDIDATES) { for (long m : NEGATIVE_LONG_CANDIDATES) { - try { - LongMath.mod(x, m); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows(ArithmeticException.class, () -> LongMath.mod(x, m)); } } } @@ -537,37 +483,20 @@ public void testGCDZero() { @GwtIncompatible // TODO public void testGCDNegativePositiveThrows() { for (long a : NEGATIVE_LONG_CANDIDATES) { - try { - LongMath.gcd(a, 3); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - LongMath.gcd(3, a); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.gcd(a, 3)); + assertThrows(IllegalArgumentException.class, () -> LongMath.gcd(3, a)); } } @GwtIncompatible // TODO public void testGCDNegativeZeroThrows() { for (long a : NEGATIVE_LONG_CANDIDATES) { - try { - LongMath.gcd(a, 0); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - LongMath.gcd(0, a); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.gcd(a, 0)); + assertThrows(IllegalArgumentException.class, () -> LongMath.gcd(0, a)); } } @AndroidIncompatible // slow - @GwtIncompatible // TODO public void testCheckedAdd() { for (long a : ALL_LONG_CANDIDATES) { for (long b : ALL_LONG_CANDIDATES) { @@ -586,7 +515,6 @@ public void testCheckedAdd() { } } - @GwtIncompatible // TODO @AndroidIncompatible // slow public void testCheckedSubtract() { for (long a : ALL_LONG_CANDIDATES) { @@ -728,11 +656,7 @@ public void testFactorial() { @GwtIncompatible // TODO public void testFactorialNegative() { for (int n : NEGATIVE_INTEGER_CANDIDATES) { - try { - LongMath.factorial(n); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.factorial(n)); } } @@ -760,36 +684,26 @@ public void testBinomial_exhaustiveNotOverflowing() { } public void testBinomialOutside() { - for (int n = 0; n <= 50; n++) { - try { - LongMath.binomial(n, -1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - LongMath.binomial(n, n + 1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + for (int i = 0; i <= 50; i++) { + int n = i; + assertThrows(IllegalArgumentException.class, () -> LongMath.binomial(n, -1)); + assertThrows(IllegalArgumentException.class, () -> LongMath.binomial(n, n + 1)); } } public void testBinomialNegative() { for (int n : NEGATIVE_INTEGER_CANDIDATES) { - try { - LongMath.binomial(n, 0); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.binomial(n, 0)); } } + @J2ktIncompatible // slow enough to cause flakiness @GwtIncompatible // far too slow public void testSqrtOfPerfectSquareAsDoubleIsPerfect() { // This takes just over a minute on my machine. for (long n = 0; n <= LongMath.FLOOR_SQRT_MAX_LONG; n++) { - long actual = (long) Math.sqrt(n * n); + long actual = (long) Math.sqrt((double) (n * n)); assertTrue(actual == n); } } @@ -861,10 +775,9 @@ private static void assertMean(long x, long y) { private static long computeMeanSafely(long x, long y) { BigInteger bigX = BigInteger.valueOf(x); BigInteger bigY = BigInteger.valueOf(y); - BigDecimal bigMean = - new BigDecimal(bigX.add(bigY)).divide(BigDecimal.valueOf(2), BigDecimal.ROUND_FLOOR); - // parseInt blows up on overflow as opposed to intValue() which does not. - return Long.parseLong(bigMean.toString()); + BigDecimal two = BigDecimal.valueOf(2); // Android doesn't have BigDecimal.TWO yet + BigDecimal bigMean = new BigDecimal(bigX.add(bigY)).divide(two, RoundingMode.FLOOR); + return bigMean.longValueExact(); } private static boolean fitsInLong(BigInteger big) { @@ -884,6 +797,7 @@ private static long saturatedCast(BigInteger big) { return big.longValue(); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNullPointers() { NullPointerTester tester = new NullPointerTester(); @@ -946,11 +860,7 @@ public void testIsPrimeOnRandomComposites() { @GwtIncompatible // isPrime is GWT-incompatible public void testIsPrimeThrowsOnNegative() { - try { - LongMath.isPrime(-1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> LongMath.isPrime(-1)); } private static final long[] roundToDoubleTestCandidates = { @@ -989,6 +899,7 @@ public void testIsPrimeThrowsOnNegative() { Long.MIN_VALUE }; + @J2ktIncompatible // EnumSet.complementOf @GwtIncompatible public void testRoundToDoubleAgainstBigInteger() { for (RoundingMode roundingMode : EnumSet.complementOf(EnumSet.of(UNNECESSARY))) { @@ -1012,16 +923,23 @@ public void testRoundToDoubleAgainstBigIntegerUnnecessary() { if (expectedDouble != null) { assertThat(LongMath.roundToDouble(candidate, UNNECESSARY)).isEqualTo(expectedDouble); } else { - try { - LongMath.roundToDouble(candidate, UNNECESSARY); - fail("Expected ArithmeticException on roundToDouble(" + candidate + ", UNNECESSARY)"); - } catch (ArithmeticException expected) { - // success - } + assertThrows( + ArithmeticException.class, () -> LongMath.roundToDouble(candidate, UNNECESSARY)); } } } + public void testSaturatedAbs() { + assertEquals(Long.MAX_VALUE, LongMath.saturatedAbs(Long.MIN_VALUE)); + assertEquals(Long.MAX_VALUE, LongMath.saturatedAbs(Long.MAX_VALUE)); + assertEquals(Long.MAX_VALUE, LongMath.saturatedAbs(-Long.MAX_VALUE)); + assertEquals(0, LongMath.saturatedAbs(0)); + assertEquals(1, LongMath.saturatedAbs(1)); + assertEquals(1, LongMath.saturatedAbs(-1)); + assertEquals(10, LongMath.saturatedAbs(10)); + assertEquals(10, LongMath.saturatedAbs(-10)); + } + private static void failFormat(String template, Object... args) { assertWithMessage(template, args).fail(); } diff --git a/android/guava-tests/test/com/google/common/math/MathBenchmarking.java b/android/guava-tests/test/com/google/common/math/MathBenchmarking.java index 0c2aecfbba33..507c2adc878e 100644 --- a/android/guava-tests/test/com/google/common/math/MathBenchmarking.java +++ b/android/guava-tests/test/com/google/common/math/MathBenchmarking.java @@ -18,6 +18,7 @@ import java.math.BigInteger; import java.util.Random; +import org.jspecify.annotations.NullUnmarked; /** * Utilities for benchmarks. @@ -28,6 +29,7 @@ * * @author Louis Wasserman */ +@NullUnmarked final class MathBenchmarking { static final int ARRAY_SIZE = 0x10000; static final int ARRAY_MASK = 0x0ffff; @@ -147,4 +149,6 @@ static int randomExponent() { static double randomPositiveDouble() { return Math.exp(randomDouble(6)); } + + private MathBenchmarking() {} } diff --git a/android/guava-tests/test/com/google/common/math/MathPreconditionsTest.java b/android/guava-tests/test/com/google/common/math/MathPreconditionsTest.java index 69719e013572..8f7b396d2c2c 100644 --- a/android/guava-tests/test/com/google/common/math/MathPreconditionsTest.java +++ b/android/guava-tests/test/com/google/common/math/MathPreconditionsTest.java @@ -16,12 +16,14 @@ package com.google.common.math; +import static com.google.common.math.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import java.math.BigInteger; import java.math.RoundingMode; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link MathPreconditions}. @@ -29,14 +31,11 @@ * @author Ben Yu */ @GwtCompatible +@NullUnmarked public class MathPreconditionsTest extends TestCase { public void testCheckPositive_zeroInt() { - try { - MathPreconditions.checkPositive("int", 0); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MathPreconditions.checkPositive("int", 0)); } public void testCheckPositive_maxInt() { @@ -44,11 +43,9 @@ public void testCheckPositive_maxInt() { } public void testCheckPositive_minInt() { - try { - MathPreconditions.checkPositive("int", Integer.MIN_VALUE); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkPositive("int", Integer.MIN_VALUE)); } public void testCheckPositive_positiveInt() { @@ -56,19 +53,11 @@ public void testCheckPositive_positiveInt() { } public void testCheckPositive_negativeInt() { - try { - MathPreconditions.checkPositive("int", -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MathPreconditions.checkPositive("int", -1)); } public void testCheckPositive_zeroLong() { - try { - MathPreconditions.checkPositive("long", 0L); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MathPreconditions.checkPositive("long", 0L)); } public void testCheckPositive_maxLong() { @@ -76,11 +65,9 @@ public void testCheckPositive_maxLong() { } public void testCheckPositive_minLong() { - try { - MathPreconditions.checkPositive("long", Long.MIN_VALUE); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkPositive("long", Long.MIN_VALUE)); } public void testCheckPositive_positiveLong() { @@ -88,31 +75,24 @@ public void testCheckPositive_positiveLong() { } public void testCheckPositive_negativeLong() { - try { - MathPreconditions.checkPositive("long", -1L); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> MathPreconditions.checkPositive("long", -1L)); } public void testCheckPositive_zeroBigInteger() { - try { - MathPreconditions.checkPositive("BigInteger", BigInteger.ZERO); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkPositive("BigInteger", BigInteger.ZERO)); } - public void testCheckPositive_postiveBigInteger() { + public void testCheckPositive_positiveBigInteger() { MathPreconditions.checkPositive("BigInteger", BigInteger.ONE); } public void testCheckPositive_negativeBigInteger() { - try { - MathPreconditions.checkPositive("BigInteger", BigInteger.ZERO.negate()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkPositive("BigInteger", BigInteger.ZERO.negate())); } public void testCheckNonNegative_zeroInt() { @@ -124,11 +104,9 @@ public void testCheckNonNegative_maxInt() { } public void testCheckNonNegative_minInt() { - try { - MathPreconditions.checkNonNegative("int", Integer.MIN_VALUE); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkNonNegative("int", Integer.MIN_VALUE)); } public void testCheckNonNegative_positiveInt() { @@ -136,11 +114,8 @@ public void testCheckNonNegative_positiveInt() { } public void testCheckNonNegative_negativeInt() { - try { - MathPreconditions.checkNonNegative("int", -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> MathPreconditions.checkNonNegative("int", -1)); } public void testCheckNonNegative_zeroLong() { @@ -152,11 +127,9 @@ public void testCheckNonNegative_maxLong() { } public void testCheckNonNegative_minLong() { - try { - MathPreconditions.checkNonNegative("long", Long.MIN_VALUE); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkNonNegative("long", Long.MIN_VALUE)); } public void testCheckNonNegative_positiveLong() { @@ -164,11 +137,8 @@ public void testCheckNonNegative_positiveLong() { } public void testCheckNonNegative_negativeLong() { - try { - MathPreconditions.checkNonNegative("int", -1L); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> MathPreconditions.checkNonNegative("int", -1L)); } public void testCheckNonNegative_zeroBigInteger() { @@ -180,11 +150,9 @@ public void testCheckNonNegative_positiveBigInteger() { } public void testCheckNonNegative_negativeBigInteger() { - try { - MathPreconditions.checkNonNegative("int", BigInteger.ONE.negate()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkNonNegative("int", BigInteger.ONE.negate())); } public void testCheckNonNegative_zeroFloat() { @@ -204,19 +172,14 @@ public void testCheckNonNegative_positiveFloat() { } public void testCheckNonNegative_negativeFloat() { - try { - MathPreconditions.checkNonNegative("float", -1f); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> MathPreconditions.checkNonNegative("float", -1f)); } public void testCheckNonNegative_nanFloat() { - try { - MathPreconditions.checkNonNegative("float", Float.NaN); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkNonNegative("float", Float.NaN)); } public void testCheckNonNegative_zeroDouble() { @@ -236,31 +199,23 @@ public void testCheckNonNegative_positiveDouble() { } public void testCheckNonNegative_negativeDouble() { - try { - MathPreconditions.checkNonNegative("double", -1d); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> MathPreconditions.checkNonNegative("double", -1d)); } public void testCheckNonNegative_nanDouble() { - try { - MathPreconditions.checkNonNegative("double", Double.NaN); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> MathPreconditions.checkNonNegative("double", Double.NaN)); } - public void testCheckRoundingUnnnecessary_success() { + public void testCheckRoundingUnnecessary_success() { MathPreconditions.checkRoundingUnnecessary(true); } public void testCheckRoundingUnnecessary_failure() { - try { - MathPreconditions.checkRoundingUnnecessary(false); - fail(); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, () -> MathPreconditions.checkRoundingUnnecessary(false)); } public void testCheckInRange_success() { @@ -268,13 +223,12 @@ public void testCheckInRange_success() { } public void testCheckInRange_failure() { - try { - MathPreconditions.checkInRangeForRoundingInputs(false, 1.0, RoundingMode.UP); - fail(); - } catch (ArithmeticException expected) { - assertThat(expected).hasMessageThat().contains("1.0"); - assertThat(expected).hasMessageThat().contains("UP"); - } + ArithmeticException expected = + assertThrows( + ArithmeticException.class, + () -> MathPreconditions.checkInRangeForRoundingInputs(false, 1.0, RoundingMode.UP)); + assertThat(expected).hasMessageThat().contains("1.0"); + assertThat(expected).hasMessageThat().contains("UP"); } public void testCheckNoOverflow_success() { @@ -282,11 +236,21 @@ public void testCheckNoOverflow_success() { } public void testCheckNoOverflow_failure() { - try { - MathPreconditions.checkNoOverflow(false, "testCheckNoOverflow_failure", 0, 0); - fail(); - } catch (ArithmeticException expected) { - assertThat(expected).hasMessageThat().contains("testCheckNoOverflow_failure(0, 0)"); - } + ArithmeticException expected = + assertThrows( + ArithmeticException.class, + () -> MathPreconditions.checkNoOverflow(false, "testCheckNoOverflow_failure", 0, 0)); + assertThat(expected).hasMessageThat().contains("testCheckNoOverflow_failure(0, 0)"); + } + + public void testNulls() { + /* + * Don't bother testing. All non-primitive parameters are used only to construct error messages. + * We never want to pass null for them, so we haven't annotated them to say that null is + * allowed. But at the same time, it seems wasteful to bother inserting the checkNotNull calls + * that NullPointerTester wants. + * + * (This empty method disables the automatic null testing provided by PackageSanityTests.) + */ } } diff --git a/android/guava-tests/test/com/google/common/math/MathTesting.java b/android/guava-tests/test/com/google/common/math/MathTesting.java index 6b74f11ee89e..c1a80c89aca3 100644 --- a/android/guava-tests/test/com/google/common/math/MathTesting.java +++ b/android/guava-tests/test/com/google/common/math/MathTesting.java @@ -36,6 +36,7 @@ import com.google.common.primitives.Doubles; import java.math.BigInteger; import java.math.RoundingMode; +import org.jspecify.annotations.NullUnmarked; /** * Exhaustive input sets for every integral type. @@ -43,7 +44,8 @@ * @author Louis Wasserman */ @GwtCompatible -public class MathTesting { +@NullUnmarked +public final class MathTesting { static final ImmutableSet ALL_ROUNDING_MODES = ImmutableSet.copyOf(RoundingMode.values()); @@ -150,7 +152,7 @@ public BigInteger apply(BigInteger x) { static { ImmutableSet.Builder longValues = ImmutableSet.builder(); - // First of all add all the integer candidate values. + // First add all the integer candidate values. longValues.addAll(Iterables.transform(POSITIVE_INTEGER_CANDIDATES, TO_LONG)); // Add boundary values manually to avoid over/under flow (this covers 2^N for 31 and 63). longValues.add(Integer.MAX_VALUE + 1L, Long.MAX_VALUE - 1L, Long.MAX_VALUE); @@ -185,7 +187,7 @@ public BigInteger apply(BigInteger x) { static { ImmutableSet.Builder bigValues = ImmutableSet.builder(); - // First of all add all the long candidate values. + // First add all the long candidate values. bigValues.addAll(Iterables.transform(POSITIVE_LONG_CANDIDATES, TO_BIGINTEGER)); // Add boundary values manually to avoid over/under flow. bigValues.add(BigInteger.valueOf(Long.MAX_VALUE).add(ONE)); @@ -299,4 +301,6 @@ public boolean apply(Double input) { DOUBLE_CANDIDATES_EXCEPT_NAN = Iterables.concat(FINITE_DOUBLE_CANDIDATES, INFINITIES); ALL_DOUBLE_CANDIDATES = Iterables.concat(DOUBLE_CANDIDATES_EXCEPT_NAN, asList(Double.NaN)); } + + private MathTesting() {} } diff --git a/android/guava-tests/test/com/google/common/math/PackageSanityTests.java b/android/guava-tests/test/com/google/common/math/PackageSanityTests.java index 1fc7cc7c23bf..a46527b6c2c2 100644 --- a/android/guava-tests/test/com/google/common/math/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/math/PackageSanityTests.java @@ -17,6 +17,7 @@ package com.google.common.math; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** * Basic sanity tests for the entire package. @@ -24,6 +25,7 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() { publicApiOnly(); diff --git a/android/guava-tests/test/com/google/common/math/PairedStatsAccumulatorTest.java b/android/guava-tests/test/com/google/common/math/PairedStatsAccumulatorTest.java index 34f82e9076ce..326ea0c70ea1 100644 --- a/android/guava-tests/test/com/google/common/math/PairedStatsAccumulatorTest.java +++ b/android/guava-tests/test/com/google/common/math/PairedStatsAccumulatorTest.java @@ -44,10 +44,12 @@ import static com.google.common.math.StatsTesting.createPartitionedFilledPairedStatsAccumulator; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.assertThrows; import com.google.common.math.StatsTesting.ManyValues; import java.util.Collections; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link PairedStatsAccumulator}. This tests the stats methods for instances built with @@ -57,6 +59,7 @@ * * @author Pete Gillin */ +@NullUnmarked public class PairedStatsAccumulatorTest extends TestCase { private PairedStatsAccumulator emptyAccumulator; @@ -174,20 +177,12 @@ public void testYStats() { } public void testPopulationCovariance() { - try { - emptyAccumulator.populationCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyPairedStats.populationCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - assertThat(oneValueAccumulator.populationCovariance()).isWithin(0.0).of(0.0); - assertThat(oneValueAccumulatorByAddAllEmptyPairedStats.populationCovariance()) - .isWithin(0.0) - .of(0.0); + assertThrows(IllegalStateException.class, () -> emptyAccumulator.populationCovariance()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyPairedStats.populationCovariance()); + assertThat(oneValueAccumulator.populationCovariance()).isEqualTo(0.0); + assertThat(oneValueAccumulatorByAddAllEmptyPairedStats.populationCovariance()).isEqualTo(0.0); assertThat(twoValuesAccumulator.populationCovariance()) .isWithin(ALLOWED_ERROR) .of(TWO_VALUES_SUM_OF_PRODUCTS_OF_DELTAS / 2); @@ -241,26 +236,14 @@ public void testPopulationCovariance() { } public void testSampleCovariance() { - try { - emptyAccumulator.sampleCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyPairedStats.sampleCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulator.sampleCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulatorByAddAllEmptyPairedStats.sampleCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> emptyAccumulator.sampleCovariance()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyPairedStats.sampleCovariance()); + assertThrows(IllegalStateException.class, () -> oneValueAccumulator.sampleCovariance()); + assertThrows( + IllegalStateException.class, + () -> oneValueAccumulatorByAddAllEmptyPairedStats.sampleCovariance()); assertThat(twoValuesAccumulator.sampleCovariance()) .isWithin(ALLOWED_ERROR) .of(TWO_VALUES_SUM_OF_PRODUCTS_OF_DELTAS); @@ -288,26 +271,16 @@ public void testSampleCovariance() { } public void testPearsonsCorrelationCoefficient() { - try { - emptyAccumulator.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyPairedStats.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulator.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulatorByAddAllEmptyPairedStats.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows( + IllegalStateException.class, () -> emptyAccumulator.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyPairedStats.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, () -> oneValueAccumulator.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> oneValueAccumulatorByAddAllEmptyPairedStats.pearsonsCorrelationCoefficient()); assertThat(twoValuesAccumulator.pearsonsCorrelationCoefficient()) .isWithin(ALLOWED_ERROR) .of( @@ -368,59 +341,41 @@ public void testPearsonsCorrelationCoefficient() { .populationStandardDeviation())); } } - try { - horizontalValuesAccumulator.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - horizontalValuesAccumulatorByAddAllPartitionedPairedStats.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - verticalValuesAccumulator.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - verticalValuesAccumulatorByAddAllPartitionedPairedStats.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - constantValuesAccumulator.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - constantValuesAccumulatorByAddAllPartitionedPairedStats.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows( + IllegalStateException.class, + () -> horizontalValuesAccumulator.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> + horizontalValuesAccumulatorByAddAllPartitionedPairedStats + .pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> verticalValuesAccumulator.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> + verticalValuesAccumulatorByAddAllPartitionedPairedStats + .pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> constantValuesAccumulator.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> + constantValuesAccumulatorByAddAllPartitionedPairedStats + .pearsonsCorrelationCoefficient()); } public void testLeastSquaresFit() { - try { - emptyAccumulator.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyPairedStats.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulator.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulatorByAddAllEmptyPairedStats.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> emptyAccumulator.leastSquaresFit()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyPairedStats.leastSquaresFit()); + assertThrows(IllegalStateException.class, () -> oneValueAccumulator.leastSquaresFit()); + assertThrows( + IllegalStateException.class, + () -> oneValueAccumulatorByAddAllEmptyPairedStats.leastSquaresFit()); assertDiagonalLinearTransformation( twoValuesAccumulator.leastSquaresFit(), twoValuesAccumulator.xStats().mean(), @@ -483,15 +438,9 @@ public void testLeastSquaresFit() { assertVerticalLinearTransformation( verticalValuesAccumulatorByAddAllPartitionedPairedStats.leastSquaresFit(), verticalValuesAccumulatorByAddAllPartitionedPairedStats.xStats().mean()); - try { - constantValuesAccumulator.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - constantValuesAccumulatorByAddAllPartitionedPairedStats.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> constantValuesAccumulator.leastSquaresFit()); + assertThrows( + IllegalStateException.class, + () -> constantValuesAccumulatorByAddAllPartitionedPairedStats.leastSquaresFit()); } } diff --git a/android/guava-tests/test/com/google/common/math/PairedStatsTest.java b/android/guava-tests/test/com/google/common/math/PairedStatsTest.java index 7dd9e94d68d7..307ed451fefc 100644 --- a/android/guava-tests/test/com/google/common/math/PairedStatsTest.java +++ b/android/guava-tests/test/com/google/common/math/PairedStatsTest.java @@ -48,6 +48,7 @@ import static com.google.common.math.StatsTesting.createPairedStatsOf; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.math.StatsTesting.ManyValues; @@ -56,6 +57,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link PairedStats}. This tests instances created by {@link @@ -63,6 +65,7 @@ * * @author Pete Gillin */ +@NullUnmarked public class PairedStatsTest extends TestCase { public void testCount() { @@ -87,12 +90,8 @@ public void testYStats() { } public void testPopulationCovariance() { - try { - EMPTY_PAIRED_STATS.populationCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - assertThat(ONE_VALUE_PAIRED_STATS.populationCovariance()).isWithin(0.0).of(0.0); + assertThrows(IllegalStateException.class, () -> EMPTY_PAIRED_STATS.populationCovariance()); + assertThat(ONE_VALUE_PAIRED_STATS.populationCovariance()).isEqualTo(0.0); assertThat(createSingleStats(Double.POSITIVE_INFINITY, 1.23).populationCovariance()).isNaN(); assertThat(createSingleStats(Double.NEGATIVE_INFINITY, 1.23).populationCovariance()).isNaN(); assertThat(createSingleStats(Double.NaN, 1.23).populationCovariance()).isNaN(); @@ -121,16 +120,8 @@ public void testPopulationCovariance() { } public void testSampleCovariance() { - try { - EMPTY_PAIRED_STATS.sampleCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - ONE_VALUE_PAIRED_STATS.sampleCovariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> EMPTY_PAIRED_STATS.sampleCovariance()); + assertThrows(IllegalStateException.class, () -> ONE_VALUE_PAIRED_STATS.sampleCovariance()); assertThat(TWO_VALUES_PAIRED_STATS.sampleCovariance()) .isWithin(ALLOWED_ERROR) .of(TWO_VALUES_SUM_OF_PRODUCTS_OF_DELTAS); @@ -143,21 +134,13 @@ public void testSampleCovariance() { } public void testPearsonsCorrelationCoefficient() { - try { - EMPTY_PAIRED_STATS.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - ONE_VALUE_PAIRED_STATS.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - createSingleStats(Double.POSITIVE_INFINITY, 1.23).pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows( + IllegalStateException.class, () -> EMPTY_PAIRED_STATS.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, () -> ONE_VALUE_PAIRED_STATS.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> createSingleStats(Double.POSITIVE_INFINITY, 1.23).pearsonsCorrelationCoefficient()); assertThat(TWO_VALUES_PAIRED_STATS.pearsonsCorrelationCoefficient()) .isWithin(ALLOWED_ERROR) .of( @@ -183,39 +166,23 @@ public void testPearsonsCorrelationCoefficient() { * stats.yStats().populationStandardDeviation())); } } - try { - HORIZONTAL_VALUES_PAIRED_STATS.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - VERTICAL_VALUES_PAIRED_STATS.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - CONSTANT_VALUES_PAIRED_STATS.pearsonsCorrelationCoefficient(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows( + IllegalStateException.class, + () -> HORIZONTAL_VALUES_PAIRED_STATS.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> VERTICAL_VALUES_PAIRED_STATS.pearsonsCorrelationCoefficient()); + assertThrows( + IllegalStateException.class, + () -> CONSTANT_VALUES_PAIRED_STATS.pearsonsCorrelationCoefficient()); } public void testLeastSquaresFit() { - try { - EMPTY_PAIRED_STATS.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - ONE_VALUE_PAIRED_STATS.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - createSingleStats(Double.POSITIVE_INFINITY, 1.23).leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> EMPTY_PAIRED_STATS.leastSquaresFit()); + assertThrows(IllegalStateException.class, () -> ONE_VALUE_PAIRED_STATS.leastSquaresFit()); + assertThrows( + IllegalStateException.class, + () -> createSingleStats(Double.POSITIVE_INFINITY, 1.23).leastSquaresFit()); assertDiagonalLinearTransformation( TWO_VALUES_PAIRED_STATS.leastSquaresFit(), TWO_VALUES_PAIRED_STATS.xStats().mean(), @@ -244,11 +211,7 @@ public void testLeastSquaresFit() { assertVerticalLinearTransformation( VERTICAL_VALUES_PAIRED_STATS.leastSquaresFit(), VERTICAL_VALUES_PAIRED_STATS.xStats().mean()); - try { - CONSTANT_VALUES_PAIRED_STATS.leastSquaresFit(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> CONSTANT_VALUES_PAIRED_STATS.leastSquaresFit()); } public void testEqualsAndHashCode() { @@ -303,19 +266,11 @@ public void testToByteArrayAndFromByteArrayRoundTrip() { } public void testFromByteArray_withNullInputThrowsNullPointerException() { - try { - PairedStats.fromByteArray(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> PairedStats.fromByteArray(null)); } public void testFromByteArray_withEmptyArrayInputThrowsIllegalArgumentException() { - try { - PairedStats.fromByteArray(new byte[0]); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> PairedStats.fromByteArray(new byte[0])); } public void testFromByteArray_withTooLongArrayInputThrowsIllegalArgumentException() { @@ -326,11 +281,7 @@ public void testFromByteArray_withTooLongArrayInputThrowsIllegalArgumentExceptio .put(buffer) .putChar('.') .array(); - try { - PairedStats.fromByteArray(tooLongByteArray); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> PairedStats.fromByteArray(tooLongByteArray)); } public void testFromByteArrayWithTooShortArrayInputThrowsIllegalArgumentException() { @@ -340,10 +291,7 @@ public void testFromByteArrayWithTooShortArrayInputThrowsIllegalArgumentExceptio .order(ByteOrder.LITTLE_ENDIAN) .put(buffer, 0, buffer.length - 1) .array(); - try { - PairedStats.fromByteArray(tooShortByteArray); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> PairedStats.fromByteArray(tooShortByteArray)); } } diff --git a/android/guava-tests/test/com/google/common/math/QuantilesAlgorithm.java b/android/guava-tests/test/com/google/common/math/QuantilesAlgorithm.java index 54d310f0d2d6..9a6fe1d39cd4 100644 --- a/android/guava-tests/test/com/google/common/math/QuantilesAlgorithm.java +++ b/android/guava-tests/test/com/google/common/math/QuantilesAlgorithm.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.NullUnmarked; /** * Enumerates several algorithms providing equivalent functionality to {@link Quantiles}, for use in @@ -31,6 +32,7 @@ * @author Pete Gillin * @since 20.0 */ +@NullUnmarked enum QuantilesAlgorithm { /** @@ -53,7 +55,7 @@ Map multipleQuantiles( for (int index : indexes) { builder.put(index, singleQuantileFromSorted(index, scale, dataset)); } - return builder.build(); + return builder.buildOrThrow(); } private double singleQuantileFromSorted(int index, int scale, double[] dataset) { @@ -97,7 +99,7 @@ Map multipleQuantiles( for (int index : indexes) { builder.put(index, singleQuantile(index, scale, dataset)); } - return builder.build(); + return builder.buildOrThrow(); } }, diff --git a/android/guava-tests/test/com/google/common/math/QuantilesAlgorithmTest.java b/android/guava-tests/test/com/google/common/math/QuantilesAlgorithmTest.java index 87a962a61299..6a913ace1e68 100644 --- a/android/guava-tests/test/com/google/common/math/QuantilesAlgorithmTest.java +++ b/android/guava-tests/test/com/google/common/math/QuantilesAlgorithmTest.java @@ -23,22 +23,25 @@ import com.google.common.collect.Sets; import java.util.Map; import java.util.Random; -import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests that the different algorithms benchmarked in {@link QuantilesBenchmark} are actually all * returning more-or-less the same answers. */ +@NullUnmarked public class QuantilesAlgorithmTest extends TestCase { - private static final Random RNG = new Random(82674067L); + private static final Random rng = new Random(82674067L); private static final int DATASET_SIZE = 1000; private static final double ALLOWED_ERROR = 1.0e-10; private static final QuantilesAlgorithm REFERENCE_ALGORITHM = QuantilesAlgorithm.SORTING; - private static final Set NON_REFERENCE_ALGORITHMS = + private static final ImmutableSet NON_REFERENCE_ALGORITHMS = Sets.difference( - ImmutableSet.copyOf(QuantilesAlgorithm.values()), ImmutableSet.of(REFERENCE_ALGORITHM)); + ImmutableSet.copyOf(QuantilesAlgorithm.values()), + ImmutableSet.of(REFERENCE_ALGORITHM)) + .immutableCopy(); private double[] dataset; @@ -46,7 +49,7 @@ public class QuantilesAlgorithmTest extends TestCase { protected void setUp() { dataset = new double[DATASET_SIZE]; for (int i = 0; i < DATASET_SIZE; i++) { - dataset[i] = RNG.nextDouble(); + dataset[i] = rng.nextDouble(); } } diff --git a/android/guava-tests/test/com/google/common/math/QuantilesTest.java b/android/guava-tests/test/com/google/common/math/QuantilesTest.java index 5ac5f6e303e6..64f74e486c45 100644 --- a/android/guava-tests/test/com/google/common/math/QuantilesTest.java +++ b/android/guava-tests/test/com/google/common/math/QuantilesTest.java @@ -27,6 +27,7 @@ import static java.math.RoundingMode.CEILING; import static java.math.RoundingMode.FLOOR; import static java.math.RoundingMode.UNNECESSARY; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -42,13 +43,15 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link Quantiles}. * * @author Pete Gillin */ +@NullUnmarked public class QuantilesTest extends TestCase { /* @@ -92,7 +95,7 @@ public class QuantilesTest extends TestCase { Correspondence.from( new BinaryPredicate() { @Override - public boolean apply(@NullableDecl Double actual, @NullableDecl Double expected) { + public boolean apply(@Nullable Double actual, @Nullable Double expected) { // Test for equality to allow non-finite values to match; otherwise, use the finite // test. return actual.equals(expected) @@ -424,12 +427,12 @@ public void testScale_indexes_varargs_compute_doubleCollection_positiveInfinity( 1, 1.5, 2, 2.0, 8, 5.0, - 9, POSITIVE_INFINITY, // interpolating between 5.0 and POSITIVE_INFNINITY + 9, POSITIVE_INFINITY, // interpolating between 5.0 and POSITIVE_INFINITY 10, POSITIVE_INFINITY); } public void testScale_index_compute_doubleCollection_positiveInfinity() { - // interpolating between 5.0 and POSITIVE_INFNINITY + // interpolating between 5.0 and POSITIVE_INFINITY assertThat(Quantiles.scale(10).index(9).compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY)) .isPositiveInfinity(); } @@ -442,7 +445,7 @@ public void testScale_indexes_varargs_compute_doubleCollection_negativeInfinity( .comparingValuesUsing(QUANTILE_CORRESPONDENCE) .containsExactly( 0, NEGATIVE_INFINITY, - 1, NEGATIVE_INFINITY, // interpolating between NEGATIVE_INFNINITY and 1.0 + 1, NEGATIVE_INFINITY, // interpolating between NEGATIVE_INFINITY and 1.0 2, 1.0, 8, 4.0, 9, 4.5, @@ -450,7 +453,7 @@ public void testScale_indexes_varargs_compute_doubleCollection_negativeInfinity( } public void testScale_index_compute_doubleCollection_negativeInfinity() { - // interpolating between NEGATIVE_INFNINITY and 1.0 + // interpolating between NEGATIVE_INFINITY and 1.0 assertThat(Quantiles.scale(10).index(1).compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY)) .isNegativeInfinity(); } @@ -540,7 +543,7 @@ public void testPercentiles_index_computeInPlace() { // Assert that the dataset contains the same elements after the in-place computation (although // they may be reordered). We only do this for one index rather than for all indexes, as it is - // quite expensives (quadratic in the size of PSEUDORANDOM_DATASET). + // quite expensive (quadratic in the size of PSEUDORANDOM_DATASET). double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET); @SuppressWarnings("unused") double actual = percentiles().index(33).computeInPlace(dataset); @@ -557,7 +560,7 @@ public void testPercentiles_indexes_varargsPairs_compute_doubleCollection() { } assertThat(percentiles().indexes(index1, index2).compute(PSEUDORANDOM_DATASET)) .comparingValuesUsing(QUANTILE_CORRESPONDENCE) - .containsExactlyEntriesIn(expectedBuilder.build()); + .containsExactlyEntriesIn(expectedBuilder.buildOrThrow()); } } } @@ -573,7 +576,7 @@ public void testPercentiles_indexes_varargsAll_compute_doubleCollection() { Collections.shuffle(indexes, random); assertThat(percentiles().indexes(Ints.toArray(indexes)).compute(PSEUDORANDOM_DATASET)) .comparingValuesUsing(QUANTILE_CORRESPONDENCE) - .containsExactlyEntriesIn(expectedBuilder.build()); + .containsExactlyEntriesIn(expectedBuilder.buildOrThrow()); } @AndroidIncompatible // slow @@ -589,7 +592,7 @@ public void testPercentiles_indexes_varargsAll_computeInPlace() { Collections.shuffle(indexes, random); assertThat(percentiles().indexes(Ints.toArray(indexes)).computeInPlace(dataset)) .comparingValuesUsing(QUANTILE_CORRESPONDENCE) - .containsExactlyEntriesIn(expectedBuilder.build()); + .containsExactlyEntriesIn(expectedBuilder.buildOrThrow()); assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET); } @@ -598,171 +601,103 @@ public void testPercentiles_indexes_varargsAll_computeInPlace() { private static final ImmutableList EMPTY_DATASET = ImmutableList.of(); public void testScale_zero() { - try { - Quantiles.scale(0); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Quantiles.scale(0)); } public void testScale_negative() { - try { - Quantiles.scale(-4); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Quantiles.scale(-4)); } public void testScale_index_negative() { Quantiles.Scale intermediate = Quantiles.scale(10); - try { - intermediate.index(-1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.index(-1)); } public void testScale_index_tooHigh() { Quantiles.Scale intermediate = Quantiles.scale(10); - try { - intermediate.index(11); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.index(11)); } public void testScale_indexes_varargs_negative() { Quantiles.Scale intermediate = Quantiles.scale(10); - try { - intermediate.indexes(1, -1, 3); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.indexes(1, -1, 3)); } public void testScale_indexes_varargs_tooHigh() { Quantiles.Scale intermediate = Quantiles.scale(10); - try { - intermediate.indexes(1, 11, 3); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.indexes(1, 11, 3)); } public void testScale_indexes_collection_negative() { Quantiles.Scale intermediate = Quantiles.scale(10); - try { - intermediate.indexes(ImmutableList.of(1, -1, 3)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> intermediate.indexes(ImmutableList.of(1, -1, 3))); } public void testScale_indexes_collection_tooHigh() { Quantiles.Scale intermediate = Quantiles.scale(10); - try { - intermediate.indexes(ImmutableList.of(1, 11, 3)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> intermediate.indexes(ImmutableList.of(1, 11, 3))); } public void testScale_index_compute_doubleCollection_empty() { Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); - try { - intermediate.compute(EMPTY_DATASET); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.compute(EMPTY_DATASET)); } public void testScale_index_compute_doubleVarargs_empty() { Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); - try { - intermediate.compute(new double[] {}); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new double[] {})); } public void testScale_index_compute_longVarargs_empty() { Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); - try { - intermediate.compute(new long[] {}); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new long[] {})); } public void testScale_index_compute_intVarargs_empty() { Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); - try { - intermediate.compute(new int[] {}); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new int[] {})); } public void testScale_index_computeInPlace_empty() { Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); - try { - intermediate.computeInPlace(new double[] {}); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> intermediate.computeInPlace(new double[] {})); } public void testScale_indexes_varargs_compute_doubleCollection_empty() { Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); - try { - intermediate.compute(EMPTY_DATASET); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.compute(EMPTY_DATASET)); } public void testScale_indexes_varargs_compute_doubleVarargs_empty() { Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); - try { - intermediate.compute(new double[] {}); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new double[] {})); } public void testScale_indexes_varargs_compute_longVarargs_empty() { Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); - try { - intermediate.compute(new long[] {}); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new long[] {})); } public void testScale_indexes_varargs_compute_intVarargs_empty() { Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); - try { - intermediate.compute(new int[] {}); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new int[] {})); } public void testScale_indexes_varargs_computeInPlace_empty() { Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); - try { - intermediate.computeInPlace(new double[] {}); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> intermediate.computeInPlace(new double[] {})); } public void testScale_indexes_indexes_computeInPlace_empty() { int[] emptyIndexes = {}; - try { - Quantiles.ScaleAndIndexes unused = Quantiles.scale(10).indexes(emptyIndexes); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> { + Quantiles.ScaleAndIndexes unused = Quantiles.scale(10).indexes(emptyIndexes); + }); } } diff --git a/android/guava-tests/test/com/google/common/math/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/math/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..cbc87e2f9aeb --- /dev/null +++ b/android/guava-tests/test/com/google/common/math/ReflectionFreeAssertThrows.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/math/StatsAccumulatorTest.java b/android/guava-tests/test/com/google/common/math/StatsAccumulatorTest.java index 6926a69c6cf3..7a160315b710 100644 --- a/android/guava-tests/test/com/google/common/math/StatsAccumulatorTest.java +++ b/android/guava-tests/test/com/google/common/math/StatsAccumulatorTest.java @@ -36,6 +36,11 @@ import static com.google.common.math.StatsTesting.MANY_VALUES_MEAN; import static com.google.common.math.StatsTesting.MANY_VALUES_MIN; import static com.google.common.math.StatsTesting.MANY_VALUES_SUM_OF_SQUARES_OF_DELTAS; +import static com.google.common.math.StatsTesting.MEGA_STREAM_COUNT; +import static com.google.common.math.StatsTesting.MEGA_STREAM_MAX; +import static com.google.common.math.StatsTesting.MEGA_STREAM_MEAN; +import static com.google.common.math.StatsTesting.MEGA_STREAM_MIN; +import static com.google.common.math.StatsTesting.MEGA_STREAM_POPULATION_VARIANCE; import static com.google.common.math.StatsTesting.ONE_VALUE; import static com.google.common.math.StatsTesting.OTHER_ONE_VALUE; import static com.google.common.math.StatsTesting.TWO_VALUES; @@ -43,15 +48,21 @@ import static com.google.common.math.StatsTesting.TWO_VALUES_MEAN; import static com.google.common.math.StatsTesting.TWO_VALUES_MIN; import static com.google.common.math.StatsTesting.TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS; +import static com.google.common.math.StatsTesting.megaPrimitiveDoubleStream; +import static com.google.common.math.StatsTesting.megaPrimitiveDoubleStreamPart1; +import static com.google.common.math.StatsTesting.megaPrimitiveDoubleStreamPart2; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.lang.Math.sqrt; +import static java.util.stream.DoubleStream.concat; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.math.StatsTesting.ManyValues; import com.google.common.primitives.Doubles; import com.google.common.primitives.Longs; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link StatsAccumulator}. This tests the stats methods for instances built with {@link @@ -61,6 +72,7 @@ * * @author Pete Gillin */ +@NullUnmarked public class StatsAccumulatorTest extends TestCase { private StatsAccumulator emptyAccumulator; @@ -188,21 +200,9 @@ public void testCountOverflow_doesNotThrow() { } public void testMean() { - try { - emptyAccumulator.mean(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyIterable.mean(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyStats.mean(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> emptyAccumulator.mean()); + assertThrows(IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyIterable.mean()); + assertThrows(IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyStats.mean()); assertThat(oneValueAccumulator.mean()).isWithin(ALLOWED_ERROR).of(ONE_VALUE); assertThat(oneValueAccumulatorByAddAllEmptyStats.mean()).isWithin(ALLOWED_ERROR).of(ONE_VALUE); assertThat(twoValuesAccumulator.mean()).isWithin(ALLOWED_ERROR).of(TWO_VALUES_MEAN); @@ -280,9 +280,9 @@ public void testMean() { } public void testSum() { - assertThat(emptyAccumulator.sum()).isWithin(0.0).of(0.0); - assertThat(emptyAccumulatorByAddAllEmptyIterable.sum()).isWithin(0.0).of(0.0); - assertThat(emptyAccumulatorByAddAllEmptyStats.sum()).isWithin(0.0).of(0.0); + assertThat(emptyAccumulator.sum()).isEqualTo(0.0); + assertThat(emptyAccumulatorByAddAllEmptyIterable.sum()).isEqualTo(0.0); + assertThat(emptyAccumulatorByAddAllEmptyStats.sum()).isEqualTo(0.0); assertThat(oneValueAccumulator.sum()).isWithin(ALLOWED_ERROR).of(ONE_VALUE); assertThat(oneValueAccumulatorByAddAllEmptyStats.sum()).isWithin(ALLOWED_ERROR).of(ONE_VALUE); assertThat(twoValuesAccumulator.sum()).isWithin(ALLOWED_ERROR).of(TWO_VALUES_MEAN * 2); @@ -322,23 +322,14 @@ public void testSum() { } public void testPopulationVariance() { - try { - emptyAccumulator.populationVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyIterable.populationVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyStats.populationVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - assertThat(oneValueAccumulator.populationVariance()).isWithin(0.0).of(0.0); - assertThat(oneValueAccumulatorByAddAllEmptyStats.populationVariance()).isWithin(0.0).of(0.0); + assertThrows(IllegalStateException.class, () -> emptyAccumulator.populationVariance()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyIterable.populationVariance()); + assertThrows( + IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyStats.populationVariance()); + assertThat(oneValueAccumulator.populationVariance()).isEqualTo(0.0); + assertThat(oneValueAccumulatorByAddAllEmptyStats.populationVariance()).isEqualTo(0.0); assertThat(twoValuesAccumulator.populationVariance()) .isWithin(ALLOWED_ERROR) .of(TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS / 2); @@ -405,25 +396,15 @@ public void testPopulationVariance() { } public void testPopulationStandardDeviation() { - try { - emptyAccumulator.populationStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyIterable.populationStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyStats.populationStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - assertThat(oneValueAccumulator.populationStandardDeviation()).isWithin(0.0).of(0.0); - assertThat(oneValueAccumulatorByAddAllEmptyStats.populationStandardDeviation()) - .isWithin(0.0) - .of(0.0); + assertThrows(IllegalStateException.class, () -> emptyAccumulator.populationStandardDeviation()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyIterable.populationStandardDeviation()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyStats.populationStandardDeviation()); + assertThat(oneValueAccumulator.populationStandardDeviation()).isEqualTo(0.0); + assertThat(oneValueAccumulatorByAddAllEmptyStats.populationStandardDeviation()).isEqualTo(0.0); assertThat(twoValuesAccumulator.populationStandardDeviation()) .isWithin(ALLOWED_ERROR) .of(sqrt(TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS / 2)); @@ -463,31 +444,14 @@ public void testPopulationStandardDeviation() { } public void testSampleVariance() { - try { - emptyAccumulator.sampleVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyIterable.sampleVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyStats.sampleVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulator.sampleVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulatorByAddAllEmptyStats.sampleVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> emptyAccumulator.sampleVariance()); + assertThrows( + IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyIterable.sampleVariance()); + assertThrows( + IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyStats.sampleVariance()); + assertThrows(IllegalStateException.class, () -> oneValueAccumulator.sampleVariance()); + assertThrows( + IllegalStateException.class, () -> oneValueAccumulatorByAddAllEmptyStats.sampleVariance()); assertThat(twoValuesAccumulator.sampleVariance()) .isWithin(ALLOWED_ERROR) .of(TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS); @@ -527,31 +491,17 @@ public void testSampleVariance() { } public void testSampleStandardDeviation() { - try { - emptyAccumulator.sampleStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyIterable.sampleStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyStats.sampleStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulator.sampleStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - oneValueAccumulatorByAddAllEmptyStats.sampleStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> emptyAccumulator.sampleStandardDeviation()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyIterable.sampleStandardDeviation()); + assertThrows( + IllegalStateException.class, + () -> emptyAccumulatorByAddAllEmptyStats.sampleStandardDeviation()); + assertThrows(IllegalStateException.class, () -> oneValueAccumulator.sampleStandardDeviation()); + assertThrows( + IllegalStateException.class, + () -> oneValueAccumulatorByAddAllEmptyStats.sampleStandardDeviation()); assertThat(twoValuesAccumulator.sampleStandardDeviation()) .isWithin(ALLOWED_ERROR) .of(sqrt(TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS)); @@ -591,21 +541,9 @@ public void testSampleStandardDeviation() { } public void testMax() { - try { - emptyAccumulator.max(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyIterable.max(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyStats.max(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> emptyAccumulator.max()); + assertThrows(IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyIterable.max()); + assertThrows(IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyStats.max()); assertThat(oneValueAccumulator.max()).isEqualTo(ONE_VALUE); assertThat(oneValueAccumulatorByAddAllEmptyStats.max()).isEqualTo(ONE_VALUE); assertThat(twoValuesAccumulator.max()).isEqualTo(TWO_VALUES_MAX); @@ -650,21 +588,9 @@ public void testMax() { } public void testMin() { - try { - emptyAccumulator.min(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyIterable.min(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - emptyAccumulatorByAddAllEmptyStats.min(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> emptyAccumulator.min()); + assertThrows(IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyIterable.min()); + assertThrows(IllegalStateException.class, () -> emptyAccumulatorByAddAllEmptyStats.min()); assertThat(oneValueAccumulator.min()).isEqualTo(ONE_VALUE); assertThat(oneValueAccumulatorByAddAllEmptyStats.min()).isEqualTo(ONE_VALUE); assertThat(twoValuesAccumulator.min()).isEqualTo(TWO_VALUES_MIN); @@ -707,4 +633,51 @@ public void testMin() { assertThat(longManyValuesAccumulatorByAddAllIterator.min()).isEqualTo(LONG_MANY_VALUES_MIN); assertThat(longManyValuesAccumulatorByAddAllVarargs.min()).isEqualTo(LONG_MANY_VALUES_MIN); } + + public void testVerifyMegaStreamHalves() { + assertThat( + concat(megaPrimitiveDoubleStreamPart1(), megaPrimitiveDoubleStreamPart2()) + .sorted() + .toArray()) + .isEqualTo(megaPrimitiveDoubleStream().toArray()); + } + + public void testAddAllPrimitiveDoubleStream() { + StatsAccumulator accumulator = new StatsAccumulator(); + accumulator.addAll(megaPrimitiveDoubleStreamPart1()); + accumulator.addAll(megaPrimitiveDoubleStreamPart2()); + assertThat(accumulator.count()).isEqualTo(MEGA_STREAM_COUNT); + assertThat(accumulator.mean()).isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT).of(MEGA_STREAM_MEAN); + assertThat(accumulator.populationVariance()) + .isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT) + .of(MEGA_STREAM_POPULATION_VARIANCE); + assertThat(accumulator.min()).isEqualTo(MEGA_STREAM_MIN); + assertThat(accumulator.max()).isEqualTo(MEGA_STREAM_MAX); + } + + public void testAddAllPrimitiveIntStream() { + StatsAccumulator accumulator = new StatsAccumulator(); + accumulator.addAll(megaPrimitiveDoubleStreamPart1().mapToInt(x -> (int) x)); + accumulator.addAll(megaPrimitiveDoubleStreamPart2().mapToInt(x -> (int) x)); + assertThat(accumulator.count()).isEqualTo(MEGA_STREAM_COUNT); + assertThat(accumulator.mean()).isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT).of(MEGA_STREAM_MEAN); + assertThat(accumulator.populationVariance()) + .isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT) + .of(MEGA_STREAM_POPULATION_VARIANCE); + assertThat(accumulator.min()).isEqualTo(MEGA_STREAM_MIN); + assertThat(accumulator.max()).isEqualTo(MEGA_STREAM_MAX); + } + + public void testAddAllPrimitiveLongStream() { + StatsAccumulator accumulator = new StatsAccumulator(); + accumulator.addAll(megaPrimitiveDoubleStreamPart1().mapToLong(x -> (long) x)); + accumulator.addAll(megaPrimitiveDoubleStreamPart2().mapToLong(x -> (long) x)); + assertThat(accumulator.count()).isEqualTo(MEGA_STREAM_COUNT); + assertThat(accumulator.mean()).isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT).of(MEGA_STREAM_MEAN); + assertThat(accumulator.populationVariance()) + .isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT) + .of(MEGA_STREAM_POPULATION_VARIANCE); + assertThat(accumulator.min()).isEqualTo(MEGA_STREAM_MIN); + assertThat(accumulator.max()).isEqualTo(MEGA_STREAM_MAX); + } } diff --git a/android/guava-tests/test/com/google/common/math/StatsTest.java b/android/guava-tests/test/com/google/common/math/StatsTest.java index 76de5b55c739..252066589ce1 100644 --- a/android/guava-tests/test/com/google/common/math/StatsTest.java +++ b/android/guava-tests/test/com/google/common/math/StatsTest.java @@ -16,6 +16,7 @@ package com.google.common.math; +import static com.google.common.math.Stats.toStats; import static com.google.common.math.StatsTesting.ALLOWED_ERROR; import static com.google.common.math.StatsTesting.ALL_MANY_VALUES; import static com.google.common.math.StatsTesting.ALL_STATS; @@ -55,6 +56,11 @@ import static com.google.common.math.StatsTesting.MANY_VALUES_STATS_SNAPSHOT; import static com.google.common.math.StatsTesting.MANY_VALUES_STATS_VARARGS; import static com.google.common.math.StatsTesting.MANY_VALUES_SUM_OF_SQUARES_OF_DELTAS; +import static com.google.common.math.StatsTesting.MEGA_STREAM_COUNT; +import static com.google.common.math.StatsTesting.MEGA_STREAM_MAX; +import static com.google.common.math.StatsTesting.MEGA_STREAM_MEAN; +import static com.google.common.math.StatsTesting.MEGA_STREAM_MIN; +import static com.google.common.math.StatsTesting.MEGA_STREAM_POPULATION_VARIANCE; import static com.google.common.math.StatsTesting.ONE_VALUE; import static com.google.common.math.StatsTesting.ONE_VALUE_STATS; import static com.google.common.math.StatsTesting.TWO_VALUES; @@ -63,12 +69,15 @@ import static com.google.common.math.StatsTesting.TWO_VALUES_MIN; import static com.google.common.math.StatsTesting.TWO_VALUES_STATS; import static com.google.common.math.StatsTesting.TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS; +import static com.google.common.math.StatsTesting.megaPrimitiveDoubleStream; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.lang.Double.NEGATIVE_INFINITY; import static java.lang.Double.NaN; import static java.lang.Double.POSITIVE_INFINITY; import static java.lang.Math.sqrt; +import static java.util.Arrays.stream; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.math.StatsTesting.ManyValues; @@ -76,9 +85,12 @@ import com.google.common.primitives.Longs; import com.google.common.testing.EqualsTester; import com.google.common.testing.SerializableTester; +import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.DoubleSummaryStatistics; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Stats}. This tests instances created by both {@link Stats#of} and {@link @@ -86,6 +98,7 @@ * * @author Pete Gillin */ +@NullUnmarked public class StatsTest extends TestCase { public void testCount() { @@ -104,16 +117,8 @@ public void testCount() { } public void testMean() { - try { - EMPTY_STATS_VARARGS.mean(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - EMPTY_STATS_ITERABLE.mean(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_VARARGS.mean()); + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_ITERABLE.mean()); assertThat(ONE_VALUE_STATS.mean()).isWithin(ALLOWED_ERROR).of(ONE_VALUE); assertThat(Stats.of(POSITIVE_INFINITY).mean()).isPositiveInfinity(); assertThat(Stats.of(NEGATIVE_INFINITY).mean()).isNegativeInfinity(); @@ -196,17 +201,9 @@ public void testSum() { } public void testPopulationVariance() { - try { - EMPTY_STATS_VARARGS.populationVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - EMPTY_STATS_ITERABLE.populationVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - assertThat(ONE_VALUE_STATS.populationVariance()).isWithin(0.0).of(0.0); + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_VARARGS.populationVariance()); + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_ITERABLE.populationVariance()); + assertThat(ONE_VALUE_STATS.populationVariance()).isEqualTo(0.0); assertThat(Stats.of(POSITIVE_INFINITY).populationVariance()).isNaN(); assertThat(Stats.of(NEGATIVE_INFINITY).populationVariance()).isNaN(); assertThat(Stats.of(NaN).populationVariance()).isNaN(); @@ -256,17 +253,11 @@ public void testPopulationVariance() { } public void testPopulationStandardDeviation() { - try { - EMPTY_STATS_VARARGS.populationStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - EMPTY_STATS_ITERABLE.populationStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - assertThat(ONE_VALUE_STATS.populationStandardDeviation()).isWithin(0.0).of(0.0); + assertThrows( + IllegalStateException.class, () -> EMPTY_STATS_VARARGS.populationStandardDeviation()); + assertThrows( + IllegalStateException.class, () -> EMPTY_STATS_ITERABLE.populationStandardDeviation()); + assertThat(ONE_VALUE_STATS.populationStandardDeviation()).isEqualTo(0.0); assertThat(TWO_VALUES_STATS.populationStandardDeviation()) .isWithin(ALLOWED_ERROR) .of(sqrt(TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS / 2)); @@ -297,21 +288,9 @@ public void testPopulationStandardDeviation() { } public void testSampleVariance() { - try { - EMPTY_STATS_VARARGS.sampleVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - EMPTY_STATS_ITERABLE.sampleVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - ONE_VALUE_STATS.sampleVariance(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_VARARGS.sampleVariance()); + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_ITERABLE.sampleVariance()); + assertThrows(IllegalStateException.class, () -> ONE_VALUE_STATS.sampleVariance()); assertThat(TWO_VALUES_STATS.sampleVariance()) .isWithin(ALLOWED_ERROR) .of(TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS); @@ -342,21 +321,9 @@ public void testSampleVariance() { } public void testSampleStandardDeviation() { - try { - EMPTY_STATS_VARARGS.sampleStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - EMPTY_STATS_ITERABLE.sampleStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - ONE_VALUE_STATS.sampleStandardDeviation(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_VARARGS.sampleStandardDeviation()); + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_ITERABLE.sampleStandardDeviation()); + assertThrows(IllegalStateException.class, () -> ONE_VALUE_STATS.sampleStandardDeviation()); assertThat(TWO_VALUES_STATS.sampleStandardDeviation()) .isWithin(ALLOWED_ERROR) .of(sqrt(TWO_VALUES_SUM_OF_SQUARES_OF_DELTAS)); @@ -387,16 +354,8 @@ public void testSampleStandardDeviation() { } public void testMax() { - try { - EMPTY_STATS_VARARGS.max(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - EMPTY_STATS_ITERABLE.max(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_VARARGS.max()); + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_ITERABLE.max()); assertThat(ONE_VALUE_STATS.max()).isEqualTo(ONE_VALUE); assertThat(Stats.of(POSITIVE_INFINITY).max()).isPositiveInfinity(); assertThat(Stats.of(NEGATIVE_INFINITY).max()).isNegativeInfinity(); @@ -424,16 +383,8 @@ public void testMax() { } public void testMin() { - try { - EMPTY_STATS_VARARGS.min(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } - try { - EMPTY_STATS_ITERABLE.min(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_VARARGS.min()); + assertThrows(IllegalStateException.class, () -> EMPTY_STATS_ITERABLE.min()); assertThat(ONE_VALUE_STATS.min()).isEqualTo(ONE_VALUE); assertThat(Stats.of(POSITIVE_INFINITY).min()).isPositiveInfinity(); assertThat(Stats.of(NEGATIVE_INFINITY).min()).isNegativeInfinity(); @@ -462,6 +413,39 @@ public void testMin() { assertThat(LONG_MANY_VALUES_STATS_SNAPSHOT.min()).isEqualTo(LONG_MANY_VALUES_MIN); } + public void testOfPrimitiveDoubleStream() { + Stats stats = Stats.of(megaPrimitiveDoubleStream()); + assertThat(stats.count()).isEqualTo(MEGA_STREAM_COUNT); + assertThat(stats.mean()).isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT).of(MEGA_STREAM_MEAN); + assertThat(stats.populationVariance()) + .isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT) + .of(MEGA_STREAM_POPULATION_VARIANCE); + assertThat(stats.min()).isEqualTo(MEGA_STREAM_MIN); + assertThat(stats.max()).isEqualTo(MEGA_STREAM_MAX); + } + + public void testOfPrimitiveIntStream() { + Stats stats = Stats.of(megaPrimitiveDoubleStream().mapToInt(x -> (int) x)); + assertThat(stats.count()).isEqualTo(MEGA_STREAM_COUNT); + assertThat(stats.mean()).isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT).of(MEGA_STREAM_MEAN); + assertThat(stats.populationVariance()) + .isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT) + .of(MEGA_STREAM_POPULATION_VARIANCE); + assertThat(stats.min()).isEqualTo(MEGA_STREAM_MIN); + assertThat(stats.max()).isEqualTo(MEGA_STREAM_MAX); + } + + public void testOfPrimitiveLongStream() { + Stats stats = Stats.of(megaPrimitiveDoubleStream().mapToLong(x -> (long) x)); + assertThat(stats.count()).isEqualTo(MEGA_STREAM_COUNT); + assertThat(stats.mean()).isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT).of(MEGA_STREAM_MEAN); + assertThat(stats.populationVariance()) + .isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT) + .of(MEGA_STREAM_POPULATION_VARIANCE); + assertThat(stats.min()).isEqualTo(MEGA_STREAM_MIN); + assertThat(stats.max()).isEqualTo(MEGA_STREAM_MAX); + } + public void testEqualsAndHashCode() { new EqualsTester() .addEqualityGroup( @@ -505,16 +489,8 @@ public void testToString() { } public void testMeanOf() { - try { - Stats.meanOf(); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } - try { - Stats.meanOf(ImmutableList.of()); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Stats.meanOf()); + assertThrows(IllegalArgumentException.class, () -> Stats.meanOf(ImmutableList.of())); assertThat(Stats.meanOf(ONE_VALUE)).isWithin(ALLOWED_ERROR).of(ONE_VALUE); assertThat(Stats.meanOf(POSITIVE_INFINITY)).isPositiveInfinity(); assertThat(Stats.meanOf(NEGATIVE_INFINITY)).isNegativeInfinity(); @@ -565,19 +541,11 @@ public void testToByteArrayAndFromByteArrayRoundTrip() { } public void testFromByteArray_withNullInputThrowsNullPointerException() { - try { - Stats.fromByteArray(null); - fail("Expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Stats.fromByteArray(null)); } public void testFromByteArray_withEmptyArrayInputThrowsIllegalArgumentException() { - try { - Stats.fromByteArray(new byte[0]); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Stats.fromByteArray(new byte[0])); } public void testFromByteArray_withTooLongArrayInputThrowsIllegalArgumentException() { @@ -588,11 +556,7 @@ public void testFromByteArray_withTooLongArrayInputThrowsIllegalArgumentExceptio .put(buffer) .putChar('.') .array(); - try { - Stats.fromByteArray(tooLongByteArray); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Stats.fromByteArray(tooLongByteArray)); } public void testFromByteArrayWithTooShortArrayInputThrowsIllegalArgumentException() { @@ -602,10 +566,64 @@ public void testFromByteArrayWithTooShortArrayInputThrowsIllegalArgumentExceptio .order(ByteOrder.LITTLE_ENDIAN) .put(buffer, 0, Stats.BYTES - 1) .array(); - try { - Stats.fromByteArray(tooShortByteArray); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { + assertThrows(IllegalArgumentException.class, () -> Stats.fromByteArray(tooShortByteArray)); + } + + public void testEquivalentStreams() { + // For datasets of many double values created from an array, we test many combinations of finite + // and non-finite values: + for (ManyValues values : ALL_MANY_VALUES) { + double[] array = values.asArray(); + Stats stats = Stats.of(array); + // instance methods on Stats vs on instance methods on DoubleStream + assertThat(stats.count()).isEqualTo(stream(array).count()); + assertEquivalent(stats.mean(), stream(array).average().getAsDouble()); + assertEquivalent(stats.sum(), stream(array).sum()); + assertEquivalent(stats.max(), stream(array).max().getAsDouble()); + assertEquivalent(stats.min(), stream(array).min().getAsDouble()); + // static method on Stats vs on instance method on DoubleStream + assertEquivalent(Stats.meanOf(array), stream(array).average().getAsDouble()); + // instance methods on Stats vs instance methods on DoubleSummaryStatistics + DoubleSummaryStatistics streamStats = stream(array).summaryStatistics(); + assertThat(stats.count()).isEqualTo(streamStats.getCount()); + assertEquivalent(stats.mean(), streamStats.getAverage()); + assertEquivalent(stats.sum(), streamStats.getSum()); + assertEquivalent(stats.max(), streamStats.getMax()); + assertEquivalent(stats.min(), streamStats.getMin()); + } + } + + private static void assertEquivalent(double actual, double expected) { + if (expected == POSITIVE_INFINITY) { + assertThat(actual).isPositiveInfinity(); + } else if (expected == NEGATIVE_INFINITY) { + assertThat(actual).isNegativeInfinity(); + } else if (Double.isNaN(expected)) { + assertThat(actual).isNaN(); + } else { + assertThat(actual).isWithin(ALLOWED_ERROR).of(expected); } } + + public void testBoxedDoubleStreamToStats() { + Stats stats = megaPrimitiveDoubleStream().boxed().collect(toStats()); + assertThat(stats.count()).isEqualTo(MEGA_STREAM_COUNT); + assertThat(stats.mean()).isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT).of(MEGA_STREAM_MEAN); + assertThat(stats.populationVariance()) + .isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT) + .of(MEGA_STREAM_POPULATION_VARIANCE); + assertThat(stats.min()).isEqualTo(MEGA_STREAM_MIN); + assertThat(stats.max()).isEqualTo(MEGA_STREAM_MAX); + } + + public void testBoxedBigDecimalStreamToStats() { + Stats stats = megaPrimitiveDoubleStream().mapToObj(BigDecimal::valueOf).collect(toStats()); + assertThat(stats.count()).isEqualTo(MEGA_STREAM_COUNT); + assertThat(stats.mean()).isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT).of(MEGA_STREAM_MEAN); + assertThat(stats.populationVariance()) + .isWithin(ALLOWED_ERROR * MEGA_STREAM_COUNT) + .of(MEGA_STREAM_POPULATION_VARIANCE); + assertThat(stats.min()).isEqualTo(MEGA_STREAM_MIN); + assertThat(stats.max()).isEqualTo(MEGA_STREAM_MAX); + } } diff --git a/android/guava-tests/test/com/google/common/math/StatsTesting.java b/android/guava-tests/test/com/google/common/math/StatsTesting.java index 12689d3e345e..8203efc81cfd 100644 --- a/android/guava-tests/test/com/google/common/math/StatsTesting.java +++ b/android/guava-tests/test/com/google/common/math/StatsTesting.java @@ -17,6 +17,7 @@ package com.google.common.math; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static java.lang.Double.NEGATIVE_INFINITY; import static java.lang.Double.NaN; @@ -31,6 +32,8 @@ import com.google.common.primitives.Ints; import java.math.BigInteger; import java.util.List; +import java.util.stream.DoubleStream; +import org.jspecify.annotations.NullUnmarked; /** * Inputs, expected outputs, and helper methods for tests of {@link StatsAccumulator}, {@link @@ -38,9 +41,10 @@ * * @author Pete Gillin */ +@NullUnmarked class StatsTesting { - - static final double ALLOWED_ERROR = 1e-10; + // TODO(cpovirk): Convince myself that this larger error makes sense. + static final double ALLOWED_ERROR = isAndroid() ? .25 : 1e-10; // Inputs and their statistics: @@ -63,7 +67,7 @@ class StatsTesting { + (-56.78 - TWO_VALUES_MEAN) * (-789.012 - OTHER_TWO_VALUES_MEAN); /** - * Helper class for testing with non-finite values. {@link #ALL_MANY_VALUES} gives a number + * Helper class for testing with non-finite values. {@link #ALL_MANY_VALUES} gives a number of * instances with many combinations of finite and non-finite values. All have {@link * #MANY_VALUES_COUNT} values. If all the values are finite then the mean is {@link * #MANY_VALUES_MEAN} and the sum-of-squares-of-deltas is {@link @@ -211,6 +215,37 @@ private static ImmutableList createAll() { .divide(BigInteger.valueOf(16L)) .doubleValue(); + /** + * Returns a stream of a million primitive doubles. The stream is parallel, which should cause + * {@code collect} calls to run in multithreaded mode, so testing the combiner as well as the + * supplier and accumulator. + */ + static DoubleStream megaPrimitiveDoubleStream() { + return DoubleStream.iterate(0.0, x -> x + 1.0).limit(MEGA_STREAM_COUNT).parallel(); + } + + /** Returns a stream containing half the values from {@link #megaPrimitiveDoubleStream}. */ + static DoubleStream megaPrimitiveDoubleStreamPart1() { + return DoubleStream.iterate(0.0, x -> x + 2.0).limit(MEGA_STREAM_COUNT / 2).parallel(); + } + + /** + * Returns a stream containing the values from {@link #megaPrimitiveDoubleStream} not in {@link + * #megaPrimitiveDoubleStreamPart1()}. + */ + static DoubleStream megaPrimitiveDoubleStreamPart2() { + return DoubleStream.iterate(MEGA_STREAM_COUNT - 1.0, x -> x - 2.0) + .limit(MEGA_STREAM_COUNT / 2) + .parallel(); + } + + static final long MEGA_STREAM_COUNT = isAndroid() ? 100 : 1_000_000; + static final double MEGA_STREAM_MIN = 0.0; + static final double MEGA_STREAM_MAX = MEGA_STREAM_COUNT - 1; + static final double MEGA_STREAM_MEAN = MEGA_STREAM_MAX / 2; + static final double MEGA_STREAM_POPULATION_VARIANCE = + (MEGA_STREAM_COUNT - 1) * (MEGA_STREAM_COUNT + 1) / 12.0; + // Stats instances: static final Stats EMPTY_STATS_VARARGS = Stats.of(); @@ -353,7 +388,7 @@ static void assertStatsApproxEqual(Stats expectedStats, Stats actualStats) { } } else if (expectedStats.count() == 1) { assertThat(actualStats.mean()).isWithin(ALLOWED_ERROR).of(expectedStats.mean()); - assertThat(actualStats.populationVariance()).isWithin(0.0).of(0.0); + assertThat(actualStats.populationVariance()).isEqualTo(0.0); assertThat(actualStats.min()).isWithin(ALLOWED_ERROR).of(expectedStats.min()); assertThat(actualStats.max()).isWithin(ALLOWED_ERROR).of(expectedStats.max()); } else { @@ -367,7 +402,7 @@ static void assertStatsApproxEqual(Stats expectedStats, Stats actualStats) { } /** - * Asserts that {@code transformation} is diagonal (i.e. neither horizontal or vertical) and + * Asserts that {@code transformation} is diagonal (i.e. neither horizontal nor vertical) and * passes through both {@code (x1, y1)} and {@code (x1 + xDelta, y1 + yDelta)}. Includes * assertions about all the public instance methods of {@link LinearTransformation} (on both * {@code transformation} and its inverse). Since the transformation is expected to be diagonal, @@ -501,5 +536,9 @@ static PairedStatsAccumulator createPartitionedFilledPairedStatsAccumulator( return accumulator; } + private static boolean isAndroid() { + return checkNotNull(System.getProperty("java.runtime.name", "")).contains("Android"); + } + private StatsTesting() {} } diff --git a/android/guava-tests/test/com/google/common/math/TestPlatform.java b/android/guava-tests/test/com/google/common/math/TestPlatform.java index 03eb2ec0da3c..00fa8b84dad9 100644 --- a/android/guava-tests/test/com/google/common/math/TestPlatform.java +++ b/android/guava-tests/test/com/google/common/math/TestPlatform.java @@ -17,15 +17,21 @@ package com.google.common.math; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullUnmarked; -/** @author Chris Povirk */ -@GwtCompatible(emulated = true) -class TestPlatform { +/** + * @author Chris Povirk + */ +@GwtCompatible +@NullUnmarked +final class TestPlatform { static boolean intsCanGoOutOfRange() { return false; } static boolean isAndroid() { - return System.getProperty("java.runtime.name").contains("Android"); + return System.getProperty("java.runtime.name", "").contains("Android"); } + + private TestPlatform() {} } diff --git a/android/guava-tests/test/com/google/common/net/HostAndPortTest.java b/android/guava-tests/test/com/google/common/net/HostAndPortTest.java index 5e7eb2f73d71..c69d83d0dc84 100644 --- a/android/guava-tests/test/com/google/common/net/HostAndPortTest.java +++ b/android/guava-tests/test/com/google/common/net/HostAndPortTest.java @@ -16,10 +16,15 @@ package com.google.common.net; +import static com.google.common.net.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.testing.EqualsTester; import com.google.common.testing.SerializableTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link HostAndPort} @@ -27,6 +32,7 @@ * @author Paul Marks */ @GwtCompatible +@NullUnmarked public class HostAndPortTest extends TestCase { public void testFromStringWellFormed() { @@ -59,6 +65,13 @@ public void testFromStringUnusedDefaultPort() { checkFromStringCase("[2001::2]:85", 77, "2001::2", 85, true); } + public void testFromStringNonAsciiDigits() { + // Same as testFromStringUnusedDefaultPort but with Gujarati digits for port numbers. + checkFromStringCase("gmail.com:૮1", 77, null, -1, false); + checkFromStringCase("192.0.2.2:૮૩", 77, null, -1, false); + checkFromStringCase("[2001::2]:૮૫", 77, null, -1, false); + } + public void testFromStringBadPort() { // Out-of-range ports. checkFromStringCase("google.com:65536", 1, null, 99, false); @@ -95,7 +108,7 @@ public void testFromStringParseableNonsense() { private static void checkFromStringCase( String hpString, int defaultPort, - String expectHost, + @Nullable String expectHost, int expectPort, boolean expectHasExplicitPort) { HostAndPort hp; @@ -103,13 +116,13 @@ private static void checkFromStringCase( hp = HostAndPort.fromString(hpString); } catch (IllegalArgumentException e) { // Make sure we expected this. - assertNull(expectHost); + assertThat(expectHost).isNull(); return; } - assertNotNull(expectHost); + assertThat(expectHost).isNotNull(); // Apply withDefaultPort(), yielding hp2. - final boolean badDefaultPort = (defaultPort < 0 || defaultPort > 65535); + boolean badDefaultPort = defaultPort < 0 || defaultPort > 65535; HostAndPort hp2 = null; try { hp2 = hp.withDefaultPort(defaultPort); @@ -152,17 +165,9 @@ public void testFromParts() { assertTrue(hp.hasPort()); assertEquals(81, hp.getPort()); - try { - HostAndPort.fromParts("gmail.com:80", 81); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HostAndPort.fromParts("gmail.com:80", 81)); - try { - HostAndPort.fromParts("gmail.com", -1); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HostAndPort.fromParts("gmail.com", -1)); } public void testFromHost() { @@ -174,17 +179,9 @@ public void testFromHost() { assertEquals("::1", hp.getHost()); assertFalse(hp.hasPort()); - try { - HostAndPort.fromHost("gmail.com:80"); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HostAndPort.fromHost("gmail.com:80")); - try { - HostAndPort.fromHost("[gmail.com]"); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> HostAndPort.fromHost("[gmail.com]")); } public void testGetPortOrDefault() { @@ -218,11 +215,9 @@ public void testRequireBracketsForIPv6() { assertEquals("x", HostAndPort.fromString("x:80").requireBracketsForIPv6().getHost()); // Non-bracketed IPv6 fails. - try { - HostAndPort.fromString("::1").requireBracketsForIPv6(); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> HostAndPort.fromString("::1").requireBracketsForIPv6()); } public void testToString() { diff --git a/android/guava-tests/test/com/google/common/net/HostSpecifierTest.java b/android/guava-tests/test/com/google/common/net/HostSpecifierTest.java index fadeff7aadf4..79ba5164919e 100644 --- a/android/guava-tests/test/com/google/common/net/HostSpecifierTest.java +++ b/android/guava-tests/test/com/google/common/net/HostSpecifierTest.java @@ -23,6 +23,7 @@ import com.google.common.testing.NullPointerTester; import java.text.ParseException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * {@link TestCase} for {@link HostSpecifier}. This is a relatively cursory test, as HostSpecifier @@ -32,6 +33,7 @@ * * @author Craig Berry */ +@NullUnmarked public final class HostSpecifierTest extends TestCase { private static final ImmutableList GOOD_IPS = @@ -85,15 +87,16 @@ private static HostSpecifier spec(String specifier) { } public void testNulls() { - final NullPointerTester tester = new NullPointerTester(); + NullPointerTester tester = new NullPointerTester(); tester.testAllPublicStaticMethods(HostSpecifier.class); tester.testAllPublicInstanceMethods(HostSpecifier.fromValid("google.com")); } private void assertGood(String spec) throws ParseException { - HostSpecifier.fromValid(spec); // Throws exception if not working correctly - HostSpecifier.from(spec); + // Throws exception if not working correctly + HostSpecifier unused = HostSpecifier.fromValid(spec); + unused = HostSpecifier.from(spec); assertTrue(HostSpecifier.isValid(spec)); } diff --git a/android/guava-tests/test/com/google/common/net/HttpHeadersTest.java b/android/guava-tests/test/com/google/common/net/HttpHeadersTest.java index 9927e6b94e00..4049b2e34e39 100644 --- a/android/guava-tests/test/com/google/common/net/HttpHeadersTest.java +++ b/android/guava-tests/test/com/google/common/net/HttpHeadersTest.java @@ -16,21 +16,26 @@ package com.google.common.net; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.base.Ascii; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the HttpHeaders class. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class HttpHeadersTest extends TestCase { public void testConstantNameMatchesString() throws Exception { @@ -40,6 +45,7 @@ public void testConstantNameMatchesString() throws Exception { .put("CDN_LOOP", "CDN-Loop") .put("ETAG", "ETag") .put("SOURCE_MAP", "SourceMap") + .put("SEC_CH_UA_WOW64", "Sec-CH-UA-WoW64") .put("SEC_WEBSOCKET_ACCEPT", "Sec-WebSocket-Accept") .put("SEC_WEBSOCKET_EXTENSIONS", "Sec-WebSocket-Extensions") .put("SEC_WEBSOCKET_KEY", "Sec-WebSocket-Key") @@ -47,30 +53,30 @@ public void testConstantNameMatchesString() throws Exception { .put("SEC_WEBSOCKET_VERSION", "Sec-WebSocket-Version") .put("X_WEBKIT_CSP", "X-WebKit-CSP") .put("X_WEBKIT_CSP_REPORT_ONLY", "X-WebKit-CSP-Report-Only") - .build(); + .buildOrThrow(); ImmutableSet uppercaseAcronyms = ImmutableSet.of( - "CH", "ID", "DNT", "DNS", "HTTP2", "IP", "MD5", "P3P", "TE", "UA", "UID", "URL", "WWW", - "XSS"); - assertConstantNameMatchesString(HttpHeaders.class, specialCases, uppercaseAcronyms); - } + "CH", "ID", "DNT", "DNS", "DPR", "ECT", "GPC", "HTTP2", "IP", "MD5", "P3P", "RTT", "TE", + "UA", "UID", "URL", "WWW", "XSS"); - // Visible for other tests to use - static void assertConstantNameMatchesString( - Class clazz, - ImmutableBiMap specialCases, - ImmutableSet uppercaseAcronyms) - throws IllegalAccessException { - for (Field field : relevantFields(clazz)) { + for (Field field : httpHeadersFields()) { assertEquals( upperToHttpHeaderName(field.getName(), specialCases, uppercaseAcronyms), field.get(null)); } } - // Visible for other tests to use - static ImmutableSet relevantFields(Class cls) { + // Tests that there are no duplicate HTTP header names + public void testNoDuplicateFields() throws Exception { + ImmutableList.Builder httpHeaders = ImmutableList.builder(); + for (Field field : httpHeadersFields()) { + httpHeaders.add((String) field.get(null)); + } + assertThat(httpHeaders.build()).containsNoDuplicates(); + } + + private static ImmutableSet httpHeadersFields() { ImmutableSet.Builder builder = ImmutableSet.builder(); - for (Field field : cls.getDeclaredFields()) { + for (Field field : HttpHeaders.class.getDeclaredFields()) { /* * Coverage mode generates synthetic fields. If we ever add private * fields, they will cause similar problems, and we may want to switch @@ -83,9 +89,6 @@ static ImmutableSet relevantFields(Class cls) { return builder.build(); } - private static final Splitter SPLITTER = Splitter.on('_'); - private static final Joiner JOINER = Joiner.on('-'); - private static String upperToHttpHeaderName( String constantName, ImmutableBiMap specialCases, @@ -93,13 +96,13 @@ private static String upperToHttpHeaderName( if (specialCases.containsKey(constantName)) { return specialCases.get(constantName); } - List parts = Lists.newArrayList(); - for (String part : SPLITTER.split(constantName)) { + List parts = new ArrayList<>(); + for (String part : Splitter.on('_').split(constantName)) { if (!uppercaseAcronyms.contains(part)) { part = part.charAt(0) + Ascii.toLowerCase(part.substring(1)); } parts.add(part); } - return JOINER.join(parts); + return Joiner.on('-').join(parts); } } diff --git a/android/guava-tests/test/com/google/common/net/InetAddressesTest.java b/android/guava-tests/test/com/google/common/net/InetAddressesTest.java index ed3aa272d3a6..03425f0abebf 100644 --- a/android/guava-tests/test/com/google/common/net/InetAddressesTest.java +++ b/android/guava-tests/test/com/google/common/net/InetAddressesTest.java @@ -17,6 +17,7 @@ package com.google.common.net; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableSet; import com.google.common.testing.NullPointerTester; @@ -24,14 +25,19 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; import java.net.UnknownHostException; +import java.util.Enumeration; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link InetAddresses}. * * @author Erik Kline */ +@NullUnmarked public class InetAddressesTest extends TestCase { public void testNulls() { @@ -109,21 +115,16 @@ public void testForStringBogusInput() { ":1:2:3:4:5:6:"); for (String bogusInput : bogusInputs) { - try { - InetAddresses.forString(bogusInput); - fail("IllegalArgumentException expected for '" + bogusInput + "'"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + "IllegalArgumentException expected for '" + bogusInput + "'", + IllegalArgumentException.class, + () -> InetAddresses.forString(bogusInput)); assertFalse(InetAddresses.isInetAddress(bogusInput)); } } public void test3ff31() { - try { - InetAddresses.forString("3ffe:::1"); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.forString("3ffe:::1")); assertFalse(InetAddresses.isInetAddress("016.016.016.016")); } @@ -135,6 +136,20 @@ public void testForStringIPv4Input() throws UnknownHostException { assertTrue(InetAddresses.isInetAddress(ipStr)); } + public void testForStringIPv4NonAsciiInput() throws UnknownHostException { + String ipStr = "૧૯૨.૧૬૮.૦.૧"; // 192.168.0.1 in Gujarati digits + // Shouldn't hit DNS, because it's an IP string literal. + InetAddress ipv4Addr; + try { + ipv4Addr = InetAddress.getByName(ipStr); + } catch (UnknownHostException e) { + // OK: this is probably Android, which is stricter. + return; + } + assertEquals(ipv4Addr, InetAddresses.forString(ipStr)); + assertTrue(InetAddresses.isInetAddress(ipStr)); + } + public void testForStringIPv6Input() throws UnknownHostException { String ipStr = "3ffe::1"; // Shouldn't hit DNS, because it's an IP string literal. @@ -143,6 +158,20 @@ public void testForStringIPv6Input() throws UnknownHostException { assertTrue(InetAddresses.isInetAddress(ipStr)); } + public void testForStringIPv6NonAsciiInput() throws UnknownHostException { + String ipStr = "૩ffe::૧"; // 3ffe::1 with Gujarati digits for 3 and 1 + // Shouldn't hit DNS, because it's an IP string literal. + InetAddress ipv6Addr; + try { + ipv6Addr = InetAddress.getByName(ipStr); + } catch (UnknownHostException e) { + // OK: this is probably Android, which is stricter. + return; + } + assertEquals(ipv6Addr, InetAddresses.forString(ipStr)); + assertTrue(InetAddresses.isInetAddress(ipStr)); + } + public void testForStringIPv6EightColons() throws UnknownHostException { ImmutableSet eightColons = ImmutableSet.of("::7:6:5:4:3:2:1", "::7:6:5:4:3:2:0", "7:6:5:4:3:2:1::", "0:6:5:4:3:2:1::"); @@ -167,14 +196,10 @@ public void testConvertDottedQuadToHex() throws UnknownHostException { } } - // see https://github.com/google/guava/issues/2587 - private static final ImmutableSet SCOPE_IDS = - ImmutableSet.of("eno1", "en1", "eth0", "X", "1", "2", "14", "20"); - - public void testIPv4AddressWithScopeId() { + public void testIPv4AddressWithScopeId() throws SocketException { ImmutableSet ipStrings = ImmutableSet.of("1.2.3.4", "192.168.0.1"); for (String ipString : ipStrings) { - for (String scopeId : SCOPE_IDS) { + for (String scopeId : getMachineScopesAndInterfaces()) { String withScopeId = ipString + "%" + scopeId; assertFalse( "InetAddresses.isInetAddress(" + withScopeId + ") should be false but was true", @@ -183,11 +208,11 @@ public void testIPv4AddressWithScopeId() { } } - public void testDottedQuadAddressWithScopeId() { + public void testDottedQuadAddressWithScopeId() throws SocketException { ImmutableSet ipStrings = ImmutableSet.of("7::0.128.0.127", "7::0.128.0.128", "7::128.128.0.127", "7::0.128.128.127"); for (String ipString : ipStrings) { - for (String scopeId : SCOPE_IDS) { + for (String scopeId : getMachineScopesAndInterfaces()) { String withScopeId = ipString + "%" + scopeId; assertFalse( "InetAddresses.isInetAddress(" + withScopeId + ") should be false but was true", @@ -196,27 +221,106 @@ public void testDottedQuadAddressWithScopeId() { } } - public void testIPv6AddressWithScopeId() { + public void testIPv6AddressWithScopeId() throws SocketException, UnknownHostException { ImmutableSet ipStrings = ImmutableSet.of( - "0:0:0:0:0:0:0:1", - "fe80::a", - "fe80::1", - "fe80::2", - "fe80::42", - "fe80::3dd0:7f8e:57b7:34d5", - "fe80::71a3:2b00:ddd3:753f", - "fe80::8b2:d61e:e5c:b333", - "fe80::b059:65f4:e877:c40"); + "::1", + "1180::a", + "1180::1", + "1180::2", + "1180::42", + "1180::3dd0:7f8e:57b7:34d5", + "1180::71a3:2b00:ddd3:753f", + "1180::8b2:d61e:e5c:b333", + "1180::b059:65f4:e877:c40", + "fe80::34", + "fec0::34"); + boolean processedNamedInterface = false; for (String ipString : ipStrings) { - for (String scopeId : SCOPE_IDS) { + for (String scopeId : getMachineScopesAndInterfaces()) { String withScopeId = ipString + "%" + scopeId; assertTrue( "InetAddresses.isInetAddress(" + withScopeId + ") should be true but was false", InetAddresses.isInetAddress(withScopeId)); - assertEquals(InetAddresses.forString(withScopeId), InetAddresses.forString(ipString)); + Inet6Address parsed; + boolean isNumeric = scopeId.matches("\\d+"); + try { + parsed = (Inet6Address) InetAddresses.forString(withScopeId); + } catch (IllegalArgumentException e) { + if (!isNumeric) { + // Android doesn't recognize %interface as valid + continue; + } + throw e; + } + processedNamedInterface |= !isNumeric; + assertThat(InetAddresses.toAddrString(parsed)).contains("%"); + if (isNumeric) { + assertEquals(Integer.parseInt(scopeId), parsed.getScopeId()); + } else { + assertEquals(scopeId, parsed.getScopedInterface().getName()); + } + Inet6Address reparsed = + (Inet6Address) InetAddresses.forString(InetAddresses.toAddrString(parsed)); + assertEquals(reparsed, parsed); + assertEquals(reparsed.getScopeId(), parsed.getScopeId()); } } + assertTrue(processedNamedInterface); + } + + public void testIPv6AddressWithScopeId_platformEquivalence() + throws SocketException, UnknownHostException { + ImmutableSet ipStrings = + ImmutableSet.of( + "::1", + "1180::a", + "1180::1", + "1180::2", + "1180::42", + "1180::3dd0:7f8e:57b7:34d5", + "1180::71a3:2b00:ddd3:753f", + "1180::8b2:d61e:e5c:b333", + "1180::b059:65f4:e877:c40", + "fe80::34", + "fec0::34"); + for (String ipString : ipStrings) { + for (String scopeId : getMachineScopesAndInterfaces()) { + String withScopeId = ipString + "%" + scopeId; + assertTrue( + "InetAddresses.isInetAddress(" + withScopeId + ") should be true but was false", + InetAddresses.isInetAddress(withScopeId)); + Inet6Address parsed; + boolean isNumeric = scopeId.matches("\\d+"); + try { + parsed = (Inet6Address) InetAddresses.forString(withScopeId); + } catch (IllegalArgumentException e) { + if (!isNumeric) { + // Android doesn't recognize %interface as valid + continue; + } + throw e; + } + Inet6Address platformValue; + try { + platformValue = (Inet6Address) InetAddress.getByName(withScopeId); + } catch (UnknownHostException e) { + // Android doesn't recognize %interface as valid + if (!isNumeric) { + continue; + } + throw e; + } + assertEquals(platformValue, parsed); + assertEquals(platformValue.getScopeId(), parsed.getScopeId()); + } + } + } + + public void testIPv6AddressWithBadScopeId() throws SocketException, UnknownHostException { + assertThrows( + IllegalArgumentException.class, + () -> InetAddresses.forString("1180::b059:65f4:e877:c40%eth9")); } public void testToAddrStringIPv4() { @@ -299,71 +403,33 @@ public void testIsUriInetAddress() { } public void testForUriStringBad() { - try { - InetAddresses.forUriString(""); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.forUriString("")); - try { - InetAddresses.forUriString("192.168.999.888"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> InetAddresses.forUriString("192.168.999.888")); - try { - InetAddresses.forUriString("www.google.com"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> InetAddresses.forUriString("www.google.com")); - try { - InetAddresses.forUriString("[1:2e]"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.forUriString("[1:2e]")); - try { - InetAddresses.forUriString("[192.168.1.1]"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.forUriString("[192.168.1.1]")); - try { - InetAddresses.forUriString("192.168.1.1]"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.forUriString("192.168.1.1]")); - try { - InetAddresses.forUriString("[192.168.1.1"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.forUriString("[192.168.1.1")); - try { - InetAddresses.forUriString("[3ffe:0:0:0:0:0:0:1"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> InetAddresses.forUriString("[3ffe:0:0:0:0:0:0:1")); - try { - InetAddresses.forUriString("3ffe:0:0:0:0:0:0:1]"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> InetAddresses.forUriString("3ffe:0:0:0:0:0:0:1]")); - try { - InetAddresses.forUriString("3ffe:0:0:0:0:0:0:1"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> InetAddresses.forUriString("3ffe:0:0:0:0:0:0:1")); - try { - InetAddresses.forUriString("::ffff:192.0.2.1"); - fail("expected IllegalArgumentException"); // COV_NF_LINE - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> InetAddresses.forUriString("::ffff:192.0.2.1")); } public void testCompatIPv4Addresses() { @@ -372,11 +438,10 @@ public void testCompatIPv4Addresses() { for (String nonCompatAddress : nonCompatAddresses) { InetAddress ip = InetAddresses.forString(nonCompatAddress); assertFalse(InetAddresses.isCompatIPv4Address((Inet6Address) ip)); - try { - InetAddresses.getCompatIPv4Address((Inet6Address) ip); - fail("IllegalArgumentException expected for '" + nonCompatAddress + "'"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + "IllegalArgumentException expected for '" + nonCompatAddress + "'", + IllegalArgumentException.class, + () -> InetAddresses.getCompatIPv4Address((Inet6Address) ip)); } ImmutableSet validCompatAddresses = ImmutableSet.of("::1.2.3.4", "::102:304"); @@ -442,11 +507,10 @@ public void test6to4Addresses() { for (String non6to4Address : non6to4Addresses) { InetAddress ip = InetAddresses.forString(non6to4Address); assertFalse(InetAddresses.is6to4Address((Inet6Address) ip)); - try { - InetAddresses.get6to4IPv4Address((Inet6Address) ip); - fail("IllegalArgumentException expected for '" + non6to4Address + "'"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + "IllegalArgumentException expected for '" + non6to4Address + "'", + IllegalArgumentException.class, + () -> InetAddresses.get6to4IPv4Address((Inet6Address) ip)); } String valid6to4Address = "2002:0102:0304::1"; @@ -464,11 +528,10 @@ public void testTeredoAddresses() { for (String nonTeredoAddress : nonTeredoAddresses) { InetAddress ip = InetAddresses.forString(nonTeredoAddress); assertFalse(InetAddresses.isTeredoAddress((Inet6Address) ip)); - try { - InetAddresses.getTeredoInfo((Inet6Address) ip); - fail("IllegalArgumentException expected for '" + nonTeredoAddress + "'"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + "IllegalArgumentException expected for '" + nonTeredoAddress + "'", + IllegalArgumentException.class, + () -> InetAddresses.getTeredoInfo((Inet6Address) ip)); } String validTeredoAddress = "2001:0000:4136:e378:8000:63bf:3fff:fdd2"; @@ -531,11 +594,10 @@ public void testIsatapAddresses() { for (String nonIsatapAddress : nonIsatapAddresses) { InetAddress ip = InetAddresses.forString(nonIsatapAddress); assertFalse(InetAddresses.isIsatapAddress((Inet6Address) ip)); - try { - InetAddresses.getIsatapIPv4Address((Inet6Address) ip); - fail("IllegalArgumentException expected for '" + nonIsatapAddress + "'"); - } catch (IllegalArgumentException expected) { - } + assertThrows( + "IllegalArgumentException expected for '" + nonIsatapAddress + "'", + IllegalArgumentException.class, + () -> InetAddresses.getIsatapIPv4Address((Inet6Address) ip)); } } @@ -617,7 +679,7 @@ public void testGetCoercedIPv4Address() { InetAddresses.getCoercedIPv4Address( InetAddresses.forString("2001:0000:4136:e378:8000:63bf:3fff:fdd3"))); - // 2 Teredo addresses NOT differing in the their embedded IPv4 addresses should hash to the same + // 2 Teredo addresses NOT differing in their embedded IPv4 addresses should hash to the same // value. assertThat( InetAddresses.getCoercedIPv4Address( @@ -655,12 +717,8 @@ public void testFromLittleEndianByteArray() throws UnknownHostException { InetAddress.getByAddress( new byte[] {16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1})); - try { - InetAddresses.fromLittleEndianByteArray(new byte[3]); - fail("expected exception"); - } catch (UnknownHostException expected) { - // success - } + assertThrows( + UnknownHostException.class, () -> InetAddresses.fromLittleEndianByteArray(new byte[3])); } public void testIsMaximum() throws UnknownHostException { @@ -677,6 +735,7 @@ public void testIsMaximum() throws UnknownHostException { assertTrue(InetAddresses.isMaximum(address)); } + @SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks public void testIncrementIPv4() throws UnknownHostException { InetAddress address_66_0 = InetAddress.getByName("172.24.66.0"); InetAddress address_66_255 = InetAddress.getByName("172.24.66.255"); @@ -692,14 +751,10 @@ public void testIncrementIPv4() throws UnknownHostException { assertEquals(address_67_0, address); InetAddress address_ffffff = InetAddress.getByName("255.255.255.255"); - address = address_ffffff; - try { - address = InetAddresses.increment(address); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.increment(address_ffffff)); } + @SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks public void testIncrementIPv6() throws UnknownHostException { InetAddress addressV6_66_0 = InetAddress.getByName("2001:db8::6600"); InetAddress addressV6_66_ff = InetAddress.getByName("2001:db8::66ff"); @@ -715,12 +770,7 @@ public void testIncrementIPv6() throws UnknownHostException { assertEquals(addressV6_67_0, address); InetAddress addressV6_ffffff = InetAddress.getByName("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); - address = addressV6_ffffff; - try { - address = InetAddresses.increment(address); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.increment(addressV6_ffffff)); } public void testDecrementIPv4() throws UnknownHostException { @@ -739,12 +789,7 @@ public void testDecrementIPv4() throws UnknownHostException { assertEquals(address660, address); InetAddress address0000 = InetAddress.getByName("0.0.0.0"); - address = address0000; - try { - address = InetAddresses.decrement(address); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.decrement(address0000)); } public void testDecrementIPv6() throws UnknownHostException { @@ -763,30 +808,27 @@ public void testDecrementIPv6() throws UnknownHostException { assertEquals(addressV6660, address); InetAddress addressV6000000 = InetAddress.getByName("0:0:0:0:0:0:0:0"); - address = addressV6000000; - try { - address = InetAddresses.decrement(address); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InetAddresses.decrement(addressV6000000)); } public void testFromIpv4BigIntegerThrowsLessThanZero() { - try { - InetAddresses.fromIPv4BigInteger(BigInteger.valueOf(-1L)); - fail(); - } catch (IllegalArgumentException expected) { - assertEquals("BigInteger must be greater than or equal to 0", expected.getMessage()); - } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> InetAddresses.fromIPv4BigInteger(BigInteger.valueOf(-1L))); + assertThat(expected) + .hasMessageThat() + .isEqualTo("BigInteger must be greater than or equal to 0"); } public void testFromIpv6BigIntegerThrowsLessThanZero() { - try { - InetAddresses.fromIPv6BigInteger(BigInteger.valueOf(-1L)); - fail(); - } catch (IllegalArgumentException expected) { - assertEquals("BigInteger must be greater than or equal to 0", expected.getMessage()); - } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> InetAddresses.fromIPv6BigInteger(BigInteger.valueOf(-1L))); + assertThat(expected) + .hasMessageThat() + .isEqualTo("BigInteger must be greater than or equal to 0"); } public void testFromIpv4BigIntegerValid() { @@ -812,27 +854,42 @@ public void testFromIpv6BigIntegerValid() { } public void testFromIpv4BigIntegerInputTooLarge() { - try { - InetAddresses.fromIPv4BigInteger(BigInteger.ONE.shiftLeft(32).add(BigInteger.ONE)); - fail(); - } catch (IllegalArgumentException expected) { - assertEquals( - "BigInteger cannot be converted to InetAddress because it has more than 4 bytes:" - + " 4294967297", - expected.getMessage()); - } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> + InetAddresses.fromIPv4BigInteger(BigInteger.ONE.shiftLeft(32).add(BigInteger.ONE))); + assertThat(expected) + .hasMessageThat() + .isEqualTo( + "BigInteger cannot be converted to InetAddress because it has more than 4 bytes:" + + " 4294967297"); } public void testFromIpv6BigIntegerInputTooLarge() { - try { - InetAddresses.fromIPv6BigInteger(BigInteger.ONE.shiftLeft(128).add(BigInteger.ONE)); - fail(); - } catch (IllegalArgumentException expected) { - assertEquals( - "BigInteger cannot be converted to InetAddress because it has more than 16 bytes:" - + " 340282366920938463463374607431768211457", - expected.getMessage()); - } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> + InetAddresses.fromIPv6BigInteger( + BigInteger.ONE.shiftLeft(128).add(BigInteger.ONE))); + assertThat(expected) + .hasMessageThat() + .isEqualTo( + "BigInteger cannot be converted to InetAddress because it has more than 16 bytes:" + + " 340282366920938463463374607431768211457"); + } + + // see https://github.com/google/guava/issues/2587 + private static ImmutableSet getMachineScopesAndInterfaces() throws SocketException { + ImmutableSet.Builder builder = ImmutableSet.builder(); + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + assertTrue(interfaces.hasMoreElements()); + while (interfaces.hasMoreElements()) { + NetworkInterface i = interfaces.nextElement(); + builder.add(i.getName()).add(String.valueOf(i.getIndex())); + } + return builder.build(); } /** Checks that the IP converts to the big integer and the big integer converts to the IP. */ diff --git a/android/guava-tests/test/com/google/common/net/InternetDomainNameTest.java b/android/guava-tests/test/com/google/common/net/InternetDomainNameTest.java index 7113fb45741a..5dff792b2da6 100644 --- a/android/guava-tests/test/com/google/common/net/InternetDomainNameTest.java +++ b/android/guava-tests/test/com/google/common/net/InternetDomainNameTest.java @@ -16,9 +16,12 @@ package com.google.common.net; +import static com.google.common.net.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Ascii; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; @@ -26,13 +29,15 @@ import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * {@link TestCase} for {@link InternetDomainName}. * * @author Craig Berry */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public final class InternetDomainNameTest extends TestCase { private static final InternetDomainName UNICODE_EXAMPLE = InternetDomainName.from("j\u00f8rpeland.no"); @@ -43,74 +48,85 @@ public final class InternetDomainNameTest extends TestCase { private static final String DELTA = "\u0394"; /** A domain part which is valid under lenient validation, but invalid under strict validation. */ + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 static final String LOTS_OF_DELTAS = Strings.repeat(DELTA, 62); + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 private static final String ALMOST_TOO_MANY_LEVELS = Strings.repeat("a.", 127); + @SuppressWarnings("InlineMeInliner") // String.repeat unavailable under Java 8 private static final String ALMOST_TOO_LONG = Strings.repeat("aaaaa.", 40) + "1234567890.c"; private static final ImmutableSet VALID_NAME = ImmutableSet.of( - "foo.com", - "f-_-o.cOM", - "f--1.com", - "f11-1.com", - "www", + // keep-sorted start + "123.cn", + "8server.shop", + "a" + DELTA + "b.com", "abc.a23", "biz.com.ua", - "x", - "fOo", + "f--1.com", "f--o", + "f-_-o.cOM", + "f11-1.com", + "fOo", "f_a", + "foo.com", "foo.net.us\uFF61ocm", "woo.com.", - "8server.shop", - "123.cn", - "a" + DELTA + "b.com", - ALMOST_TOO_MANY_LEVELS, - ALMOST_TOO_LONG); + "www", + "x", + ALMOST_TOO_LONG, + ALMOST_TOO_MANY_LEVELS + // keep-sorted end + ); private static final ImmutableSet INVALID_NAME = ImmutableSet.of( - "", + // keep-sorted start " ", + "", + ".", + "..", + "...", + "..bar.com", + "..quiffle.com", + ".foo.com", "127.0.0.1", - "::1", "13", - "abc.12c", - "foo-.com", + "::1", "_bar.quux", - "foo+bar.com", - "foo!bar.com", - ".foo.com", - "..bar.com", + "a" + DELTA + " .com", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com", + "abc.12c", "baz..com", - "..quiffle.com", "fleeb.com..", - ".", - "..", - "...", - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com", - "a" + DELTA + " .com", - ALMOST_TOO_MANY_LEVELS + "com", - ALMOST_TOO_LONG + ".c"); + "foo!bar.com", + "foo+bar.com", + "foo-.com", + ALMOST_TOO_LONG + ".c", + ALMOST_TOO_MANY_LEVELS + "com" + // keep-sorted end + ); private static final ImmutableSet RS = ImmutableSet.of( - "com", + // keep-sorted start + "\u7f51\u7edc.Cn", // "网络.Cn" "co.uk", - "foo.bd", - "xxxxxx.bd", - "org.mK", - "us", "co.uk.", // Trailing dot "co\uFF61uk", // Alternate dot character - "\u7f51\u7edc.Cn", // "网络.Cn" + "com", + "gov.ck", + "org.ck", + "org.mK", + "us", + // keep-sorted end "j\u00f8rpeland.no", // "jorpeland.no" (first o slashed) - "xn--jrpeland-54a.no"); // IDNA (punycode) encoding of above + "xn--jrpeland-54a.no" // IDNA (punycode) encoding of above + ); - private static final ImmutableSet PS_NOT_RS = - ImmutableSet.of("blogspot.com", "blogspot.co.uk", "uk.com"); + private static final ImmutableSet PS_NOT_RS = ImmutableSet.of("blogspot.com", "uk.com"); private static final ImmutableSet PS = ImmutableSet.builder().addAll(RS).addAll(PS_NOT_RS).build(); @@ -126,23 +142,26 @@ public final class InternetDomainNameTest extends TestCase { private static final ImmutableSet NON_PS = ImmutableSet.of( - "foo.bar.com", - "foo.ca", + // keep-sorted start + "dominio.com.co", "foo.bar.ca", - "foo.blogspot.com", + "foo.bar.co.il", + "foo.bar.com", "foo.blogspot.co.uk", + "foo.blogspot.com", + "foo.ca", + "foo.eDu.au", "foo.uk.com", - "foo.bar.co.il", - "state.CA.us", - "www.state.pa.us", - "pvt.k12.ca.us", - "www.google.com", - "www4.yahoo.co.uk", "home.netscape.com", - "web.MIT.edu", - "foo.eDu.au", + "pvt.k12.ca.us", + "state.CA.us", "utenti.blah.IT", - "dominio.com.co"); + "web.MIT.edu", + "www.google.com", + "www.state.pa.us", + "www4.yahoo.co.uk" + // keep-sorted end + ); private static final ImmutableSet NON_RS = ImmutableSet.builder().addAll(NON_PS).addAll(PS_NOT_RS).build(); @@ -168,63 +187,64 @@ public final class InternetDomainNameTest extends TestCase { private static final ImmutableSet SOMEWHERE_UNDER_PS = ImmutableSet.of( - "foo.bar.google.com", + // keep-sorted start + "1.fm", "a.b.c.1.2.3.ca.us", - "site.jp", - "uomi-online.kir.jp", + "a\u7f51\u7edcA.\u7f51\u7edc.Cn", // "a网络A.网络.Cn" + "cnn.ca", + "cool.co.uk", + "cool.de", + "cool.dk", + "cool.es", + "cool.nl", + "cool.se", + "cool\uFF61fr", // Alternate dot character + "foo.bar.google.com", + "google.Co.uK", + "google.com", + "home.netscape.com", + "it-trace.ch", + "jobs.kt.com.", "jprs.co.jp", - "site.quick.jp", - "site.tenki.jp", - "site.or.jp", - "site.gr.jp", - "site.ne.jp", + "kt.co", + "ledger-enquirer.com", + "members.blah.nl.", + "pvt.k12.ca.us", "site.ac.jp", "site.ad.jp", - "site.ed.jp", - "site.geo.jp", - "site.go.jp", - "site.lg.jp", - "1.fm", "site.cc", + "site.ed.jp", "site.ee", "site.fi", "site.fm", + "site.geo.jp", + "site.go.jp", "site.gr", - "www.leguide.ma", + "site.gr.jp", + "site.jp", + "site.lg.jp", "site.ma", - "some.org.mk", "site.mk", + "site.ne.jp", + "site.or.jp", + "site.quick.jp", + "site.tenki.jp", "site.tv", "site.us", - "www.odev.us", - "www.GOOGLE.com", - "www.com", - "google.com", - "www7.google.co.uk", - "google.Co.uK", - "jobs.kt.com.", - "home.netscape.com", - "web.stanford.edu", + "some.org.mk", "stanford.edu", "state.ca.us", - "www.state.ca.us", - "state.ca.us", - "pvt.k12.ca.us", - "www.rave.ca.", - "cnn.ca", - "ledger-enquirer.com", - "it-trace.ch", - "cool.dk", - "cool.co.uk", - "cool.de", - "cool.es", - "cool\uFF61fr", // Alternate dot character - "cool.nl", - "members.blah.nl.", - "cool.se", + "uomi-online.kir.jp", "utenti.blah.it", - "kt.co", - "a\u7f51\u7edcA.\u7f51\u7edc.Cn" // "a网络A.网络.Cn" + "web.stanford.edu", + "www.GOOGLE.com", + "www.com", + "www.leguide.ma", + "www.odev.us", + "www.rave.ca.", + "www.state.ca.us", + "www7.google.co.uk" + // keep-sorted end ); private static final ImmutableSet SOMEWHERE_UNDER_RS = @@ -232,23 +252,19 @@ public final class InternetDomainNameTest extends TestCase { public void testValid() { for (String name : VALID_NAME) { - InternetDomainName.from(name); + InternetDomainName unused = InternetDomainName.from(name); } } public void testInvalid() { for (String name : INVALID_NAME) { - try { - InternetDomainName.from(name); - fail("Should have been invalid: '" + name + "'"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> InternetDomainName.from(name)); } } public void testPublicSuffix() { for (String name : PS) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertTrue(name, domain.isPublicSuffix()); assertTrue(name, domain.hasPublicSuffix()); assertFalse(name, domain.isUnderPublicSuffix()); @@ -257,16 +273,16 @@ public void testPublicSuffix() { } for (String name : NO_PS) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isPublicSuffix()); assertFalse(name, domain.hasPublicSuffix()); assertFalse(name, domain.isUnderPublicSuffix()); assertFalse(name, domain.isTopPrivateDomain()); - assertNull(domain.publicSuffix()); + assertThat(domain.publicSuffix()).isNull(); } for (String name : NON_PS) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isPublicSuffix()); assertTrue(name, domain.hasPublicSuffix()); assertTrue(name, domain.isUnderPublicSuffix()); @@ -275,7 +291,7 @@ public void testPublicSuffix() { public void testUnderPublicSuffix() { for (String name : SOMEWHERE_UNDER_PS) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isPublicSuffix()); assertTrue(name, domain.hasPublicSuffix()); assertTrue(name, domain.isUnderPublicSuffix()); @@ -284,7 +300,7 @@ public void testUnderPublicSuffix() { public void testTopPrivateDomain() { for (String name : TOP_PRIVATE_DOMAIN) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isPublicSuffix()); assertTrue(name, domain.hasPublicSuffix()); assertTrue(name, domain.isUnderPublicSuffix()); @@ -295,7 +311,7 @@ public void testTopPrivateDomain() { public void testUnderPrivateDomain() { for (String name : UNDER_PRIVATE_DOMAIN) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isPublicSuffix()); assertTrue(name, domain.hasPublicSuffix()); assertTrue(name, domain.isUnderPublicSuffix()); @@ -305,7 +321,7 @@ public void testUnderPrivateDomain() { public void testRegistrySuffix() { for (String name : RS) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertTrue(name, domain.isRegistrySuffix()); assertTrue(name, domain.hasRegistrySuffix()); assertFalse(name, domain.isUnderRegistrySuffix()); @@ -314,16 +330,16 @@ public void testRegistrySuffix() { } for (String name : NO_RS) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isRegistrySuffix()); assertFalse(name, domain.hasRegistrySuffix()); assertFalse(name, domain.isUnderRegistrySuffix()); assertFalse(name, domain.isTopDomainUnderRegistrySuffix()); - assertNull(domain.registrySuffix()); + assertThat(domain.registrySuffix()).isNull(); } for (String name : NON_RS) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isRegistrySuffix()); assertTrue(name, domain.hasRegistrySuffix()); assertTrue(name, domain.isUnderRegistrySuffix()); @@ -332,7 +348,7 @@ public void testRegistrySuffix() { public void testUnderRegistrySuffix() { for (String name : SOMEWHERE_UNDER_RS) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isRegistrySuffix()); assertTrue(name, domain.hasRegistrySuffix()); assertTrue(name, domain.isUnderRegistrySuffix()); @@ -341,7 +357,7 @@ public void testUnderRegistrySuffix() { public void testTopDomainUnderRegistrySuffix() { for (String name : TOP_UNDER_REGISTRY_SUFFIX) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isRegistrySuffix()); assertTrue(name, domain.hasRegistrySuffix()); assertTrue(name, domain.isUnderRegistrySuffix()); @@ -352,7 +368,7 @@ public void testTopDomainUnderRegistrySuffix() { public void testUnderTopDomainUnderRegistrySuffix() { for (String name : UNDER_TOP_UNDER_REGISTRY_SUFFIX) { - final InternetDomainName domain = InternetDomainName.from(name); + InternetDomainName domain = InternetDomainName.from(name); assertFalse(name, domain.isRegistrySuffix()); assertTrue(name, domain.hasRegistrySuffix()); assertTrue(name, domain.isUnderRegistrySuffix()); @@ -365,11 +381,7 @@ public void testParent() { assertEquals("uk", InternetDomainName.from("co.uk").parent().toString()); assertEquals("google.com", InternetDomainName.from("www.google.com").parent().toString()); - try { - InternetDomainName.from("com").parent(); - fail("'com' should throw ISE on .parent() call"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> InternetDomainName.from("com").parent()); } public void testChild() { @@ -377,11 +389,7 @@ public void testChild() { assertEquals("www.foo.com", domain.child("www").toString()); - try { - domain.child("www."); - fail("www..google.com should have been invalid"); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> domain.child("www.")); } public void testParentChild() { @@ -392,7 +400,7 @@ public void testParentChild() { // These would throw an exception if leniency were not preserved during parent() and child() // calls. InternetDomainName child = parent.child(LOTS_OF_DELTAS); - child.child(LOTS_OF_DELTAS); + InternetDomainName unused = child.child(LOTS_OF_DELTAS); } public void testValidTopPrivateDomain() { @@ -407,17 +415,14 @@ public void testInvalidTopPrivateDomain() { ImmutableSet badCookieDomains = ImmutableSet.of("co.uk", "foo", "com"); for (String domain : badCookieDomains) { - try { - InternetDomainName.from(domain).topPrivateDomain(); - fail(domain); - } catch (IllegalStateException expected) { - } + assertThrows( + IllegalStateException.class, () -> InternetDomainName.from(domain).topPrivateDomain()); } } public void testIsValid() { - final Iterable validCases = Iterables.concat(VALID_NAME, PS, NO_PS, NON_PS); - final Iterable invalidCases = + Iterable validCases = Iterables.concat(VALID_NAME, PS, NO_PS, NON_PS); + Iterable invalidCases = Iterables.concat(INVALID_NAME, VALID_IP_ADDRS, INVALID_IP_ADDRS); for (String valid : validCases) { @@ -461,7 +466,7 @@ public void testPublicSuffixExclusion() { public void testPublicSuffixMultipleUnders() { // PSL has both *.uk and *.sch.uk; the latter should win. - // See http://code.google.com/p/guava-libraries/issues/detail?id=1176 + // See https://github.com/google/guava/issues/1176 InternetDomainName domain = InternetDomainName.from("www.essex.sch.uk"); assertTrue(domain.hasPublicSuffix()); @@ -480,7 +485,7 @@ public void testRegistrySuffixExclusion() { public void testRegistrySuffixMultipleUnders() { // PSL has both *.uk and *.sch.uk; the latter should win. - // See http://code.google.com/p/guava-libraries/issues/detail?id=1176 + // See https://github.com/google/guava/issues/1176 InternetDomainName domain = InternetDomainName.from("www.essex.sch.uk"); assertTrue(domain.hasRegistrySuffix()); @@ -501,9 +506,10 @@ private static InternetDomainName idn(String domain) { return InternetDomainName.from(domain); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { - final NullPointerTester tester = new NullPointerTester(); + NullPointerTester tester = new NullPointerTester(); tester.testAllPublicStaticMethods(InternetDomainName.class); tester.testAllPublicInstanceMethods(InternetDomainName.from("google.com")); diff --git a/android/guava-tests/test/com/google/common/net/MediaTypeTest.java b/android/guava-tests/test/com/google/common/net/MediaTypeTest.java index cec3cdd28cce..fa7d99a30775 100644 --- a/android/guava-tests/test/com/google/common/net/MediaTypeTest.java +++ b/android/guava-tests/test/com/google/common/net/MediaTypeTest.java @@ -16,8 +16,6 @@ package com.google.common.net; -import static com.google.common.base.Charsets.UTF_16; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.net.MediaType.ANY_APPLICATION_TYPE; import static com.google.common.net.MediaType.ANY_AUDIO_TYPE; import static com.google.common.net.MediaType.ANY_IMAGE_TYPE; @@ -27,14 +25,18 @@ import static com.google.common.net.MediaType.HTML_UTF_8; import static com.google.common.net.MediaType.JPEG; import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8; +import static com.google.common.net.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import static java.lang.reflect.Modifier.isFinal; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; +import static java.nio.charset.StandardCharsets.UTF_16; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; @@ -46,18 +48,20 @@ import com.google.common.testing.NullPointerTester; import java.lang.reflect.Field; import java.nio.charset.Charset; -import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; import java.util.Arrays; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link MediaType}. * * @author Gregory Kick */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class MediaTypeTest extends TestCase { + @J2ktIncompatible @GwtIncompatible // reflection public void testParse_useConstants() throws Exception { for (MediaType constant : getConstants()) { @@ -65,6 +69,7 @@ public void testParse_useConstants() throws Exception { } } + @J2ktIncompatible @GwtIncompatible // reflection public void testCreate_useConstants() throws Exception { for (MediaType constant : getConstants()) { @@ -75,6 +80,7 @@ public void testCreate_useConstants() throws Exception { } } + @J2ktIncompatible @GwtIncompatible // reflection public void testConstants_charset() throws Exception { for (Field field : getConstantFields()) { @@ -87,11 +93,13 @@ public void testConstants_charset() throws Exception { } } + @J2ktIncompatible @GwtIncompatible // reflection public void testConstants_areUnique() { assertThat(getConstants()).containsNoDuplicates(); } + @J2ktIncompatible @GwtIncompatible // reflection private static FluentIterable getConstantFields() { return FluentIterable.from(asList(MediaType.class.getDeclaredFields())) @@ -108,6 +116,7 @@ && isFinal(modifiers) }); } + @J2ktIncompatible @GwtIncompatible // reflection private static FluentIterable getConstants() { return getConstantFields() @@ -125,59 +134,31 @@ public MediaType apply(Field input) { } public void testCreate_invalidType() { - try { - MediaType.create("te> MediaType.create("te> MediaType.create("text", "pl@intext")); } public void testCreate_wildcardTypeDeclaredSubtype() { - try { - MediaType.create("*", "text"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MediaType.create("*", "text")); } public void testCreate_nonAsciiType() { - try { - MediaType.create("…", "a"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MediaType.create("…", "a")); } public void testCreate_nonAsciiSubtype() { - try { - MediaType.create("a", "…"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MediaType.create("a", "…")); } public void testCreate_emptyType() { - try { - MediaType.create("", "a"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MediaType.create("", "a")); } public void testCreate_emptySubtype() { - try { - MediaType.create("a", ""); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MediaType.create("a", "")); } public void testCreateApplicationType() { @@ -256,31 +237,19 @@ public void testWithParameters_invalidAttribute() { MediaType mediaType = MediaType.parse("text/plain"); ImmutableListMultimap parameters = ImmutableListMultimap.of("a", "1", "@", "2", "b", "3"); - try { - mediaType.withParameters(parameters); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> mediaType.withParameters(parameters)); } public void testWithParameters_nonAsciiParameter() { MediaType mediaType = MediaType.parse("text/plain"); ImmutableListMultimap parameters = ImmutableListMultimap.of("…", "a"); - try { - mediaType.withParameters(parameters); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> mediaType.withParameters(parameters)); } public void testWithParameters_nonAsciiParameterValue() { MediaType mediaType = MediaType.parse("text/plain"); ImmutableListMultimap parameters = ImmutableListMultimap.of("a", "…"); - try { - mediaType.withParameters(parameters); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> mediaType.withParameters(parameters)); } public void testWithParameter() { @@ -299,38 +268,22 @@ public void testWithParameter() { public void testWithParameter_invalidAttribute() { MediaType mediaType = MediaType.parse("text/plain"); - try { - mediaType.withParameter("@", "2"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> mediaType.withParameter("@", "2")); } public void testWithParameter_nonAsciiParameter() { MediaType mediaType = MediaType.parse("text/plain"); - try { - mediaType.withParameter("…", "a"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> mediaType.withParameter("…", "a")); } public void testWithParameter_nonAsciiParameterValue() { MediaType mediaType = MediaType.parse("text/plain"); - try { - mediaType.withParameter("a", "…"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> mediaType.withParameter("a", "…")); } public void testWithParameter_emptyParameter() { MediaType mediaType = MediaType.parse("text/plain"); - try { - mediaType.withParameter("", "a"); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> mediaType.withParameter("", "a")); } public void testWithParametersIterable() { @@ -353,38 +306,27 @@ public void testWithParametersIterable() { public void testWithParametersIterable_invalidAttribute() { MediaType mediaType = MediaType.parse("text/plain"); - try { - mediaType.withParameters("@", ImmutableSet.of("2")); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> mediaType.withParameters("@", ImmutableSet.of("2"))); } public void testWithParametersIterable_nonAsciiParameter() { MediaType mediaType = MediaType.parse("text/plain"); - try { - mediaType.withParameters("…", ImmutableSet.of("a")); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> mediaType.withParameters("…", ImmutableSet.of("a"))); } public void testWithParametersIterable_nonAsciiParameterValue() { MediaType mediaType = MediaType.parse("text/plain"); - try { - mediaType.withParameters("a", ImmutableSet.of("…")); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> mediaType.withParameters("a", ImmutableSet.of("…"))); } public void testWithParametersIterable_nullValue() { MediaType mediaType = MediaType.parse("text/plain"); - try { - mediaType.withParameters("a", Arrays.asList((String) null)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> mediaType.withParameters("a", Arrays.asList((String) null))); } public void testWithCharset() { @@ -423,94 +365,34 @@ public void testIs() { } public void testParse_empty() { - try { - MediaType.parse(""); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("")); } public void testParse_badInput() { - try { - MediaType.parse("/"); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - MediaType.parse("text"); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - MediaType.parse("text/"); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - MediaType.parse("te MediaType.parse("/")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("te MediaType.parse("text/pl@in")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain;")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; ")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a=")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a=@")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a=\"@")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a=1;")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a=1; ")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a=1; b")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a=1; b=")); + assertThrows(IllegalArgumentException.class, () -> MediaType.parse("text/plain; a=\u2025")); + } + + // https://github.com/google/guava/issues/6663 + public void testParse_spaceInParameterSeparator() { + assertThat(MediaType.parse("text/plain; charset =utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain; charset= utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain; charset = utf-8").charset()).hasValue(UTF_8); + assertThat(MediaType.parse("text/plain;charset =utf-8").charset()).hasValue(UTF_8); } public void testGetCharset() { @@ -518,6 +400,7 @@ public void testGetCharset() { assertThat(MediaType.parse("text/plain; charset=utf-8").charset()).hasValue(UTF_8); } + @J2ktIncompatible @GwtIncompatible // Non-UTF-8 Charset public void testGetCharset_utf16() { assertThat(MediaType.parse("text/plain; charset=utf-16").charset()).hasValue(UTF_16); @@ -525,29 +408,17 @@ public void testGetCharset_utf16() { public void testGetCharset_tooMany() { MediaType mediaType = MediaType.parse("text/plain; charset=utf-8; charset=utf-16"); - try { - mediaType.charset(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, mediaType::charset); } public void testGetCharset_illegalCharset() { MediaType mediaType = MediaType.parse("text/plain; charset=\"!@#$%^&*()\""); - try { - mediaType.charset(); - fail(); - } catch (IllegalCharsetNameException expected) { - } + assertThrows(IllegalArgumentException.class, mediaType::charset); } public void testGetCharset_unsupportedCharset() { MediaType mediaType = MediaType.parse("text/plain; charset=utf-wtf"); - try { - mediaType.charset(); - fail(); - } catch (UnsupportedCharsetException expected) { - } + assertThrows(UnsupportedCharsetException.class, mediaType::charset); } public void testEquals() { @@ -557,6 +428,9 @@ public void testEquals() { MediaType.create("TEXT", "PLAIN"), MediaType.parse("text/plain"), MediaType.parse("TEXT/PLAIN"), + MediaType.parse("text /plain"), + MediaType.parse("TEXT/ plain"), + MediaType.parse("text / plain"), MediaType.create("text", "plain").withParameter("a", "1").withoutParameters()) .addEqualityGroup( MediaType.create("text", "plain").withCharset(UTF_8), @@ -572,7 +446,11 @@ public void testEquals() { MediaType.parse("text/plain; charset=\"utf-8\""), MediaType.parse("text/plain; charset=\"\\u\\tf-\\8\""), MediaType.parse("text/plain; charset=UTF-8"), - MediaType.parse("text/plain ; charset=utf-8")) + MediaType.parse("text/plain ; charset=utf-8"), + MediaType.parse("text/plain; charset =UTF-8"), + MediaType.parse("text/plain; charset= UTF-8"), + MediaType.parse("text/plain; charset = UTF-8"), + MediaType.parse("text/plain; charset=\tUTF-8")) .addEqualityGroup(MediaType.parse("text/plain; charset=utf-8; charset=utf-8")) .addEqualityGroup( MediaType.create("text", "plain").withParameter("a", "value"), @@ -590,6 +468,7 @@ public void testEquals() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // Non-UTF-8 Charset public void testEquals_nonUtf8Charsets() { new EqualsTester() @@ -599,6 +478,7 @@ public void testEquals_nonUtf8Charsets() { .testEquals(); } + @J2ktIncompatible @GwtIncompatible // com.google.common.testing.NullPointerTester public void testNullPointer() { NullPointerTester tester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/net/PackageSanityTests.java b/android/guava-tests/test/com/google/common/net/PackageSanityTests.java index 3d18ad6dee6b..1a0a4c452f7a 100644 --- a/android/guava-tests/test/com/google/common/net/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/net/PackageSanityTests.java @@ -17,6 +17,7 @@ package com.google.common.net; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** * Basic sanity tests for the entire package. @@ -24,6 +25,7 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() { setDefault(InternetDomainName.class, InternetDomainName.from("google.com")); diff --git a/android/guava-tests/test/com/google/common/net/PercentEscaperTest.java b/android/guava-tests/test/com/google/common/net/PercentEscaperTest.java index 8443680e7f11..e3f7b17383c1 100644 --- a/android/guava-tests/test/com/google/common/net/PercentEscaperTest.java +++ b/android/guava-tests/test/com/google/common/net/PercentEscaperTest.java @@ -19,12 +19,14 @@ import static com.google.common.escape.testing.EscaperAsserts.assertEscaping; import static com.google.common.escape.testing.EscaperAsserts.assertUnescaped; import static com.google.common.escape.testing.EscaperAsserts.assertUnicodeEscaping; +import static com.google.common.net.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Preconditions; import com.google.common.escape.UnicodeEscaper; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link PercentEscaper}. @@ -32,6 +34,7 @@ * @author David Beaumont */ @GwtCompatible +@NullUnmarked public class PercentEscaperTest extends TestCase { /** Tests that the simple escaper treats 0-9, a-z and A-Z as safe */ @@ -45,7 +48,7 @@ public void testSimpleEscaper() { } } - // Testing mutlibyte escape sequences + // Testing multibyte escape sequences assertEscaping(e, "%00", '\u0000'); // nul assertEscaping(e, "%7F", '\u007f'); // del assertEscaping(e, "%C2%80", '\u0080'); // xx-00010,x-000000 @@ -97,12 +100,7 @@ public void testCustomEscaper_withpercent() { /** Test that giving a null 'safeChars' string causes a {@link NullPointerException}. */ public void testBadArguments_null() { - try { - new PercentEscaper(null, false); - fail("Expected null pointer exception for null parameter"); - } catch (NullPointerException expected) { - // pass - } + assertThrows(NullPointerException.class, () -> new PercentEscaper(null, false)); } /** @@ -110,33 +108,21 @@ public void testBadArguments_null() { * IllegalArgumentException}. */ public void testBadArguments_badchars() { - String msg = - "Alphanumeric characters are always 'safe' " + "and should not be explicitly specified"; - try { - new PercentEscaper("-+#abc.!", false); - fail(msg); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo(msg); - } + String msg = "Alphanumeric characters are always 'safe' and should not be explicitly specified"; + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> new PercentEscaper("-+#abc.!", false)); + assertThat(expected).hasMessageThat().isEqualTo(msg); } - /** - * Tests that if space is a safe character you cannot also specify 'plusForSpace' (throws {@link - * IllegalArgumentException}). - */ public void testBadArguments_plusforspace() { - try { - new PercentEscaper(" ", false); - } catch (IllegalArgumentException e) { - fail("Space can be a 'safe' character if plusForSpace is false"); - } + // space can be a safe char if plusForSpace is false + PercentEscaper unused = new PercentEscaper(" ", false); + + // space cannot be a safe char is plusForSpace is true String msg = "plusForSpace cannot be specified when space is a 'safe' character"; - try { - new PercentEscaper(" ", true); - fail(msg); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo(msg); - } + IllegalArgumentException expected = + assertThrows(IllegalArgumentException.class, () -> new PercentEscaper(" ", true)); + assertThat(expected).hasMessageThat().isEqualTo(msg); } /** Helper to manually escape a 7-bit ascii character */ diff --git a/android/guava-tests/test/com/google/common/net/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/net/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..054b6f62f7ac --- /dev/null +++ b/android/guava-tests/test/com/google/common/net/ReflectionFreeAssertThrows.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/net/UrlEscaperTesting.java b/android/guava-tests/test/com/google/common/net/UrlEscaperTesting.java new file mode 100644 index 000000000000..9992a1898ce1 --- /dev/null +++ b/android/guava-tests/test/com/google/common/net/UrlEscaperTesting.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import static com.google.common.escape.testing.EscaperAsserts.assertEscaping; +import static com.google.common.escape.testing.EscaperAsserts.assertUnescaped; +import static com.google.common.escape.testing.EscaperAsserts.assertUnicodeEscaping; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.escape.UnicodeEscaper; +import org.jspecify.annotations.NullUnmarked; + +/** + * Testing utilities for {@link UrlEscapers} and {@link LegacyUrlEscapersTest}. + * + * @author David Beaumont + */ +@GwtCompatible +@NullUnmarked +final class UrlEscaperTesting { + /** + * Helper to assert common expected behaviour of uri escapers. You should call + * assertBasicUrlEscaper() unless the escaper explicitly does not escape '%'. + */ + static void assertBasicUrlEscaperExceptPercent(UnicodeEscaper e) { + // URL escapers should throw null pointer exceptions for null input + try { + e.escape((String) null); + fail("Escaping null string should throw exception"); + } catch (NullPointerException x) { + // pass + } + + // All URL escapers should leave 0-9, A-Z, a-z unescaped + assertUnescaped(e, 'a'); + assertUnescaped(e, 'z'); + assertUnescaped(e, 'A'); + assertUnescaped(e, 'Z'); + assertUnescaped(e, '0'); + assertUnescaped(e, '9'); + + // Unreserved characters used in java.net.URLEncoder + assertUnescaped(e, '-'); + assertUnescaped(e, '_'); + assertUnescaped(e, '.'); + assertUnescaped(e, '*'); + + assertEscaping(e, "%00", '\u0000'); // nul + assertEscaping(e, "%7F", '\u007f'); // del + assertEscaping(e, "%C2%80", '\u0080'); // xx-00010,x-000000 + assertEscaping(e, "%DF%BF", '\u07ff'); // xx-11111,x-111111 + assertEscaping(e, "%E0%A0%80", '\u0800'); // xxx-0000,x-100000,x-00,0000 + assertEscaping(e, "%EF%BF%BF", '\uffff'); // xxx-1111,x-111111,x-11,1111 + assertUnicodeEscaping(e, "%F0%90%80%80", '\uD800', '\uDC00'); + assertUnicodeEscaping(e, "%F4%8F%BF%BF", '\uDBFF', '\uDFFF'); + + assertEquals("", e.escape("")); + assertEquals("safestring", e.escape("safestring")); + assertEquals("embedded%00null", e.escape("embedded\0null")); + assertEquals("max%EF%BF%BFchar", e.escape("max\uffffchar")); + } + + // Helper to assert common expected behaviour of uri escapers. + static void assertBasicUrlEscaper(UnicodeEscaper e) { + assertBasicUrlEscaperExceptPercent(e); + // The escape character must always be escaped + assertEscaping(e, "%25", '%'); + } + + static void assertPathEscaper(UnicodeEscaper e) { + assertBasicUrlEscaper(e); + + assertUnescaped(e, '!'); + assertUnescaped(e, '\''); + assertUnescaped(e, '('); + assertUnescaped(e, ')'); + assertUnescaped(e, '~'); + assertUnescaped(e, ':'); + assertUnescaped(e, '@'); + + // Don't use plus for spaces + assertEscaping(e, "%20", ' '); + + assertEquals("safe%20with%20spaces", e.escape("safe with spaces")); + assertEquals("foo@bar.com", e.escape("foo@bar.com")); + } + + private UrlEscaperTesting() {} +} diff --git a/android/guava-tests/test/com/google/common/net/UrlEscapersTest.java b/android/guava-tests/test/com/google/common/net/UrlEscapersTest.java index 9a67a95327d2..e9c0cd0a85d2 100644 --- a/android/guava-tests/test/com/google/common/net/UrlEscapersTest.java +++ b/android/guava-tests/test/com/google/common/net/UrlEscapersTest.java @@ -18,7 +18,8 @@ import static com.google.common.escape.testing.EscaperAsserts.assertEscaping; import static com.google.common.escape.testing.EscaperAsserts.assertUnescaped; -import static com.google.common.escape.testing.EscaperAsserts.assertUnicodeEscaping; +import static com.google.common.net.UrlEscaperTesting.assertBasicUrlEscaper; +import static com.google.common.net.UrlEscaperTesting.assertPathEscaper; import static com.google.common.net.UrlEscapers.urlFormParameterEscaper; import static com.google.common.net.UrlEscapers.urlFragmentEscaper; import static com.google.common.net.UrlEscapers.urlPathSegmentEscaper; @@ -26,6 +27,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.escape.UnicodeEscaper; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the {@link UrlEscapers} class. @@ -33,56 +35,8 @@ * @author David Beaumont */ @GwtCompatible +@NullUnmarked public class UrlEscapersTest extends TestCase { - /** - * Helper to assert common expected behaviour of uri escapers. You should call - * assertBasicUrlEscaper() unless the escaper explicitly does not escape '%'. - */ - static void assertBasicUrlEscaperExceptPercent(UnicodeEscaper e) { - // URL escapers should throw null pointer exceptions for null input - try { - e.escape((String) null); - fail("Escaping null string should throw exception"); - } catch (NullPointerException x) { - // pass - } - - // All URL escapers should leave 0-9, A-Z, a-z unescaped - assertUnescaped(e, 'a'); - assertUnescaped(e, 'z'); - assertUnescaped(e, 'A'); - assertUnescaped(e, 'Z'); - assertUnescaped(e, '0'); - assertUnescaped(e, '9'); - - // Unreserved characters used in java.net.URLEncoder - assertUnescaped(e, '-'); - assertUnescaped(e, '_'); - assertUnescaped(e, '.'); - assertUnescaped(e, '*'); - - assertEscaping(e, "%00", '\u0000'); // nul - assertEscaping(e, "%7F", '\u007f'); // del - assertEscaping(e, "%C2%80", '\u0080'); // xx-00010,x-000000 - assertEscaping(e, "%DF%BF", '\u07ff'); // xx-11111,x-111111 - assertEscaping(e, "%E0%A0%80", '\u0800'); // xxx-0000,x-100000,x-00,0000 - assertEscaping(e, "%EF%BF%BF", '\uffff'); // xxx-1111,x-111111,x-11,1111 - assertUnicodeEscaping(e, "%F0%90%80%80", '\uD800', '\uDC00'); - assertUnicodeEscaping(e, "%F4%8F%BF%BF", '\uDBFF', '\uDFFF'); - - assertEquals("", e.escape("")); - assertEquals("safestring", e.escape("safestring")); - assertEquals("embedded%00null", e.escape("embedded\0null")); - assertEquals("max%EF%BF%BFchar", e.escape("max\uffffchar")); - } - - // Helper to assert common expected behaviour of uri escapers. - static void assertBasicUrlEscaper(UnicodeEscaper e) { - assertBasicUrlEscaperExceptPercent(e); - // The escape character must always be escaped - assertEscaping(e, "%25", '%'); - } - public void testUrlFormParameterEscaper() { UnicodeEscaper e = (UnicodeEscaper) urlFormParameterEscaper(); // Verify that these are the same escaper (as documented) @@ -114,24 +68,6 @@ public void testUrlPathSegmentEscaper() { assertUnescaped(e, '+'); } - static void assertPathEscaper(UnicodeEscaper e) { - assertBasicUrlEscaper(e); - - assertUnescaped(e, '!'); - assertUnescaped(e, '\''); - assertUnescaped(e, '('); - assertUnescaped(e, ')'); - assertUnescaped(e, '~'); - assertUnescaped(e, ':'); - assertUnescaped(e, '@'); - - // Don't use plus for spaces - assertEscaping(e, "%20", ' '); - - assertEquals("safe%20with%20spaces", e.escape("safe with spaces")); - assertEquals("foo@bar.com", e.escape("foo@bar.com")); - } - public void testUrlFragmentEscaper() { UnicodeEscaper e = (UnicodeEscaper) urlFragmentEscaper(); assertUnescaped(e, '+'); diff --git a/android/guava-tests/test/com/google/common/primitives/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/primitives/AndroidIncompatible.java index 2cf4a28d003b..ad1a2ab4e987 100644 --- a/android/guava-tests/test/com/google/common/primitives/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/primitives/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/primitives/BooleansTest.java b/android/guava-tests/test/com/google/common/primitives/BooleansTest.java index 560c33700382..4c98fc364b79 100644 --- a/android/guava-tests/test/com/google/common/primitives/BooleansTest.java +++ b/android/guava-tests/test/com/google/common/primitives/BooleansTest.java @@ -16,24 +16,32 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Booleans}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked public class BooleansTest extends TestCase { private static final boolean[] EMPTY = {}; private static final boolean[] ARRAY_FALSE = {false}; @@ -43,117 +51,139 @@ public class BooleansTest extends TestCase { private static final boolean[] VALUES = {false, true}; + @SuppressWarnings("InlineMeInliner") // We need to test our method. public void testHashCode() { - assertEquals(Boolean.TRUE.hashCode(), Booleans.hashCode(true)); - assertEquals(Boolean.FALSE.hashCode(), Booleans.hashCode(false)); + assertThat(Booleans.hashCode(true)).isEqualTo(Boolean.TRUE.hashCode()); + assertThat(Booleans.hashCode(false)).isEqualTo(Boolean.FALSE.hashCode()); } public void testTrueFirst() { - assertEquals(0, Booleans.trueFirst().compare(true, true)); - assertEquals(0, Booleans.trueFirst().compare(false, false)); - assertTrue(Booleans.trueFirst().compare(true, false) < 0); - assertTrue(Booleans.trueFirst().compare(false, true) > 0); + assertThat(Booleans.trueFirst().compare(true, true)).isEqualTo(0); + assertThat(Booleans.trueFirst().compare(false, false)).isEqualTo(0); + assertThat(Booleans.trueFirst().compare(true, false)).isLessThan(0); + assertThat(Booleans.trueFirst().compare(false, true)).isGreaterThan(0); } public void testFalseFirst() { - assertEquals(0, Booleans.falseFirst().compare(true, true)); - assertEquals(0, Booleans.falseFirst().compare(false, false)); - assertTrue(Booleans.falseFirst().compare(false, true) < 0); - assertTrue(Booleans.falseFirst().compare(true, false) > 0); + assertThat(Booleans.falseFirst().compare(true, true)).isEqualTo(0); + assertThat(Booleans.falseFirst().compare(false, false)).isEqualTo(0); + assertThat(Booleans.falseFirst().compare(false, true)).isLessThan(0); + assertThat(Booleans.falseFirst().compare(true, false)).isGreaterThan(0); } + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testCompare() { for (boolean x : VALUES) { for (boolean y : VALUES) { // note: spec requires only that the sign is the same - assertEquals(x + ", " + y, Boolean.valueOf(x).compareTo(y), Booleans.compare(x, y)); + assertWithMessage(x + ", " + y) + .that(Booleans.compare(x, y)) + .isEqualTo(Boolean.valueOf(x).compareTo(y)); } } } public void testContains() { - assertFalse(Booleans.contains(EMPTY, false)); - assertFalse(Booleans.contains(ARRAY_FALSE, true)); - assertTrue(Booleans.contains(ARRAY_FALSE, false)); - assertTrue(Booleans.contains(ARRAY_FALSE_TRUE, false)); - assertTrue(Booleans.contains(ARRAY_FALSE_TRUE, true)); + assertThat(Booleans.contains(EMPTY, false)).isFalse(); + assertThat(Booleans.contains(ARRAY_FALSE, true)).isFalse(); + assertThat(Booleans.contains(ARRAY_FALSE, false)).isTrue(); + assertThat(Booleans.contains(ARRAY_FALSE_TRUE, false)).isTrue(); + assertThat(Booleans.contains(ARRAY_FALSE_TRUE, true)).isTrue(); } public void testIndexOf() { - assertEquals(-1, Booleans.indexOf(EMPTY, ARRAY_FALSE)); - assertEquals(-1, Booleans.indexOf(ARRAY_FALSE, ARRAY_FALSE_TRUE)); - assertEquals(0, Booleans.indexOf(ARRAY_FALSE_FALSE, ARRAY_FALSE)); - assertEquals(0, Booleans.indexOf(ARRAY_FALSE, ARRAY_FALSE)); - assertEquals(0, Booleans.indexOf(ARRAY_FALSE_TRUE, ARRAY_FALSE)); - assertEquals(1, Booleans.indexOf(ARRAY_FALSE_TRUE, ARRAY_TRUE)); - assertEquals(0, Booleans.indexOf(ARRAY_TRUE, new boolean[0])); + assertThat(Booleans.indexOf(EMPTY, ARRAY_FALSE)).isEqualTo(-1); + assertThat(Booleans.indexOf(ARRAY_FALSE, ARRAY_FALSE_TRUE)).isEqualTo(-1); + assertThat(Booleans.indexOf(ARRAY_FALSE_FALSE, ARRAY_FALSE)).isEqualTo(0); + assertThat(Booleans.indexOf(ARRAY_FALSE, ARRAY_FALSE)).isEqualTo(0); + assertThat(Booleans.indexOf(ARRAY_FALSE_TRUE, ARRAY_FALSE)).isEqualTo(0); + assertThat(Booleans.indexOf(ARRAY_FALSE_TRUE, ARRAY_TRUE)).isEqualTo(1); + assertThat(Booleans.indexOf(ARRAY_TRUE, new boolean[0])).isEqualTo(0); } public void testIndexOf_arrays() { - assertEquals(-1, Booleans.indexOf(EMPTY, false)); - assertEquals(-1, Booleans.indexOf(ARRAY_FALSE, true)); - assertEquals(-1, Booleans.indexOf(ARRAY_FALSE_FALSE, true)); - assertEquals(0, Booleans.indexOf(ARRAY_FALSE, false)); - assertEquals(0, Booleans.indexOf(ARRAY_FALSE_TRUE, false)); - assertEquals(1, Booleans.indexOf(ARRAY_FALSE_TRUE, true)); - assertEquals(2, Booleans.indexOf(new boolean[] {false, false, true}, true)); + assertThat(Booleans.indexOf(EMPTY, false)).isEqualTo(-1); + assertThat(Booleans.indexOf(ARRAY_FALSE, true)).isEqualTo(-1); + assertThat(Booleans.indexOf(ARRAY_FALSE_FALSE, true)).isEqualTo(-1); + assertThat(Booleans.indexOf(ARRAY_FALSE, false)).isEqualTo(0); + assertThat(Booleans.indexOf(ARRAY_FALSE_TRUE, false)).isEqualTo(0); + assertThat(Booleans.indexOf(ARRAY_FALSE_TRUE, true)).isEqualTo(1); + assertThat(Booleans.indexOf(new boolean[] {false, false, true}, true)).isEqualTo(2); } public void testLastIndexOf() { - assertEquals(-1, Booleans.lastIndexOf(EMPTY, false)); - assertEquals(-1, Booleans.lastIndexOf(ARRAY_FALSE, true)); - assertEquals(-1, Booleans.lastIndexOf(ARRAY_FALSE_FALSE, true)); - assertEquals(0, Booleans.lastIndexOf(ARRAY_FALSE, false)); - assertEquals(0, Booleans.lastIndexOf(ARRAY_FALSE_TRUE, false)); - assertEquals(1, Booleans.lastIndexOf(ARRAY_FALSE_TRUE, true)); - assertEquals(2, Booleans.lastIndexOf(new boolean[] {false, true, true}, true)); + assertThat(Booleans.lastIndexOf(EMPTY, false)).isEqualTo(-1); + assertThat(Booleans.lastIndexOf(ARRAY_FALSE, true)).isEqualTo(-1); + assertThat(Booleans.lastIndexOf(ARRAY_FALSE_FALSE, true)).isEqualTo(-1); + assertThat(Booleans.lastIndexOf(ARRAY_FALSE, false)).isEqualTo(0); + assertThat(Booleans.lastIndexOf(ARRAY_FALSE_TRUE, false)).isEqualTo(0); + assertThat(Booleans.lastIndexOf(ARRAY_FALSE_TRUE, true)).isEqualTo(1); + assertThat(Booleans.lastIndexOf(new boolean[] {false, true, true}, true)).isEqualTo(2); } public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Booleans.concat())); - assertTrue(Arrays.equals(EMPTY, Booleans.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Booleans.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY_FALSE, Booleans.concat(ARRAY_FALSE))); - assertNotSame(ARRAY_FALSE, Booleans.concat(ARRAY_FALSE)); - assertTrue(Arrays.equals(ARRAY_FALSE, Booleans.concat(EMPTY, ARRAY_FALSE, EMPTY))); - assertTrue( - Arrays.equals( - new boolean[] {false, false, false}, - Booleans.concat(ARRAY_FALSE, ARRAY_FALSE, ARRAY_FALSE))); - assertTrue( - Arrays.equals( - new boolean[] {false, false, true}, Booleans.concat(ARRAY_FALSE, ARRAY_FALSE_TRUE))); + assertThat(Booleans.concat()).isEqualTo(EMPTY); + assertThat(Booleans.concat(EMPTY)).isEqualTo(EMPTY); + assertThat(Booleans.concat(EMPTY, EMPTY, EMPTY)).isEqualTo(EMPTY); + assertThat(Booleans.concat(ARRAY_FALSE)).isEqualTo(ARRAY_FALSE); + assertThat(Booleans.concat(ARRAY_FALSE)).isNotSameInstanceAs(ARRAY_FALSE); + assertThat(Booleans.concat(EMPTY, ARRAY_FALSE, EMPTY)).isEqualTo(ARRAY_FALSE); + assertThat(Booleans.concat(ARRAY_FALSE, ARRAY_FALSE, ARRAY_FALSE)) + .isEqualTo(new boolean[] {false, false, false}); + assertThat(Booleans.concat(ARRAY_FALSE, ARRAY_FALSE_TRUE)) + .isEqualTo(new boolean[] {false, false, true}); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_negative() { + int dim1 = 1 << 16; + int dim2 = 1 << 15; + assertThat(dim1 * dim2).isLessThan(0); + testConcatOverflow(dim1, dim2); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_nonNegative() { + int dim1 = 1 << 16; + int dim2 = 1 << 16; + assertThat(dim1 * dim2).isAtLeast(0); + testConcatOverflow(dim1, dim2); + } + + private static void testConcatOverflow(int arraysDim1, int arraysDim2) { + assertThat((long) arraysDim1 * arraysDim2).isNotEqualTo((long) (arraysDim1 * arraysDim2)); + + boolean[][] arrays = new boolean[arraysDim1][]; + // it's shared to avoid using too much memory in tests + boolean[] sharedArray = new boolean[arraysDim2]; + Arrays.fill(arrays, sharedArray); + + try { + Booleans.concat(arrays); + fail(); + } catch (IllegalArgumentException expected) { + } } public void testEnsureCapacity() { - assertSame(EMPTY, Booleans.ensureCapacity(EMPTY, 0, 1)); - assertSame(ARRAY_FALSE, Booleans.ensureCapacity(ARRAY_FALSE, 0, 1)); - assertSame(ARRAY_FALSE, Booleans.ensureCapacity(ARRAY_FALSE, 1, 1)); - assertTrue( - Arrays.equals( - new boolean[] {true, false, false}, - Booleans.ensureCapacity(new boolean[] {true}, 2, 1))); + assertThat(Booleans.ensureCapacity(EMPTY, 0, 1)).isSameInstanceAs(EMPTY); + assertThat(Booleans.ensureCapacity(ARRAY_FALSE, 0, 1)).isSameInstanceAs(ARRAY_FALSE); + assertThat(Booleans.ensureCapacity(ARRAY_FALSE, 1, 1)).isSameInstanceAs(ARRAY_FALSE); + assertThat(Booleans.ensureCapacity(new boolean[] {true}, 2, 1)) + .isEqualTo(new boolean[] {true, false, false}); } public void testEnsureCapacity_fail() { - try { - Booleans.ensureCapacity(ARRAY_FALSE, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - // notice that this should even fail when no growth was needed - Booleans.ensureCapacity(ARRAY_FALSE, 1, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Booleans.ensureCapacity(ARRAY_FALSE, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> Booleans.ensureCapacity(ARRAY_FALSE, 1, -1)); } public void testJoin() { - assertEquals("", Booleans.join(",", EMPTY)); - assertEquals("false", Booleans.join(",", ARRAY_FALSE)); - assertEquals("false,true", Booleans.join(",", false, true)); - assertEquals("falsetruefalse", Booleans.join("", false, true, false)); + assertThat(Booleans.join(",", EMPTY)).isEmpty(); + assertThat(Booleans.join(",", ARRAY_FALSE)).isEqualTo("false"); + assertThat(Booleans.join(",", false, true)).isEqualTo("false,true"); + assertThat(Booleans.join("", false, true, false)).isEqualTo("falsetruefalse"); } public void testLexicographicalComparator() { @@ -172,10 +202,11 @@ public void testLexicographicalComparator() { Helpers.testComparator(comparator, ordered); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testLexicographicalComparatorSerializable() { Comparator comparator = Booleans.lexicographicalComparator(); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); } public void testReverse() { @@ -189,14 +220,14 @@ public void testReverse() { private static void testReverse(boolean[] input, boolean[] expectedOutput) { input = Arrays.copyOf(input, input.length); Booleans.reverse(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testReverse( boolean[] input, int fromIndex, int toIndex, boolean[] expectedOutput) { input = Arrays.copyOf(input, input.length); Booleans.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testReverseIndexed() { @@ -209,20 +240,251 @@ public void testReverseIndexed() { new boolean[] {true, true, false, false}, 1, 3, new boolean[] {true, false, true, false}); } + private static void testRotate(boolean[] input, int distance, boolean[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Booleans.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + boolean[] input, int distance, int fromIndex, int toIndex, boolean[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Booleans.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new boolean[] {}, -1, new boolean[] {}); + testRotate(new boolean[] {}, 0, new boolean[] {}); + testRotate(new boolean[] {}, 1, new boolean[] {}); + + testRotate(new boolean[] {true}, -2, new boolean[] {true}); + testRotate(new boolean[] {true}, -1, new boolean[] {true}); + testRotate(new boolean[] {true}, 0, new boolean[] {true}); + testRotate(new boolean[] {true}, 1, new boolean[] {true}); + testRotate(new boolean[] {true}, 2, new boolean[] {true}); + + testRotate(new boolean[] {true, false}, -3, new boolean[] {false, true}); + testRotate(new boolean[] {true, false}, -1, new boolean[] {false, true}); + testRotate(new boolean[] {true, false}, -2, new boolean[] {true, false}); + testRotate(new boolean[] {true, false}, 0, new boolean[] {true, false}); + testRotate(new boolean[] {true, false}, 1, new boolean[] {false, true}); + testRotate(new boolean[] {true, false}, 2, new boolean[] {true, false}); + testRotate(new boolean[] {true, false}, 3, new boolean[] {false, true}); + + testRotate(new boolean[] {true, false, true}, -5, new boolean[] {true, true, false}); + testRotate(new boolean[] {true, false, true}, -4, new boolean[] {false, true, true}); + testRotate(new boolean[] {true, false, true}, -3, new boolean[] {true, false, true}); + testRotate(new boolean[] {true, false, true}, -2, new boolean[] {true, true, false}); + testRotate(new boolean[] {true, false, true}, -1, new boolean[] {false, true, true}); + testRotate(new boolean[] {true, false, true}, 0, new boolean[] {true, false, true}); + testRotate(new boolean[] {true, false, true}, 1, new boolean[] {true, true, false}); + testRotate(new boolean[] {true, false, true}, 2, new boolean[] {false, true, true}); + testRotate(new boolean[] {true, false, true}, 3, new boolean[] {true, false, true}); + testRotate(new boolean[] {true, false, true}, 4, new boolean[] {true, true, false}); + testRotate(new boolean[] {true, false, true}, 5, new boolean[] {false, true, true}); + + testRotate( + new boolean[] {true, false, true, false}, -9, new boolean[] {false, true, false, true}); + testRotate( + new boolean[] {true, false, true, false}, -5, new boolean[] {false, true, false, true}); + testRotate( + new boolean[] {true, false, true, false}, -1, new boolean[] {false, true, false, true}); + testRotate( + new boolean[] {true, false, true, false}, 0, new boolean[] {true, false, true, false}); + testRotate( + new boolean[] {true, false, true, false}, 1, new boolean[] {false, true, false, true}); + testRotate( + new boolean[] {true, false, true, false}, 5, new boolean[] {false, true, false, true}); + testRotate( + new boolean[] {true, false, true, false}, 9, new boolean[] {false, true, false, true}); + + testRotate( + new boolean[] {true, false, true, false, true}, + -6, + new boolean[] {false, true, false, true, true}); + testRotate( + new boolean[] {true, false, true, false, true}, + -4, + new boolean[] {true, true, false, true, false}); + testRotate( + new boolean[] {true, false, true, false, true}, + -3, + new boolean[] {false, true, true, false, true}); + testRotate( + new boolean[] {true, false, true, false, true}, + -1, + new boolean[] {false, true, false, true, true}); + testRotate( + new boolean[] {true, false, true, false, true}, + 0, + new boolean[] {true, false, true, false, true}); + testRotate( + new boolean[] {true, false, true, false, true}, + 1, + new boolean[] {true, true, false, true, false}); + testRotate( + new boolean[] {true, false, true, false, true}, + 3, + new boolean[] {true, false, true, true, false}); + testRotate( + new boolean[] {true, false, true, false, true}, + 4, + new boolean[] {false, true, false, true, true}); + testRotate( + new boolean[] {true, false, true, false, true}, + 6, + new boolean[] {true, true, false, true, false}); + } + + public void testRotateIndexed() { + testRotate(new boolean[] {}, 0, 0, 0, new boolean[] {}); + + testRotate(new boolean[] {true}, 0, 0, 1, new boolean[] {true}); + testRotate(new boolean[] {true}, 1, 0, 1, new boolean[] {true}); + testRotate(new boolean[] {true}, 1, 1, 1, new boolean[] {true}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -6, + 1, + 6, + new boolean[] {false, false, true, false, true, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -1, + 1, + 6, + new boolean[] {false, false, true, false, true, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 0, + 1, + 6, + new boolean[] {false, true, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 5, + 1, + 6, + new boolean[] {false, true, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 14, + 1, + 6, + new boolean[] {false, false, true, false, true, true, false}); + + // Rotate the first three elements + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -2, + 0, + 3, + new boolean[] {false, false, true, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -1, + 0, + 3, + new boolean[] {true, false, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 0, + 0, + 3, + new boolean[] {false, true, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 1, + 0, + 3, + new boolean[] {false, false, true, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 2, + 0, + 3, + new boolean[] {true, false, false, true, false, true, false}); + + // Rotate the last four elements + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -6, + 3, + 7, + new boolean[] {false, true, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -5, + 3, + 7, + new boolean[] {false, true, false, false, true, false, true}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -4, + 3, + 7, + new boolean[] {false, true, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -3, + 3, + 7, + new boolean[] {false, true, false, false, true, false, true}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -2, + 3, + 7, + new boolean[] {false, true, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + -1, + 3, + 7, + new boolean[] {false, true, false, false, true, false, true}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 0, + 3, + 7, + new boolean[] {false, true, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 1, + 3, + 7, + new boolean[] {false, true, false, false, true, false, true}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 2, + 3, + 7, + new boolean[] {false, true, false, true, false, true, false}); + testRotate( + new boolean[] {false, true, false, true, false, true, false}, + 3, + 3, + 7, + new boolean[] {false, true, false, false, true, false, true}); + } + public void testToArray() { // need explicit type parameter to avoid javac warning!? List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Booleans.toArray(none))); + assertThat(Booleans.toArray(none)).isEqualTo(EMPTY); List one = Arrays.asList(false); - assertTrue(Arrays.equals(ARRAY_FALSE, Booleans.toArray(one))); + assertThat(Booleans.toArray(one)).isEqualTo(ARRAY_FALSE); boolean[] array = {false, false, true}; List three = Arrays.asList(false, false, true); - assertTrue(Arrays.equals(array, Booleans.toArray(three))); + assertThat(Booleans.toArray(three)).isEqualTo(array); - assertTrue(Arrays.equals(array, Booleans.toArray(Booleans.asList(array)))); + assertThat(Booleans.toArray(Booleans.asList(array))).isEqualTo(array); } public void testToArray_threadSafe() { @@ -236,108 +498,120 @@ public void testToArray_threadSafe() { Collection misleadingSize = Helpers.misleadingSizeCollection(delta); misleadingSize.addAll(list); boolean[] arr = Booleans.toArray(misleadingSize); - assertEquals(i, arr.length); + assertThat(arr).hasLength(i); for (int j = 0; j < i; j++) { - assertEquals(VALUES[j], arr[j]); + assertThat(arr[j]).isEqualTo(VALUES[j]); } } } } public void testToArray_withNull() { - List list = Arrays.asList(false, true, null); - try { - Booleans.toArray(list); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Boolean> list = Arrays.asList(false, true, null); + assertThrows(NullPointerException.class, () -> Booleans.toArray(list)); } + @SuppressWarnings({"CollectionIsEmptyTruth", "CollectionIsNotEmptyTruth"}) public void testAsListIsEmpty() { - assertTrue(Booleans.asList(EMPTY).isEmpty()); - assertFalse(Booleans.asList(ARRAY_FALSE).isEmpty()); + assertThat(Booleans.asList(EMPTY).isEmpty()).isTrue(); + assertThat(Booleans.asList(ARRAY_FALSE).isEmpty()).isFalse(); } + @SuppressWarnings("CollectionSizeTruth") public void testAsListSize() { - assertEquals(0, Booleans.asList(EMPTY).size()); - assertEquals(1, Booleans.asList(ARRAY_FALSE).size()); - assertEquals(2, Booleans.asList(ARRAY_FALSE_TRUE).size()); + assertThat(Booleans.asList(EMPTY).size()).isEqualTo(0); + assertThat(Booleans.asList(ARRAY_FALSE).size()).isEqualTo(1); + assertThat(Booleans.asList(ARRAY_FALSE_TRUE).size()).isEqualTo(2); } + @SuppressWarnings("BooleanArrayIndexOfBoolean") public void testAsListIndexOf() { - assertEquals(-1, Booleans.asList(EMPTY).indexOf((Object) "wrong type")); - assertEquals(-1, Booleans.asList(EMPTY).indexOf(true)); - assertEquals(-1, Booleans.asList(ARRAY_FALSE).indexOf(true)); - assertEquals(0, Booleans.asList(ARRAY_FALSE).indexOf(false)); - assertEquals(1, Booleans.asList(ARRAY_FALSE_TRUE).indexOf(true)); + assertThat(Booleans.asList(EMPTY).indexOf((Object) "wrong type")).isEqualTo(-1); + assertThat(Booleans.asList(EMPTY).indexOf(true)).isEqualTo(-1); + assertThat(Booleans.asList(ARRAY_FALSE).indexOf(true)).isEqualTo(-1); + assertThat(Booleans.asList(ARRAY_FALSE).indexOf(false)).isEqualTo(0); + assertThat(Booleans.asList(ARRAY_FALSE_TRUE).indexOf(true)).isEqualTo(1); } public void testAsListLastIndexOf() { - assertEquals(-1, Booleans.asList(EMPTY).lastIndexOf((Object) "wrong type")); - assertEquals(-1, Booleans.asList(EMPTY).lastIndexOf(true)); - assertEquals(-1, Booleans.asList(ARRAY_FALSE).lastIndexOf(true)); - assertEquals(1, Booleans.asList(ARRAY_FALSE_TRUE).lastIndexOf(true)); - assertEquals(1, Booleans.asList(ARRAY_FALSE_FALSE).lastIndexOf(false)); + assertThat(Booleans.asList(EMPTY).lastIndexOf((Object) "wrong type")).isEqualTo(-1); + assertThat(Booleans.asList(EMPTY).lastIndexOf(true)).isEqualTo(-1); + assertThat(Booleans.asList(ARRAY_FALSE).lastIndexOf(true)).isEqualTo(-1); + assertThat(Booleans.asList(ARRAY_FALSE_TRUE).lastIndexOf(true)).isEqualTo(1); + assertThat(Booleans.asList(ARRAY_FALSE_FALSE).lastIndexOf(false)).isEqualTo(1); } + @SuppressWarnings({"BooleanArrayContainsBoolean", "CollectionDoesNotContainTruth"}) public void testAsListContains() { - assertFalse(Booleans.asList(EMPTY).contains((Object) "wrong type")); - assertFalse(Booleans.asList(EMPTY).contains(true)); - assertFalse(Booleans.asList(ARRAY_FALSE).contains(true)); - assertTrue(Booleans.asList(ARRAY_TRUE).contains(true)); - assertTrue(Booleans.asList(ARRAY_FALSE_TRUE).contains(false)); - assertTrue(Booleans.asList(ARRAY_FALSE_TRUE).contains(true)); + assertThat(Booleans.asList(EMPTY).contains((Object) "wrong type")).isFalse(); + assertThat(Booleans.asList(EMPTY).contains(true)).isFalse(); + assertThat(Booleans.asList(ARRAY_FALSE).contains(true)).isFalse(); + assertThat(Booleans.asList(ARRAY_TRUE).contains(true)).isTrue(); + assertThat(Booleans.asList(ARRAY_FALSE_TRUE).contains(false)).isTrue(); + assertThat(Booleans.asList(ARRAY_FALSE_TRUE).contains(true)).isTrue(); } public void testAsListEquals() { - assertEquals(Booleans.asList(EMPTY), Collections.emptyList()); - assertEquals(Booleans.asList(ARRAY_FALSE), Booleans.asList(ARRAY_FALSE)); - assertFalse(Booleans.asList(ARRAY_FALSE).equals(ARRAY_FALSE)); - assertFalse(Booleans.asList(ARRAY_FALSE).equals(null)); - assertFalse(Booleans.asList(ARRAY_FALSE).equals(Booleans.asList(ARRAY_FALSE_TRUE))); - assertFalse(Booleans.asList(ARRAY_FALSE_FALSE).equals(Booleans.asList(ARRAY_FALSE_TRUE))); + assertThat(Booleans.asList(EMPTY).equals(ImmutableList.of())).isTrue(); + assertThat(Booleans.asList(ARRAY_FALSE).equals(Booleans.asList(ARRAY_FALSE))).isTrue(); + @SuppressWarnings("EqualsIncompatibleType") + boolean listEqualsArray = Booleans.asList(ARRAY_FALSE).equals(ARRAY_FALSE); + assertThat(listEqualsArray).isFalse(); + assertThat(Booleans.asList(ARRAY_FALSE).equals(null)).isFalse(); + assertThat(Booleans.asList(ARRAY_FALSE).equals(Booleans.asList(ARRAY_FALSE_TRUE))).isFalse(); + assertThat(Booleans.asList(ARRAY_FALSE_FALSE).equals(Booleans.asList(ARRAY_FALSE_TRUE))) + .isFalse(); assertEquals(1, Booleans.asList(ARRAY_FALSE_TRUE).lastIndexOf(true)); List reference = Booleans.asList(ARRAY_FALSE); assertEquals(Booleans.asList(ARRAY_FALSE), reference); - assertEquals(reference, reference); + // Explicitly call `equals`; `assertEquals` might return fast + assertThat(reference.equals(reference)).isTrue(); } public void testAsListHashcode() { - assertEquals(1, Booleans.asList(EMPTY).hashCode()); - assertEquals(Booleans.asList(ARRAY_FALSE).hashCode(), Booleans.asList(ARRAY_FALSE).hashCode()); + assertThat(Booleans.asList(EMPTY).hashCode()).isEqualTo(1); + assertThat(Booleans.asList(ARRAY_FALSE).hashCode()) + .isEqualTo(Booleans.asList(ARRAY_FALSE).hashCode()); List reference = Booleans.asList(ARRAY_FALSE); - assertEquals(Booleans.asList(ARRAY_FALSE).hashCode(), reference.hashCode()); + assertThat(reference.hashCode()).isEqualTo(Booleans.asList(ARRAY_FALSE).hashCode()); } public void testAsListToString() { - assertEquals("[false]", Booleans.asList(ARRAY_FALSE).toString()); - assertEquals("[false, true]", Booleans.asList(ARRAY_FALSE_TRUE).toString()); + assertThat(Booleans.asList(ARRAY_FALSE).toString()).isEqualTo("[false]"); + assertThat(Booleans.asList(ARRAY_FALSE_TRUE).toString()).isEqualTo("[false, true]"); } public void testAsListSet() { List list = Booleans.asList(ARRAY_FALSE); - assertFalse(list.set(0, true)); - assertTrue(list.set(0, false)); - try { - list.set(0, null); - fail(); - } catch (NullPointerException expected) { - } - try { - list.set(1, true); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThat(list.set(0, true)).isFalse(); + assertThat(list.set(0, false)).isTrue(); + assertThrows(NullPointerException.class, () -> list.set(0, null)); + assertThrows(IndexOutOfBoundsException.class, () -> list.set(1, true)); + } + + public void testAsListCanonicalValues() { + List list = Booleans.asList(true, false); + assertThat(list.get(0)).isSameInstanceAs(true); + assertThat(list.get(1)).isSameInstanceAs(false); + @SuppressWarnings("deprecation") + Boolean anotherTrue = new Boolean(true); + @SuppressWarnings("deprecation") + Boolean anotherFalse = new Boolean(false); + list.set(0, anotherTrue); + assertThat(list.get(0)).isSameInstanceAs(true); + list.set(1, anotherFalse); + assertThat(list.get(1)).isSameInstanceAs(false); } public void testCountTrue() { - assertEquals(0, Booleans.countTrue()); - assertEquals(0, Booleans.countTrue(false)); - assertEquals(1, Booleans.countTrue(true)); - assertEquals(3, Booleans.countTrue(false, true, false, true, false, true)); - assertEquals(1, Booleans.countTrue(false, false, true, false, false)); + assertThat(Booleans.countTrue()).isEqualTo(0); + assertThat(Booleans.countTrue(false)).isEqualTo(0); + assertThat(Booleans.countTrue(true)).isEqualTo(1); + assertThat(Booleans.countTrue(false, true, false, true, false, true)).isEqualTo(3); + assertThat(Booleans.countTrue(false, false, true, false, false)).isEqualTo(1); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Booleans.class); diff --git a/android/guava-tests/test/com/google/common/primitives/ByteArrayAsListTest.java b/android/guava-tests/test/com/google/common/primitives/ByteArrayAsListTest.java index c3d0be1ab76c..9417fad4c4f6 100644 --- a/android/guava-tests/test/com/google/common/primitives/ByteArrayAsListTest.java +++ b/android/guava-tests/test/com/google/common/primitives/ByteArrayAsListTest.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -31,13 +32,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Bytes#asList(byte[])}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked +@AndroidIncompatible // test-suite builders public class ByteArrayAsListTest extends TestCase { private static List asList(Byte[] values) { @@ -48,6 +52,7 @@ private static List asList(Byte[] values) { return Bytes.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = diff --git a/android/guava-tests/test/com/google/common/primitives/BytesTest.java b/android/guava-tests/test/com/google/common/primitives/BytesTest.java index 233a0150cedb..75d39744f833 100644 --- a/android/guava-tests/test/com/google/common/primitives/BytesTest.java +++ b/android/guava-tests/test/com/google/common/primitives/BytesTest.java @@ -16,8 +16,12 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; import java.util.Arrays; @@ -25,13 +29,16 @@ import java.util.Collections; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Bytes}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class BytesTest extends TestCase { private static final byte[] EMPTY = {}; private static final byte[] ARRAY1 = {(byte) 1}; @@ -39,129 +46,153 @@ public class BytesTest extends TestCase { private static final byte[] VALUES = {Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE}; + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testHashCode() { for (byte value : VALUES) { - assertEquals(((Byte) value).hashCode(), Bytes.hashCode(value)); + assertThat(Bytes.hashCode(value)).isEqualTo(Byte.hashCode(value)); } } public void testContains() { - assertFalse(Bytes.contains(EMPTY, (byte) 1)); - assertFalse(Bytes.contains(ARRAY1, (byte) 2)); - assertFalse(Bytes.contains(ARRAY234, (byte) 1)); - assertTrue(Bytes.contains(new byte[] {(byte) -1}, (byte) -1)); - assertTrue(Bytes.contains(ARRAY234, (byte) 2)); - assertTrue(Bytes.contains(ARRAY234, (byte) 3)); - assertTrue(Bytes.contains(ARRAY234, (byte) 4)); + assertThat(Bytes.contains(EMPTY, (byte) 1)).isFalse(); + assertThat(Bytes.contains(ARRAY1, (byte) 2)).isFalse(); + assertThat(Bytes.contains(ARRAY234, (byte) 1)).isFalse(); + assertThat(Bytes.contains(new byte[] {(byte) -1}, (byte) -1)).isTrue(); + assertThat(Bytes.contains(ARRAY234, (byte) 2)).isTrue(); + assertThat(Bytes.contains(ARRAY234, (byte) 3)).isTrue(); + assertThat(Bytes.contains(ARRAY234, (byte) 4)).isTrue(); } public void testIndexOf() { - assertEquals(-1, Bytes.indexOf(EMPTY, (byte) 1)); - assertEquals(-1, Bytes.indexOf(ARRAY1, (byte) 2)); - assertEquals(-1, Bytes.indexOf(ARRAY234, (byte) 1)); - assertEquals(0, Bytes.indexOf(new byte[] {(byte) -1}, (byte) -1)); - assertEquals(0, Bytes.indexOf(ARRAY234, (byte) 2)); - assertEquals(1, Bytes.indexOf(ARRAY234, (byte) 3)); - assertEquals(2, Bytes.indexOf(ARRAY234, (byte) 4)); - assertEquals(1, Bytes.indexOf(new byte[] {(byte) 2, (byte) 3, (byte) 2, (byte) 3}, (byte) 3)); + assertThat(Bytes.indexOf(EMPTY, (byte) 1)).isEqualTo(-1); + assertThat(Bytes.indexOf(ARRAY1, (byte) 2)).isEqualTo(-1); + assertThat(Bytes.indexOf(ARRAY234, (byte) 1)).isEqualTo(-1); + assertThat(Bytes.indexOf(new byte[] {(byte) -1}, (byte) -1)).isEqualTo(0); + assertThat(Bytes.indexOf(ARRAY234, (byte) 2)).isEqualTo(0); + assertThat(Bytes.indexOf(ARRAY234, (byte) 3)).isEqualTo(1); + assertThat(Bytes.indexOf(ARRAY234, (byte) 4)).isEqualTo(2); + assertThat(Bytes.indexOf(new byte[] {(byte) 2, (byte) 3, (byte) 2, (byte) 3}, (byte) 3)) + .isEqualTo(1); } public void testIndexOf_arrayTarget() { - assertEquals(0, Bytes.indexOf(EMPTY, EMPTY)); - assertEquals(0, Bytes.indexOf(ARRAY234, EMPTY)); - assertEquals(-1, Bytes.indexOf(EMPTY, ARRAY234)); - assertEquals(-1, Bytes.indexOf(ARRAY234, ARRAY1)); - assertEquals(-1, Bytes.indexOf(ARRAY1, ARRAY234)); - assertEquals(0, Bytes.indexOf(ARRAY1, ARRAY1)); - assertEquals(0, Bytes.indexOf(ARRAY234, ARRAY234)); - assertEquals(0, Bytes.indexOf(ARRAY234, new byte[] {(byte) 2, (byte) 3})); - assertEquals(1, Bytes.indexOf(ARRAY234, new byte[] {(byte) 3, (byte) 4})); - assertEquals(1, Bytes.indexOf(ARRAY234, new byte[] {(byte) 3})); - assertEquals(2, Bytes.indexOf(ARRAY234, new byte[] {(byte) 4})); - assertEquals( - 1, - Bytes.indexOf( - new byte[] {(byte) 2, (byte) 3, (byte) 3, (byte) 3, (byte) 3}, new byte[] {(byte) 3})); - assertEquals( - 2, - Bytes.indexOf( - new byte[] {(byte) 2, (byte) 3, (byte) 2, (byte) 3, (byte) 4, (byte) 2, (byte) 3}, - new byte[] {(byte) 2, (byte) 3, (byte) 4})); - assertEquals( - 1, - Bytes.indexOf( - new byte[] {(byte) 2, (byte) 2, (byte) 3, (byte) 4, (byte) 2, (byte) 3, (byte) 4}, - new byte[] {(byte) 2, (byte) 3, (byte) 4})); - assertEquals( - -1, - Bytes.indexOf( - new byte[] {(byte) 4, (byte) 3, (byte) 2}, new byte[] {(byte) 2, (byte) 3, (byte) 4})); + assertThat(Bytes.indexOf(EMPTY, EMPTY)).isEqualTo(0); + assertThat(Bytes.indexOf(ARRAY234, EMPTY)).isEqualTo(0); + assertThat(Bytes.indexOf(EMPTY, ARRAY234)).isEqualTo(-1); + assertThat(Bytes.indexOf(ARRAY234, ARRAY1)).isEqualTo(-1); + assertThat(Bytes.indexOf(ARRAY1, ARRAY234)).isEqualTo(-1); + assertThat(Bytes.indexOf(ARRAY1, ARRAY1)).isEqualTo(0); + assertThat(Bytes.indexOf(ARRAY234, ARRAY234)).isEqualTo(0); + assertThat(Bytes.indexOf(ARRAY234, new byte[] {(byte) 2, (byte) 3})).isEqualTo(0); + assertThat(Bytes.indexOf(ARRAY234, new byte[] {(byte) 3, (byte) 4})).isEqualTo(1); + assertThat(Bytes.indexOf(ARRAY234, new byte[] {(byte) 3})).isEqualTo(1); + assertThat(Bytes.indexOf(ARRAY234, new byte[] {(byte) 4})).isEqualTo(2); + assertThat( + Bytes.indexOf( + new byte[] {(byte) 2, (byte) 3, (byte) 3, (byte) 3, (byte) 3}, + new byte[] {(byte) 3})) + .isEqualTo(1); + assertThat( + Bytes.indexOf( + new byte[] {(byte) 2, (byte) 3, (byte) 2, (byte) 3, (byte) 4, (byte) 2, (byte) 3}, + new byte[] {(byte) 2, (byte) 3, (byte) 4})) + .isEqualTo(2); + assertThat( + Bytes.indexOf( + new byte[] {(byte) 2, (byte) 2, (byte) 3, (byte) 4, (byte) 2, (byte) 3, (byte) 4}, + new byte[] {(byte) 2, (byte) 3, (byte) 4})) + .isEqualTo(1); + assertThat( + Bytes.indexOf( + new byte[] {(byte) 4, (byte) 3, (byte) 2}, + new byte[] {(byte) 2, (byte) 3, (byte) 4})) + .isEqualTo(-1); } public void testLastIndexOf() { - assertEquals(-1, Bytes.lastIndexOf(EMPTY, (byte) 1)); - assertEquals(-1, Bytes.lastIndexOf(ARRAY1, (byte) 2)); - assertEquals(-1, Bytes.lastIndexOf(ARRAY234, (byte) 1)); - assertEquals(0, Bytes.lastIndexOf(new byte[] {(byte) -1}, (byte) -1)); - assertEquals(0, Bytes.lastIndexOf(ARRAY234, (byte) 2)); - assertEquals(1, Bytes.lastIndexOf(ARRAY234, (byte) 3)); - assertEquals(2, Bytes.lastIndexOf(ARRAY234, (byte) 4)); - assertEquals( - 3, Bytes.lastIndexOf(new byte[] {(byte) 2, (byte) 3, (byte) 2, (byte) 3}, (byte) 3)); + assertThat(Bytes.lastIndexOf(EMPTY, (byte) 1)).isEqualTo(-1); + assertThat(Bytes.lastIndexOf(ARRAY1, (byte) 2)).isEqualTo(-1); + assertThat(Bytes.lastIndexOf(ARRAY234, (byte) 1)).isEqualTo(-1); + assertThat(Bytes.lastIndexOf(new byte[] {(byte) -1}, (byte) -1)).isEqualTo(0); + assertThat(Bytes.lastIndexOf(ARRAY234, (byte) 2)).isEqualTo(0); + assertThat(Bytes.lastIndexOf(ARRAY234, (byte) 3)).isEqualTo(1); + assertThat(Bytes.lastIndexOf(ARRAY234, (byte) 4)).isEqualTo(2); + assertThat(Bytes.lastIndexOf(new byte[] {(byte) 2, (byte) 3, (byte) 2, (byte) 3}, (byte) 3)) + .isEqualTo(3); } public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Bytes.concat())); - assertTrue(Arrays.equals(EMPTY, Bytes.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Bytes.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY1, Bytes.concat(ARRAY1))); - assertNotSame(ARRAY1, Bytes.concat(ARRAY1)); - assertTrue(Arrays.equals(ARRAY1, Bytes.concat(EMPTY, ARRAY1, EMPTY))); - assertTrue( - Arrays.equals( - new byte[] {(byte) 1, (byte) 1, (byte) 1}, Bytes.concat(ARRAY1, ARRAY1, ARRAY1))); - assertTrue( - Arrays.equals( - new byte[] {(byte) 1, (byte) 2, (byte) 3, (byte) 4}, Bytes.concat(ARRAY1, ARRAY234))); + assertThat(Bytes.concat()).isEqualTo(EMPTY); + assertThat(Bytes.concat(EMPTY)).isEqualTo(EMPTY); + assertThat(Bytes.concat(EMPTY, EMPTY, EMPTY)).isEqualTo(EMPTY); + assertThat(Bytes.concat(ARRAY1)).isEqualTo(ARRAY1); + assertThat(Bytes.concat(ARRAY1)).isNotSameInstanceAs(ARRAY1); + assertThat(Bytes.concat(EMPTY, ARRAY1, EMPTY)).isEqualTo(ARRAY1); + assertThat(Bytes.concat(ARRAY1, ARRAY1, ARRAY1)) + .isEqualTo(new byte[] {(byte) 1, (byte) 1, (byte) 1}); + assertThat(Bytes.concat(ARRAY1, ARRAY234)) + .isEqualTo(new byte[] {(byte) 1, (byte) 2, (byte) 3, (byte) 4}); } - public void testEnsureCapacity() { - assertSame(EMPTY, Bytes.ensureCapacity(EMPTY, 0, 1)); - assertSame(ARRAY1, Bytes.ensureCapacity(ARRAY1, 0, 1)); - assertSame(ARRAY1, Bytes.ensureCapacity(ARRAY1, 1, 1)); - assertTrue( - Arrays.equals( - new byte[] {(byte) 1, (byte) 0, (byte) 0}, Bytes.ensureCapacity(ARRAY1, 2, 1))); + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_negative() { + int dim1 = 1 << 16; + int dim2 = 1 << 15; + assertThat(dim1 * dim2).isLessThan(0); + testConcatOverflow(dim1, dim2); } - public void testEnsureCapacity_fail() { - try { - Bytes.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_nonNegative() { + int dim1 = 1 << 16; + int dim2 = 1 << 16; + assertThat(dim1 * dim2).isAtLeast(0); + testConcatOverflow(dim1, dim2); + } + + private static void testConcatOverflow(int arraysDim1, int arraysDim2) { + assertThat((long) arraysDim1 * arraysDim2).isNotEqualTo((long) (arraysDim1 * arraysDim2)); + + byte[][] arrays = new byte[arraysDim1][]; + // it's shared to avoid using too much memory in tests + byte[] sharedArray = new byte[arraysDim2]; + Arrays.fill(arrays, sharedArray); + try { - // notice that this should even fail when no growth was needed - Bytes.ensureCapacity(ARRAY1, 1, -1); + Bytes.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } + public void testEnsureCapacity() { + assertThat(Bytes.ensureCapacity(EMPTY, 0, 1)).isSameInstanceAs(EMPTY); + assertThat(Bytes.ensureCapacity(ARRAY1, 0, 1)).isSameInstanceAs(ARRAY1); + assertThat(Bytes.ensureCapacity(ARRAY1, 1, 1)).isSameInstanceAs(ARRAY1); + assertThat(Bytes.ensureCapacity(ARRAY1, 2, 1)) + .isEqualTo(new byte[] {(byte) 1, (byte) 0, (byte) 0}); + } + + public void testEnsureCapacity_fail() { + assertThrows(IllegalArgumentException.class, () -> Bytes.ensureCapacity(ARRAY1, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> Bytes.ensureCapacity(ARRAY1, 1, -1)); + } + public void testToArray() { // need explicit type parameter to avoid javac warning!? List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Bytes.toArray(none))); + assertThat(Bytes.toArray(none)).isEqualTo(EMPTY); List one = Arrays.asList((byte) 1); - assertTrue(Arrays.equals(ARRAY1, Bytes.toArray(one))); + assertThat(Bytes.toArray(one)).isEqualTo(ARRAY1); byte[] array = {(byte) 0, (byte) 1, (byte) 0x55}; List three = Arrays.asList((byte) 0, (byte) 1, (byte) 0x55); - assertTrue(Arrays.equals(array, Bytes.toArray(three))); + assertThat(Bytes.toArray(three)).isEqualTo(array); - assertTrue(Arrays.equals(array, Bytes.toArray(Bytes.asList(array)))); + assertThat(Bytes.toArray(Bytes.asList(array))).isEqualTo(array); } public void testToArray_threadSafe() { @@ -171,21 +202,17 @@ public void testToArray_threadSafe() { Collection misleadingSize = Helpers.misleadingSizeCollection(delta); misleadingSize.addAll(list); byte[] arr = Bytes.toArray(misleadingSize); - assertEquals(i, arr.length); + assertThat(arr).hasLength(i); for (int j = 0; j < i; j++) { - assertEquals(VALUES[j], arr[j]); + assertThat(arr[j]).isEqualTo(VALUES[j]); } } } } public void testToArray_withNull() { - List list = Arrays.asList((byte) 0, (byte) 1, null); - try { - Bytes.toArray(list); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Byte> list = Arrays.asList((byte) 0, (byte) 1, null); + assertThrows(NullPointerException.class, () -> Bytes.toArray(list)); } public void testToArray_withConversion() { @@ -194,25 +221,26 @@ public void testToArray_withConversion() { List bytes = Arrays.asList((byte) 0, (byte) 1, (byte) 2); List shorts = Arrays.asList((short) 0, (short) 1, (short) 2); List ints = Arrays.asList(0, 1, 2); - List floats = Arrays.asList((float) 0, (float) 1, (float) 2); - List longs = Arrays.asList((long) 0, (long) 1, (long) 2); - List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); - - assertTrue(Arrays.equals(array, Bytes.toArray(bytes))); - assertTrue(Arrays.equals(array, Bytes.toArray(shorts))); - assertTrue(Arrays.equals(array, Bytes.toArray(ints))); - assertTrue(Arrays.equals(array, Bytes.toArray(floats))); - assertTrue(Arrays.equals(array, Bytes.toArray(longs))); - assertTrue(Arrays.equals(array, Bytes.toArray(doubles))); + List floats = Arrays.asList(0.0f, 1.0f, 2.0f); + List longs = Arrays.asList(0L, 1L, 2L); + List doubles = Arrays.asList(0.0, 1.0, 2.0); + + assertThat(Bytes.toArray(bytes)).isEqualTo(array); + assertThat(Bytes.toArray(shorts)).isEqualTo(array); + assertThat(Bytes.toArray(ints)).isEqualTo(array); + assertThat(Bytes.toArray(floats)).isEqualTo(array); + assertThat(Bytes.toArray(longs)).isEqualTo(array); + assertThat(Bytes.toArray(doubles)).isEqualTo(array); } + @J2ktIncompatible // b/239034072: Kotlin varargs copy parameter arrays. public void testAsList_isAView() { byte[] array = {(byte) 0, (byte) 1}; List list = Bytes.asList(array); list.set(0, (byte) 2); - assertTrue(Arrays.equals(new byte[] {(byte) 2, (byte) 1}, array)); + assertThat(array).isEqualTo(new byte[] {(byte) 2, (byte) 1}); array[1] = (byte) 3; - assertEquals(Arrays.asList((byte) 2, (byte) 3), list); + assertThat(list).containsExactly((byte) 2, (byte) 3).inOrder(); } public void testAsList_toArray_roundTrip() { @@ -222,21 +250,23 @@ public void testAsList_toArray_roundTrip() { // Make sure it returned a copy list.set(0, (byte) 4); - assertTrue(Arrays.equals(new byte[] {(byte) 0, (byte) 1, (byte) 2}, newArray)); + assertThat(newArray).isEqualTo(new byte[] {(byte) 0, (byte) 1, (byte) 2}); newArray[1] = (byte) 5; - assertEquals((byte) 1, (byte) list.get(1)); + assertThat((byte) list.get(1)).isEqualTo((byte) 1); } // This test stems from a real bug found by andrewk public void testAsList_subList_toArray_roundTrip() { byte[] array = {(byte) 0, (byte) 1, (byte) 2, (byte) 3}; List list = Bytes.asList(array); - assertTrue(Arrays.equals(new byte[] {(byte) 1, (byte) 2}, Bytes.toArray(list.subList(1, 3)))); - assertTrue(Arrays.equals(new byte[] {}, Bytes.toArray(list.subList(2, 2)))); + assertThat(Bytes.toArray(list.subList(1, 3))).isEqualTo(new byte[] {(byte) 1, (byte) 2}); + assertThat(Bytes.toArray(list.subList(2, 2))).isEqualTo(new byte[] {}); } + // `primitives` can't depend on `collect`, so this is what the prod code has to return. + @SuppressWarnings("EmptyList") public void testAsListEmpty() { - assertSame(Collections.emptyList(), Bytes.asList(EMPTY)); + assertThat(Bytes.asList(EMPTY)).isSameInstanceAs(Collections.emptyList()); } public void testReverse() { @@ -250,13 +280,13 @@ public void testReverse() { private static void testReverse(byte[] input, byte[] expectedOutput) { input = Arrays.copyOf(input, input.length); Bytes.reverse(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testReverse(byte[] input, int fromIndex, int toIndex, byte[] expectedOutput) { input = Arrays.copyOf(input, input.length); Bytes.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testReverseIndexed() { @@ -268,6 +298,104 @@ public void testReverseIndexed() { testReverse(new byte[] {-1, 1, -2, 2}, 1, 3, new byte[] {-1, -2, 1, 2}); } + private static void testRotate(byte[] input, int distance, byte[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Bytes.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + byte[] input, int distance, int fromIndex, int toIndex, byte[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Bytes.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new byte[] {}, -1, new byte[] {}); + testRotate(new byte[] {}, 0, new byte[] {}); + testRotate(new byte[] {}, 1, new byte[] {}); + + testRotate(new byte[] {1}, -2, new byte[] {1}); + testRotate(new byte[] {1}, -1, new byte[] {1}); + testRotate(new byte[] {1}, 0, new byte[] {1}); + testRotate(new byte[] {1}, 1, new byte[] {1}); + testRotate(new byte[] {1}, 2, new byte[] {1}); + + testRotate(new byte[] {1, 2}, -3, new byte[] {2, 1}); + testRotate(new byte[] {1, 2}, -1, new byte[] {2, 1}); + testRotate(new byte[] {1, 2}, -2, new byte[] {1, 2}); + testRotate(new byte[] {1, 2}, 0, new byte[] {1, 2}); + testRotate(new byte[] {1, 2}, 1, new byte[] {2, 1}); + testRotate(new byte[] {1, 2}, 2, new byte[] {1, 2}); + testRotate(new byte[] {1, 2}, 3, new byte[] {2, 1}); + + testRotate(new byte[] {1, 2, 3}, -5, new byte[] {3, 1, 2}); + testRotate(new byte[] {1, 2, 3}, -4, new byte[] {2, 3, 1}); + testRotate(new byte[] {1, 2, 3}, -3, new byte[] {1, 2, 3}); + testRotate(new byte[] {1, 2, 3}, -2, new byte[] {3, 1, 2}); + testRotate(new byte[] {1, 2, 3}, -1, new byte[] {2, 3, 1}); + testRotate(new byte[] {1, 2, 3}, 0, new byte[] {1, 2, 3}); + testRotate(new byte[] {1, 2, 3}, 1, new byte[] {3, 1, 2}); + testRotate(new byte[] {1, 2, 3}, 2, new byte[] {2, 3, 1}); + testRotate(new byte[] {1, 2, 3}, 3, new byte[] {1, 2, 3}); + testRotate(new byte[] {1, 2, 3}, 4, new byte[] {3, 1, 2}); + testRotate(new byte[] {1, 2, 3}, 5, new byte[] {2, 3, 1}); + + testRotate(new byte[] {1, 2, 3, 4}, -9, new byte[] {2, 3, 4, 1}); + testRotate(new byte[] {1, 2, 3, 4}, -5, new byte[] {2, 3, 4, 1}); + testRotate(new byte[] {1, 2, 3, 4}, -1, new byte[] {2, 3, 4, 1}); + testRotate(new byte[] {1, 2, 3, 4}, 0, new byte[] {1, 2, 3, 4}); + testRotate(new byte[] {1, 2, 3, 4}, 1, new byte[] {4, 1, 2, 3}); + testRotate(new byte[] {1, 2, 3, 4}, 5, new byte[] {4, 1, 2, 3}); + testRotate(new byte[] {1, 2, 3, 4}, 9, new byte[] {4, 1, 2, 3}); + + testRotate(new byte[] {1, 2, 3, 4, 5}, -6, new byte[] {2, 3, 4, 5, 1}); + testRotate(new byte[] {1, 2, 3, 4, 5}, -4, new byte[] {5, 1, 2, 3, 4}); + testRotate(new byte[] {1, 2, 3, 4, 5}, -3, new byte[] {4, 5, 1, 2, 3}); + testRotate(new byte[] {1, 2, 3, 4, 5}, -1, new byte[] {2, 3, 4, 5, 1}); + testRotate(new byte[] {1, 2, 3, 4, 5}, 0, new byte[] {1, 2, 3, 4, 5}); + testRotate(new byte[] {1, 2, 3, 4, 5}, 1, new byte[] {5, 1, 2, 3, 4}); + testRotate(new byte[] {1, 2, 3, 4, 5}, 3, new byte[] {3, 4, 5, 1, 2}); + testRotate(new byte[] {1, 2, 3, 4, 5}, 4, new byte[] {2, 3, 4, 5, 1}); + testRotate(new byte[] {1, 2, 3, 4, 5}, 6, new byte[] {5, 1, 2, 3, 4}); + } + + public void testRotateIndexed() { + testRotate(new byte[] {}, 0, 0, 0, new byte[] {}); + + testRotate(new byte[] {1}, 0, 0, 1, new byte[] {1}); + testRotate(new byte[] {1}, 1, 0, 1, new byte[] {1}); + testRotate(new byte[] {1}, 1, 1, 1, new byte[] {1}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -6, 1, 6, new byte[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -1, 1, 6, new byte[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 1, 6, new byte[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 5, 1, 6, new byte[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 14, 1, 6, new byte[] {0, 2, 3, 4, 5, 1, 6}); + + // Rotate the first three elements + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -2, 0, 3, new byte[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -1, 0, 3, new byte[] {1, 2, 0, 3, 4, 5, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 0, 3, new byte[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 1, 0, 3, new byte[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 2, 0, 3, new byte[] {1, 2, 0, 3, 4, 5, 6}); + + // Rotate the last four elements + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -6, 3, 7, new byte[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -5, 3, 7, new byte[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -4, 3, 7, new byte[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -3, 3, 7, new byte[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -2, 3, 7, new byte[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, -1, 3, 7, new byte[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 3, 7, new byte[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 1, 3, 7, new byte[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 2, 3, 7, new byte[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new byte[] {0, 1, 2, 3, 4, 5, 6}, 3, 3, 7, new byte[] {0, 1, 2, 4, 5, 6, 3}); + } + + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Bytes.class); diff --git a/android/guava-tests/test/com/google/common/primitives/CharArrayAsListTest.java b/android/guava-tests/test/com/google/common/primitives/CharArrayAsListTest.java index fa2a53dabc1a..119f6fbda4d1 100644 --- a/android/guava-tests/test/com/google/common/primitives/CharArrayAsListTest.java +++ b/android/guava-tests/test/com/google/common/primitives/CharArrayAsListTest.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -31,13 +32,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Chars#asList(char[])}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked +@AndroidIncompatible // test-suite builders public class CharArrayAsListTest extends TestCase { private static List asList(Character[] values) { @@ -48,6 +52,7 @@ private static List asList(Character[] values) { return Chars.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = diff --git a/android/guava-tests/test/com/google/common/primitives/CharsTest.java b/android/guava-tests/test/com/google/common/primitives/CharsTest.java index f1da7fd8e67a..2f7fbbd16cca 100644 --- a/android/guava-tests/test/com/google/common/primitives/CharsTest.java +++ b/android/guava-tests/test/com/google/common/primitives/CharsTest.java @@ -16,8 +16,15 @@ package com.google.common.primitives; +import static com.google.common.primitives.Chars.max; +import static com.google.common.primitives.Chars.min; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; @@ -28,14 +35,16 @@ import java.util.List; import java.util.Locale; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Chars}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless +@GwtCompatible +@NullMarked public class CharsTest extends TestCase { private static final char[] EMPTY = {}; private static final char[] ARRAY1 = {(char) 1}; @@ -46,15 +55,17 @@ public class CharsTest extends TestCase { private static final char[] VALUES = {LEAST, 'a', '\u00e0', '\udcaa', GREATEST}; + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testHashCode() { for (char value : VALUES) { - assertEquals(((Character) value).hashCode(), Chars.hashCode(value)); + assertThat(Chars.hashCode(value)).isEqualTo(Character.hashCode(value)); } } public void testCheckedCast() { for (char value : VALUES) { - assertEquals(value, Chars.checkedCast((long) value)); + assertThat(Chars.checkedCast((long) value)).isEqualTo(value); } assertCastFails(GREATEST + 1L); assertCastFails(LEAST - 1L); @@ -64,12 +75,12 @@ public void testCheckedCast() { public void testSaturatedCast() { for (char value : VALUES) { - assertEquals(value, Chars.saturatedCast((long) value)); + assertThat(Chars.saturatedCast((long) value)).isEqualTo(value); } - assertEquals(GREATEST, Chars.saturatedCast(GREATEST + 1L)); - assertEquals(LEAST, Chars.saturatedCast(LEAST - 1L)); - assertEquals(GREATEST, Chars.saturatedCast(Long.MAX_VALUE)); - assertEquals(LEAST, Chars.saturatedCast(Long.MIN_VALUE)); + assertThat(Chars.saturatedCast(GREATEST + 1L)).isEqualTo(GREATEST); + assertThat(Chars.saturatedCast(LEAST - 1L)).isEqualTo(LEAST); + assertThat(Chars.saturatedCast(Long.MAX_VALUE)).isEqualTo(GREATEST); + assertThat(Chars.saturatedCast(Long.MIN_VALUE)).isEqualTo(LEAST); } private void assertCastFails(long value) { @@ -77,163 +88,184 @@ private void assertCastFails(long value) { Chars.checkedCast(value); fail("Cast to char should have failed: " + value); } catch (IllegalArgumentException ex) { - assertTrue( - value + " not found in exception text: " + ex.getMessage(), - ex.getMessage().contains(String.valueOf(value))); + assertWithMessage(value + " not found in exception text: " + ex.getMessage()) + .that(ex.getMessage().contains(String.valueOf(value))) + .isTrue(); } } + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testCompare() { for (char x : VALUES) { for (char y : VALUES) { - // note: spec requires only that the sign is the same - assertEquals(x + ", " + y, Character.valueOf(x).compareTo(y), Chars.compare(x, y)); + assertWithMessage(x + ", " + y) + .that(Math.signum(Chars.compare(x, y))) + .isEqualTo(Math.signum(Character.compare(x, y))); } } } public void testContains() { - assertFalse(Chars.contains(EMPTY, (char) 1)); - assertFalse(Chars.contains(ARRAY1, (char) 2)); - assertFalse(Chars.contains(ARRAY234, (char) 1)); - assertTrue(Chars.contains(new char[] {(char) -1}, (char) -1)); - assertTrue(Chars.contains(ARRAY234, (char) 2)); - assertTrue(Chars.contains(ARRAY234, (char) 3)); - assertTrue(Chars.contains(ARRAY234, (char) 4)); + assertThat(Chars.contains(EMPTY, (char) 1)).isFalse(); + assertThat(Chars.contains(ARRAY1, (char) 2)).isFalse(); + assertThat(Chars.contains(ARRAY234, (char) 1)).isFalse(); + assertThat(Chars.contains(new char[] {(char) -1}, (char) -1)).isTrue(); + assertThat(Chars.contains(ARRAY234, (char) 2)).isTrue(); + assertThat(Chars.contains(ARRAY234, (char) 3)).isTrue(); + assertThat(Chars.contains(ARRAY234, (char) 4)).isTrue(); } public void testIndexOf() { - assertEquals(-1, Chars.indexOf(EMPTY, (char) 1)); - assertEquals(-1, Chars.indexOf(ARRAY1, (char) 2)); - assertEquals(-1, Chars.indexOf(ARRAY234, (char) 1)); - assertEquals(0, Chars.indexOf(new char[] {(char) -1}, (char) -1)); - assertEquals(0, Chars.indexOf(ARRAY234, (char) 2)); - assertEquals(1, Chars.indexOf(ARRAY234, (char) 3)); - assertEquals(2, Chars.indexOf(ARRAY234, (char) 4)); - assertEquals(1, Chars.indexOf(new char[] {(char) 2, (char) 3, (char) 2, (char) 3}, (char) 3)); + assertThat(Chars.indexOf(EMPTY, (char) 1)).isEqualTo(-1); + assertThat(Chars.indexOf(ARRAY1, (char) 2)).isEqualTo(-1); + assertThat(Chars.indexOf(ARRAY234, (char) 1)).isEqualTo(-1); + assertThat(Chars.indexOf(new char[] {(char) -1}, (char) -1)).isEqualTo(0); + assertThat(Chars.indexOf(ARRAY234, (char) 2)).isEqualTo(0); + assertThat(Chars.indexOf(ARRAY234, (char) 3)).isEqualTo(1); + assertThat(Chars.indexOf(ARRAY234, (char) 4)).isEqualTo(2); + assertThat(Chars.indexOf(new char[] {(char) 2, (char) 3, (char) 2, (char) 3}, (char) 3)) + .isEqualTo(1); } public void testIndexOf_arrayTarget() { - assertEquals(0, Chars.indexOf(EMPTY, EMPTY)); - assertEquals(0, Chars.indexOf(ARRAY234, EMPTY)); - assertEquals(-1, Chars.indexOf(EMPTY, ARRAY234)); - assertEquals(-1, Chars.indexOf(ARRAY234, ARRAY1)); - assertEquals(-1, Chars.indexOf(ARRAY1, ARRAY234)); - assertEquals(0, Chars.indexOf(ARRAY1, ARRAY1)); - assertEquals(0, Chars.indexOf(ARRAY234, ARRAY234)); - assertEquals(0, Chars.indexOf(ARRAY234, new char[] {(char) 2, (char) 3})); - assertEquals(1, Chars.indexOf(ARRAY234, new char[] {(char) 3, (char) 4})); - assertEquals(1, Chars.indexOf(ARRAY234, new char[] {(char) 3})); - assertEquals(2, Chars.indexOf(ARRAY234, new char[] {(char) 4})); - assertEquals( - 1, - Chars.indexOf( - new char[] {(char) 2, (char) 3, (char) 3, (char) 3, (char) 3}, new char[] {(char) 3})); - assertEquals( - 2, - Chars.indexOf( - new char[] {(char) 2, (char) 3, (char) 2, (char) 3, (char) 4, (char) 2, (char) 3}, - new char[] {(char) 2, (char) 3, (char) 4})); - assertEquals( - 1, - Chars.indexOf( - new char[] {(char) 2, (char) 2, (char) 3, (char) 4, (char) 2, (char) 3, (char) 4}, - new char[] {(char) 2, (char) 3, (char) 4})); - assertEquals( - -1, - Chars.indexOf( - new char[] {(char) 4, (char) 3, (char) 2}, new char[] {(char) 2, (char) 3, (char) 4})); + assertThat(Chars.indexOf(EMPTY, EMPTY)).isEqualTo(0); + assertThat(Chars.indexOf(ARRAY234, EMPTY)).isEqualTo(0); + assertThat(Chars.indexOf(EMPTY, ARRAY234)).isEqualTo(-1); + assertThat(Chars.indexOf(ARRAY234, ARRAY1)).isEqualTo(-1); + assertThat(Chars.indexOf(ARRAY1, ARRAY234)).isEqualTo(-1); + assertThat(Chars.indexOf(ARRAY1, ARRAY1)).isEqualTo(0); + assertThat(Chars.indexOf(ARRAY234, ARRAY234)).isEqualTo(0); + assertThat(Chars.indexOf(ARRAY234, new char[] {(char) 2, (char) 3})).isEqualTo(0); + assertThat(Chars.indexOf(ARRAY234, new char[] {(char) 3, (char) 4})).isEqualTo(1); + assertThat(Chars.indexOf(ARRAY234, new char[] {(char) 3})).isEqualTo(1); + assertThat(Chars.indexOf(ARRAY234, new char[] {(char) 4})).isEqualTo(2); + assertThat( + Chars.indexOf( + new char[] {(char) 2, (char) 3, (char) 3, (char) 3, (char) 3}, + new char[] {(char) 3})) + .isEqualTo(1); + assertThat( + Chars.indexOf( + new char[] {(char) 2, (char) 3, (char) 2, (char) 3, (char) 4, (char) 2, (char) 3}, + new char[] {(char) 2, (char) 3, (char) 4})) + .isEqualTo(2); + assertThat( + Chars.indexOf( + new char[] {(char) 2, (char) 2, (char) 3, (char) 4, (char) 2, (char) 3, (char) 4}, + new char[] {(char) 2, (char) 3, (char) 4})) + .isEqualTo(1); + assertThat( + Chars.indexOf( + new char[] {(char) 4, (char) 3, (char) 2}, + new char[] {(char) 2, (char) 3, (char) 4})) + .isEqualTo(-1); } public void testLastIndexOf() { - assertEquals(-1, Chars.lastIndexOf(EMPTY, (char) 1)); - assertEquals(-1, Chars.lastIndexOf(ARRAY1, (char) 2)); - assertEquals(-1, Chars.lastIndexOf(ARRAY234, (char) 1)); - assertEquals(0, Chars.lastIndexOf(new char[] {(char) -1}, (char) -1)); - assertEquals(0, Chars.lastIndexOf(ARRAY234, (char) 2)); - assertEquals(1, Chars.lastIndexOf(ARRAY234, (char) 3)); - assertEquals(2, Chars.lastIndexOf(ARRAY234, (char) 4)); - assertEquals( - 3, Chars.lastIndexOf(new char[] {(char) 2, (char) 3, (char) 2, (char) 3}, (char) 3)); + assertThat(Chars.lastIndexOf(EMPTY, (char) 1)).isEqualTo(-1); + assertThat(Chars.lastIndexOf(ARRAY1, (char) 2)).isEqualTo(-1); + assertThat(Chars.lastIndexOf(ARRAY234, (char) 1)).isEqualTo(-1); + assertThat(Chars.lastIndexOf(new char[] {(char) -1}, (char) -1)).isEqualTo(0); + assertThat(Chars.lastIndexOf(ARRAY234, (char) 2)).isEqualTo(0); + assertThat(Chars.lastIndexOf(ARRAY234, (char) 3)).isEqualTo(1); + assertThat(Chars.lastIndexOf(ARRAY234, (char) 4)).isEqualTo(2); + assertThat(Chars.lastIndexOf(new char[] {(char) 2, (char) 3, (char) 2, (char) 3}, (char) 3)) + .isEqualTo(3); } public void testMax_noArgs() { - try { - Chars.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(LEAST, Chars.max(LEAST)); - assertEquals(GREATEST, Chars.max(GREATEST)); - assertEquals( - (char) 9, Chars.max((char) 8, (char) 6, (char) 7, (char) 5, (char) 3, (char) 0, (char) 9)); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat(max((char) 8, (char) 6, (char) 7, (char) 5, (char) 3, (char) 0, (char) 9)) + .isEqualTo((char) 9); } public void testMin_noArgs() { - try { - Chars.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, Chars.min(LEAST)); - assertEquals(GREATEST, Chars.min(GREATEST)); - assertEquals( - (char) 0, Chars.min((char) 8, (char) 6, (char) 7, (char) 5, (char) 3, (char) 0, (char) 9)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat(min((char) 8, (char) 6, (char) 7, (char) 5, (char) 3, (char) 0, (char) 9)) + .isEqualTo((char) 0); } public void testConstrainToRange() { - assertEquals((char) 1, Chars.constrainToRange((char) 1, (char) 0, (char) 5)); - assertEquals((char) 1, Chars.constrainToRange((char) 1, (char) 1, (char) 5)); - assertEquals((char) 3, Chars.constrainToRange((char) 1, (char) 3, (char) 5)); - assertEquals((char) 254, Chars.constrainToRange((char) 255, (char) 250, (char) 254)); - assertEquals((char) 2, Chars.constrainToRange((char) 5, (char) 2, (char) 2)); + assertThat(Chars.constrainToRange((char) 1, (char) 0, (char) 5)).isEqualTo((char) 1); + assertThat(Chars.constrainToRange((char) 1, (char) 1, (char) 5)).isEqualTo((char) 1); + assertThat(Chars.constrainToRange((char) 1, (char) 3, (char) 5)).isEqualTo((char) 3); + assertThat(Chars.constrainToRange((char) 255, (char) 250, (char) 254)).isEqualTo((char) 254); + assertThat(Chars.constrainToRange((char) 5, (char) 2, (char) 2)).isEqualTo((char) 2); + assertThrows( + IllegalArgumentException.class, () -> Chars.constrainToRange((char) 1, (char) 3, (char) 2)); + } + + public void testConcat() { + assertThat(Chars.concat()).isEqualTo(EMPTY); + assertThat(Chars.concat(EMPTY)).isEqualTo(EMPTY); + assertThat(Chars.concat(EMPTY, EMPTY, EMPTY)).isEqualTo(EMPTY); + assertThat(Chars.concat(ARRAY1)).isEqualTo(ARRAY1); + assertThat(Chars.concat(ARRAY1)).isNotSameInstanceAs(ARRAY1); + assertThat(Chars.concat(EMPTY, ARRAY1, EMPTY)).isEqualTo(ARRAY1); + assertThat(Chars.concat(ARRAY1, ARRAY1, ARRAY1)) + .isEqualTo(new char[] {(char) 1, (char) 1, (char) 1}); + assertThat(Chars.concat(ARRAY1, ARRAY234)) + .isEqualTo(new char[] {(char) 1, (char) 2, (char) 3, (char) 4}); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_negative() { + int dim1 = 1 << 16; + int dim2 = 1 << 15; + assertThat(dim1 * dim2).isLessThan(0); + testConcatOverflow(dim1, dim2); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_nonNegative() { + int dim1 = 1 << 16; + int dim2 = 1 << 16; + assertThat(dim1 * dim2).isAtLeast(0); + testConcatOverflow(dim1, dim2); + } + + private static void testConcatOverflow(int arraysDim1, int arraysDim2) { + assertThat((long) arraysDim1 * arraysDim2).isNotEqualTo((long) (arraysDim1 * arraysDim2)); + + char[][] arrays = new char[arraysDim1][]; + // it's shared to avoid using too much memory in tests + char[] sharedArray = new char[arraysDim2]; + Arrays.fill(arrays, sharedArray); + try { - Chars.constrainToRange((char) 1, (char) 3, (char) 2); + Chars.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } - public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Chars.concat())); - assertTrue(Arrays.equals(EMPTY, Chars.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Chars.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY1, Chars.concat(ARRAY1))); - assertNotSame(ARRAY1, Chars.concat(ARRAY1)); - assertTrue(Arrays.equals(ARRAY1, Chars.concat(EMPTY, ARRAY1, EMPTY))); - assertTrue( - Arrays.equals( - new char[] {(char) 1, (char) 1, (char) 1}, Chars.concat(ARRAY1, ARRAY1, ARRAY1))); - assertTrue( - Arrays.equals( - new char[] {(char) 1, (char) 2, (char) 3, (char) 4}, Chars.concat(ARRAY1, ARRAY234))); - } - @GwtIncompatible // Chars.fromByteArray public void testFromByteArray() { - assertEquals('\u2345', Chars.fromByteArray(new byte[] {0x23, 0x45, (byte) 0xDC})); - assertEquals('\uFEDC', Chars.fromByteArray(new byte[] {(byte) 0xFE, (byte) 0xDC})); + assertThat(Chars.fromByteArray(new byte[] {0x23, 0x45, (byte) 0xDC})).isEqualTo('\u2345'); + assertThat(Chars.fromByteArray(new byte[] {(byte) 0xFE, (byte) 0xDC})).isEqualTo('\uFEDC'); } @GwtIncompatible // Chars.fromByteArray public void testFromByteArrayFails() { - try { - Chars.fromByteArray(new byte[Chars.BYTES - 1]); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> Chars.fromByteArray(new byte[Chars.BYTES - 1])); } @GwtIncompatible // Chars.fromBytes public void testFromBytes() { - assertEquals('\u2345', Chars.fromBytes((byte) 0x23, (byte) 0x45)); - assertEquals('\uFEDC', Chars.fromBytes((byte) 0xFE, (byte) 0xDC)); + assertThat(Chars.fromBytes((byte) 0x23, (byte) 0x45)).isEqualTo('\u2345'); + assertThat(Chars.fromBytes((byte) 0xFE, (byte) 0xDC)).isEqualTo('\uFEDC'); } @GwtIncompatible // Chars.fromByteArray, Chars.toByteArray @@ -242,59 +274,50 @@ public void testByteArrayRoundTrips() { for (int hi = 0; hi < 256; hi++) { for (int lo = 0; lo < 256; lo++) { char result = Chars.fromByteArray(new byte[] {(byte) hi, (byte) lo}); - assertEquals( - String.format( - Locale.ROOT, "hi=%s, lo=%s, expected=%s, result=%s", hi, lo, (int) c, (int) result), - c, - result); + assertWithMessage( + String.format( + Locale.ROOT, + "hi=%s, lo=%s, expected=%s, result=%s", + hi, + lo, + (int) c, + (int) result)) + .that(result) + .isEqualTo(c); byte[] bytes = Chars.toByteArray(c); - assertEquals((byte) hi, bytes[0]); - assertEquals((byte) lo, bytes[1]); + assertThat(bytes[0]).isEqualTo((byte) hi); + assertThat(bytes[1]).isEqualTo((byte) lo); c++; } } - assertEquals((char) 0, c); // sanity check + assertThat(c).isEqualTo((char) 0); // sanity check } @GwtIncompatible // Chars.fromByteArray, Chars.toByteArray public void testByteArrayRoundTripsFails() { - try { - Chars.fromByteArray(new byte[] {0x11}); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Chars.fromByteArray(new byte[] {0x11})); } public void testEnsureCapacity() { - assertSame(EMPTY, Chars.ensureCapacity(EMPTY, 0, 1)); - assertSame(ARRAY1, Chars.ensureCapacity(ARRAY1, 0, 1)); - assertSame(ARRAY1, Chars.ensureCapacity(ARRAY1, 1, 1)); - assertTrue( - Arrays.equals( - new char[] {(char) 1, (char) 0, (char) 0}, Chars.ensureCapacity(ARRAY1, 2, 1))); + assertThat(Chars.ensureCapacity(EMPTY, 0, 1)).isSameInstanceAs(EMPTY); + assertThat(Chars.ensureCapacity(ARRAY1, 0, 1)).isSameInstanceAs(ARRAY1); + assertThat(Chars.ensureCapacity(ARRAY1, 1, 1)).isSameInstanceAs(ARRAY1); + assertThat(Chars.ensureCapacity(ARRAY1, 2, 1)) + .isEqualTo(new char[] {(char) 1, (char) 0, (char) 0}); } public void testEnsureCapacity_fail() { - try { - Chars.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - // notice that this should even fail when no growth was needed - Chars.ensureCapacity(ARRAY1, 1, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Chars.ensureCapacity(ARRAY1, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> Chars.ensureCapacity(ARRAY1, 1, -1)); } public void testJoin() { - assertEquals("", Chars.join(",", EMPTY)); - assertEquals("1", Chars.join(",", '1')); - assertEquals("1,2", Chars.join(",", '1', '2')); - assertEquals("123", Chars.join("", '1', '2', '3')); + assertThat(Chars.join(",", EMPTY)).isEmpty(); + assertThat(Chars.join(",", '1')).isEqualTo("1"); + assertThat(Chars.join(",", '1', '2')).isEqualTo("1,2"); + assertThat(Chars.join("", '1', '2', '3')).isEqualTo("123"); } public void testLexicographicalComparator() { @@ -314,10 +337,11 @@ public void testLexicographicalComparator() { Helpers.testComparator(comparator, ordered); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testLexicographicalComparatorSerializable() { Comparator comparator = Chars.lexicographicalComparator(); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); } public void testReverse() { @@ -331,13 +355,13 @@ public void testReverse() { private static void testReverse(char[] input, char[] expectedOutput) { input = Arrays.copyOf(input, input.length); Chars.reverse(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testReverse(char[] input, int fromIndex, int toIndex, char[] expectedOutput) { input = Arrays.copyOf(input, input.length); Chars.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testReverseIndexed() { @@ -349,6 +373,203 @@ public void testReverseIndexed() { testReverse(new char[] {'A', '1', 'B', '2'}, 1, 3, new char[] {'A', 'B', '1', '2'}); } + private static void testRotate(char[] input, int distance, char[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Chars.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + char[] input, int distance, int fromIndex, int toIndex, char[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Chars.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new char[] {}, -1, new char[] {}); + testRotate(new char[] {}, 0, new char[] {}); + testRotate(new char[] {}, 1, new char[] {}); + + testRotate(new char[] {'1'}, -2, new char[] {'1'}); + testRotate(new char[] {'1'}, -1, new char[] {'1'}); + testRotate(new char[] {'1'}, 0, new char[] {'1'}); + testRotate(new char[] {'1'}, 1, new char[] {'1'}); + testRotate(new char[] {'1'}, 2, new char[] {'1'}); + + testRotate(new char[] {'1', '2'}, -3, new char[] {'2', '1'}); + testRotate(new char[] {'1', '2'}, -1, new char[] {'2', '1'}); + testRotate(new char[] {'1', '2'}, -2, new char[] {'1', '2'}); + testRotate(new char[] {'1', '2'}, 0, new char[] {'1', '2'}); + testRotate(new char[] {'1', '2'}, 1, new char[] {'2', '1'}); + testRotate(new char[] {'1', '2'}, 2, new char[] {'1', '2'}); + testRotate(new char[] {'1', '2'}, 3, new char[] {'2', '1'}); + + testRotate(new char[] {'1', '2', '3'}, -5, new char[] {'3', '1', '2'}); + testRotate(new char[] {'1', '2', '3'}, -4, new char[] {'2', '3', '1'}); + testRotate(new char[] {'1', '2', '3'}, -3, new char[] {'1', '2', '3'}); + testRotate(new char[] {'1', '2', '3'}, -2, new char[] {'3', '1', '2'}); + testRotate(new char[] {'1', '2', '3'}, -1, new char[] {'2', '3', '1'}); + testRotate(new char[] {'1', '2', '3'}, 0, new char[] {'1', '2', '3'}); + testRotate(new char[] {'1', '2', '3'}, 1, new char[] {'3', '1', '2'}); + testRotate(new char[] {'1', '2', '3'}, 2, new char[] {'2', '3', '1'}); + testRotate(new char[] {'1', '2', '3'}, 3, new char[] {'1', '2', '3'}); + testRotate(new char[] {'1', '2', '3'}, 4, new char[] {'3', '1', '2'}); + testRotate(new char[] {'1', '2', '3'}, 5, new char[] {'2', '3', '1'}); + + testRotate(new char[] {'1', '2', '3', '4'}, -9, new char[] {'2', '3', '4', '1'}); + testRotate(new char[] {'1', '2', '3', '4'}, -5, new char[] {'2', '3', '4', '1'}); + testRotate(new char[] {'1', '2', '3', '4'}, -1, new char[] {'2', '3', '4', '1'}); + testRotate(new char[] {'1', '2', '3', '4'}, 0, new char[] {'1', '2', '3', '4'}); + testRotate(new char[] {'1', '2', '3', '4'}, 1, new char[] {'4', '1', '2', '3'}); + testRotate(new char[] {'1', '2', '3', '4'}, 5, new char[] {'4', '1', '2', '3'}); + testRotate(new char[] {'1', '2', '3', '4'}, 9, new char[] {'4', '1', '2', '3'}); + + testRotate(new char[] {'1', '2', '3', '4', '5'}, -6, new char[] {'2', '3', '4', '5', '1'}); + testRotate(new char[] {'1', '2', '3', '4', '5'}, -4, new char[] {'5', '1', '2', '3', '4'}); + testRotate(new char[] {'1', '2', '3', '4', '5'}, -3, new char[] {'4', '5', '1', '2', '3'}); + testRotate(new char[] {'1', '2', '3', '4', '5'}, -1, new char[] {'2', '3', '4', '5', '1'}); + testRotate(new char[] {'1', '2', '3', '4', '5'}, 0, new char[] {'1', '2', '3', '4', '5'}); + testRotate(new char[] {'1', '2', '3', '4', '5'}, 1, new char[] {'5', '1', '2', '3', '4'}); + testRotate(new char[] {'1', '2', '3', '4', '5'}, 3, new char[] {'3', '4', '5', '1', '2'}); + testRotate(new char[] {'1', '2', '3', '4', '5'}, 4, new char[] {'2', '3', '4', '5', '1'}); + testRotate(new char[] {'1', '2', '3', '4', '5'}, 6, new char[] {'5', '1', '2', '3', '4'}); + } + + public void testRotateIndexed() { + testRotate(new char[] {}, 0, 0, 0, new char[] {}); + + testRotate(new char[] {'1'}, 0, 0, 1, new char[] {'1'}); + testRotate(new char[] {'1'}, 1, 0, 1, new char[] {'1'}); + testRotate(new char[] {'1'}, 1, 1, 1, new char[] {'1'}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -6, + 1, + 6, + new char[] {'0', '2', '3', '4', '5', '1', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -1, + 1, + 6, + new char[] {'0', '2', '3', '4', '5', '1', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 0, + 1, + 6, + new char[] {'0', '1', '2', '3', '4', '5', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 5, + 1, + 6, + new char[] {'0', '1', '2', '3', '4', '5', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 14, + 1, + 6, + new char[] {'0', '2', '3', '4', '5', '1', '6'}); + + // Rotate the first three elements + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -2, + 0, + 3, + new char[] {'2', '0', '1', '3', '4', '5', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -1, + 0, + 3, + new char[] {'1', '2', '0', '3', '4', '5', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 0, + 0, + 3, + new char[] {'0', '1', '2', '3', '4', '5', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 1, + 0, + 3, + new char[] {'2', '0', '1', '3', '4', '5', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 2, + 0, + 3, + new char[] {'1', '2', '0', '3', '4', '5', '6'}); + + // Rotate the last four elements + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -6, + 3, + 7, + new char[] {'0', '1', '2', '5', '6', '3', '4'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -5, + 3, + 7, + new char[] {'0', '1', '2', '4', '5', '6', '3'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -4, + 3, + 7, + new char[] {'0', '1', '2', '3', '4', '5', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -3, + 3, + 7, + new char[] {'0', '1', '2', '6', '3', '4', '5'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -2, + 3, + 7, + new char[] {'0', '1', '2', '5', '6', '3', '4'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + -1, + 3, + 7, + new char[] {'0', '1', '2', '4', '5', '6', '3'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 0, + 3, + 7, + new char[] {'0', '1', '2', '3', '4', '5', '6'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 1, + 3, + 7, + new char[] {'0', '1', '2', '6', '3', '4', '5'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 2, + 3, + 7, + new char[] {'0', '1', '2', '5', '6', '3', '4'}); + testRotate( + new char[] {'0', '1', '2', '3', '4', '5', '6'}, + 3, + 3, + 7, + new char[] {'0', '1', '2', '4', '5', '6', '3'}); + } + public void testSortDescending() { testSortDescending(new char[] {}, new char[] {}); testSortDescending(new char[] {'1'}, new char[] {'1'}); @@ -360,14 +581,14 @@ public void testSortDescending() { private static void testSortDescending(char[] input, char[] expectedOutput) { input = Arrays.copyOf(input, input.length); Chars.sortDescending(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testSortDescending( char[] input, int fromIndex, int toIndex, char[] expectedOutput) { input = Arrays.copyOf(input, input.length); Chars.sortDescending(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testSortDescendingIndexed() { @@ -382,17 +603,17 @@ public void testSortDescendingIndexed() { public void testToArray() { // need explicit type parameter to avoid javac warning!? List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Chars.toArray(none))); + assertThat(Chars.toArray(none)).isEqualTo(EMPTY); List one = Arrays.asList((char) 1); - assertTrue(Arrays.equals(ARRAY1, Chars.toArray(one))); + assertThat(Chars.toArray(one)).isEqualTo(ARRAY1); char[] array = {(char) 0, (char) 1, 'A'}; List three = Arrays.asList((char) 0, (char) 1, 'A'); - assertTrue(Arrays.equals(array, Chars.toArray(three))); + assertThat(Chars.toArray(three)).isEqualTo(array); - assertTrue(Arrays.equals(array, Chars.toArray(Chars.asList(array)))); + assertThat(Chars.toArray(Chars.asList(array))).isEqualTo(array); } public void testToArray_threadSafe() { @@ -402,30 +623,27 @@ public void testToArray_threadSafe() { Collection misleadingSize = Helpers.misleadingSizeCollection(delta); misleadingSize.addAll(list); char[] arr = Chars.toArray(misleadingSize); - assertEquals(i, arr.length); + assertThat(arr).hasLength(i); for (int j = 0; j < i; j++) { - assertEquals(VALUES[j], arr[j]); + assertThat(arr[j]).isEqualTo(VALUES[j]); } } } } public void testToArray_withNull() { - List list = Arrays.asList((char) 0, (char) 1, null); - try { - Chars.toArray(list); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Character> list = Arrays.asList((char) 0, (char) 1, null); + assertThrows(NullPointerException.class, () -> Chars.toArray(list)); } + @J2ktIncompatible // b/285319375 public void testAsList_isAView() { char[] array = {(char) 0, (char) 1}; List list = Chars.asList(array); list.set(0, (char) 2); - assertTrue(Arrays.equals(new char[] {(char) 2, (char) 1}, array)); + assertThat(array).isEqualTo(new char[] {(char) 2, (char) 1}); array[1] = (char) 3; - assertEquals(Arrays.asList((char) 2, (char) 3), list); + assertThat(list).containsExactly((char) 2, (char) 3).inOrder(); } public void testAsList_toArray_roundTrip() { @@ -435,23 +653,26 @@ public void testAsList_toArray_roundTrip() { // Make sure it returned a copy list.set(0, (char) 4); - assertTrue(Arrays.equals(new char[] {(char) 0, (char) 1, (char) 2}, newArray)); + assertThat(newArray).isEqualTo(new char[] {(char) 0, (char) 1, (char) 2}); newArray[1] = (char) 5; - assertEquals((char) 1, (char) list.get(1)); + assertThat((char) list.get(1)).isEqualTo((char) 1); } // This test stems from a real bug found by andrewk public void testAsList_subList_toArray_roundTrip() { char[] array = {(char) 0, (char) 1, (char) 2, (char) 3}; List list = Chars.asList(array); - assertTrue(Arrays.equals(new char[] {(char) 1, (char) 2}, Chars.toArray(list.subList(1, 3)))); - assertTrue(Arrays.equals(new char[] {}, Chars.toArray(list.subList(2, 2)))); + assertThat(Chars.toArray(list.subList(1, 3))).isEqualTo(new char[] {(char) 1, (char) 2}); + assertThat(Chars.toArray(list.subList(2, 2))).isEqualTo(new char[] {}); } + // `primitives` can't depend on `collect`, so this is what the prod code has to return. + @SuppressWarnings("EmptyList") public void testAsListEmpty() { - assertSame(Collections.emptyList(), Chars.asList(EMPTY)); + assertThat(Chars.asList(EMPTY)).isSameInstanceAs(Collections.emptyList()); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Chars.class); diff --git a/android/guava-tests/test/com/google/common/primitives/DoubleArrayAsListTest.java b/android/guava-tests/test/com/google/common/primitives/DoubleArrayAsListTest.java index 23a7ca14083b..475203bd1e1e 100644 --- a/android/guava-tests/test/com/google/common/primitives/DoubleArrayAsListTest.java +++ b/android/guava-tests/test/com/google/common/primitives/DoubleArrayAsListTest.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -31,13 +32,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Doubles#asList(double[])}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked +@AndroidIncompatible // test-suite builders public class DoubleArrayAsListTest extends TestCase { private static List asList(Double[] values) { @@ -48,12 +52,13 @@ private static List asList(Double[] values) { return Doubles.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = ImmutableList.of( ListTestSuiteBuilder.using(new DoublesAsListGenerator()).named("Doubles.asList"), - ListTestSuiteBuilder.using(new DoublsAsListHeadSubListGenerator()) + ListTestSuiteBuilder.using(new DoublesAsListHeadSubListGenerator()) .named("Doubles.asList, head subList"), ListTestSuiteBuilder.using(new DoublesAsListTailSubListGenerator()) .named("Doubles.asList, tail subList"), @@ -84,7 +89,7 @@ protected List create(Double[] elements) { } } - public static final class DoublsAsListHeadSubListGenerator extends TestDoubleListGenerator { + public static final class DoublesAsListHeadSubListGenerator extends TestDoubleListGenerator { @Override protected List create(Double[] elements) { Double[] suffix = {Double.MIN_VALUE, Double.MAX_VALUE}; @@ -96,7 +101,7 @@ protected List create(Double[] elements) { public static final class DoublesAsListTailSubListGenerator extends TestDoubleListGenerator { @Override protected List create(Double[] elements) { - Double[] prefix = {(double) 86, (double) 99}; + Double[] prefix = {86.0, 99.0}; Double[] all = concat(prefix, elements); return asList(all).subList(2, elements.length + 2); } @@ -106,7 +111,7 @@ public static final class DoublesAsListMiddleSubListGenerator extends TestDouble @Override protected List create(Double[] elements) { Double[] prefix = {Double.MIN_VALUE, Double.MAX_VALUE}; - Double[] suffix = {(double) 86, (double) 99}; + Double[] suffix = {86.0, 99.0}; Double[] all = concat(concat(prefix, elements), suffix); return asList(all).subList(2, elements.length + 2); } @@ -155,7 +160,7 @@ public List order(List insertionOrder) { public static class SampleDoubles extends SampleElements { public SampleDoubles() { - super((double) 0, (double) 1, (double) 2, (double) 3, (double) 4); + super(0.0, 1.0, 2.0, 3.0, 4.0); } } } diff --git a/android/guava-tests/test/com/google/common/primitives/DoublesTest.java b/android/guava-tests/test/com/google/common/primitives/DoublesTest.java index 871b84c286ff..0a9a126bcda6 100644 --- a/android/guava-tests/test/com/google/common/primitives/DoublesTest.java +++ b/android/guava-tests/test/com/google/common/primitives/DoublesTest.java @@ -16,11 +16,16 @@ package com.google.common.primitives; +import static com.google.common.primitives.Doubles.max; +import static com.google.common.primitives.Doubles.min; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static java.lang.Double.NaN; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.Helpers; @@ -33,18 +38,20 @@ import java.util.List; import java.util.regex.Pattern; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Doubles}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless +@NullMarked +@GwtCompatible public class DoublesTest extends TestCase { private static final double[] EMPTY = {}; - private static final double[] ARRAY1 = {(double) 1}; - private static final double[] ARRAY234 = {(double) 2, (double) 3, (double) 4}; + private static final double[] ARRAY1 = {1.0}; + private static final double[] ARRAY234 = {2.0, 3.0, 4.0}; private static final double LEAST = Double.NEGATIVE_INFINITY; private static final double GREATEST = Double.POSITIVE_INFINITY; @@ -75,244 +82,228 @@ public class DoublesTest extends TestCase { private static final double[] VALUES = Doubles.concat(NUMBERS, new double[] {NaN}); + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testHashCode() { for (double value : VALUES) { - assertEquals(((Double) value).hashCode(), Doubles.hashCode(value)); + assertThat(Doubles.hashCode(value)).isEqualTo(Double.hashCode(value)); } } + @SuppressWarnings("InlineMeInliner") // We need to test our method. public void testIsFinite() { for (double value : NUMBERS) { - assertEquals(!(Double.isNaN(value) || Double.isInfinite(value)), Doubles.isFinite(value)); + assertThat(Doubles.isFinite(value)).isEqualTo(Double.isFinite(value)); } } + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testCompare() { for (double x : VALUES) { for (double y : VALUES) { // note: spec requires only that the sign is the same - assertEquals(x + ", " + y, Double.valueOf(x).compareTo(y), Doubles.compare(x, y)); + assertWithMessage(x + ", " + y).that(Doubles.compare(x, y)).isEqualTo(Double.compare(x, y)); } } } public void testContains() { - assertFalse(Doubles.contains(EMPTY, (double) 1)); - assertFalse(Doubles.contains(ARRAY1, (double) 2)); - assertFalse(Doubles.contains(ARRAY234, (double) 1)); - assertTrue(Doubles.contains(new double[] {(double) -1}, (double) -1)); - assertTrue(Doubles.contains(ARRAY234, (double) 2)); - assertTrue(Doubles.contains(ARRAY234, (double) 3)); - assertTrue(Doubles.contains(ARRAY234, (double) 4)); + assertThat(Doubles.contains(EMPTY, 1.0)).isFalse(); + assertThat(Doubles.contains(ARRAY1, 2.0)).isFalse(); + assertThat(Doubles.contains(ARRAY234, 1.0)).isFalse(); + assertThat(Doubles.contains(new double[] {-1.0}, -1.0)).isTrue(); + assertThat(Doubles.contains(ARRAY234, 2.0)).isTrue(); + assertThat(Doubles.contains(ARRAY234, 3.0)).isTrue(); + assertThat(Doubles.contains(ARRAY234, 4.0)).isTrue(); for (double value : NUMBERS) { - assertTrue("" + value, Doubles.contains(new double[] {5.0, value}, value)); + assertWithMessage("" + value) + .that(Doubles.contains(new double[] {5.0, value}, value)) + .isTrue(); } - assertFalse(Doubles.contains(new double[] {5.0, NaN}, NaN)); + assertThat(Doubles.contains(new double[] {5.0, NaN}, NaN)).isFalse(); } public void testIndexOf() { - assertEquals(-1, Doubles.indexOf(EMPTY, (double) 1)); - assertEquals(-1, Doubles.indexOf(ARRAY1, (double) 2)); - assertEquals(-1, Doubles.indexOf(ARRAY234, (double) 1)); - assertEquals(0, Doubles.indexOf(new double[] {(double) -1}, (double) -1)); - assertEquals(0, Doubles.indexOf(ARRAY234, (double) 2)); - assertEquals(1, Doubles.indexOf(ARRAY234, (double) 3)); - assertEquals(2, Doubles.indexOf(ARRAY234, (double) 4)); - assertEquals( - 1, - Doubles.indexOf(new double[] {(double) 2, (double) 3, (double) 2, (double) 3}, (double) 3)); + assertThat(Doubles.indexOf(EMPTY, 1.0)).isEqualTo(-1); + assertThat(Doubles.indexOf(ARRAY1, 2.0)).isEqualTo(-1); + assertThat(Doubles.indexOf(ARRAY234, 1.0)).isEqualTo(-1); + assertThat(Doubles.indexOf(new double[] {-1.0}, -1.0)).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, 2.0)).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, 3.0)).isEqualTo(1); + assertThat(Doubles.indexOf(ARRAY234, 4.0)).isEqualTo(2); + assertThat(Doubles.indexOf(new double[] {2.0, 3.0, 2.0, 3.0}, 3.0)).isEqualTo(1); for (double value : NUMBERS) { - assertEquals("" + value, 1, Doubles.indexOf(new double[] {5.0, value}, value)); + assertWithMessage("" + value) + .that(Doubles.indexOf(new double[] {5.0, value}, value)) + .isEqualTo(1); } - assertEquals(-1, Doubles.indexOf(new double[] {5.0, NaN}, NaN)); + assertThat(Doubles.indexOf(new double[] {5.0, NaN}, NaN)).isEqualTo(-1); } public void testIndexOf_arrayTarget() { - assertEquals(0, Doubles.indexOf(EMPTY, EMPTY)); - assertEquals(0, Doubles.indexOf(ARRAY234, EMPTY)); - assertEquals(-1, Doubles.indexOf(EMPTY, ARRAY234)); - assertEquals(-1, Doubles.indexOf(ARRAY234, ARRAY1)); - assertEquals(-1, Doubles.indexOf(ARRAY1, ARRAY234)); - assertEquals(0, Doubles.indexOf(ARRAY1, ARRAY1)); - assertEquals(0, Doubles.indexOf(ARRAY234, ARRAY234)); - assertEquals(0, Doubles.indexOf(ARRAY234, new double[] {(double) 2, (double) 3})); - assertEquals(1, Doubles.indexOf(ARRAY234, new double[] {(double) 3, (double) 4})); - assertEquals(1, Doubles.indexOf(ARRAY234, new double[] {(double) 3})); - assertEquals(2, Doubles.indexOf(ARRAY234, new double[] {(double) 4})); - assertEquals( - 1, - Doubles.indexOf( - new double[] {(double) 2, (double) 3, (double) 3, (double) 3, (double) 3}, - new double[] {(double) 3})); - assertEquals( - 2, - Doubles.indexOf( - new double[] { - (double) 2, (double) 3, (double) 2, (double) 3, (double) 4, (double) 2, (double) 3 - }, - new double[] {(double) 2, (double) 3, (double) 4})); - assertEquals( - 1, - Doubles.indexOf( - new double[] { - (double) 2, (double) 2, (double) 3, (double) 4, (double) 2, (double) 3, (double) 4 - }, - new double[] {(double) 2, (double) 3, (double) 4})); - assertEquals( - -1, - Doubles.indexOf( - new double[] {(double) 4, (double) 3, (double) 2}, - new double[] {(double) 2, (double) 3, (double) 4})); + assertThat(Doubles.indexOf(EMPTY, EMPTY)).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, EMPTY)).isEqualTo(0); + assertThat(Doubles.indexOf(EMPTY, ARRAY234)).isEqualTo(-1); + assertThat(Doubles.indexOf(ARRAY234, ARRAY1)).isEqualTo(-1); + assertThat(Doubles.indexOf(ARRAY1, ARRAY234)).isEqualTo(-1); + assertThat(Doubles.indexOf(ARRAY1, ARRAY1)).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, ARRAY234)).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, new double[] {2.0, 3.0})).isEqualTo(0); + assertThat(Doubles.indexOf(ARRAY234, new double[] {3.0, 4.0})).isEqualTo(1); + assertThat(Doubles.indexOf(ARRAY234, new double[] {3.0})).isEqualTo(1); + assertThat(Doubles.indexOf(ARRAY234, new double[] {4.0})).isEqualTo(2); + assertThat(Doubles.indexOf(new double[] {2.0, 3.0, 3.0, 3.0, 3.0}, new double[] {3.0})) + .isEqualTo(1); + assertThat( + Doubles.indexOf( + new double[] {2.0, 3.0, 2.0, 3.0, 4.0, 2.0, 3.0}, new double[] {2.0, 3.0, 4.0})) + .isEqualTo(2); + assertThat( + Doubles.indexOf( + new double[] {2.0, 2.0, 3.0, 4.0, 2.0, 3.0, 4.0}, new double[] {2.0, 3.0, 4.0})) + .isEqualTo(1); + assertThat(Doubles.indexOf(new double[] {4.0, 3.0, 2.0}, new double[] {2.0, 3.0, 4.0})) + .isEqualTo(-1); for (double value : NUMBERS) { - assertEquals( - "" + value, - 1, - Doubles.indexOf(new double[] {5.0, value, value, 5.0}, new double[] {value, value})); + assertWithMessage("" + value) + .that(Doubles.indexOf(new double[] {5.0, value, value, 5.0}, new double[] {value, value})) + .isEqualTo(1); } - assertEquals(-1, Doubles.indexOf(new double[] {5.0, NaN, NaN, 5.0}, new double[] {NaN, NaN})); + assertThat(Doubles.indexOf(new double[] {5.0, NaN, NaN, 5.0}, new double[] {NaN, NaN})) + .isEqualTo(-1); } public void testLastIndexOf() { - assertEquals(-1, Doubles.lastIndexOf(EMPTY, (double) 1)); - assertEquals(-1, Doubles.lastIndexOf(ARRAY1, (double) 2)); - assertEquals(-1, Doubles.lastIndexOf(ARRAY234, (double) 1)); - assertEquals(0, Doubles.lastIndexOf(new double[] {(double) -1}, (double) -1)); - assertEquals(0, Doubles.lastIndexOf(ARRAY234, (double) 2)); - assertEquals(1, Doubles.lastIndexOf(ARRAY234, (double) 3)); - assertEquals(2, Doubles.lastIndexOf(ARRAY234, (double) 4)); - assertEquals( - 3, - Doubles.lastIndexOf( - new double[] {(double) 2, (double) 3, (double) 2, (double) 3}, (double) 3)); + assertThat(Doubles.lastIndexOf(EMPTY, 1.0)).isEqualTo(-1); + assertThat(Doubles.lastIndexOf(ARRAY1, 2.0)).isEqualTo(-1); + assertThat(Doubles.lastIndexOf(ARRAY234, 1.0)).isEqualTo(-1); + assertThat(Doubles.lastIndexOf(new double[] {-1.0}, -1.0)).isEqualTo(0); + assertThat(Doubles.lastIndexOf(ARRAY234, 2.0)).isEqualTo(0); + assertThat(Doubles.lastIndexOf(ARRAY234, 3.0)).isEqualTo(1); + assertThat(Doubles.lastIndexOf(ARRAY234, 4.0)).isEqualTo(2); + assertThat(Doubles.lastIndexOf(new double[] {2.0, 3.0, 2.0, 3.0}, 3.0)).isEqualTo(3); for (double value : NUMBERS) { - assertEquals("" + value, 0, Doubles.lastIndexOf(new double[] {value, 5.0}, value)); + assertWithMessage("" + value) + .that(Doubles.lastIndexOf(new double[] {value, 5.0}, value)) + .isEqualTo(0); } - assertEquals(-1, Doubles.lastIndexOf(new double[] {NaN, 5.0}, NaN)); + assertThat(Doubles.lastIndexOf(new double[] {NaN, 5.0}, NaN)).isEqualTo(-1); } @GwtIncompatible public void testMax_noArgs() { - try { - Doubles.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(LEAST, Doubles.max(LEAST)); - assertEquals(GREATEST, Doubles.max(GREATEST)); - assertEquals( - (double) 9, - Doubles.max( - (double) 8, (double) 6, (double) 7, (double) 5, (double) 3, (double) 0, (double) 9)); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat(max(8.0, 6.0, 7.0, 5.0, 3.0, 0.0, 9.0)).isEqualTo(9.0); - assertEquals(0.0, Doubles.max(-0.0, 0.0)); - assertEquals(0.0, Doubles.max(0.0, -0.0)); - assertEquals(GREATEST, Doubles.max(NUMBERS)); - assertTrue(Double.isNaN(Doubles.max(VALUES))); + assertThat(max(-0.0, 0.0)).isEqualTo(0.0); + assertThat(max(0.0, -0.0)).isEqualTo(0.0); + assertThat(max(NUMBERS)).isEqualTo(GREATEST); + assertThat(Double.isNaN(max(VALUES))).isTrue(); } @GwtIncompatible public void testMin_noArgs() { - try { - Doubles.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, Doubles.min(LEAST)); - assertEquals(GREATEST, Doubles.min(GREATEST)); - assertEquals( - (double) 0, - Doubles.min( - (double) 8, (double) 6, (double) 7, (double) 5, (double) 3, (double) 0, (double) 9)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat(min(8.0, 6.0, 7.0, 5.0, 3.0, 0.0, 9.0)).isEqualTo(0.0); - assertEquals(-0.0, Doubles.min(-0.0, 0.0)); - assertEquals(-0.0, Doubles.min(0.0, -0.0)); - assertEquals(LEAST, Doubles.min(NUMBERS)); - assertTrue(Double.isNaN(Doubles.min(VALUES))); + assertThat(min(-0.0, 0.0)).isEqualTo(-0.0); + assertThat(min(0.0, -0.0)).isEqualTo(-0.0); + assertThat(min(NUMBERS)).isEqualTo(LEAST); + assertThat(Double.isNaN(min(VALUES))).isTrue(); } public void testConstrainToRange() { - double tolerance = 1e-10; - assertEquals( - (double) 1, Doubles.constrainToRange((double) 1, (double) 0, (double) 5), tolerance); - assertEquals( - (double) 1, Doubles.constrainToRange((double) 1, (double) 1, (double) 5), tolerance); - assertEquals( - (double) 3, Doubles.constrainToRange((double) 1, (double) 3, (double) 5), tolerance); - assertEquals( - (double) -1, Doubles.constrainToRange((double) 0, (double) -5, (double) -1), tolerance); - assertEquals( - (double) 2, Doubles.constrainToRange((double) 5, (double) 2, (double) 2), tolerance); - try { - Doubles.constrainToRange((double) 1, (double) 3, (double) 2); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThat(Doubles.constrainToRange(1.0, 0.0, 5.0)).isEqualTo(1.0); + assertThat(Doubles.constrainToRange(1.0, 1.0, 5.0)).isEqualTo(1.0); + assertThat(Doubles.constrainToRange(1.0, 3.0, 5.0)).isEqualTo(3.0); + assertThat(Doubles.constrainToRange(0.0, -5.0, -1.0)).isEqualTo(-1.0); + assertThat(Doubles.constrainToRange(5.0, 2.0, 2.0)).isEqualTo(2.0); + assertThrows(IllegalArgumentException.class, () -> Doubles.constrainToRange(1.0, 3.0, 2.0)); } public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Doubles.concat())); - assertTrue(Arrays.equals(EMPTY, Doubles.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Doubles.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY1, Doubles.concat(ARRAY1))); - assertNotSame(ARRAY1, Doubles.concat(ARRAY1)); - assertTrue(Arrays.equals(ARRAY1, Doubles.concat(EMPTY, ARRAY1, EMPTY))); - assertTrue( - Arrays.equals( - new double[] {(double) 1, (double) 1, (double) 1}, - Doubles.concat(ARRAY1, ARRAY1, ARRAY1))); - assertTrue( - Arrays.equals( - new double[] {(double) 1, (double) 2, (double) 3, (double) 4}, - Doubles.concat(ARRAY1, ARRAY234))); + assertThat(Doubles.concat()).isEqualTo(EMPTY); + assertThat(Doubles.concat(EMPTY)).isEqualTo(EMPTY); + assertThat(Doubles.concat(EMPTY, EMPTY, EMPTY)).isEqualTo(EMPTY); + assertThat(Doubles.concat(ARRAY1)).isEqualTo(ARRAY1); + assertThat(Doubles.concat(ARRAY1)).isNotSameInstanceAs(ARRAY1); + assertThat(Doubles.concat(EMPTY, ARRAY1, EMPTY)).isEqualTo(ARRAY1); + assertThat(Doubles.concat(ARRAY1, ARRAY1, ARRAY1)).isEqualTo(new double[] {1.0, 1.0, 1.0}); + assertThat(Doubles.concat(ARRAY1, ARRAY234)).isEqualTo(new double[] {1.0, 2.0, 3.0, 4.0}); } - public void testEnsureCapacity() { - assertSame(EMPTY, Doubles.ensureCapacity(EMPTY, 0, 1)); - assertSame(ARRAY1, Doubles.ensureCapacity(ARRAY1, 0, 1)); - assertSame(ARRAY1, Doubles.ensureCapacity(ARRAY1, 1, 1)); - assertTrue( - Arrays.equals( - new double[] {(double) 1, (double) 0, (double) 0}, - Doubles.ensureCapacity(ARRAY1, 2, 1))); + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_negative() { + int dim1 = 1 << 16; + int dim2 = 1 << 15; + assertThat(dim1 * dim2).isLessThan(0); + testConcatOverflow(dim1, dim2); } - public void testEnsureCapacity_fail() { - try { - Doubles.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_nonNegative() { + int dim1 = 1 << 16; + int dim2 = 1 << 16; + assertThat(dim1 * dim2).isAtLeast(0); + testConcatOverflow(dim1, dim2); + } + + private static void testConcatOverflow(int arraysDim1, int arraysDim2) { + assertThat((long) arraysDim1 * arraysDim2).isNotEqualTo((long) (arraysDim1 * arraysDim2)); + + double[][] arrays = new double[arraysDim1][]; + // it's shared to avoid using too much memory in tests + double[] sharedArray = new double[arraysDim2]; + Arrays.fill(arrays, sharedArray); + try { - // notice that this should even fail when no growth was needed - Doubles.ensureCapacity(ARRAY1, 1, -1); + Doubles.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } + public void testEnsureCapacity() { + assertThat(Doubles.ensureCapacity(EMPTY, 0, 1)).isSameInstanceAs(EMPTY); + assertThat(Doubles.ensureCapacity(ARRAY1, 0, 1)).isSameInstanceAs(ARRAY1); + assertThat(Doubles.ensureCapacity(ARRAY1, 1, 1)).isSameInstanceAs(ARRAY1); + assertThat(Arrays.equals(new double[] {1.0, 0.0, 0.0}, Doubles.ensureCapacity(ARRAY1, 2, 1))) + .isTrue(); + } + + public void testEnsureCapacity_fail() { + assertThrows(IllegalArgumentException.class, () -> Doubles.ensureCapacity(ARRAY1, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> Doubles.ensureCapacity(ARRAY1, 1, -1)); + } + @GwtIncompatible // Double.toString returns different value in GWT. public void testJoin() { - assertEquals("", Doubles.join(",", EMPTY)); - assertEquals("1.0", Doubles.join(",", ARRAY1)); - assertEquals("1.0,2.0", Doubles.join(",", (double) 1, (double) 2)); - assertEquals("1.02.03.0", Doubles.join("", (double) 1, (double) 2, (double) 3)); + assertThat(Doubles.join(",", EMPTY)).isEmpty(); + assertThat(Doubles.join(",", ARRAY1)).isEqualTo("1.0"); + assertThat(Doubles.join(",", 1.0, 2.0)).isEqualTo("1.0,2.0"); + assertThat(Doubles.join("", 1.0, 2.0, 3.0)).isEqualTo("1.02.03.0"); } public void testJoinNonTrivialDoubles() { - assertEquals("", Doubles.join(",", EMPTY)); - assertEquals("1.2", Doubles.join(",", 1.2)); - assertEquals("1.3,2.4", Doubles.join(",", 1.3, 2.4)); - assertEquals("1.42.53.6", Doubles.join("", 1.4, 2.5, 3.6)); + assertThat(Doubles.join(",", EMPTY)).isEmpty(); + assertThat(Doubles.join(",", 1.2)).isEqualTo("1.2"); + assertThat(Doubles.join(",", 1.3, 2.4)).isEqualTo("1.3,2.4"); + assertThat(Doubles.join("", 1.4, 2.5, 3.6)).isEqualTo("1.42.53.6"); } public void testLexicographicalComparator() { @@ -321,9 +312,9 @@ public void testLexicographicalComparator() { new double[] {}, new double[] {LEAST}, new double[] {LEAST, LEAST}, - new double[] {LEAST, (double) 1}, - new double[] {(double) 1}, - new double[] {(double) 1, LEAST}, + new double[] {LEAST, 1.0}, + new double[] {1.0}, + new double[] {1.0, LEAST}, new double[] {GREATEST, Double.MAX_VALUE}, new double[] {GREATEST, GREATEST}, new double[] {GREATEST, GREATEST, GREATEST}); @@ -343,14 +334,14 @@ public void testReverse() { private static void testReverse(double[] input, double[] expectedOutput) { input = Arrays.copyOf(input, input.length); Doubles.reverse(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testReverse( double[] input, int fromIndex, int toIndex, double[] expectedOutput) { input = Arrays.copyOf(input, input.length); Doubles.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testReverseIndexed() { @@ -362,6 +353,103 @@ public void testReverseIndexed() { testReverse(new double[] {-1, 1, -2, 2}, 1, 3, new double[] {-1, -2, 1, 2}); } + private static void testRotate(double[] input, int distance, double[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Doubles.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + double[] input, int distance, int fromIndex, int toIndex, double[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Doubles.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new double[] {}, -1, new double[] {}); + testRotate(new double[] {}, 0, new double[] {}); + testRotate(new double[] {}, 1, new double[] {}); + + testRotate(new double[] {1}, -2, new double[] {1}); + testRotate(new double[] {1}, -1, new double[] {1}); + testRotate(new double[] {1}, 0, new double[] {1}); + testRotate(new double[] {1}, 1, new double[] {1}); + testRotate(new double[] {1}, 2, new double[] {1}); + + testRotate(new double[] {1, 2}, -3, new double[] {2, 1}); + testRotate(new double[] {1, 2}, -1, new double[] {2, 1}); + testRotate(new double[] {1, 2}, -2, new double[] {1, 2}); + testRotate(new double[] {1, 2}, 0, new double[] {1, 2}); + testRotate(new double[] {1, 2}, 1, new double[] {2, 1}); + testRotate(new double[] {1, 2}, 2, new double[] {1, 2}); + testRotate(new double[] {1, 2}, 3, new double[] {2, 1}); + + testRotate(new double[] {1, 2, 3}, -5, new double[] {3, 1, 2}); + testRotate(new double[] {1, 2, 3}, -4, new double[] {2, 3, 1}); + testRotate(new double[] {1, 2, 3}, -3, new double[] {1, 2, 3}); + testRotate(new double[] {1, 2, 3}, -2, new double[] {3, 1, 2}); + testRotate(new double[] {1, 2, 3}, -1, new double[] {2, 3, 1}); + testRotate(new double[] {1, 2, 3}, 0, new double[] {1, 2, 3}); + testRotate(new double[] {1, 2, 3}, 1, new double[] {3, 1, 2}); + testRotate(new double[] {1, 2, 3}, 2, new double[] {2, 3, 1}); + testRotate(new double[] {1, 2, 3}, 3, new double[] {1, 2, 3}); + testRotate(new double[] {1, 2, 3}, 4, new double[] {3, 1, 2}); + testRotate(new double[] {1, 2, 3}, 5, new double[] {2, 3, 1}); + + testRotate(new double[] {1, 2, 3, 4}, -9, new double[] {2, 3, 4, 1}); + testRotate(new double[] {1, 2, 3, 4}, -5, new double[] {2, 3, 4, 1}); + testRotate(new double[] {1, 2, 3, 4}, -1, new double[] {2, 3, 4, 1}); + testRotate(new double[] {1, 2, 3, 4}, 0, new double[] {1, 2, 3, 4}); + testRotate(new double[] {1, 2, 3, 4}, 1, new double[] {4, 1, 2, 3}); + testRotate(new double[] {1, 2, 3, 4}, 5, new double[] {4, 1, 2, 3}); + testRotate(new double[] {1, 2, 3, 4}, 9, new double[] {4, 1, 2, 3}); + + testRotate(new double[] {1, 2, 3, 4, 5}, -6, new double[] {2, 3, 4, 5, 1}); + testRotate(new double[] {1, 2, 3, 4, 5}, -4, new double[] {5, 1, 2, 3, 4}); + testRotate(new double[] {1, 2, 3, 4, 5}, -3, new double[] {4, 5, 1, 2, 3}); + testRotate(new double[] {1, 2, 3, 4, 5}, -1, new double[] {2, 3, 4, 5, 1}); + testRotate(new double[] {1, 2, 3, 4, 5}, 0, new double[] {1, 2, 3, 4, 5}); + testRotate(new double[] {1, 2, 3, 4, 5}, 1, new double[] {5, 1, 2, 3, 4}); + testRotate(new double[] {1, 2, 3, 4, 5}, 3, new double[] {3, 4, 5, 1, 2}); + testRotate(new double[] {1, 2, 3, 4, 5}, 4, new double[] {2, 3, 4, 5, 1}); + testRotate(new double[] {1, 2, 3, 4, 5}, 6, new double[] {5, 1, 2, 3, 4}); + } + + public void testRotateIndexed() { + testRotate(new double[] {}, 0, 0, 0, new double[] {}); + + testRotate(new double[] {1}, 0, 0, 1, new double[] {1}); + testRotate(new double[] {1}, 1, 0, 1, new double[] {1}); + testRotate(new double[] {1}, 1, 1, 1, new double[] {1}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -6, 1, 6, new double[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -1, 1, 6, new double[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 0, 1, 6, new double[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 5, 1, 6, new double[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 14, 1, 6, new double[] {0, 2, 3, 4, 5, 1, 6}); + + // Rotate the first three elements + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -2, 0, 3, new double[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -1, 0, 3, new double[] {1, 2, 0, 3, 4, 5, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 0, 0, 3, new double[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 1, 0, 3, new double[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 2, 0, 3, new double[] {1, 2, 0, 3, 4, 5, 6}); + + // Rotate the last four elements + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -6, 3, 7, new double[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -5, 3, 7, new double[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -4, 3, 7, new double[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -3, 3, 7, new double[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -2, 3, 7, new double[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, -1, 3, 7, new double[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 0, 3, 7, new double[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 1, 3, 7, new double[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 2, 3, 7, new double[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new double[] {0, 1, 2, 3, 4, 5, 6}, 3, 3, 7, new double[] {0, 1, 2, 4, 5, 6, 3}); + } + public void testSortDescending() { testSortDescending(new double[] {}, new double[] {}); testSortDescending(new double[] {1}, new double[] {1}); @@ -369,16 +457,15 @@ public void testSortDescending() { testSortDescending(new double[] {1, 3, 1}, new double[] {3, 1, 1}); testSortDescending(new double[] {-1, 1, -2, 2}, new double[] {2, 1, -1, -2}); testSortDescending( - new double[] {-1, 1, Double.NaN, -2, -0, 0, 2}, - new double[] {Double.NaN, 2, 1, 0, -0, -1, -2}); + new double[] {-1, 1, Double.NaN, -2, -0.0, 0, 2}, + new double[] {Double.NaN, 2, 1, 0, -0.0, -1, -2}); } private static void testSortDescending(double[] input, double[] expectedOutput) { input = Arrays.copyOf(input, input.length); Doubles.sortDescending(input); - // GWT's Arrays.equals doesn't appear to handle NaN correctly, so test each element individually for (int i = 0; i < input.length; i++) { - assertEquals(0, Double.compare(expectedOutput[i], input[i])); + assertThat(input[i]).isEqualTo(expectedOutput[i]); } } @@ -386,9 +473,8 @@ private static void testSortDescending( double[] input, int fromIndex, int toIndex, double[] expectedOutput) { input = Arrays.copyOf(input, input.length); Doubles.sortDescending(input, fromIndex, toIndex); - // GWT's Arrays.equals doesn't appear to handle NaN correctly, so test each element individually for (int i = 0; i < input.length; i++) { - assertEquals(0, Double.compare(expectedOutput[i], input[i])); + assertThat(input[i]).isEqualTo(expectedOutput[i]); } } @@ -403,12 +489,14 @@ public void testSortDescendingIndexed() { new double[] {-1, 1, Double.NaN, -2, 2}, 1, 4, new double[] {-1, Double.NaN, 1, -2, 2}); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testLexicographicalComparatorSerializable() { Comparator comparator = Doubles.lexicographicalComparator(); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testStringConverterSerialization() { SerializableTester.reserializeAndAssert(Doubles.stringConverter()); @@ -417,17 +505,17 @@ public void testStringConverterSerialization() { public void testToArray() { // need explicit type parameter to avoid javac warning!? List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Doubles.toArray(none))); + assertThat(Doubles.toArray(none)).isEqualTo(EMPTY); - List one = Arrays.asList((double) 1); - assertTrue(Arrays.equals(ARRAY1, Doubles.toArray(one))); + List one = Arrays.asList(1.0); + assertThat(Doubles.toArray(one)).isEqualTo(ARRAY1); - double[] array = {(double) 0, (double) 1, Math.PI}; + double[] array = {0.0, 1.0, Math.PI}; - List three = Arrays.asList((double) 0, (double) 1, Math.PI); - assertTrue(Arrays.equals(array, Doubles.toArray(three))); + List three = Arrays.asList(0.0, 1.0, Math.PI); + assertThat(Doubles.toArray(three)).isEqualTo(array); - assertTrue(Arrays.equals(array, Doubles.toArray(Doubles.asList(array)))); + assertThat(Doubles.toArray(Doubles.asList(array))).isEqualTo(array); } public void testToArray_threadSafe() { @@ -437,80 +525,78 @@ public void testToArray_threadSafe() { Collection misleadingSize = Helpers.misleadingSizeCollection(delta); misleadingSize.addAll(list); double[] arr = Doubles.toArray(misleadingSize); - assertEquals(i, arr.length); + assertThat(arr.length).isEqualTo(i); for (int j = 0; j < i; j++) { - assertEquals(VALUES[j], arr[j]); + assertThat(arr[j]).isEqualTo(VALUES[j]); } } } } public void testToArray_withNull() { - List list = Arrays.asList((double) 0, (double) 1, null); - try { - Doubles.toArray(list); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Double> list = Arrays.asList(0.0, 1.0, null); + assertThrows(NullPointerException.class, () -> Doubles.toArray(list)); } public void testToArray_withConversion() { - double[] array = {(double) 0, (double) 1, (double) 2}; + double[] array = {0.0, 1.0, 2.0}; List bytes = Arrays.asList((byte) 0, (byte) 1, (byte) 2); List shorts = Arrays.asList((short) 0, (short) 1, (short) 2); List ints = Arrays.asList(0, 1, 2); - List floats = Arrays.asList((float) 0, (float) 1, (float) 2); - List longs = Arrays.asList((long) 0, (long) 1, (long) 2); - List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); + List floats = Arrays.asList(0.0f, 1.0f, 2.0f); + List longs = Arrays.asList(0L, 1L, 2L); + List doubles = Arrays.asList(0.0, 1.0, 2.0); - assertTrue(Arrays.equals(array, Doubles.toArray(bytes))); - assertTrue(Arrays.equals(array, Doubles.toArray(shorts))); - assertTrue(Arrays.equals(array, Doubles.toArray(ints))); - assertTrue(Arrays.equals(array, Doubles.toArray(floats))); - assertTrue(Arrays.equals(array, Doubles.toArray(longs))); - assertTrue(Arrays.equals(array, Doubles.toArray(doubles))); + assertThat(Doubles.toArray(bytes)).isEqualTo(array); + assertThat(Doubles.toArray(shorts)).isEqualTo(array); + assertThat(Doubles.toArray(ints)).isEqualTo(array); + assertThat(Doubles.toArray(floats)).isEqualTo(array); + assertThat(Doubles.toArray(longs)).isEqualTo(array); + assertThat(Doubles.toArray(doubles)).isEqualTo(array); } + @J2ktIncompatible // b/239034072: Kotlin varargs copy parameter arrays. public void testAsList_isAView() { - double[] array = {(double) 0, (double) 1}; + double[] array = {0.0, 1.0}; List list = Doubles.asList(array); - list.set(0, (double) 2); - assertTrue(Arrays.equals(new double[] {(double) 2, (double) 1}, array)); - array[1] = (double) 3; - assertThat(list).containsExactly((double) 2, (double) 3).inOrder(); + list.set(0, 2.0); + assertThat(array).isEqualTo(new double[] {2.0, 1.0}); + array[1] = 3.0; + assertThat(list).containsExactly(2.0, 3.0).inOrder(); } public void testAsList_toArray_roundTrip() { - double[] array = {(double) 0, (double) 1, (double) 2}; + double[] array = {0.0, 1.0, 2.0}; List list = Doubles.asList(array); double[] newArray = Doubles.toArray(list); // Make sure it returned a copy - list.set(0, (double) 4); - assertTrue(Arrays.equals(new double[] {(double) 0, (double) 1, (double) 2}, newArray)); - newArray[1] = (double) 5; - assertEquals((double) 1, (double) list.get(1)); + list.set(0, 4.0); + assertThat(newArray).isEqualTo(new double[] {0.0, 1.0, 2.0}); + newArray[1] = 5.0; + assertThat((double) list.get(1)).isEqualTo(1.0); } // This test stems from a real bug found by andrewk public void testAsList_subList_toArray_roundTrip() { - double[] array = {(double) 0, (double) 1, (double) 2, (double) 3}; + double[] array = {0.0, 1.0, 2.0, 3.0}; List list = Doubles.asList(array); - assertTrue( - Arrays.equals(new double[] {(double) 1, (double) 2}, Doubles.toArray(list.subList(1, 3)))); - assertTrue(Arrays.equals(new double[] {}, Doubles.toArray(list.subList(2, 2)))); + assertThat(Doubles.toArray(list.subList(1, 3))).isEqualTo(new double[] {1.0, 2.0}); + assertThat(Doubles.toArray(list.subList(2, 2))).isEmpty(); } + // `primitives` can't depend on `collect`, so this is what the prod code has to return. + @SuppressWarnings("EmptyList") public void testAsListEmpty() { - assertSame(Collections.emptyList(), Doubles.asList(EMPTY)); + assertThat(Doubles.asList(EMPTY)).isSameInstanceAs(Collections.emptyList()); } /** * A reference implementation for {@code tryParse} that just catches the exception from {@link * Double#valueOf}. */ - private static Double referenceTryParse(String input) { + private static @Nullable Double referenceTryParse(String input) { if (input.trim().length() < input.length()) { return null; } @@ -524,7 +610,7 @@ private static Double referenceTryParse(String input) { @GwtIncompatible // Doubles.tryParse private static void checkTryParse(String input) { Double expected = referenceTryParse(input); - assertEquals(expected, Doubles.tryParse(input)); + assertThat(Doubles.tryParse(input)).isEqualTo(expected); if (expected != null && !Doubles.FLOATING_POINT_PATTERN.matcher(input).matches()) { // TODO(cpovirk): Use SourceCodeEscapers if it is added to Guava. StringBuilder escapedInput = new StringBuilder(); @@ -541,7 +627,7 @@ private static void checkTryParse(String input) { @GwtIncompatible // Doubles.tryParse private static void checkTryParse(double expected, String input) { - assertEquals(Double.valueOf(expected), Doubles.tryParse(input)); + assertThat(Doubles.tryParse(input)).isEqualTo(Double.valueOf(expected)); assertThat(input) .matches( Pattern.compile( @@ -586,6 +672,7 @@ public void testTryParseOfToStringIsOriginal() { } } + @J2ktIncompatible // hexadecimal doubles @GwtIncompatible // Doubles.tryParse public void testTryParseOfToHexStringIsOriginal() { for (double d : NUMBERS) { @@ -632,11 +719,12 @@ public void testTryParseFailures() { Pattern.compile( Doubles.FLOATING_POINT_PATTERN.pattern(), Doubles.FLOATING_POINT_PATTERN.flags())); - assertEquals(referenceTryParse(badInput), Doubles.tryParse(badInput)); - assertNull(Doubles.tryParse(badInput)); + assertThat(Doubles.tryParse(badInput)).isEqualTo(referenceTryParse(badInput)); + assertThat(Doubles.tryParse(badInput)).isNull(); } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Doubles.class); @@ -644,39 +732,37 @@ public void testNulls() { public void testStringConverter_convert() { Converter converter = Doubles.stringConverter(); - assertEquals((Double) 1.0, converter.convert("1.0")); - assertEquals((Double) 0.0, converter.convert("0.0")); - assertEquals((Double) (-1.0), converter.convert("-1.0")); - assertEquals((Double) 1.0, converter.convert("1")); - assertEquals((Double) 0.0, converter.convert("0")); - assertEquals((Double) (-1.0), converter.convert("-1")); - assertEquals((Double) 1e6, converter.convert("1e6")); - assertEquals((Double) 1e-6, converter.convert("1e-6")); + assertThat(converter.convert("1.0")).isEqualTo(1.0); + assertThat(converter.convert("0.0")).isEqualTo(0.0); + assertThat(converter.convert("-1.0")).isEqualTo(-1.0); + assertThat(converter.convert("1")).isEqualTo(1.0); + assertThat(converter.convert("0")).isEqualTo(0.0); + assertThat(converter.convert("-1")).isEqualTo(-1.0); + assertThat(converter.convert("1e6")).isEqualTo(1e6); + assertThat(converter.convert("1e-6")).isEqualTo(1e-6); } public void testStringConverter_convertError() { - try { - Doubles.stringConverter().convert("notanumber"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows( + NumberFormatException.class, () -> Doubles.stringConverter().convert("notanumber")); } public void testStringConverter_nullConversions() { - assertNull(Doubles.stringConverter().convert(null)); - assertNull(Doubles.stringConverter().reverse().convert(null)); + assertThat(Doubles.stringConverter().convert(null)).isNull(); + assertThat(Doubles.stringConverter().reverse().convert(null)).isNull(); } @GwtIncompatible // Double.toString returns different value in GWT. public void testStringConverter_reverse() { Converter converter = Doubles.stringConverter(); - assertEquals("1.0", converter.reverse().convert(1.0)); - assertEquals("0.0", converter.reverse().convert(0.0)); - assertEquals("-1.0", converter.reverse().convert(-1.0)); - assertEquals("1000000.0", converter.reverse().convert(1e6)); - assertEquals("1.0E-6", converter.reverse().convert(1e-6)); + assertThat(converter.reverse().convert(1.0)).isEqualTo("1.0"); + assertThat(converter.reverse().convert(0.0)).isEqualTo("0.0"); + assertThat(converter.reverse().convert(-1.0)).isEqualTo("-1.0"); + assertThat(converter.reverse().convert(1e6)).isEqualTo("1000000.0"); + assertThat(converter.reverse().convert(1e-6)).isEqualTo("1.0E-6"); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testStringConverter_nullPointerTester() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -685,11 +771,7 @@ public void testStringConverter_nullPointerTester() throws Exception { @GwtIncompatible public void testTryParse_withNullNoGwt() { - assertNull(Doubles.tryParse("null")); - try { - Doubles.tryParse(null); - fail("Expected NPE"); - } catch (NullPointerException expected) { - } + assertThat(Doubles.tryParse("null")).isNull(); + assertThrows(NullPointerException.class, () -> Doubles.tryParse(null)); } } diff --git a/android/guava-tests/test/com/google/common/primitives/FloatArrayAsListTest.java b/android/guava-tests/test/com/google/common/primitives/FloatArrayAsListTest.java index 233a0211b03b..57bcab8bb6f2 100644 --- a/android/guava-tests/test/com/google/common/primitives/FloatArrayAsListTest.java +++ b/android/guava-tests/test/com/google/common/primitives/FloatArrayAsListTest.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -31,13 +32,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Floats#asList(float[])})}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked +@AndroidIncompatible // test-suite builders public class FloatArrayAsListTest extends TestCase { private static List asList(Float[] values) { @@ -48,6 +52,7 @@ private static List asList(Float[] values) { return Floats.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = @@ -96,7 +101,7 @@ protected List create(Float[] elements) { public static final class FloatsAsListTailSubListGenerator extends TestFloatListGenerator { @Override protected List create(Float[] elements) { - Float[] prefix = {(float) 86, (float) 99}; + Float[] prefix = {86.0f, 99.0f}; Float[] all = concat(prefix, elements); return asList(all).subList(2, elements.length + 2); } @@ -106,7 +111,7 @@ public static final class FloatsAsListMiddleSubListGenerator extends TestFloatLi @Override protected List create(Float[] elements) { Float[] prefix = {Float.MIN_VALUE, Float.MAX_VALUE}; - Float[] suffix = {(float) 86, (float) 99}; + Float[] suffix = {86.0f, 99.0f}; Float[] all = concat(concat(prefix, elements), suffix); return asList(all).subList(2, elements.length + 2); } @@ -155,7 +160,7 @@ public List order(List insertionOrder) { public static class SampleFloats extends SampleElements { public SampleFloats() { - super((float) 0, (float) 1, (float) 2, (float) 3, (float) 4); + super(0.0f, 1.0f, 2.0f, 3.0f, 4.0f); } } } diff --git a/android/guava-tests/test/com/google/common/primitives/FloatsTest.java b/android/guava-tests/test/com/google/common/primitives/FloatsTest.java index fd59ad4d82a6..972e528c3e29 100644 --- a/android/guava-tests/test/com/google/common/primitives/FloatsTest.java +++ b/android/guava-tests/test/com/google/common/primitives/FloatsTest.java @@ -16,11 +16,16 @@ package com.google.common.primitives; +import static com.google.common.primitives.Floats.max; +import static com.google.common.primitives.Floats.min; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static java.lang.Float.NaN; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.Helpers; @@ -32,18 +37,20 @@ import java.util.Comparator; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Floats}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless +@NullMarked +@GwtCompatible public class FloatsTest extends TestCase { private static final float[] EMPTY = {}; - private static final float[] ARRAY1 = {(float) 1}; - private static final float[] ARRAY234 = {(float) 2, (float) 3, (float) 4}; + private static final float[] ARRAY1 = {1.0f}; + private static final float[] ARRAY234 = {2.0f, 3.0f, 4.0f}; private static final float LEAST = Float.NEGATIVE_INFINITY; private static final float GREATEST = Float.POSITIVE_INFINITY; @@ -70,225 +77,221 @@ public class FloatsTest extends TestCase { private static final float[] VALUES = Floats.concat(NUMBERS, new float[] {NaN}); + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testHashCode() { for (float value : VALUES) { - assertEquals(((Float) value).hashCode(), Floats.hashCode(value)); + assertThat(Floats.hashCode(value)).isEqualTo(Float.hashCode(value)); } } + @SuppressWarnings("InlineMeInliner") // We need to test our method. public void testIsFinite() { for (float value : NUMBERS) { - assertEquals(!(Float.isInfinite(value) || Float.isNaN(value)), Floats.isFinite(value)); + assertThat(Floats.isFinite(value)).isEqualTo(Float.isFinite(value)); } } + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testCompare() { for (float x : VALUES) { for (float y : VALUES) { // note: spec requires only that the sign is the same - assertEquals(x + ", " + y, Float.valueOf(x).compareTo(y), Floats.compare(x, y)); + assertWithMessage(x + ", " + y).that(Floats.compare(x, y)).isEqualTo(Float.compare(x, y)); } } } public void testContains() { - assertFalse(Floats.contains(EMPTY, (float) 1)); - assertFalse(Floats.contains(ARRAY1, (float) 2)); - assertFalse(Floats.contains(ARRAY234, (float) 1)); - assertTrue(Floats.contains(new float[] {(float) -1}, (float) -1)); - assertTrue(Floats.contains(ARRAY234, (float) 2)); - assertTrue(Floats.contains(ARRAY234, (float) 3)); - assertTrue(Floats.contains(ARRAY234, (float) 4)); + assertThat(Floats.contains(EMPTY, 1.0f)).isFalse(); + assertThat(Floats.contains(ARRAY1, 2.0f)).isFalse(); + assertThat(Floats.contains(ARRAY234, 1.0f)).isFalse(); + assertThat(Floats.contains(new float[] {-1.0f}, -1.0f)).isTrue(); + assertThat(Floats.contains(ARRAY234, 2.0f)).isTrue(); + assertThat(Floats.contains(ARRAY234, 3.0f)).isTrue(); + assertThat(Floats.contains(ARRAY234, 4.0f)).isTrue(); for (float value : NUMBERS) { - assertTrue("" + value, Floats.contains(new float[] {5f, value}, value)); + assertWithMessage("" + value).that(Floats.contains(new float[] {5f, value}, value)).isTrue(); } - assertFalse(Floats.contains(new float[] {5f, NaN}, NaN)); + assertThat(Floats.contains(new float[] {5f, NaN}, NaN)).isFalse(); } public void testIndexOf() { - assertEquals(-1, Floats.indexOf(EMPTY, (float) 1)); - assertEquals(-1, Floats.indexOf(ARRAY1, (float) 2)); - assertEquals(-1, Floats.indexOf(ARRAY234, (float) 1)); - assertEquals(0, Floats.indexOf(new float[] {(float) -1}, (float) -1)); - assertEquals(0, Floats.indexOf(ARRAY234, (float) 2)); - assertEquals(1, Floats.indexOf(ARRAY234, (float) 3)); - assertEquals(2, Floats.indexOf(ARRAY234, (float) 4)); - assertEquals( - 1, Floats.indexOf(new float[] {(float) 2, (float) 3, (float) 2, (float) 3}, (float) 3)); + assertThat(Floats.indexOf(EMPTY, 1.0f)).isEqualTo(-1); + assertThat(Floats.indexOf(ARRAY1, 2.0f)).isEqualTo(-1); + assertThat(Floats.indexOf(ARRAY234, 1.0f)).isEqualTo(-1); + assertThat(Floats.indexOf(new float[] {-1.0f}, -1.0f)).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, 2.0f)).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, 3.0f)).isEqualTo(1); + assertThat(Floats.indexOf(ARRAY234, 4.0f)).isEqualTo(2); + assertThat(Floats.indexOf(new float[] {2.0f, 3.0f, 2.0f, 3.0f}, 3.0f)).isEqualTo(1); for (float value : NUMBERS) { - assertEquals("" + value, 1, Floats.indexOf(new float[] {5f, value}, value)); + assertWithMessage("" + value) + .that(Floats.indexOf(new float[] {5f, value}, value)) + .isEqualTo(1); } - assertEquals(-1, Floats.indexOf(new float[] {5f, NaN}, NaN)); + assertThat(Floats.indexOf(new float[] {5f, NaN}, NaN)).isEqualTo(-1); } public void testIndexOf_arrayTarget() { - assertEquals(0, Floats.indexOf(EMPTY, EMPTY)); - assertEquals(0, Floats.indexOf(ARRAY234, EMPTY)); - assertEquals(-1, Floats.indexOf(EMPTY, ARRAY234)); - assertEquals(-1, Floats.indexOf(ARRAY234, ARRAY1)); - assertEquals(-1, Floats.indexOf(ARRAY1, ARRAY234)); - assertEquals(0, Floats.indexOf(ARRAY1, ARRAY1)); - assertEquals(0, Floats.indexOf(ARRAY234, ARRAY234)); - assertEquals(0, Floats.indexOf(ARRAY234, new float[] {(float) 2, (float) 3})); - assertEquals(1, Floats.indexOf(ARRAY234, new float[] {(float) 3, (float) 4})); - assertEquals(1, Floats.indexOf(ARRAY234, new float[] {(float) 3})); - assertEquals(2, Floats.indexOf(ARRAY234, new float[] {(float) 4})); - assertEquals( - 1, - Floats.indexOf( - new float[] {(float) 2, (float) 3, (float) 3, (float) 3, (float) 3}, - new float[] {(float) 3})); - assertEquals( - 2, - Floats.indexOf( - new float[] { - (float) 2, (float) 3, (float) 2, (float) 3, (float) 4, (float) 2, (float) 3 - }, - new float[] {(float) 2, (float) 3, (float) 4})); - assertEquals( - 1, - Floats.indexOf( - new float[] { - (float) 2, (float) 2, (float) 3, (float) 4, (float) 2, (float) 3, (float) 4 - }, - new float[] {(float) 2, (float) 3, (float) 4})); - assertEquals( - -1, - Floats.indexOf( - new float[] {(float) 4, (float) 3, (float) 2}, - new float[] {(float) 2, (float) 3, (float) 4})); + assertThat(Floats.indexOf(EMPTY, EMPTY)).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, EMPTY)).isEqualTo(0); + assertThat(Floats.indexOf(EMPTY, ARRAY234)).isEqualTo(-1); + assertThat(Floats.indexOf(ARRAY234, ARRAY1)).isEqualTo(-1); + assertThat(Floats.indexOf(ARRAY1, ARRAY234)).isEqualTo(-1); + assertThat(Floats.indexOf(ARRAY1, ARRAY1)).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, ARRAY234)).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, new float[] {2.0f, 3.0f})).isEqualTo(0); + assertThat(Floats.indexOf(ARRAY234, new float[] {3.0f, 4.0f})).isEqualTo(1); + assertThat(Floats.indexOf(ARRAY234, new float[] {3.0f})).isEqualTo(1); + assertThat(Floats.indexOf(ARRAY234, new float[] {4.0f})).isEqualTo(2); + assertThat(Floats.indexOf(new float[] {2.0f, 3.0f, 3.0f, 3.0f, 3.0f}, new float[] {3.0f})) + .isEqualTo(1); + assertThat( + Floats.indexOf( + new float[] {2.0f, 3.0f, 2.0f, 3.0f, 4.0f, 2.0f, 3.0f}, + new float[] {2.0f, 3.0f, 4.0f})) + .isEqualTo(2); + assertThat( + Floats.indexOf( + new float[] {2.0f, 2.0f, 3.0f, 4.0f, 2.0f, 3.0f, 4.0f}, + new float[] {2.0f, 3.0f, 4.0f})) + .isEqualTo(1); + assertThat(Floats.indexOf(new float[] {4.0f, 3.0f, 2.0f}, new float[] {2.0f, 3.0f, 4.0f})) + .isEqualTo(-1); for (float value : NUMBERS) { - assertEquals( - "" + value, - 1, - Floats.indexOf(new float[] {5f, value, value, 5f}, new float[] {value, value})); + assertWithMessage("" + value) + .that(Floats.indexOf(new float[] {5f, value, value, 5f}, new float[] {value, value})) + .isEqualTo(1); } - assertEquals(-1, Floats.indexOf(new float[] {5f, NaN, NaN, 5f}, new float[] {NaN, NaN})); + assertThat(Floats.indexOf(new float[] {5f, NaN, NaN, 5f}, new float[] {NaN, NaN})) + .isEqualTo(-1); } public void testLastIndexOf() { - assertEquals(-1, Floats.lastIndexOf(EMPTY, (float) 1)); - assertEquals(-1, Floats.lastIndexOf(ARRAY1, (float) 2)); - assertEquals(-1, Floats.lastIndexOf(ARRAY234, (float) 1)); - assertEquals(0, Floats.lastIndexOf(new float[] {(float) -1}, (float) -1)); - assertEquals(0, Floats.lastIndexOf(ARRAY234, (float) 2)); - assertEquals(1, Floats.lastIndexOf(ARRAY234, (float) 3)); - assertEquals(2, Floats.lastIndexOf(ARRAY234, (float) 4)); - assertEquals( - 3, Floats.lastIndexOf(new float[] {(float) 2, (float) 3, (float) 2, (float) 3}, (float) 3)); + assertThat(Floats.lastIndexOf(EMPTY, 1.0f)).isEqualTo(-1); + assertThat(Floats.lastIndexOf(ARRAY1, 2.0f)).isEqualTo(-1); + assertThat(Floats.lastIndexOf(ARRAY234, 1.0f)).isEqualTo(-1); + assertThat(Floats.lastIndexOf(new float[] {-1.0f}, -1.0f)).isEqualTo(0); + assertThat(Floats.lastIndexOf(ARRAY234, 2.0f)).isEqualTo(0); + assertThat(Floats.lastIndexOf(ARRAY234, 3.0f)).isEqualTo(1); + assertThat(Floats.lastIndexOf(ARRAY234, 4.0f)).isEqualTo(2); + assertThat(Floats.lastIndexOf(new float[] {2.0f, 3.0f, 2.0f, 3.0f}, 3.0f)).isEqualTo(3); for (float value : NUMBERS) { - assertEquals("" + value, 0, Floats.lastIndexOf(new float[] {value, 5f}, value)); + assertWithMessage("" + value) + .that(Floats.lastIndexOf(new float[] {value, 5f}, value)) + .isEqualTo(0); } - assertEquals(-1, Floats.lastIndexOf(new float[] {NaN, 5f}, NaN)); + assertThat(Floats.lastIndexOf(new float[] {NaN, 5f}, NaN)).isEqualTo(-1); } @GwtIncompatible public void testMax_noArgs() { - try { - Floats.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(GREATEST, Floats.max(GREATEST)); - assertEquals(LEAST, Floats.max(LEAST)); - assertEquals( - (float) 9, - Floats.max((float) 8, (float) 6, (float) 7, (float) 5, (float) 3, (float) 0, (float) 9)); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(8.0f, 6.0f, 7.0f, 5.0f, 3.0f, 0.0f, 9.0f)).isEqualTo(9.0f); - assertEquals(0f, Floats.max(-0f, 0f)); - assertEquals(0f, Floats.max(0f, -0f)); - assertEquals(GREATEST, Floats.max(NUMBERS)); - assertTrue(Float.isNaN(Floats.max(VALUES))); + assertThat(max(-0f, 0f)).isEqualTo(0f); + assertThat(max(0f, -0f)).isEqualTo(0f); + assertThat(max(NUMBERS)).isEqualTo(GREATEST); + assertThat(Float.isNaN(max(VALUES))).isTrue(); } @GwtIncompatible public void testMin_noArgs() { - try { - Floats.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, Floats.min(LEAST)); - assertEquals(GREATEST, Floats.min(GREATEST)); - assertEquals( - (float) 0, - Floats.min((float) 8, (float) 6, (float) 7, (float) 5, (float) 3, (float) 0, (float) 9)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat(min(8.0f, 6.0f, 7.0f, 5.0f, 3.0f, 0.0f, 9.0f)).isEqualTo(0.0f); - assertEquals(-0f, Floats.min(-0f, 0f)); - assertEquals(-0f, Floats.min(0f, -0f)); - assertEquals(LEAST, Floats.min(NUMBERS)); - assertTrue(Float.isNaN(Floats.min(VALUES))); + assertThat(min(-0f, 0f)).isEqualTo(-0f); + assertThat(min(0f, -0f)).isEqualTo(-0f); + assertThat(min(NUMBERS)).isEqualTo(LEAST); + assertThat(Float.isNaN(min(VALUES))).isTrue(); } public void testConstrainToRange() { - float tolerance = 1e-10f; - assertEquals((float) 1, Floats.constrainToRange((float) 1, (float) 0, (float) 5), tolerance); - assertEquals((float) 1, Floats.constrainToRange((float) 1, (float) 1, (float) 5), tolerance); - assertEquals((float) 3, Floats.constrainToRange((float) 1, (float) 3, (float) 5), tolerance); - assertEquals((float) -1, Floats.constrainToRange((float) 0, (float) -5, (float) -1), tolerance); - assertEquals((float) 2, Floats.constrainToRange((float) 5, (float) 2, (float) 2), tolerance); - try { - Floats.constrainToRange((float) 1, (float) 3, (float) 2); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThat(Floats.constrainToRange(1.0f, 0.0f, 5.0f)).isEqualTo(1.0f); + assertThat(Floats.constrainToRange(1.0f, 1.0f, 5.0f)).isEqualTo(1.0f); + assertThat(Floats.constrainToRange(1.0f, 3.0f, 5.0f)).isEqualTo(3.0f); + assertThat(Floats.constrainToRange(0.0f, -5.0f, -1.0f)).isEqualTo(-1.0f); + assertThat(Floats.constrainToRange(5.0f, 2.0f, 2.0f)).isEqualTo(2.0f); + assertThrows(IllegalArgumentException.class, () -> Floats.constrainToRange(1.0f, 3.0f, 2.0f)); } public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Floats.concat())); - assertTrue(Arrays.equals(EMPTY, Floats.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Floats.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY1, Floats.concat(ARRAY1))); - assertNotSame(ARRAY1, Floats.concat(ARRAY1)); - assertTrue(Arrays.equals(ARRAY1, Floats.concat(EMPTY, ARRAY1, EMPTY))); - assertTrue( - Arrays.equals( - new float[] {(float) 1, (float) 1, (float) 1}, Floats.concat(ARRAY1, ARRAY1, ARRAY1))); - assertTrue( - Arrays.equals( - new float[] {(float) 1, (float) 2, (float) 3, (float) 4}, - Floats.concat(ARRAY1, ARRAY234))); + assertThat(Floats.concat()).isEqualTo(EMPTY); + assertThat(Floats.concat(EMPTY)).isEqualTo(EMPTY); + assertThat(Floats.concat(EMPTY, EMPTY, EMPTY)).isEqualTo(EMPTY); + assertThat(Floats.concat(ARRAY1)).isEqualTo(ARRAY1); + assertThat(Floats.concat(ARRAY1)).isNotSameInstanceAs(ARRAY1); + assertThat(Floats.concat(EMPTY, ARRAY1, EMPTY)).isEqualTo(ARRAY1); + assertThat(Floats.concat(ARRAY1, ARRAY1, ARRAY1)).isEqualTo(new float[] {1.0f, 1.0f, 1.0f}); + assertThat(Floats.concat(ARRAY1, ARRAY234)).isEqualTo(new float[] {1.0f, 2.0f, 3.0f, 4.0f}); } - public void testEnsureCapacity() { - assertSame(EMPTY, Floats.ensureCapacity(EMPTY, 0, 1)); - assertSame(ARRAY1, Floats.ensureCapacity(ARRAY1, 0, 1)); - assertSame(ARRAY1, Floats.ensureCapacity(ARRAY1, 1, 1)); - assertTrue( - Arrays.equals( - new float[] {(float) 1, (float) 0, (float) 0}, Floats.ensureCapacity(ARRAY1, 2, 1))); + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_negative() { + int dim1 = 1 << 16; + int dim2 = 1 << 15; + assertThat(dim1 * dim2).isLessThan(0); + testConcatOverflow(dim1, dim2); } - public void testEnsureCapacity_fail() { - try { - Floats.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_nonNegative() { + int dim1 = 1 << 16; + int dim2 = 1 << 16; + assertThat(dim1 * dim2).isAtLeast(0); + testConcatOverflow(dim1, dim2); + } + + private static void testConcatOverflow(int arraysDim1, int arraysDim2) { + assertThat((long) arraysDim1 * arraysDim2).isNotEqualTo((long) (arraysDim1 * arraysDim2)); + + float[][] arrays = new float[arraysDim1][]; + // it's shared to avoid using too much memory in tests + float[] sharedArray = new float[arraysDim2]; + Arrays.fill(arrays, sharedArray); + try { - // notice that this should even fail when no growth was needed - Floats.ensureCapacity(ARRAY1, 1, -1); + Floats.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } + public void testEnsureCapacity() { + assertThat(Floats.ensureCapacity(EMPTY, 0, 1)).isSameInstanceAs(EMPTY); + assertThat(Floats.ensureCapacity(ARRAY1, 0, 1)).isSameInstanceAs(ARRAY1); + assertThat(Floats.ensureCapacity(ARRAY1, 1, 1)).isSameInstanceAs(ARRAY1); + assertThat(Arrays.equals(new float[] {1.0f, 0.0f, 0.0f}, Floats.ensureCapacity(ARRAY1, 2, 1))) + .isTrue(); + } + + public void testEnsureCapacity_fail() { + assertThrows(IllegalArgumentException.class, () -> Floats.ensureCapacity(ARRAY1, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> Floats.ensureCapacity(ARRAY1, 1, -1)); + } + @GwtIncompatible // Float.toString returns different value in GWT. public void testJoin() { - assertEquals("", Floats.join(",", EMPTY)); - assertEquals("1.0", Floats.join(",", ARRAY1)); - assertEquals("1.0,2.0", Floats.join(",", (float) 1, (float) 2)); - assertEquals("1.02.03.0", Floats.join("", (float) 1, (float) 2, (float) 3)); + assertThat(Floats.join(",", EMPTY)).isEmpty(); + assertThat(Floats.join(",", ARRAY1)).isEqualTo("1.0"); + assertThat(Floats.join(",", 1.0f, 2.0f)).isEqualTo("1.0,2.0"); + assertThat(Floats.join("", 1.0f, 2.0f, 3.0f)).isEqualTo("1.02.03.0"); } public void testLexicographicalComparator() { @@ -297,9 +300,9 @@ public void testLexicographicalComparator() { new float[] {}, new float[] {LEAST}, new float[] {LEAST, LEAST}, - new float[] {LEAST, (float) 1}, - new float[] {(float) 1}, - new float[] {(float) 1, LEAST}, + new float[] {LEAST, 1.0f}, + new float[] {1.0f}, + new float[] {1.0f, LEAST}, new float[] {GREATEST, Float.MAX_VALUE}, new float[] {GREATEST, GREATEST}, new float[] {GREATEST, GREATEST, GREATEST}); @@ -308,10 +311,11 @@ public void testLexicographicalComparator() { Helpers.testComparator(comparator, ordered); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testLexicographicalComparatorSerializable() { Comparator comparator = Floats.lexicographicalComparator(); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); } public void testReverse() { @@ -325,14 +329,14 @@ public void testReverse() { private static void testReverse(float[] input, float[] expectedOutput) { input = Arrays.copyOf(input, input.length); Floats.reverse(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testReverse( float[] input, int fromIndex, int toIndex, float[] expectedOutput) { input = Arrays.copyOf(input, input.length); Floats.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testReverseIndexed() { @@ -344,6 +348,103 @@ public void testReverseIndexed() { testReverse(new float[] {-1, 1, -2, 2}, 1, 3, new float[] {-1, -2, 1, 2}); } + private static void testRotate(float[] input, int distance, float[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Floats.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + float[] input, int distance, int fromIndex, int toIndex, float[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Floats.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new float[] {}, -1, new float[] {}); + testRotate(new float[] {}, 0, new float[] {}); + testRotate(new float[] {}, 1, new float[] {}); + + testRotate(new float[] {1}, -2, new float[] {1}); + testRotate(new float[] {1}, -1, new float[] {1}); + testRotate(new float[] {1}, 0, new float[] {1}); + testRotate(new float[] {1}, 1, new float[] {1}); + testRotate(new float[] {1}, 2, new float[] {1}); + + testRotate(new float[] {1, 2}, -3, new float[] {2, 1}); + testRotate(new float[] {1, 2}, -1, new float[] {2, 1}); + testRotate(new float[] {1, 2}, -2, new float[] {1, 2}); + testRotate(new float[] {1, 2}, 0, new float[] {1, 2}); + testRotate(new float[] {1, 2}, 1, new float[] {2, 1}); + testRotate(new float[] {1, 2}, 2, new float[] {1, 2}); + testRotate(new float[] {1, 2}, 3, new float[] {2, 1}); + + testRotate(new float[] {1, 2, 3}, -5, new float[] {3, 1, 2}); + testRotate(new float[] {1, 2, 3}, -4, new float[] {2, 3, 1}); + testRotate(new float[] {1, 2, 3}, -3, new float[] {1, 2, 3}); + testRotate(new float[] {1, 2, 3}, -2, new float[] {3, 1, 2}); + testRotate(new float[] {1, 2, 3}, -1, new float[] {2, 3, 1}); + testRotate(new float[] {1, 2, 3}, 0, new float[] {1, 2, 3}); + testRotate(new float[] {1, 2, 3}, 1, new float[] {3, 1, 2}); + testRotate(new float[] {1, 2, 3}, 2, new float[] {2, 3, 1}); + testRotate(new float[] {1, 2, 3}, 3, new float[] {1, 2, 3}); + testRotate(new float[] {1, 2, 3}, 4, new float[] {3, 1, 2}); + testRotate(new float[] {1, 2, 3}, 5, new float[] {2, 3, 1}); + + testRotate(new float[] {1, 2, 3, 4}, -9, new float[] {2, 3, 4, 1}); + testRotate(new float[] {1, 2, 3, 4}, -5, new float[] {2, 3, 4, 1}); + testRotate(new float[] {1, 2, 3, 4}, -1, new float[] {2, 3, 4, 1}); + testRotate(new float[] {1, 2, 3, 4}, 0, new float[] {1, 2, 3, 4}); + testRotate(new float[] {1, 2, 3, 4}, 1, new float[] {4, 1, 2, 3}); + testRotate(new float[] {1, 2, 3, 4}, 5, new float[] {4, 1, 2, 3}); + testRotate(new float[] {1, 2, 3, 4}, 9, new float[] {4, 1, 2, 3}); + + testRotate(new float[] {1, 2, 3, 4, 5}, -6, new float[] {2, 3, 4, 5, 1}); + testRotate(new float[] {1, 2, 3, 4, 5}, -4, new float[] {5, 1, 2, 3, 4}); + testRotate(new float[] {1, 2, 3, 4, 5}, -3, new float[] {4, 5, 1, 2, 3}); + testRotate(new float[] {1, 2, 3, 4, 5}, -1, new float[] {2, 3, 4, 5, 1}); + testRotate(new float[] {1, 2, 3, 4, 5}, 0, new float[] {1, 2, 3, 4, 5}); + testRotate(new float[] {1, 2, 3, 4, 5}, 1, new float[] {5, 1, 2, 3, 4}); + testRotate(new float[] {1, 2, 3, 4, 5}, 3, new float[] {3, 4, 5, 1, 2}); + testRotate(new float[] {1, 2, 3, 4, 5}, 4, new float[] {2, 3, 4, 5, 1}); + testRotate(new float[] {1, 2, 3, 4, 5}, 6, new float[] {5, 1, 2, 3, 4}); + } + + public void testRotateIndexed() { + testRotate(new float[] {}, 0, 0, 0, new float[] {}); + + testRotate(new float[] {1}, 0, 0, 1, new float[] {1}); + testRotate(new float[] {1}, 1, 0, 1, new float[] {1}); + testRotate(new float[] {1}, 1, 1, 1, new float[] {1}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -6, 1, 6, new float[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -1, 1, 6, new float[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 0, 1, 6, new float[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 5, 1, 6, new float[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 14, 1, 6, new float[] {0, 2, 3, 4, 5, 1, 6}); + + // Rotate the first three elements + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -2, 0, 3, new float[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -1, 0, 3, new float[] {1, 2, 0, 3, 4, 5, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 0, 0, 3, new float[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 1, 0, 3, new float[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 2, 0, 3, new float[] {1, 2, 0, 3, 4, 5, 6}); + + // Rotate the last four elements + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -6, 3, 7, new float[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -5, 3, 7, new float[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -4, 3, 7, new float[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -3, 3, 7, new float[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -2, 3, 7, new float[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, -1, 3, 7, new float[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 0, 3, 7, new float[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 1, 3, 7, new float[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 2, 3, 7, new float[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new float[] {0, 1, 2, 3, 4, 5, 6}, 3, 3, 7, new float[] {0, 1, 2, 4, 5, 6, 3}); + } + public void testSortDescending() { testSortDescending(new float[] {}, new float[] {}); testSortDescending(new float[] {1}, new float[] {1}); @@ -351,15 +452,15 @@ public void testSortDescending() { testSortDescending(new float[] {1, 3, 1}, new float[] {3, 1, 1}); testSortDescending(new float[] {-1, 1, -2, 2}, new float[] {2, 1, -1, -2}); testSortDescending( - new float[] {-1, 1, Float.NaN, -2, -0, 0, 2}, new float[] {Float.NaN, 2, 1, 0, -0, -1, -2}); + new float[] {-1, 1, Float.NaN, -2, -0f, 0, 2}, + new float[] {Float.NaN, 2, 1, 0, -0f, -1, -2}); } private static void testSortDescending(float[] input, float[] expectedOutput) { input = Arrays.copyOf(input, input.length); Floats.sortDescending(input); - // GWT's Arrays.equals doesn't appear to handle NaN correctly, so test each element individually for (int i = 0; i < input.length; i++) { - assertEquals(0, Float.compare(expectedOutput[i], input[i])); + assertThat(input[i]).isEqualTo(expectedOutput[i]); } } @@ -367,9 +468,8 @@ private static void testSortDescending( float[] input, int fromIndex, int toIndex, float[] expectedOutput) { input = Arrays.copyOf(input, input.length); Floats.sortDescending(input, fromIndex, toIndex); - // GWT's Arrays.equals doesn't appear to handle NaN correctly, so test each element individually for (int i = 0; i < input.length; i++) { - assertEquals(0, Float.compare(expectedOutput[i], input[i])); + assertThat(input[i]).isEqualTo(expectedOutput[i]); } } @@ -384,6 +484,7 @@ public void testSortDescendingIndexed() { new float[] {-1, 1, Float.NaN, -2, 2}, 1, 4, new float[] {-1, Float.NaN, 1, -2, 2}); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testStringConverterSerialization() { SerializableTester.reserializeAndAssert(Floats.stringConverter()); @@ -392,17 +493,17 @@ public void testStringConverterSerialization() { public void testToArray() { // need explicit type parameter to avoid javac warning!? List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Floats.toArray(none))); + assertThat(Floats.toArray(none)).isEqualTo(EMPTY); - List one = Arrays.asList((float) 1); - assertTrue(Arrays.equals(ARRAY1, Floats.toArray(one))); + List one = Arrays.asList(1.0f); + assertThat(Floats.toArray(one)).isEqualTo(ARRAY1); - float[] array = {(float) 0, (float) 1, (float) 3}; + float[] array = {0.0f, 1.0f, 3.0f}; - List three = Arrays.asList((float) 0, (float) 1, (float) 3); - assertTrue(Arrays.equals(array, Floats.toArray(three))); + List three = Arrays.asList(0.0f, 1.0f, 3.0f); + assertThat(Floats.toArray(three)).isEqualTo(array); - assertTrue(Arrays.equals(array, Floats.toArray(Floats.asList(array)))); + assertThat(Floats.toArray(Floats.asList(array))).isEqualTo(array); } public void testToArray_threadSafe() { @@ -412,80 +513,78 @@ public void testToArray_threadSafe() { Collection misleadingSize = Helpers.misleadingSizeCollection(delta); misleadingSize.addAll(list); float[] arr = Floats.toArray(misleadingSize); - assertEquals(i, arr.length); + assertThat(arr.length).isEqualTo(i); for (int j = 0; j < i; j++) { - assertEquals(VALUES[j], arr[j]); + assertThat(arr[j]).isEqualTo(VALUES[j]); } } } } public void testToArray_withNull() { - List list = Arrays.asList((float) 0, (float) 1, null); - try { - Floats.toArray(list); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Float> list = Arrays.asList(0.0f, 1.0f, null); + assertThrows(NullPointerException.class, () -> Floats.toArray(list)); } public void testToArray_withConversion() { - float[] array = {(float) 0, (float) 1, (float) 2}; + float[] array = {0.0f, 1.0f, 2.0f}; List bytes = Arrays.asList((byte) 0, (byte) 1, (byte) 2); List shorts = Arrays.asList((short) 0, (short) 1, (short) 2); List ints = Arrays.asList(0, 1, 2); - List floats = Arrays.asList((float) 0, (float) 1, (float) 2); - List longs = Arrays.asList((long) 0, (long) 1, (long) 2); - List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); + List floats = Arrays.asList(0.0f, 1.0f, 2.0f); + List longs = Arrays.asList(0L, 1L, 2L); + List doubles = Arrays.asList(0.0, 1.0, 2.0); - assertTrue(Arrays.equals(array, Floats.toArray(bytes))); - assertTrue(Arrays.equals(array, Floats.toArray(shorts))); - assertTrue(Arrays.equals(array, Floats.toArray(ints))); - assertTrue(Arrays.equals(array, Floats.toArray(floats))); - assertTrue(Arrays.equals(array, Floats.toArray(longs))); - assertTrue(Arrays.equals(array, Floats.toArray(doubles))); + assertThat(Floats.toArray(bytes)).isEqualTo(array); + assertThat(Floats.toArray(shorts)).isEqualTo(array); + assertThat(Floats.toArray(ints)).isEqualTo(array); + assertThat(Floats.toArray(floats)).isEqualTo(array); + assertThat(Floats.toArray(longs)).isEqualTo(array); + assertThat(Floats.toArray(doubles)).isEqualTo(array); } + @J2ktIncompatible // b/239034072: Kotlin varargs copy parameter arrays. public void testAsList_isAView() { - float[] array = {(float) 0, (float) 1}; + float[] array = {0.0f, 1.0f}; List list = Floats.asList(array); - list.set(0, (float) 2); - assertTrue(Arrays.equals(new float[] {(float) 2, (float) 1}, array)); - array[1] = (float) 3; - assertThat(list).containsExactly((float) 2, (float) 3).inOrder(); + list.set(0, 2.0f); + assertThat(array).isEqualTo(new float[] {2.0f, 1.0f}); + array[1] = 3.0f; + assertThat(list).containsExactly(2.0f, 3.0f).inOrder(); } public void testAsList_toArray_roundTrip() { - float[] array = {(float) 0, (float) 1, (float) 2}; + float[] array = {0.0f, 1.0f, 2.0f}; List list = Floats.asList(array); float[] newArray = Floats.toArray(list); // Make sure it returned a copy - list.set(0, (float) 4); - assertTrue(Arrays.equals(new float[] {(float) 0, (float) 1, (float) 2}, newArray)); - newArray[1] = (float) 5; - assertEquals((float) 1, (float) list.get(1)); + list.set(0, 4.0f); + assertThat(newArray).isEqualTo(new float[] {0.0f, 1.0f, 2.0f}); + newArray[1] = 5.0f; + assertThat((float) list.get(1)).isEqualTo(1.0f); } // This test stems from a real bug found by andrewk public void testAsList_subList_toArray_roundTrip() { - float[] array = {(float) 0, (float) 1, (float) 2, (float) 3}; + float[] array = {0.0f, 1.0f, 2.0f, 3.0f}; List list = Floats.asList(array); - assertTrue( - Arrays.equals(new float[] {(float) 1, (float) 2}, Floats.toArray(list.subList(1, 3)))); - assertTrue(Arrays.equals(new float[] {}, Floats.toArray(list.subList(2, 2)))); + assertThat(Floats.toArray(list.subList(1, 3))).isEqualTo(new float[] {1.0f, 2.0f}); + assertThat(Floats.toArray(list.subList(2, 2))).isEmpty(); } + // `primitives` can't depend on `collect`, so this is what the prod code has to return. + @SuppressWarnings("EmptyList") public void testAsListEmpty() { - assertSame(Collections.emptyList(), Floats.asList(EMPTY)); + assertThat(Floats.asList(EMPTY)).isSameInstanceAs(Collections.emptyList()); } /** * A reference implementation for {@code tryParse} that just catches the exception from {@link * Float#valueOf}. */ - private static Float referenceTryParse(String input) { + private static @Nullable Float referenceTryParse(String input) { if (input.trim().length() < input.length()) { return null; } @@ -498,12 +597,12 @@ private static Float referenceTryParse(String input) { @GwtIncompatible // Floats.tryParse private static void checkTryParse(String input) { - assertEquals(referenceTryParse(input), Floats.tryParse(input)); + assertThat(Floats.tryParse(input)).isEqualTo(referenceTryParse(input)); } @GwtIncompatible // Floats.tryParse private static void checkTryParse(float expected, String input) { - assertEquals(Float.valueOf(expected), Floats.tryParse(input)); + assertThat(Floats.tryParse(input)).isEqualTo(Float.valueOf(expected)); } @GwtIncompatible // Floats.tryParse @@ -544,6 +643,7 @@ public void testTryParseOfToStringIsOriginal() { } } + @J2ktIncompatible // hexadecimal floats @GwtIncompatible // Floats.tryParse public void testTryParseOfToHexStringIsOriginal() { for (float f : NUMBERS) { @@ -585,11 +685,12 @@ public void testTryParseInfinity() { @GwtIncompatible // Floats.tryParse public void testTryParseFailures() { for (String badInput : BAD_TRY_PARSE_INPUTS) { - assertEquals(referenceTryParse(badInput), Floats.tryParse(badInput)); - assertNull(Floats.tryParse(badInput)); + assertThat(Floats.tryParse(badInput)).isEqualTo(referenceTryParse(badInput)); + assertThat(Floats.tryParse(badInput)).isNull(); } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Floats.class); @@ -598,39 +699,37 @@ public void testNulls() { @GwtIncompatible // Float.toString returns different value in GWT. public void testStringConverter_convert() { Converter converter = Floats.stringConverter(); - assertEquals((Float) 1.0f, converter.convert("1.0")); - assertEquals((Float) 0.0f, converter.convert("0.0")); - assertEquals((Float) (-1.0f), converter.convert("-1.0")); - assertEquals((Float) 1.0f, converter.convert("1")); - assertEquals((Float) 0.0f, converter.convert("0")); - assertEquals((Float) (-1.0f), converter.convert("-1")); - assertEquals((Float) 1e6f, converter.convert("1e6")); - assertEquals((Float) 1e-6f, converter.convert("1e-6")); + assertThat(converter.convert("1.0")).isEqualTo(1.0f); + assertThat(converter.convert("0.0")).isEqualTo(0.0f); + assertThat(converter.convert("-1.0")).isEqualTo(-1.0f); + assertThat(converter.convert("1")).isEqualTo(1.0f); + assertThat(converter.convert("0")).isEqualTo(0.0f); + assertThat(converter.convert("-1")).isEqualTo(-1.0f); + assertThat(converter.convert("1e6")).isEqualTo(1e6f); + assertThat(converter.convert("1e-6")).isEqualTo(1e-6f); } public void testStringConverter_convertError() { - try { - Floats.stringConverter().convert("notanumber"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> Floats.stringConverter().convert("notanumber")); } public void testStringConverter_nullConversions() { - assertNull(Floats.stringConverter().convert(null)); - assertNull(Floats.stringConverter().reverse().convert(null)); + assertThat(Floats.stringConverter().convert(null)).isNull(); + assertThat(Floats.stringConverter().reverse().convert(null)).isNull(); } + @J2ktIncompatible @GwtIncompatible // Float.toString returns different value in GWT. public void testStringConverter_reverse() { Converter converter = Floats.stringConverter(); - assertEquals("1.0", converter.reverse().convert(1.0f)); - assertEquals("0.0", converter.reverse().convert(0.0f)); - assertEquals("-1.0", converter.reverse().convert(-1.0f)); - assertEquals("1000000.0", converter.reverse().convert(1e6f)); - assertEquals("1.0E-6", converter.reverse().convert(1e-6f)); + assertThat(converter.reverse().convert(1.0f)).isEqualTo("1.0"); + assertThat(converter.reverse().convert(0.0f)).isEqualTo("0.0"); + assertThat(converter.reverse().convert(-1.0f)).isEqualTo("-1.0"); + assertThat(converter.reverse().convert(1e6f)).isEqualTo("1000000.0"); + assertThat(converter.reverse().convert(1e-6f)).isEqualTo("1.0E-6"); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testStringConverter_nullPointerTester() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -639,11 +738,7 @@ public void testStringConverter_nullPointerTester() throws Exception { @GwtIncompatible public void testTryParse_withNullNoGwt() { - assertNull(Floats.tryParse("null")); - try { - Floats.tryParse(null); - fail("Expected NPE"); - } catch (NullPointerException expected) { - } + assertThat(Floats.tryParse("null")).isNull(); + assertThrows(NullPointerException.class, () -> Floats.tryParse(null)); } } diff --git a/android/guava-tests/test/com/google/common/primitives/ImmutableDoubleArrayTest.java b/android/guava-tests/test/com/google/common/primitives/ImmutableDoubleArrayTest.java index 4d988365fadd..b165d68b8e92 100644 --- a/android/guava-tests/test/com/google/common/primitives/ImmutableDoubleArrayTest.java +++ b/android/guava-tests/test/com/google/common/primitives/ImmutableDoubleArrayTest.java @@ -14,12 +14,15 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.primitives.TestPlatform.reduceIterationsIfGwt; import static com.google.common.testing.SerializableTester.reserialize; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.stream; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.ObjectArrays; import com.google.common.collect.testing.ListTestSuiteBuilder; @@ -36,12 +39,17 @@ import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.DoubleStream; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; -/** @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +/** + * @author Kevin Bourrillion + */ +@GwtCompatible +@NullUnmarked public class ImmutableDoubleArrayTest extends TestCase { // Test all creation paths very lazily: by assuming asList() works @@ -137,6 +145,14 @@ public void testCopyOf_collection_nonempty() { assertThat(iia.asList()).containsExactly(0.0, 1.0, 3.0).inOrder(); } + public void testCopyOf_stream() { + assertThat(ImmutableDoubleArray.copyOf(DoubleStream.empty())) + .isSameInstanceAs(ImmutableDoubleArray.of()); + assertThat(ImmutableDoubleArray.copyOf(DoubleStream.of(0, 1, 3)).asList()) + .containsExactly(0.0, 1.0, 3.0) + .inOrder(); + } + public void testBuilder_presize_zero() { ImmutableDoubleArray.Builder builder = ImmutableDoubleArray.builder(0); builder.add(5.0); @@ -145,11 +161,7 @@ public void testBuilder_presize_zero() { } public void testBuilder_presize_negative() { - try { - ImmutableDoubleArray.builder(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> ImmutableDoubleArray.builder(-1)); } /** @@ -158,7 +170,7 @@ public void testBuilder_presize_negative() { */ public void testBuilder_bruteForce() { for (int i = 0; i < reduceIterationsIfGwt(100); i++) { - ImmutableDoubleArray.Builder builder = ImmutableDoubleArray.builder(RANDOM.nextInt(20)); + ImmutableDoubleArray.Builder builder = ImmutableDoubleArray.builder(random.nextInt(20)); AtomicInteger counter = new AtomicInteger(0); while (counter.get() < 1000) { BuilderOp op = BuilderOp.randomOp(); @@ -181,7 +193,7 @@ void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { ADD_ARRAY { @Override void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { - double[] array = new double[RANDOM.nextInt(10)]; + double[] array = new double[random.nextInt(10)]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -192,7 +204,7 @@ void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { @Override void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { List list = new ArrayList<>(); - double num = RANDOM.nextInt(10); + double num = random.nextInt(10); for (int i = 0; i < num; i++) { list.add((double) counter.getAndIncrement()); } @@ -203,17 +215,27 @@ void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { @Override void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { List list = new ArrayList<>(); - double num = RANDOM.nextInt(10); + double num = random.nextInt(10); for (int i = 0; i < num; i++) { list.add((double) counter.getAndIncrement()); } builder.addAll(iterable(list)); } }, + ADD_STREAM { + @Override + void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { + double[] array = new double[random.nextInt(10)]; + for (int i = 0; i < array.length; i++) { + array[i] = counter.getAndIncrement(); + } + builder.addAll(stream(array)); + } + }, ADD_IIA { @Override void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { - double[] array = new double[RANDOM.nextInt(10)]; + double[] array = new double[random.nextInt(10)]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -223,7 +245,7 @@ void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { ADD_LARGER_ARRAY { @Override void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { - double[] array = new double[RANDOM.nextInt(200) + 200]; + double[] array = new double[random.nextInt(200) + 200]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -235,13 +257,13 @@ void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter) { static final BuilderOp[] values = values(); static BuilderOp randomOp() { - return values[RANDOM.nextInt(values.length)]; + return values[random.nextInt(values.length)]; } abstract void doIt(ImmutableDoubleArray.Builder builder, AtomicInteger counter); } - private static final Random RANDOM = new Random(42); + private static final Random random = new Random(42); public void testLength() { assertThat(ImmutableDoubleArray.of().length()).isEqualTo(0); @@ -268,23 +290,11 @@ public void testGet_good() { public void testGet_bad() { ImmutableDoubleArray iia = ImmutableDoubleArray.of(0, 1, 3); - try { - iia.get(-1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - iia.get(3); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> iia.get(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> iia.get(3)); - iia = iia.subArray(1, 2); - try { - iia.get(-1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + ImmutableDoubleArray sub = iia.subArray(1, 2); + assertThrows(IndexOutOfBoundsException.class, () -> sub.get(-1)); } public void testIndexOf() { @@ -327,6 +337,23 @@ public void testContains() { assertThat(iia.subArray(1, 5).contains(1)).isTrue(); } + public void testForEach() { + ImmutableDoubleArray.of().forEach(i -> fail()); + ImmutableDoubleArray.of(0, 1, 3).subArray(1, 1).forEach(i -> fail()); + + AtomicInteger count = new AtomicInteger(0); + ImmutableDoubleArray.of(0, 1, 2, 3) + .forEach(i -> assertThat(i).isEqualTo((double) count.getAndIncrement())); + assertThat(count.get()).isEqualTo(4); + } + + public void testStream() { + ImmutableDoubleArray.of().stream().forEach(i -> fail()); + ImmutableDoubleArray.of(0, 1, 3).subArray(1, 1).stream().forEach(i -> fail()); + assertThat(ImmutableDoubleArray.of(0, 1, 3).stream().toArray()) + .isEqualTo(new double[] {0, 1, 3}); + } + public void testSubArray() { ImmutableDoubleArray iia0 = ImmutableDoubleArray.of(); ImmutableDoubleArray iia1 = ImmutableDoubleArray.of(5); @@ -339,16 +366,8 @@ public void testSubArray() { assertThat(iia3.subArray(0, 2).asList()).containsExactly(5.0, 25.0).inOrder(); assertThat(iia3.subArray(1, 3).asList()).containsExactly(25.0, 125.0).inOrder(); - try { - iia3.subArray(-1, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - iia3.subArray(1, 4); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> iia3.subArray(-1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> iia3.subArray(1, 4)); } /* @@ -356,7 +375,7 @@ public void testSubArray() { * (so much for "black box") and try instances that both do and don't pass the check. The "don't" * half of that is more awkward to arrange... */ - private static Iterable iterable(final Collection collection) { + private static Iterable iterable(Collection collection) { // return collection::iterator; return new Iterable() { @Override @@ -399,6 +418,7 @@ public void testTrimmed() { assertActuallyTrims(underSized); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { assertThat(reserialize(ImmutableDoubleArray.of())).isSameInstanceAs(ImmutableDoubleArray.of()); @@ -423,7 +443,9 @@ private static void assertDoesntActuallyTrim(ImmutableDoubleArray iia) { assertThat(iia.trimmed()).isSameInstanceAs(iia); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { List> builders = ImmutableList.of( @@ -454,7 +476,9 @@ public static Test suite() { return suite; } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible private static ImmutableDoubleArray makeArray(Double[] values) { return ImmutableDoubleArray.copyOf(Arrays.asList(values)); } @@ -462,7 +486,9 @@ private static ImmutableDoubleArray makeArray(Double[] values) { // Test generators. To let the GWT test suite generator access them, they need to be public named // classes with a public default constructor (not that we run these suites under GWT yet). + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static final class ImmutableDoubleArrayAsListGenerator extends TestDoubleListGenerator { @Override protected List create(Double[] elements) { @@ -470,7 +496,9 @@ protected List create(Double[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static final class ImmutableDoubleArrayHeadSubListAsListGenerator extends TestDoubleListGenerator { @Override @@ -481,7 +509,9 @@ protected List create(Double[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static final class ImmutableDoubleArrayTailSubListAsListGenerator extends TestDoubleListGenerator { @Override @@ -492,7 +522,9 @@ protected List create(Double[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static final class ImmutableDoubleArrayMiddleSubListAsListGenerator extends TestDoubleListGenerator { @Override @@ -504,12 +536,16 @@ protected List create(Double[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible private static Double[] concat(Double[] a, Double[] b) { return ObjectArrays.concat(a, b, Double.class); } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public abstract static class TestDoubleListGenerator implements TestListGenerator { @Override public SampleElements samples() { @@ -544,7 +580,9 @@ public List order(List insertionOrder) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static class SampleDoubles extends SampleElements { public SampleDoubles() { super(-0.0, Long.MAX_VALUE * 3.0, Double.MAX_VALUE, Double.POSITIVE_INFINITY, Double.NaN); diff --git a/android/guava-tests/test/com/google/common/primitives/ImmutableIntArrayTest.java b/android/guava-tests/test/com/google/common/primitives/ImmutableIntArrayTest.java index 86274d4531cf..d551c6717036 100644 --- a/android/guava-tests/test/com/google/common/primitives/ImmutableIntArrayTest.java +++ b/android/guava-tests/test/com/google/common/primitives/ImmutableIntArrayTest.java @@ -14,12 +14,15 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.primitives.TestPlatform.reduceIterationsIfGwt; import static com.google.common.testing.SerializableTester.reserialize; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.stream; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.ObjectArrays; import com.google.common.collect.testing.ListTestSuiteBuilder; @@ -36,12 +39,17 @@ import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; -/** @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +/** + * @author Kevin Bourrillion + */ +@GwtCompatible +@NullUnmarked public class ImmutableIntArrayTest extends TestCase { // Test all creation paths very lazily: by assuming asList() works @@ -134,6 +142,14 @@ public void testCopyOf_collection_nonempty() { assertThat(iia.asList()).containsExactly(0, 1, 3).inOrder(); } + public void testCopyOf_stream() { + assertThat(ImmutableIntArray.copyOf(IntStream.empty())) + .isSameInstanceAs(ImmutableIntArray.of()); + assertThat(ImmutableIntArray.copyOf(IntStream.of(0, 1, 3)).asList()) + .containsExactly(0, 1, 3) + .inOrder(); + } + public void testBuilder_presize_zero() { ImmutableIntArray.Builder builder = ImmutableIntArray.builder(0); builder.add(5); @@ -142,11 +158,7 @@ public void testBuilder_presize_zero() { } public void testBuilder_presize_negative() { - try { - ImmutableIntArray.builder(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> ImmutableIntArray.builder(-1)); } /** @@ -155,7 +167,7 @@ public void testBuilder_presize_negative() { */ public void testBuilder_bruteForce() { for (int i = 0; i < reduceIterationsIfGwt(100); i++) { - ImmutableIntArray.Builder builder = ImmutableIntArray.builder(RANDOM.nextInt(20)); + ImmutableIntArray.Builder builder = ImmutableIntArray.builder(random.nextInt(20)); AtomicInteger counter = new AtomicInteger(0); while (counter.get() < 1000) { BuilderOp op = BuilderOp.randomOp(); @@ -178,7 +190,7 @@ void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { ADD_ARRAY { @Override void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { - int[] array = new int[RANDOM.nextInt(10)]; + int[] array = new int[random.nextInt(10)]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -189,7 +201,7 @@ void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { @Override void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { List list = new ArrayList<>(); - int num = RANDOM.nextInt(10); + int num = random.nextInt(10); for (int i = 0; i < num; i++) { list.add(counter.getAndIncrement()); } @@ -200,17 +212,27 @@ void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { @Override void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { List list = new ArrayList<>(); - int num = RANDOM.nextInt(10); + int num = random.nextInt(10); for (int i = 0; i < num; i++) { list.add(counter.getAndIncrement()); } builder.addAll(iterable(list)); } }, + ADD_STREAM { + @Override + void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { + int[] array = new int[random.nextInt(10)]; + for (int i = 0; i < array.length; i++) { + array[i] = counter.getAndIncrement(); + } + builder.addAll(stream(array)); + } + }, ADD_IIA { @Override void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { - int[] array = new int[RANDOM.nextInt(10)]; + int[] array = new int[random.nextInt(10)]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -220,7 +242,7 @@ void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { ADD_LARGER_ARRAY { @Override void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { - int[] array = new int[RANDOM.nextInt(200) + 200]; + int[] array = new int[random.nextInt(200) + 200]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -232,13 +254,13 @@ void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter) { static final BuilderOp[] values = values(); static BuilderOp randomOp() { - return values[RANDOM.nextInt(values.length)]; + return values[random.nextInt(values.length)]; } abstract void doIt(ImmutableIntArray.Builder builder, AtomicInteger counter); } - private static final Random RANDOM = new Random(42); + private static final Random random = new Random(42); public void testLength() { assertThat(ImmutableIntArray.of().length()).isEqualTo(0); @@ -265,23 +287,11 @@ public void testGet_good() { public void testGet_bad() { ImmutableIntArray iia = ImmutableIntArray.of(0, 1, 3); - try { - iia.get(-1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - iia.get(3); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> iia.get(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> iia.get(3)); - iia = iia.subArray(1, 2); - try { - iia.get(-1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + ImmutableIntArray sub = iia.subArray(1, 2); + assertThrows(IndexOutOfBoundsException.class, () -> sub.get(-1)); } public void testIndexOf() { @@ -314,6 +324,21 @@ public void testContains() { assertThat(iia.subArray(1, 5).contains(1)).isTrue(); } + public void testForEach() { + ImmutableIntArray.of().forEach(i -> fail()); + ImmutableIntArray.of(0, 1, 3).subArray(1, 1).forEach(i -> fail()); + + AtomicInteger count = new AtomicInteger(0); + ImmutableIntArray.of(0, 1, 2, 3).forEach(i -> assertThat(i).isEqualTo(count.getAndIncrement())); + assertThat(count.get()).isEqualTo(4); + } + + public void testStream() { + ImmutableIntArray.of().stream().forEach(i -> fail()); + ImmutableIntArray.of(0, 1, 3).subArray(1, 1).stream().forEach(i -> fail()); + assertThat(ImmutableIntArray.of(0, 1, 3).stream().toArray()).isEqualTo(new int[] {0, 1, 3}); + } + public void testSubArray() { ImmutableIntArray iia0 = ImmutableIntArray.of(); ImmutableIntArray iia1 = ImmutableIntArray.of(5); @@ -326,16 +351,8 @@ public void testSubArray() { assertThat(iia3.subArray(0, 2).asList()).containsExactly(5, 25).inOrder(); assertThat(iia3.subArray(1, 3).asList()).containsExactly(25, 125).inOrder(); - try { - iia3.subArray(-1, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - iia3.subArray(1, 4); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> iia3.subArray(-1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> iia3.subArray(1, 4)); } /* @@ -343,7 +360,7 @@ public void testSubArray() { * (so much for "black box") and try instances that both do and don't pass the check. The "don't" * half of that is more awkward to arrange... */ - private static Iterable iterable(final Collection collection) { + private static Iterable iterable(Collection collection) { // return collection::iterator; return new Iterable() { @Override @@ -386,6 +403,7 @@ public void testTrimmed() { assertActuallyTrims(underSized); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { assertThat(reserialize(ImmutableIntArray.of())).isSameInstanceAs(ImmutableIntArray.of()); @@ -410,7 +428,9 @@ private static void assertDoesntActuallyTrim(ImmutableIntArray iia) { assertThat(iia.trimmed()).isSameInstanceAs(iia); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { List> builders = ImmutableList.of( @@ -441,6 +461,7 @@ public static Test suite() { return suite; } + @J2ktIncompatible @GwtIncompatible // used only from suite private static ImmutableIntArray makeArray(Integer[] values) { return ImmutableIntArray.copyOf(Arrays.asList(values)); @@ -449,6 +470,7 @@ private static ImmutableIntArray makeArray(Integer[] values) { // Test generators. To let the GWT test suite generator access them, they need to be public named // classes with a public default constructor (not that we run these suites under GWT yet). + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableIntArrayAsListGenerator extends TestIntegerListGenerator { @Override @@ -457,6 +479,7 @@ protected List create(Integer[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableIntArrayHeadSubListAsListGenerator extends TestIntegerListGenerator { @@ -468,6 +491,7 @@ protected List create(Integer[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableIntArrayTailSubListAsListGenerator extends TestIntegerListGenerator { @@ -479,6 +503,7 @@ protected List create(Integer[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static final class ImmutableIntArrayMiddleSubListAsListGenerator extends TestIntegerListGenerator { @@ -491,11 +516,13 @@ protected List create(Integer[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite private static Integer[] concat(Integer[] a, Integer[] b) { return ObjectArrays.concat(a, b, Integer.class); } + @J2ktIncompatible @GwtIncompatible // used only from suite public abstract static class TestIntegerListGenerator implements TestListGenerator { @Override @@ -531,6 +558,7 @@ public List order(List insertionOrder) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite public static class SampleIntegers extends SampleElements { public SampleIntegers() { diff --git a/android/guava-tests/test/com/google/common/primitives/ImmutableLongArrayTest.java b/android/guava-tests/test/com/google/common/primitives/ImmutableLongArrayTest.java index ff879ec11f09..13de0819773d 100644 --- a/android/guava-tests/test/com/google/common/primitives/ImmutableLongArrayTest.java +++ b/android/guava-tests/test/com/google/common/primitives/ImmutableLongArrayTest.java @@ -14,12 +14,15 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.primitives.TestPlatform.reduceIterationsIfGwt; import static com.google.common.testing.SerializableTester.reserialize; import static com.google.common.truth.Truth.assertThat; +import static java.util.Arrays.stream; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.ObjectArrays; import com.google.common.collect.testing.ListTestSuiteBuilder; @@ -36,12 +39,17 @@ import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.LongStream; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; -/** @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +/** + * @author Kevin Bourrillion + */ +@GwtCompatible +@NullUnmarked public class ImmutableLongArrayTest extends TestCase { // Test all creation paths very lazily: by assuming asList() works @@ -136,6 +144,14 @@ public void testCopyOf_collection_nonempty() { assertThat(iia.asList()).containsExactly(0L, 1L, 3L).inOrder(); } + public void testCopyOf_stream() { + assertThat(ImmutableLongArray.copyOf(LongStream.empty())) + .isSameInstanceAs(ImmutableLongArray.of()); + assertThat(ImmutableLongArray.copyOf(LongStream.of(0, 1, 3)).asList()) + .containsExactly(0L, 1L, 3L) + .inOrder(); + } + public void testBuilder_presize_zero() { ImmutableLongArray.Builder builder = ImmutableLongArray.builder(0); builder.add(5L); @@ -144,11 +160,7 @@ public void testBuilder_presize_zero() { } public void testBuilder_presize_negative() { - try { - ImmutableLongArray.builder(-1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> ImmutableLongArray.builder(-1)); } /** @@ -157,7 +169,7 @@ public void testBuilder_presize_negative() { */ public void testBuilder_bruteForce() { for (int i = 0; i < reduceIterationsIfGwt(100); i++) { - ImmutableLongArray.Builder builder = ImmutableLongArray.builder(RANDOM.nextInt(20)); + ImmutableLongArray.Builder builder = ImmutableLongArray.builder(random.nextInt(20)); AtomicLong counter = new AtomicLong(0); while (counter.get() < 1000) { BuilderOp op = BuilderOp.randomOp(); @@ -180,7 +192,7 @@ void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { ADD_ARRAY { @Override void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { - long[] array = new long[RANDOM.nextInt(10)]; + long[] array = new long[random.nextInt(10)]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -191,7 +203,7 @@ void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { @Override void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { List list = new ArrayList<>(); - long num = RANDOM.nextInt(10); + long num = random.nextInt(10); for (int i = 0; i < num; i++) { list.add(counter.getAndIncrement()); } @@ -202,17 +214,27 @@ void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { @Override void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { List list = new ArrayList<>(); - long num = RANDOM.nextInt(10); + long num = random.nextInt(10); for (int i = 0; i < num; i++) { list.add(counter.getAndIncrement()); } builder.addAll(iterable(list)); } }, + ADD_STREAM { + @Override + void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { + long[] array = new long[random.nextInt(10)]; + for (int i = 0; i < array.length; i++) { + array[i] = counter.getAndIncrement(); + } + builder.addAll(stream(array)); + } + }, ADD_IIA { @Override void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { - long[] array = new long[RANDOM.nextInt(10)]; + long[] array = new long[random.nextInt(10)]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -222,7 +244,7 @@ void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { ADD_LARGER_ARRAY { @Override void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { - long[] array = new long[RANDOM.nextInt(200) + 200]; + long[] array = new long[random.nextInt(200) + 200]; for (int i = 0; i < array.length; i++) { array[i] = counter.getAndIncrement(); } @@ -234,13 +256,13 @@ void doIt(ImmutableLongArray.Builder builder, AtomicLong counter) { static final BuilderOp[] values = values(); static BuilderOp randomOp() { - return values[RANDOM.nextInt(values.length)]; + return values[random.nextInt(values.length)]; } abstract void doIt(ImmutableLongArray.Builder builder, AtomicLong counter); } - private static final Random RANDOM = new Random(42); + private static final Random random = new Random(42); public void testLength() { assertThat(ImmutableLongArray.of().length()).isEqualTo(0); @@ -267,23 +289,11 @@ public void testGet_good() { public void testGet_bad() { ImmutableLongArray iia = ImmutableLongArray.of(0, 1, 3); - try { - iia.get(-1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - iia.get(3); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> iia.get(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> iia.get(3)); - iia = iia.subArray(1, 2); - try { - iia.get(-1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + ImmutableLongArray sub = iia.subArray(1, 2); + assertThrows(IndexOutOfBoundsException.class, () -> sub.get(-1)); } public void testIndexOf() { @@ -316,6 +326,22 @@ public void testContains() { assertThat(iia.subArray(1, 5).contains(1)).isTrue(); } + public void testForEach() { + ImmutableLongArray.of().forEach(i -> fail()); + ImmutableLongArray.of(0, 1, 3).subArray(1, 1).forEach(i -> fail()); + + AtomicLong count = new AtomicLong(0); + ImmutableLongArray.of(0, 1, 2, 3) + .forEach(i -> assertThat(i).isEqualTo(count.getAndIncrement())); + assertThat(count.get()).isEqualTo(4); + } + + public void testStream() { + ImmutableLongArray.of().stream().forEach(i -> fail()); + ImmutableLongArray.of(0, 1, 3).subArray(1, 1).stream().forEach(i -> fail()); + assertThat(ImmutableLongArray.of(0, 1, 3).stream().toArray()).isEqualTo(new long[] {0, 1, 3}); + } + public void testSubArray() { ImmutableLongArray iia0 = ImmutableLongArray.of(); ImmutableLongArray iia1 = ImmutableLongArray.of(5); @@ -328,16 +354,8 @@ public void testSubArray() { assertThat(iia3.subArray(0, 2).asList()).containsExactly(5L, 25L).inOrder(); assertThat(iia3.subArray(1, 3).asList()).containsExactly(25L, 125L).inOrder(); - try { - iia3.subArray(-1, 1); - fail(); - } catch (IndexOutOfBoundsException expected) { - } - try { - iia3.subArray(1, 4); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> iia3.subArray(-1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> iia3.subArray(1, 4)); } /* @@ -345,7 +363,7 @@ public void testSubArray() { * (so much for "black box") and try instances that both do and don't pass the check. The "don't" * half of that is more awkward to arrange... */ - private static Iterable iterable(final Collection collection) { + private static Iterable iterable(Collection collection) { // return collection::iterator; return new Iterable() { @Override @@ -388,6 +406,7 @@ public void testTrimmed() { assertActuallyTrims(underSized); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testSerialization() { assertThat(reserialize(ImmutableLongArray.of())).isSameInstanceAs(ImmutableLongArray.of()); @@ -412,7 +431,9 @@ private static void assertDoesntActuallyTrim(ImmutableLongArray iia) { assertThat(iia.trimmed()).isSameInstanceAs(iia); } + @J2ktIncompatible @GwtIncompatible // suite + @AndroidIncompatible // test-suite builders public static Test suite() { List> builders = ImmutableList.of( @@ -443,7 +464,9 @@ public static Test suite() { return suite; } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible private static ImmutableLongArray makeArray(Long[] values) { return ImmutableLongArray.copyOf(Arrays.asList(values)); } @@ -451,7 +474,9 @@ private static ImmutableLongArray makeArray(Long[] values) { // Test generators. To let the GWT test suite generator access them, they need to be public named // classes with a public default constructor (not that we run these suites under GWT yet). + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static final class ImmutableLongArrayAsListGenerator extends TestLongListGenerator { @Override protected List create(Long[] elements) { @@ -459,7 +484,9 @@ protected List create(Long[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static final class ImmutableLongArrayHeadSubListAsListGenerator extends TestLongListGenerator { @Override @@ -470,7 +497,9 @@ protected List create(Long[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static final class ImmutableLongArrayTailSubListAsListGenerator extends TestLongListGenerator { @Override @@ -481,7 +510,9 @@ protected List create(Long[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static final class ImmutableLongArrayMiddleSubListAsListGenerator extends TestLongListGenerator { @Override @@ -493,12 +524,16 @@ protected List create(Long[] elements) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible private static Long[] concat(Long[] a, Long[] b) { return ObjectArrays.concat(a, b, Long.class); } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public abstract static class TestLongListGenerator implements TestListGenerator { @Override public SampleElements samples() { @@ -533,7 +568,9 @@ public List order(List insertionOrder) { } } + @J2ktIncompatible @GwtIncompatible // used only from suite + @AndroidIncompatible public static class SampleLongs extends SampleElements { public SampleLongs() { super(1L << 31, 1L << 33, 1L << 36, 1L << 40, 1L << 45); diff --git a/android/guava-tests/test/com/google/common/primitives/IntArrayAsListTest.java b/android/guava-tests/test/com/google/common/primitives/IntArrayAsListTest.java index e02d1aa5a52f..e8062e0d6bd6 100644 --- a/android/guava-tests/test/com/google/common/primitives/IntArrayAsListTest.java +++ b/android/guava-tests/test/com/google/common/primitives/IntArrayAsListTest.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -31,14 +32,17 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Ints#asList(int[])}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible @SuppressWarnings("cast") // redundant casts are intentional and harmless +@NullUnmarked +@AndroidIncompatible // test-suite builders public class IntArrayAsListTest extends TestCase { private static List asList(Integer[] values) { @@ -49,6 +53,7 @@ private static List asList(Integer[] values) { return Ints.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = diff --git a/android/guava-tests/test/com/google/common/primitives/IntsTest.java b/android/guava-tests/test/com/google/common/primitives/IntsTest.java index 72154224c7f4..132932df6e0c 100644 --- a/android/guava-tests/test/com/google/common/primitives/IntsTest.java +++ b/android/guava-tests/test/com/google/common/primitives/IntsTest.java @@ -16,8 +16,15 @@ package com.google.common.primitives; +import static com.google.common.primitives.Ints.max; +import static com.google.common.primitives.Ints.min; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; @@ -29,13 +36,16 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Ints}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullMarked @SuppressWarnings("cast") // redundant casts are intentional and harmless public class IntsTest extends TestCase { private static final int[] EMPTY = {}; @@ -47,15 +57,17 @@ public class IntsTest extends TestCase { private static final int[] VALUES = {LEAST, (int) -1, (int) 0, (int) 1, GREATEST}; + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testHashCode() { for (int value : VALUES) { - assertEquals(((Integer) value).hashCode(), Ints.hashCode(value)); + assertThat(Ints.hashCode(value)).isEqualTo(Integer.hashCode(value)); } } public void testCheckedCast() { for (int value : VALUES) { - assertEquals(value, Ints.checkedCast((long) value)); + assertThat(Ints.checkedCast((long) value)).isEqualTo(value); } assertCastFails(GREATEST + 1L); assertCastFails(LEAST - 1L); @@ -65,12 +77,12 @@ public void testCheckedCast() { public void testSaturatedCast() { for (int value : VALUES) { - assertEquals(value, Ints.saturatedCast((long) value)); + assertThat(Ints.saturatedCast((long) value)).isEqualTo(value); } - assertEquals(GREATEST, Ints.saturatedCast(GREATEST + 1L)); - assertEquals(LEAST, Ints.saturatedCast(LEAST - 1L)); - assertEquals(GREATEST, Ints.saturatedCast(Long.MAX_VALUE)); - assertEquals(LEAST, Ints.saturatedCast(Long.MIN_VALUE)); + assertThat(Ints.saturatedCast(GREATEST + 1L)).isEqualTo(GREATEST); + assertThat(Ints.saturatedCast(LEAST - 1L)).isEqualTo(LEAST); + assertThat(Ints.saturatedCast(Long.MAX_VALUE)).isEqualTo(GREATEST); + assertThat(Ints.saturatedCast(Long.MIN_VALUE)).isEqualTo(LEAST); } private static void assertCastFails(long value) { @@ -78,166 +90,188 @@ private static void assertCastFails(long value) { Ints.checkedCast(value); fail("Cast to int should have failed: " + value); } catch (IllegalArgumentException ex) { - assertTrue( - value + " not found in exception text: " + ex.getMessage(), - ex.getMessage().contains(String.valueOf(value))); + assertWithMessage(value + " not found in exception text: " + ex.getMessage()) + .that(ex.getMessage().contains(String.valueOf(value))) + .isTrue(); } } + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testCompare() { for (int x : VALUES) { for (int y : VALUES) { // note: spec requires only that the sign is the same - assertEquals(x + ", " + y, Integer.valueOf(x).compareTo(y), Ints.compare(x, y)); + assertWithMessage(x + ", " + y).that(Ints.compare(x, y)).isEqualTo(Integer.compare(x, y)); } } } public void testContains() { - assertFalse(Ints.contains(EMPTY, (int) 1)); - assertFalse(Ints.contains(ARRAY1, (int) 2)); - assertFalse(Ints.contains(ARRAY234, (int) 1)); - assertTrue(Ints.contains(new int[] {(int) -1}, (int) -1)); - assertTrue(Ints.contains(ARRAY234, (int) 2)); - assertTrue(Ints.contains(ARRAY234, (int) 3)); - assertTrue(Ints.contains(ARRAY234, (int) 4)); + assertThat(Ints.contains(EMPTY, (int) 1)).isFalse(); + assertThat(Ints.contains(ARRAY1, (int) 2)).isFalse(); + assertThat(Ints.contains(ARRAY234, (int) 1)).isFalse(); + assertThat(Ints.contains(new int[] {(int) -1}, (int) -1)).isTrue(); + assertThat(Ints.contains(ARRAY234, (int) 2)).isTrue(); + assertThat(Ints.contains(ARRAY234, (int) 3)).isTrue(); + assertThat(Ints.contains(ARRAY234, (int) 4)).isTrue(); } public void testIndexOf() { - assertEquals(-1, Ints.indexOf(EMPTY, (int) 1)); - assertEquals(-1, Ints.indexOf(ARRAY1, (int) 2)); - assertEquals(-1, Ints.indexOf(ARRAY234, (int) 1)); - assertEquals(0, Ints.indexOf(new int[] {(int) -1}, (int) -1)); - assertEquals(0, Ints.indexOf(ARRAY234, (int) 2)); - assertEquals(1, Ints.indexOf(ARRAY234, (int) 3)); - assertEquals(2, Ints.indexOf(ARRAY234, (int) 4)); - assertEquals(1, Ints.indexOf(new int[] {(int) 2, (int) 3, (int) 2, (int) 3}, (int) 3)); + assertThat(Ints.indexOf(EMPTY, (int) 1)).isEqualTo(-1); + assertThat(Ints.indexOf(ARRAY1, (int) 2)).isEqualTo(-1); + assertThat(Ints.indexOf(ARRAY234, (int) 1)).isEqualTo(-1); + assertThat(Ints.indexOf(new int[] {(int) -1}, (int) -1)).isEqualTo(0); + assertThat(Ints.indexOf(ARRAY234, (int) 2)).isEqualTo(0); + assertThat(Ints.indexOf(ARRAY234, (int) 3)).isEqualTo(1); + assertThat(Ints.indexOf(ARRAY234, (int) 4)).isEqualTo(2); + assertThat(Ints.indexOf(new int[] {(int) 2, (int) 3, (int) 2, (int) 3}, (int) 3)).isEqualTo(1); } public void testIndexOf_arrayTarget() { - assertEquals(0, Ints.indexOf(EMPTY, EMPTY)); - assertEquals(0, Ints.indexOf(ARRAY234, EMPTY)); - assertEquals(-1, Ints.indexOf(EMPTY, ARRAY234)); - assertEquals(-1, Ints.indexOf(ARRAY234, ARRAY1)); - assertEquals(-1, Ints.indexOf(ARRAY1, ARRAY234)); - assertEquals(0, Ints.indexOf(ARRAY1, ARRAY1)); - assertEquals(0, Ints.indexOf(ARRAY234, ARRAY234)); - assertEquals(0, Ints.indexOf(ARRAY234, new int[] {(int) 2, (int) 3})); - assertEquals(1, Ints.indexOf(ARRAY234, new int[] {(int) 3, (int) 4})); - assertEquals(1, Ints.indexOf(ARRAY234, new int[] {(int) 3})); - assertEquals(2, Ints.indexOf(ARRAY234, new int[] {(int) 4})); - assertEquals( - 1, - Ints.indexOf(new int[] {(int) 2, (int) 3, (int) 3, (int) 3, (int) 3}, new int[] {(int) 3})); - assertEquals( - 2, - Ints.indexOf( - new int[] {(int) 2, (int) 3, (int) 2, (int) 3, (int) 4, (int) 2, (int) 3}, - new int[] {(int) 2, (int) 3, (int) 4})); - assertEquals( - 1, - Ints.indexOf( - new int[] {(int) 2, (int) 2, (int) 3, (int) 4, (int) 2, (int) 3, (int) 4}, - new int[] {(int) 2, (int) 3, (int) 4})); - assertEquals( - -1, - Ints.indexOf(new int[] {(int) 4, (int) 3, (int) 2}, new int[] {(int) 2, (int) 3, (int) 4})); + assertThat(Ints.indexOf(EMPTY, EMPTY)).isEqualTo(0); + assertThat(Ints.indexOf(ARRAY234, EMPTY)).isEqualTo(0); + assertThat(Ints.indexOf(EMPTY, ARRAY234)).isEqualTo(-1); + assertThat(Ints.indexOf(ARRAY234, ARRAY1)).isEqualTo(-1); + assertThat(Ints.indexOf(ARRAY1, ARRAY234)).isEqualTo(-1); + assertThat(Ints.indexOf(ARRAY1, ARRAY1)).isEqualTo(0); + assertThat(Ints.indexOf(ARRAY234, ARRAY234)).isEqualTo(0); + assertThat(Ints.indexOf(ARRAY234, new int[] {(int) 2, (int) 3})).isEqualTo(0); + assertThat(Ints.indexOf(ARRAY234, new int[] {(int) 3, (int) 4})).isEqualTo(1); + assertThat(Ints.indexOf(ARRAY234, new int[] {(int) 3})).isEqualTo(1); + assertThat(Ints.indexOf(ARRAY234, new int[] {(int) 4})).isEqualTo(2); + assertThat( + Ints.indexOf( + new int[] {(int) 2, (int) 3, (int) 3, (int) 3, (int) 3}, new int[] {(int) 3})) + .isEqualTo(1); + assertThat( + Ints.indexOf( + new int[] {(int) 2, (int) 3, (int) 2, (int) 3, (int) 4, (int) 2, (int) 3}, + new int[] {(int) 2, (int) 3, (int) 4})) + .isEqualTo(2); + assertThat( + Ints.indexOf( + new int[] {(int) 2, (int) 2, (int) 3, (int) 4, (int) 2, (int) 3, (int) 4}, + new int[] {(int) 2, (int) 3, (int) 4})) + .isEqualTo(1); + assertThat( + Ints.indexOf( + new int[] {(int) 4, (int) 3, (int) 2}, new int[] {(int) 2, (int) 3, (int) 4})) + .isEqualTo(-1); } public void testLastIndexOf() { - assertEquals(-1, Ints.lastIndexOf(EMPTY, (int) 1)); - assertEquals(-1, Ints.lastIndexOf(ARRAY1, (int) 2)); - assertEquals(-1, Ints.lastIndexOf(ARRAY234, (int) 1)); - assertEquals(0, Ints.lastIndexOf(new int[] {(int) -1}, (int) -1)); - assertEquals(0, Ints.lastIndexOf(ARRAY234, (int) 2)); - assertEquals(1, Ints.lastIndexOf(ARRAY234, (int) 3)); - assertEquals(2, Ints.lastIndexOf(ARRAY234, (int) 4)); - assertEquals(3, Ints.lastIndexOf(new int[] {(int) 2, (int) 3, (int) 2, (int) 3}, (int) 3)); + assertThat(Ints.lastIndexOf(EMPTY, (int) 1)).isEqualTo(-1); + assertThat(Ints.lastIndexOf(ARRAY1, (int) 2)).isEqualTo(-1); + assertThat(Ints.lastIndexOf(ARRAY234, (int) 1)).isEqualTo(-1); + assertThat(Ints.lastIndexOf(new int[] {(int) -1}, (int) -1)).isEqualTo(0); + assertThat(Ints.lastIndexOf(ARRAY234, (int) 2)).isEqualTo(0); + assertThat(Ints.lastIndexOf(ARRAY234, (int) 3)).isEqualTo(1); + assertThat(Ints.lastIndexOf(ARRAY234, (int) 4)).isEqualTo(2); + assertThat(Ints.lastIndexOf(new int[] {(int) 2, (int) 3, (int) 2, (int) 3}, (int) 3)) + .isEqualTo(3); } @GwtIncompatible public void testMax_noArgs() { - try { - Ints.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(LEAST, Ints.max(LEAST)); - assertEquals(GREATEST, Ints.max(GREATEST)); - assertEquals((int) 9, Ints.max((int) 8, (int) 6, (int) 7, (int) 5, (int) 3, (int) 0, (int) 9)); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat(max((int) 8, (int) 6, (int) 7, (int) 5, (int) 3, (int) 0, (int) 9)) + .isEqualTo((int) 9); } @GwtIncompatible public void testMin_noArgs() { - try { - Ints.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, Ints.min(LEAST)); - assertEquals(GREATEST, Ints.min(GREATEST)); - assertEquals((int) 0, Ints.min((int) 8, (int) 6, (int) 7, (int) 5, (int) 3, (int) 0, (int) 9)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat(min((int) 8, (int) 6, (int) 7, (int) 5, (int) 3, (int) 0, (int) 9)) + .isEqualTo((int) 0); } public void testConstrainToRange() { - assertEquals((int) 1, Ints.constrainToRange((int) 1, (int) 0, (int) 5)); - assertEquals((int) 1, Ints.constrainToRange((int) 1, (int) 1, (int) 5)); - assertEquals((int) 3, Ints.constrainToRange((int) 1, (int) 3, (int) 5)); - assertEquals((int) -1, Ints.constrainToRange((int) 0, (int) -5, (int) -1)); - assertEquals((int) 2, Ints.constrainToRange((int) 5, (int) 2, (int) 2)); + assertThat(Ints.constrainToRange((int) 1, (int) 0, (int) 5)).isEqualTo((int) 1); + assertThat(Ints.constrainToRange((int) 1, (int) 1, (int) 5)).isEqualTo((int) 1); + assertThat(Ints.constrainToRange((int) 1, (int) 3, (int) 5)).isEqualTo((int) 3); + assertThat(Ints.constrainToRange((int) 0, (int) -5, (int) -1)).isEqualTo((int) -1); + assertThat(Ints.constrainToRange((int) 5, (int) 2, (int) 2)).isEqualTo((int) 2); + assertThrows( + IllegalArgumentException.class, () -> Ints.constrainToRange((int) 1, (int) 3, (int) 2)); + } + + public void testConcat() { + assertThat(Ints.concat()).isEqualTo(EMPTY); + assertThat(Ints.concat(EMPTY)).isEqualTo(EMPTY); + assertThat(Ints.concat(EMPTY, EMPTY, EMPTY)).isEqualTo(EMPTY); + assertThat(Ints.concat(ARRAY1)).isEqualTo(ARRAY1); + assertThat(Ints.concat(ARRAY1)).isNotSameInstanceAs(ARRAY1); + assertThat(Ints.concat(EMPTY, ARRAY1, EMPTY)).isEqualTo(ARRAY1); + assertThat(Ints.concat(ARRAY1, ARRAY1, ARRAY1)) + .isEqualTo(new int[] {(int) 1, (int) 1, (int) 1}); + assertThat(Ints.concat(ARRAY1, ARRAY234)) + .isEqualTo(new int[] {(int) 1, (int) 2, (int) 3, (int) 4}); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_negative() { + int dim1 = 1 << 16; + int dim2 = 1 << 15; + assertThat(dim1 * dim2).isLessThan(0); + testConcatOverflow(dim1, dim2); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_nonNegative() { + int dim1 = 1 << 16; + int dim2 = 1 << 16; + assertThat(dim1 * dim2).isAtLeast(0); + testConcatOverflow(dim1, dim2); + } + + private static void testConcatOverflow(int arraysDim1, int arraysDim2) { + assertThat((long) arraysDim1 * arraysDim2).isNotEqualTo((long) (arraysDim1 * arraysDim2)); + + int[][] arrays = new int[arraysDim1][]; + // it's shared to avoid using too much memory in tests + int[] sharedArray = new int[arraysDim2]; + Arrays.fill(arrays, sharedArray); + try { - Ints.constrainToRange((int) 1, (int) 3, (int) 2); + Ints.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } - public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Ints.concat())); - assertTrue(Arrays.equals(EMPTY, Ints.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Ints.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY1, Ints.concat(ARRAY1))); - assertNotSame(ARRAY1, Ints.concat(ARRAY1)); - assertTrue(Arrays.equals(ARRAY1, Ints.concat(EMPTY, ARRAY1, EMPTY))); - assertTrue( - Arrays.equals(new int[] {(int) 1, (int) 1, (int) 1}, Ints.concat(ARRAY1, ARRAY1, ARRAY1))); - assertTrue( - Arrays.equals( - new int[] {(int) 1, (int) 2, (int) 3, (int) 4}, Ints.concat(ARRAY1, ARRAY234))); - } - public void testToByteArray() { - assertTrue(Arrays.equals(new byte[] {0x12, 0x13, 0x14, 0x15}, Ints.toByteArray(0x12131415))); - assertTrue( - Arrays.equals( - new byte[] {(byte) 0xFF, (byte) 0xEE, (byte) 0xDD, (byte) 0xCC}, - Ints.toByteArray(0xFFEEDDCC))); + assertThat(Ints.toByteArray(0x12131415)).isEqualTo(new byte[] {0x12, 0x13, 0x14, 0x15}); + assertThat(Ints.toByteArray(0xFFEEDDCC)) + .isEqualTo(new byte[] {(byte) 0xFF, (byte) 0xEE, (byte) 0xDD, (byte) 0xCC}); } public void testFromByteArray() { - assertEquals(0x12131415, Ints.fromByteArray(new byte[] {0x12, 0x13, 0x14, 0x15, 0x33})); - assertEquals( - 0xFFEEDDCC, - Ints.fromByteArray(new byte[] {(byte) 0xFF, (byte) 0xEE, (byte) 0xDD, (byte) 0xCC})); + assertThat(Ints.fromByteArray(new byte[] {0x12, 0x13, 0x14, 0x15, 0x33})).isEqualTo(0x12131415); + assertThat(Ints.fromByteArray(new byte[] {(byte) 0xFF, (byte) 0xEE, (byte) 0xDD, (byte) 0xCC})) + .isEqualTo(0xFFEEDDCC); } public void testFromByteArrayFails() { - try { - Ints.fromByteArray(new byte[Ints.BYTES - 1]); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> Ints.fromByteArray(new byte[Ints.BYTES - 1])); } public void testFromBytes() { - assertEquals(0x12131415, Ints.fromBytes((byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15)); - assertEquals(0xFFEEDDCC, Ints.fromBytes((byte) 0xFF, (byte) 0xEE, (byte) 0xDD, (byte) 0xCC)); + assertThat(Ints.fromBytes((byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15)) + .isEqualTo(0x12131415); + assertThat(Ints.fromBytes((byte) 0xFF, (byte) 0xEE, (byte) 0xDD, (byte) 0xCC)) + .isEqualTo(0xFFEEDDCC); } public void testByteArrayRoundTrips() { @@ -247,40 +281,30 @@ public void testByteArrayRoundTrips() { // total overkill, but, it takes 0.1 sec so why not... for (int i = 0; i < 10000; i++) { int num = r.nextInt(); - assertEquals(num, Ints.fromByteArray(Ints.toByteArray(num))); + assertThat(Ints.fromByteArray(Ints.toByteArray(num))).isEqualTo(num); r.nextBytes(b); - assertTrue(Arrays.equals(b, Ints.toByteArray(Ints.fromByteArray(b)))); + assertThat(Ints.toByteArray(Ints.fromByteArray(b))).isEqualTo(b); } } public void testEnsureCapacity() { - assertSame(EMPTY, Ints.ensureCapacity(EMPTY, 0, 1)); - assertSame(ARRAY1, Ints.ensureCapacity(ARRAY1, 0, 1)); - assertSame(ARRAY1, Ints.ensureCapacity(ARRAY1, 1, 1)); - assertTrue( - Arrays.equals(new int[] {(int) 1, (int) 0, (int) 0}, Ints.ensureCapacity(ARRAY1, 2, 1))); + assertThat(Ints.ensureCapacity(EMPTY, 0, 1)).isSameInstanceAs(EMPTY); + assertThat(Ints.ensureCapacity(ARRAY1, 0, 1)).isSameInstanceAs(ARRAY1); + assertThat(Ints.ensureCapacity(ARRAY1, 1, 1)).isSameInstanceAs(ARRAY1); + assertThat(Ints.ensureCapacity(ARRAY1, 2, 1)).isEqualTo(new int[] {(int) 1, (int) 0, (int) 0}); } public void testEnsureCapacity_fail() { - try { - Ints.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - // notice that this should even fail when no growth was needed - Ints.ensureCapacity(ARRAY1, 1, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Ints.ensureCapacity(ARRAY1, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> Ints.ensureCapacity(ARRAY1, 1, -1)); } public void testJoin() { - assertEquals("", Ints.join(",", EMPTY)); - assertEquals("1", Ints.join(",", ARRAY1)); - assertEquals("1,2", Ints.join(",", (int) 1, (int) 2)); - assertEquals("123", Ints.join("", (int) 1, (int) 2, (int) 3)); + assertThat(Ints.join(",", EMPTY)).isEmpty(); + assertThat(Ints.join(",", ARRAY1)).isEqualTo("1"); + assertThat(Ints.join(",", (int) 1, (int) 2)).isEqualTo("1,2"); + assertThat(Ints.join("", (int) 1, (int) 2, (int) 3)).isEqualTo("123"); } public void testLexicographicalComparator() { @@ -300,10 +324,11 @@ public void testLexicographicalComparator() { Helpers.testComparator(comparator, ordered); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testLexicographicalComparatorSerializable() { Comparator comparator = Ints.lexicographicalComparator(); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); } public void testReverse() { @@ -317,13 +342,13 @@ public void testReverse() { private static void testReverse(int[] input, int[] expectedOutput) { input = Arrays.copyOf(input, input.length); Ints.reverse(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testReverse(int[] input, int fromIndex, int toIndex, int[] expectedOutput) { input = Arrays.copyOf(input, input.length); Ints.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testReverseIndexed() { @@ -335,6 +360,103 @@ public void testReverseIndexed() { testReverse(new int[] {-1, 1, -2, 2}, 1, 3, new int[] {-1, -2, 1, 2}); } + private static void testRotate(int[] input, int distance, int[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Ints.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + int[] input, int distance, int fromIndex, int toIndex, int[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Ints.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new int[] {}, -1, new int[] {}); + testRotate(new int[] {}, 0, new int[] {}); + testRotate(new int[] {}, 1, new int[] {}); + + testRotate(new int[] {1}, -2, new int[] {1}); + testRotate(new int[] {1}, -1, new int[] {1}); + testRotate(new int[] {1}, 0, new int[] {1}); + testRotate(new int[] {1}, 1, new int[] {1}); + testRotate(new int[] {1}, 2, new int[] {1}); + + testRotate(new int[] {1, 2}, -3, new int[] {2, 1}); + testRotate(new int[] {1, 2}, -1, new int[] {2, 1}); + testRotate(new int[] {1, 2}, -2, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 0, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 1, new int[] {2, 1}); + testRotate(new int[] {1, 2}, 2, new int[] {1, 2}); + testRotate(new int[] {1, 2}, 3, new int[] {2, 1}); + + testRotate(new int[] {1, 2, 3}, -5, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, -4, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, -3, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, -2, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, -1, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, 0, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, 1, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, 2, new int[] {2, 3, 1}); + testRotate(new int[] {1, 2, 3}, 3, new int[] {1, 2, 3}); + testRotate(new int[] {1, 2, 3}, 4, new int[] {3, 1, 2}); + testRotate(new int[] {1, 2, 3}, 5, new int[] {2, 3, 1}); + + testRotate(new int[] {1, 2, 3, 4}, -9, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, -5, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, -1, new int[] {2, 3, 4, 1}); + testRotate(new int[] {1, 2, 3, 4}, 0, new int[] {1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4}, 1, new int[] {4, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4}, 5, new int[] {4, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4}, 9, new int[] {4, 1, 2, 3}); + + testRotate(new int[] {1, 2, 3, 4, 5}, -6, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, -4, new int[] {5, 1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4, 5}, -3, new int[] {4, 5, 1, 2, 3}); + testRotate(new int[] {1, 2, 3, 4, 5}, -1, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, 0, new int[] {1, 2, 3, 4, 5}); + testRotate(new int[] {1, 2, 3, 4, 5}, 1, new int[] {5, 1, 2, 3, 4}); + testRotate(new int[] {1, 2, 3, 4, 5}, 3, new int[] {3, 4, 5, 1, 2}); + testRotate(new int[] {1, 2, 3, 4, 5}, 4, new int[] {2, 3, 4, 5, 1}); + testRotate(new int[] {1, 2, 3, 4, 5}, 6, new int[] {5, 1, 2, 3, 4}); + } + + public void testRotateIndexed() { + testRotate(new int[] {}, 0, 0, 0, new int[] {}); + + testRotate(new int[] {1}, 0, 0, 1, new int[] {1}); + testRotate(new int[] {1}, 1, 0, 1, new int[] {1}); + testRotate(new int[] {1}, 1, 1, 1, new int[] {1}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -6, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 1, 6, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 5, 1, 6, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 14, 1, 6, new int[] {0, 2, 3, 4, 5, 1, 6}); + + // Rotate the first three elements + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -2, 0, 3, new int[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 0, 3, new int[] {1, 2, 0, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 0, 3, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 1, 0, 3, new int[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 2, 0, 3, new int[] {1, 2, 0, 3, 4, 5, 6}); + + // Rotate the last four elements + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -6, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -5, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -4, 3, 7, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -3, 3, 7, new int[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -2, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, -1, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 0, 3, 7, new int[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 1, 3, 7, new int[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 2, 3, 7, new int[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new int[] {0, 1, 2, 3, 4, 5, 6}, 3, 3, 7, new int[] {0, 1, 2, 4, 5, 6, 3}); + } + public void testSortDescending() { testSortDescending(new int[] {}, new int[] {}); testSortDescending(new int[] {1}, new int[] {1}); @@ -346,14 +468,14 @@ public void testSortDescending() { private static void testSortDescending(int[] input, int[] expectedOutput) { input = Arrays.copyOf(input, input.length); Ints.sortDescending(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testSortDescending( int[] input, int fromIndex, int toIndex, int[] expectedOutput) { input = Arrays.copyOf(input, input.length); Ints.sortDescending(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testSortDescendingIndexed() { @@ -365,6 +487,7 @@ public void testSortDescendingIndexed() { testSortDescending(new int[] {-1, -2, 1, 2}, 1, 3, new int[] {-1, 1, -2, 2}); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testStringConverterSerialization() { SerializableTester.reserializeAndAssert(Ints.stringConverter()); @@ -373,17 +496,17 @@ public void testStringConverterSerialization() { public void testToArray() { // need explicit type parameter to avoid javac warning!? List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Ints.toArray(none))); + assertThat(Ints.toArray(none)).isEqualTo(EMPTY); List one = Arrays.asList((int) 1); - assertTrue(Arrays.equals(ARRAY1, Ints.toArray(one))); + assertThat(Ints.toArray(one)).isEqualTo(ARRAY1); int[] array = {(int) 0, (int) 1, (int) 0xdeadbeef}; List three = Arrays.asList((int) 0, (int) 1, (int) 0xdeadbeef); - assertTrue(Arrays.equals(array, Ints.toArray(three))); + assertThat(Ints.toArray(three)).isEqualTo(array); - assertTrue(Arrays.equals(array, Ints.toArray(Ints.asList(array)))); + assertThat(Ints.toArray(Ints.asList(array))).isEqualTo(array); } public void testToArray_threadSafe() { @@ -393,21 +516,17 @@ public void testToArray_threadSafe() { Collection misleadingSize = Helpers.misleadingSizeCollection(delta); misleadingSize.addAll(list); int[] arr = Ints.toArray(misleadingSize); - assertEquals(i, arr.length); + assertThat(arr).hasLength(i); for (int j = 0; j < i; j++) { - assertEquals(VALUES[j], arr[j]); + assertThat(arr[j]).isEqualTo(VALUES[j]); } } } } public void testToArray_withNull() { - List list = Arrays.asList((int) 0, (int) 1, null); - try { - Ints.toArray(list); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Integer> list = Arrays.asList((int) 0, (int) 1, null); + assertThrows(NullPointerException.class, () -> Ints.toArray(list)); } public void testToArray_withConversion() { @@ -416,25 +535,26 @@ public void testToArray_withConversion() { List bytes = Arrays.asList((byte) 0, (byte) 1, (byte) 2); List shorts = Arrays.asList((short) 0, (short) 1, (short) 2); List ints = Arrays.asList(0, 1, 2); - List floats = Arrays.asList((float) 0, (float) 1, (float) 2); - List longs = Arrays.asList((long) 0, (long) 1, (long) 2); - List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); + List floats = Arrays.asList(0.0f, 1.0f, 2.0f); + List longs = Arrays.asList(0L, 1L, 2L); + List doubles = Arrays.asList(0.0, 1.0, 2.0); - assertTrue(Arrays.equals(array, Ints.toArray(bytes))); - assertTrue(Arrays.equals(array, Ints.toArray(shorts))); - assertTrue(Arrays.equals(array, Ints.toArray(ints))); - assertTrue(Arrays.equals(array, Ints.toArray(floats))); - assertTrue(Arrays.equals(array, Ints.toArray(longs))); - assertTrue(Arrays.equals(array, Ints.toArray(doubles))); + assertThat(Ints.toArray(bytes)).isEqualTo(array); + assertThat(Ints.toArray(shorts)).isEqualTo(array); + assertThat(Ints.toArray(ints)).isEqualTo(array); + assertThat(Ints.toArray(floats)).isEqualTo(array); + assertThat(Ints.toArray(longs)).isEqualTo(array); + assertThat(Ints.toArray(doubles)).isEqualTo(array); } + @J2ktIncompatible // b/239034072: Kotlin varargs copy parameter arrays. public void testAsList_isAView() { int[] array = {(int) 0, (int) 1}; List list = Ints.asList(array); list.set(0, (int) 2); - assertTrue(Arrays.equals(new int[] {(int) 2, (int) 1}, array)); + assertThat(array).isEqualTo(new int[] {(int) 2, (int) 1}); array[1] = (int) 3; - assertEquals(Arrays.asList((int) 2, (int) 3), list); + assertThat(list).containsExactly((int) 2, (int) 3).inOrder(); } public void testAsList_toArray_roundTrip() { @@ -444,23 +564,26 @@ public void testAsList_toArray_roundTrip() { // Make sure it returned a copy list.set(0, (int) 4); - assertTrue(Arrays.equals(new int[] {(int) 0, (int) 1, (int) 2}, newArray)); + assertThat(newArray).isEqualTo(new int[] {(int) 0, (int) 1, (int) 2}); newArray[1] = (int) 5; - assertEquals((int) 1, (int) list.get(1)); + assertThat((int) list.get(1)).isEqualTo((int) 1); } // This test stems from a real bug found by andrewk public void testAsList_subList_toArray_roundTrip() { int[] array = {(int) 0, (int) 1, (int) 2, (int) 3}; List list = Ints.asList(array); - assertTrue(Arrays.equals(new int[] {(int) 1, (int) 2}, Ints.toArray(list.subList(1, 3)))); - assertTrue(Arrays.equals(new int[] {}, Ints.toArray(list.subList(2, 2)))); + assertThat(Ints.toArray(list.subList(1, 3))).isEqualTo(new int[] {(int) 1, (int) 2}); + assertThat(Ints.toArray(list.subList(2, 2))).isEqualTo(new int[] {}); } + // `primitives` can't depend on `collect`, so this is what the prod code has to return. + @SuppressWarnings("EmptyList") public void testAsListEmpty() { - assertSame(Collections.emptyList(), Ints.asList(EMPTY)); + assertThat(Ints.asList(EMPTY)).isSameInstanceAs(Collections.emptyList()); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Ints.class); @@ -468,40 +591,37 @@ public void testNulls() { public void testStringConverter_convert() { Converter converter = Ints.stringConverter(); - assertEquals((Integer) 1, converter.convert("1")); - assertEquals((Integer) 0, converter.convert("0")); - assertEquals((Integer) (-1), converter.convert("-1")); - assertEquals((Integer) 255, converter.convert("0xff")); - assertEquals((Integer) 255, converter.convert("0xFF")); - assertEquals((Integer) (-255), converter.convert("-0xFF")); - assertEquals((Integer) 255, converter.convert("#0000FF")); - assertEquals((Integer) 438, converter.convert("0666")); + assertThat(converter.convert("1")).isEqualTo(1); + assertThat(converter.convert("0")).isEqualTo(0); + assertThat(converter.convert("-1")).isEqualTo(-1); + assertThat(converter.convert("0xff")).isEqualTo(255); + assertThat(converter.convert("0xFF")).isEqualTo(255); + assertThat(converter.convert("-0xFF")).isEqualTo(-255); + assertThat(converter.convert("#0000FF")).isEqualTo(255); + assertThat(converter.convert("0666")).isEqualTo(438); } public void testStringConverter_convertError() { - try { - Ints.stringConverter().convert("notanumber"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> Ints.stringConverter().convert("notanumber")); } public void testStringConverter_nullConversions() { - assertNull(Ints.stringConverter().convert(null)); - assertNull(Ints.stringConverter().reverse().convert(null)); + assertThat(Ints.stringConverter().convert(null)).isNull(); + assertThat(Ints.stringConverter().reverse().convert(null)).isNull(); } public void testStringConverter_reverse() { Converter converter = Ints.stringConverter(); - assertEquals("1", converter.reverse().convert(1)); - assertEquals("0", converter.reverse().convert(0)); - assertEquals("-1", converter.reverse().convert(-1)); - assertEquals("255", converter.reverse().convert(0xff)); - assertEquals("255", converter.reverse().convert(0xFF)); - assertEquals("-255", converter.reverse().convert(-0xFF)); - assertEquals("438", converter.reverse().convert(0666)); + assertThat(converter.reverse().convert(1)).isEqualTo("1"); + assertThat(converter.reverse().convert(0)).isEqualTo("0"); + assertThat(converter.reverse().convert(-1)).isEqualTo("-1"); + assertThat(converter.reverse().convert(0xff)).isEqualTo("255"); + assertThat(converter.reverse().convert(0xFF)).isEqualTo("255"); + assertThat(converter.reverse().convert(-0xFF)).isEqualTo("-255"); + assertThat(converter.reverse().convert(0666)).isEqualTo("438"); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testStringConverter_nullPointerTester() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -517,17 +637,25 @@ public void testTryParse() { tryParseAndAssertEquals(-8900, "-8900"); tryParseAndAssertEquals(GREATEST, Integer.toString(GREATEST)); tryParseAndAssertEquals(LEAST, Integer.toString(LEAST)); - assertNull(Ints.tryParse("")); - assertNull(Ints.tryParse("-")); - assertNull(Ints.tryParse("+1")); - assertNull(Ints.tryParse("9999999999999999")); - assertNull("Max integer + 1", Ints.tryParse(Long.toString(((long) GREATEST) + 1))); - assertNull("Max integer * 10", Ints.tryParse(Long.toString(((long) GREATEST) * 10))); - assertNull("Min integer - 1", Ints.tryParse(Long.toString(((long) LEAST) - 1))); - assertNull("Min integer * 10", Ints.tryParse(Long.toString(((long) LEAST) * 10))); - assertNull("Max long", Ints.tryParse(Long.toString(Long.MAX_VALUE))); - assertNull("Min long", Ints.tryParse(Long.toString(Long.MIN_VALUE))); - assertNull(Ints.tryParse("\u0662\u06f3")); + assertThat(Ints.tryParse("")).isNull(); + assertThat(Ints.tryParse("-")).isNull(); + assertThat(Ints.tryParse("+1")).isNull(); + assertThat(Ints.tryParse("9999999999999999")).isNull(); + assertWithMessage("Max integer + 1") + .that(Ints.tryParse(Long.toString(((long) GREATEST) + 1))) + .isNull(); + assertWithMessage("Max integer * 10") + .that(Ints.tryParse(Long.toString(((long) GREATEST) * 10))) + .isNull(); + assertWithMessage("Min integer - 1") + .that(Ints.tryParse(Long.toString(((long) LEAST) - 1))) + .isNull(); + assertWithMessage("Min integer * 10") + .that(Ints.tryParse(Long.toString(((long) LEAST) * 10))) + .isNull(); + assertWithMessage("Max long").that(Ints.tryParse(Long.toString(Long.MAX_VALUE))).isNull(); + assertWithMessage("Min long").that(Ints.tryParse(Long.toString(Long.MIN_VALUE))).isNull(); + assertThat(Ints.tryParse("\u0662\u06f3")).isNull(); } /** @@ -535,7 +663,7 @@ public void testTryParse() { * expected. */ private static void tryParseAndAssertEquals(Integer expected, String value) { - assertEquals(expected, Ints.tryParse(value)); + assertThat(Ints.tryParse(value)).isEqualTo(expected); } public void testTryParse_radix() { @@ -545,45 +673,38 @@ public void testTryParse_radix() { radixEncodeParseAndAssertEquals(-8000, radix); radixEncodeParseAndAssertEquals(GREATEST, radix); radixEncodeParseAndAssertEquals(LEAST, radix); - assertNull("Radix: " + radix, Ints.tryParse("9999999999999999", radix)); - assertNull( - "Radix: " + radix, Ints.tryParse(Long.toString((long) GREATEST + 1, radix), radix)); - assertNull("Radix: " + radix, Ints.tryParse(Long.toString((long) LEAST - 1, radix), radix)); + assertWithMessage("Radix: " + radix).that(Ints.tryParse("9999999999999999", radix)).isNull(); + assertWithMessage("Radix: " + radix) + .that(Ints.tryParse(Long.toString((long) GREATEST + 1, radix), radix)) + .isNull(); + assertWithMessage("Radix: " + radix) + .that(Ints.tryParse(Long.toString((long) LEAST - 1, radix), radix)) + .isNull(); } - assertNull("Hex string and dec parm", Ints.tryParse("FFFF", 10)); - assertEquals("Mixed hex case", 65535, (int) Ints.tryParse("ffFF", 16)); + assertWithMessage("Hex string and dec parm").that(Ints.tryParse("FFFF", 10)).isNull(); + assertWithMessage("Mixed hex case").that((int) Ints.tryParse("ffFF", 16)).isEqualTo(65535); } /** - * Encodes the an integer as a string with given radix, then uses {@link Ints#tryParse(String, - * int)} to parse the result. Asserts the result is the same as what we started with. + * Encodes an integer as a string with given radix, then uses {@link Ints#tryParse(String, int)} + * to parse the result. Asserts the result is the same as what we started with. */ private static void radixEncodeParseAndAssertEquals(Integer value, int radix) { - assertEquals("Radix: " + radix, value, Ints.tryParse(Integer.toString(value, radix), radix)); + assertWithMessage("Radix: " + radix) + .that(Ints.tryParse(Integer.toString(value, radix), radix)) + .isEqualTo(value); } public void testTryParse_radixTooBig() { - try { - Ints.tryParse("0", Character.MAX_RADIX + 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Ints.tryParse("0", Character.MAX_RADIX + 1)); } public void testTryParse_radixTooSmall() { - try { - Ints.tryParse("0", Character.MIN_RADIX - 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Ints.tryParse("0", Character.MIN_RADIX - 1)); } public void testTryParse_withNullGwt() { - assertNull(Ints.tryParse("null")); - try { - Ints.tryParse(null); - fail("Expected NPE"); - } catch (NullPointerException expected) { - } + assertThat(Ints.tryParse("null")).isNull(); + assertThrows(NullPointerException.class, () -> Ints.tryParse(null)); } } diff --git a/android/guava-tests/test/com/google/common/primitives/LongArrayAsListTest.java b/android/guava-tests/test/com/google/common/primitives/LongArrayAsListTest.java index 1c51e7ea05e5..0cb78b95cde1 100644 --- a/android/guava-tests/test/com/google/common/primitives/LongArrayAsListTest.java +++ b/android/guava-tests/test/com/google/common/primitives/LongArrayAsListTest.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -31,13 +32,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Longs#asList(long[])}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked +@AndroidIncompatible // test-suite builders public class LongArrayAsListTest extends TestCase { private static List asList(Long[] values) { @@ -48,6 +52,7 @@ private static List asList(Long[] values) { return Longs.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = @@ -96,7 +101,7 @@ protected List create(Long[] elements) { public static final class LongsAsListTailSubListGenerator extends TestLongListGenerator { @Override protected List create(Long[] elements) { - Long[] prefix = {(long) 86, (long) 99}; + Long[] prefix = {86L, 99L}; Long[] all = concat(prefix, elements); return asList(all).subList(2, elements.length + 2); } @@ -106,7 +111,7 @@ public static final class LongsAsListMiddleSubListGenerator extends TestLongList @Override protected List create(Long[] elements) { Long[] prefix = {Long.MIN_VALUE, Long.MAX_VALUE}; - Long[] suffix = {(long) 86, (long) 99}; + Long[] suffix = {86L, 99L}; Long[] all = concat(concat(prefix, elements), suffix); return asList(all).subList(2, elements.length + 2); } @@ -155,7 +160,7 @@ public List order(List insertionOrder) { public static class SampleLongs extends SampleElements { public SampleLongs() { - super((long) 0, (long) 1, (long) 2, (long) 3, (long) 4); + super(0L, 1L, 2L, 3L, 4L); } } } diff --git a/android/guava-tests/test/com/google/common/primitives/LongsTest.java b/android/guava-tests/test/com/google/common/primitives/LongsTest.java index 236d46165c1b..65e517efe02e 100644 --- a/android/guava-tests/test/com/google/common/primitives/LongsTest.java +++ b/android/guava-tests/test/com/google/common/primitives/LongsTest.java @@ -16,11 +16,17 @@ package com.google.common.primitives; +import static com.google.common.primitives.Longs.max; +import static com.google.common.primitives.Longs.min; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static java.lang.Long.MAX_VALUE; import static java.lang.Long.MIN_VALUE; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; @@ -33,164 +39,172 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Longs}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless +@NullMarked +@GwtCompatible public class LongsTest extends TestCase { private static final long[] EMPTY = {}; - private static final long[] ARRAY1 = {(long) 1}; - private static final long[] ARRAY234 = {(long) 2, (long) 3, (long) 4}; + private static final long[] ARRAY1 = {1L}; + private static final long[] ARRAY234 = {2L, 3L, 4L}; - private static final long[] VALUES = {MIN_VALUE, (long) -1, (long) 0, (long) 1, MAX_VALUE}; + private static final long[] VALUES = {MIN_VALUE, -1L, 0L, 1L, MAX_VALUE}; - @GwtIncompatible // Long.hashCode returns different values in GWT. + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testHashCode() { for (long value : VALUES) { - assertEquals("hashCode for " + value, ((Long) value).hashCode(), Longs.hashCode(value)); + assertWithMessage("hashCode for " + value) + .that(Longs.hashCode(value)) + .isEqualTo(Long.hashCode(value)); } } + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testCompare() { for (long x : VALUES) { for (long y : VALUES) { // note: spec requires only that the sign is the same - assertEquals(x + ", " + y, Long.valueOf(x).compareTo(y), Longs.compare(x, y)); + assertWithMessage(x + ", " + y).that(Longs.compare(x, y)).isEqualTo(Long.compare(x, y)); } } } public void testContains() { - assertFalse(Longs.contains(EMPTY, (long) 1)); - assertFalse(Longs.contains(ARRAY1, (long) 2)); - assertFalse(Longs.contains(ARRAY234, (long) 1)); - assertTrue(Longs.contains(new long[] {(long) -1}, (long) -1)); - assertTrue(Longs.contains(ARRAY234, (long) 2)); - assertTrue(Longs.contains(ARRAY234, (long) 3)); - assertTrue(Longs.contains(ARRAY234, (long) 4)); + assertThat(Longs.contains(EMPTY, 1L)).isFalse(); + assertThat(Longs.contains(ARRAY1, 2L)).isFalse(); + assertThat(Longs.contains(ARRAY234, 1L)).isFalse(); + assertThat(Longs.contains(new long[] {-1L}, -1L)).isTrue(); + assertThat(Longs.contains(ARRAY234, 2L)).isTrue(); + assertThat(Longs.contains(ARRAY234, 3L)).isTrue(); + assertThat(Longs.contains(ARRAY234, 4L)).isTrue(); } public void testIndexOf() { - assertEquals(-1, Longs.indexOf(EMPTY, (long) 1)); - assertEquals(-1, Longs.indexOf(ARRAY1, (long) 2)); - assertEquals(-1, Longs.indexOf(ARRAY234, (long) 1)); - assertEquals(0, Longs.indexOf(new long[] {(long) -1}, (long) -1)); - assertEquals(0, Longs.indexOf(ARRAY234, (long) 2)); - assertEquals(1, Longs.indexOf(ARRAY234, (long) 3)); - assertEquals(2, Longs.indexOf(ARRAY234, (long) 4)); - assertEquals(1, Longs.indexOf(new long[] {(long) 2, (long) 3, (long) 2, (long) 3}, (long) 3)); + assertThat(Longs.indexOf(EMPTY, 1L)).isEqualTo(-1); + assertThat(Longs.indexOf(ARRAY1, 2L)).isEqualTo(-1); + assertThat(Longs.indexOf(ARRAY234, 1L)).isEqualTo(-1); + assertThat(Longs.indexOf(new long[] {-1L}, -1L)).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, 2L)).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, 3L)).isEqualTo(1); + assertThat(Longs.indexOf(ARRAY234, 4L)).isEqualTo(2); + assertThat(Longs.indexOf(new long[] {2L, 3L, 2L, 3L}, 3L)).isEqualTo(1); } public void testIndexOf_arrayTarget() { - assertEquals(0, Longs.indexOf(EMPTY, EMPTY)); - assertEquals(0, Longs.indexOf(ARRAY234, EMPTY)); - assertEquals(-1, Longs.indexOf(EMPTY, ARRAY234)); - assertEquals(-1, Longs.indexOf(ARRAY234, ARRAY1)); - assertEquals(-1, Longs.indexOf(ARRAY1, ARRAY234)); - assertEquals(0, Longs.indexOf(ARRAY1, ARRAY1)); - assertEquals(0, Longs.indexOf(ARRAY234, ARRAY234)); - assertEquals(0, Longs.indexOf(ARRAY234, new long[] {(long) 2, (long) 3})); - assertEquals(1, Longs.indexOf(ARRAY234, new long[] {(long) 3, (long) 4})); - assertEquals(1, Longs.indexOf(ARRAY234, new long[] {(long) 3})); - assertEquals(2, Longs.indexOf(ARRAY234, new long[] {(long) 4})); - assertEquals( - 1, - Longs.indexOf( - new long[] {(long) 2, (long) 3, (long) 3, (long) 3, (long) 3}, new long[] {(long) 3})); - assertEquals( - 2, - Longs.indexOf( - new long[] {(long) 2, (long) 3, (long) 2, (long) 3, (long) 4, (long) 2, (long) 3}, - new long[] {(long) 2, (long) 3, (long) 4})); - assertEquals( - 1, - Longs.indexOf( - new long[] {(long) 2, (long) 2, (long) 3, (long) 4, (long) 2, (long) 3, (long) 4}, - new long[] {(long) 2, (long) 3, (long) 4})); - assertEquals( - -1, - Longs.indexOf( - new long[] {(long) 4, (long) 3, (long) 2}, new long[] {(long) 2, (long) 3, (long) 4})); + assertThat(Longs.indexOf(EMPTY, EMPTY)).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, EMPTY)).isEqualTo(0); + assertThat(Longs.indexOf(EMPTY, ARRAY234)).isEqualTo(-1); + assertThat(Longs.indexOf(ARRAY234, ARRAY1)).isEqualTo(-1); + assertThat(Longs.indexOf(ARRAY1, ARRAY234)).isEqualTo(-1); + assertThat(Longs.indexOf(ARRAY1, ARRAY1)).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, ARRAY234)).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, new long[] {2L, 3L})).isEqualTo(0); + assertThat(Longs.indexOf(ARRAY234, new long[] {3L, 4L})).isEqualTo(1); + assertThat(Longs.indexOf(ARRAY234, new long[] {3L})).isEqualTo(1); + assertThat(Longs.indexOf(ARRAY234, new long[] {4L})).isEqualTo(2); + assertThat(Longs.indexOf(new long[] {2L, 3L, 3L, 3L, 3L}, new long[] {3L})).isEqualTo(1); + assertThat(Longs.indexOf(new long[] {2L, 3L, 2L, 3L, 4L, 2L, 3L}, new long[] {2L, 3L, 4L})) + .isEqualTo(2); + assertThat(Longs.indexOf(new long[] {2L, 2L, 3L, 4L, 2L, 3L, 4L}, new long[] {2L, 3L, 4L})) + .isEqualTo(1); + assertThat(Longs.indexOf(new long[] {4L, 3L, 2L}, new long[] {2L, 3L, 4L})).isEqualTo(-1); } public void testLastIndexOf() { - assertEquals(-1, Longs.lastIndexOf(EMPTY, (long) 1)); - assertEquals(-1, Longs.lastIndexOf(ARRAY1, (long) 2)); - assertEquals(-1, Longs.lastIndexOf(ARRAY234, (long) 1)); - assertEquals(0, Longs.lastIndexOf(new long[] {(long) -1}, (long) -1)); - assertEquals(0, Longs.lastIndexOf(ARRAY234, (long) 2)); - assertEquals(1, Longs.lastIndexOf(ARRAY234, (long) 3)); - assertEquals(2, Longs.lastIndexOf(ARRAY234, (long) 4)); - assertEquals( - 3, Longs.lastIndexOf(new long[] {(long) 2, (long) 3, (long) 2, (long) 3}, (long) 3)); + assertThat(Longs.lastIndexOf(EMPTY, 1L)).isEqualTo(-1); + assertThat(Longs.lastIndexOf(ARRAY1, 2L)).isEqualTo(-1); + assertThat(Longs.lastIndexOf(ARRAY234, 1L)).isEqualTo(-1); + assertThat(Longs.lastIndexOf(new long[] {-1L}, -1L)).isEqualTo(0); + assertThat(Longs.lastIndexOf(ARRAY234, 2L)).isEqualTo(0); + assertThat(Longs.lastIndexOf(ARRAY234, 3L)).isEqualTo(1); + assertThat(Longs.lastIndexOf(ARRAY234, 4L)).isEqualTo(2); + assertThat(Longs.lastIndexOf(new long[] {2L, 3L, 2L, 3L}, 3L)).isEqualTo(3); } public void testMax_noArgs() { - try { - Longs.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(MIN_VALUE, Longs.max(MIN_VALUE)); - assertEquals(MAX_VALUE, Longs.max(MAX_VALUE)); - assertEquals( - (long) 9, Longs.max((long) 8, (long) 6, (long) 7, (long) 5, (long) 3, (long) 0, (long) 9)); + assertThat(max(MIN_VALUE)).isEqualTo(MIN_VALUE); + assertThat(max(MAX_VALUE)).isEqualTo(MAX_VALUE); + assertThat(max(8L, 6L, 7L, 5L, 3L, 0L, 9L)).isEqualTo(9L); } public void testMin_noArgs() { - try { - Longs.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(MIN_VALUE, Longs.min(MIN_VALUE)); - assertEquals(MAX_VALUE, Longs.min(MAX_VALUE)); - assertEquals( - (long) 0, Longs.min((long) 8, (long) 6, (long) 7, (long) 5, (long) 3, (long) 0, (long) 9)); + assertThat(min(MIN_VALUE)).isEqualTo(MIN_VALUE); + assertThat(min(MAX_VALUE)).isEqualTo(MAX_VALUE); + assertThat(min(8L, 6L, 7L, 5L, 3L, 0L, 9L)).isEqualTo(0L); } public void testConstrainToRange() { - assertEquals((long) 1, Longs.constrainToRange((long) 1, (long) 0, (long) 5)); - assertEquals((long) 1, Longs.constrainToRange((long) 1, (long) 1, (long) 5)); - assertEquals((long) 3, Longs.constrainToRange((long) 1, (long) 3, (long) 5)); - assertEquals((long) -1, Longs.constrainToRange((long) 0, (long) -5, (long) -1)); - assertEquals((long) 2, Longs.constrainToRange((long) 5, (long) 2, (long) 2)); + assertThat(Longs.constrainToRange(1L, 0L, 5L)).isEqualTo(1L); + assertThat(Longs.constrainToRange(1L, 1L, 5L)).isEqualTo(1L); + assertThat(Longs.constrainToRange(1L, 3L, 5L)).isEqualTo(3L); + assertThat(Longs.constrainToRange(0L, -5L, -1L)).isEqualTo(-1L); + assertThat(Longs.constrainToRange(5L, 2L, 2L)).isEqualTo(2L); + assertThrows(IllegalArgumentException.class, () -> Longs.constrainToRange(1L, 3L, 2L)); + } + + public void testConcat() { + assertThat(Longs.concat()).isEqualTo(EMPTY); + assertThat(Longs.concat(EMPTY)).isEqualTo(EMPTY); + assertThat(Longs.concat(EMPTY, EMPTY, EMPTY)).isEqualTo(EMPTY); + assertThat(Longs.concat(ARRAY1)).isEqualTo(ARRAY1); + assertThat(Longs.concat(ARRAY1)).isNotSameInstanceAs(ARRAY1); + assertThat(Longs.concat(EMPTY, ARRAY1, EMPTY)).isEqualTo(ARRAY1); + assertThat(Longs.concat(ARRAY1, ARRAY1, ARRAY1)).isEqualTo(new long[] {1L, 1L, 1L}); + assertThat(Longs.concat(ARRAY1, ARRAY234)).isEqualTo(new long[] {1L, 2L, 3L, 4L}); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_negative() { + int dim1 = 1 << 16; + int dim2 = 1 << 15; + assertThat(dim1 * dim2).isLessThan(0); + testConcatOverflow(dim1, dim2); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_nonNegative() { + int dim1 = 1 << 16; + int dim2 = 1 << 16; + assertThat(dim1 * dim2).isAtLeast(0); + testConcatOverflow(dim1, dim2); + } + + private static void testConcatOverflow(int arraysDim1, int arraysDim2) { + assertThat((long) arraysDim1 * arraysDim2).isNotEqualTo((long) (arraysDim1 * arraysDim2)); + + long[][] arrays = new long[arraysDim1][]; + // it's shared to avoid using too much memory in tests + long[] sharedArray = new long[arraysDim2]; + Arrays.fill(arrays, sharedArray); + try { - Longs.constrainToRange((long) 1, (long) 3, (long) 2); + Longs.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } - public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Longs.concat())); - assertTrue(Arrays.equals(EMPTY, Longs.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Longs.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY1, Longs.concat(ARRAY1))); - assertNotSame(ARRAY1, Longs.concat(ARRAY1)); - assertTrue(Arrays.equals(ARRAY1, Longs.concat(EMPTY, ARRAY1, EMPTY))); - assertTrue( - Arrays.equals( - new long[] {(long) 1, (long) 1, (long) 1}, Longs.concat(ARRAY1, ARRAY1, ARRAY1))); - assertTrue( - Arrays.equals( - new long[] {(long) 1, (long) 2, (long) 3, (long) 4}, Longs.concat(ARRAY1, ARRAY234))); - } - private static void assertByteArrayEquals(byte[] expected, byte[] actual) { - assertTrue( - "Expected: " + Arrays.toString(expected) + ", but got: " + Arrays.toString(actual), - Arrays.equals(expected, actual)); + assertWithMessage( + "Expected: " + Arrays.toString(expected) + ", but got: " + Arrays.toString(actual)) + .that(Arrays.equals(expected, actual)) + .isTrue(); } public void testToByteArray() { @@ -206,49 +220,46 @@ public void testToByteArray() { } public void testFromByteArray() { - assertEquals( - 0x1213141516171819L, - Longs.fromByteArray(new byte[] {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x33})); - assertEquals( - 0xFFEEDDCCBBAA9988L, - Longs.fromByteArray( - new byte[] { - (byte) 0xFF, (byte) 0xEE, (byte) 0xDD, (byte) 0xCC, - (byte) 0xBB, (byte) 0xAA, (byte) 0x99, (byte) 0x88 - })); + assertThat( + Longs.fromByteArray(new byte[] {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x33})) + .isEqualTo(0x1213141516171819L); + assertThat( + Longs.fromByteArray( + new byte[] { + (byte) 0xFF, (byte) 0xEE, (byte) 0xDD, (byte) 0xCC, + (byte) 0xBB, (byte) 0xAA, (byte) 0x99, (byte) 0x88 + })) + .isEqualTo(0xFFEEDDCCBBAA9988L); } public void testFromByteArrayFails() { - try { - Longs.fromByteArray(new byte[Longs.BYTES - 1]); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> Longs.fromByteArray(new byte[Longs.BYTES - 1])); } public void testFromBytes() { - assertEquals( - 0x1213141516171819L, - Longs.fromBytes( - (byte) 0x12, - (byte) 0x13, - (byte) 0x14, - (byte) 0x15, - (byte) 0x16, - (byte) 0x17, - (byte) 0x18, - (byte) 0x19)); - assertEquals( - 0xFFEEDDCCBBAA9988L, - Longs.fromBytes( - (byte) 0xFF, - (byte) 0xEE, - (byte) 0xDD, - (byte) 0xCC, - (byte) 0xBB, - (byte) 0xAA, - (byte) 0x99, - (byte) 0x88)); + assertThat( + Longs.fromBytes( + (byte) 0x12, + (byte) 0x13, + (byte) 0x14, + (byte) 0x15, + (byte) 0x16, + (byte) 0x17, + (byte) 0x18, + (byte) 0x19)) + .isEqualTo(0x1213141516171819L); + assertThat( + Longs.fromBytes( + (byte) 0xFF, + (byte) 0xEE, + (byte) 0xDD, + (byte) 0xCC, + (byte) 0xBB, + (byte) 0xAA, + (byte) 0x99, + (byte) 0x88)) + .isEqualTo(0xFFEEDDCCBBAA9988L); } public void testByteArrayRoundTrips() { @@ -257,42 +268,31 @@ public void testByteArrayRoundTrips() { for (int i = 0; i < 1000; i++) { long num = r.nextLong(); - assertEquals(num, Longs.fromByteArray(Longs.toByteArray(num))); + assertThat(Longs.fromByteArray(Longs.toByteArray(num))).isEqualTo(num); r.nextBytes(b); long value = Longs.fromByteArray(b); - assertTrue("" + value, Arrays.equals(b, Longs.toByteArray(value))); + assertWithMessage("" + value).that(Arrays.equals(b, Longs.toByteArray(value))).isTrue(); } } public void testEnsureCapacity() { - assertSame(EMPTY, Longs.ensureCapacity(EMPTY, 0, 1)); - assertSame(ARRAY1, Longs.ensureCapacity(ARRAY1, 0, 1)); - assertSame(ARRAY1, Longs.ensureCapacity(ARRAY1, 1, 1)); - assertTrue( - Arrays.equals( - new long[] {(long) 1, (long) 0, (long) 0}, Longs.ensureCapacity(ARRAY1, 2, 1))); + assertThat(Longs.ensureCapacity(EMPTY, 0, 1)).isSameInstanceAs(EMPTY); + assertThat(Longs.ensureCapacity(ARRAY1, 0, 1)).isSameInstanceAs(ARRAY1); + assertThat(Longs.ensureCapacity(ARRAY1, 1, 1)).isSameInstanceAs(ARRAY1); + assertThat(Longs.ensureCapacity(ARRAY1, 2, 1)).isEqualTo(new long[] {1L, 0L, 0L}); } public void testEnsureCapacity_fail() { - try { - Longs.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - // notice that this should even fail when no growth was needed - Longs.ensureCapacity(ARRAY1, 1, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Longs.ensureCapacity(ARRAY1, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> Longs.ensureCapacity(ARRAY1, 1, -1)); } public void testJoin() { - assertEquals("", Longs.join(",", EMPTY)); - assertEquals("1", Longs.join(",", ARRAY1)); - assertEquals("1,2", Longs.join(",", (long) 1, (long) 2)); - assertEquals("123", Longs.join("", (long) 1, (long) 2, (long) 3)); + assertThat(Longs.join(",", EMPTY)).isEmpty(); + assertThat(Longs.join(",", ARRAY1)).isEqualTo("1"); + assertThat(Longs.join(",", 1L, 2L)).isEqualTo("1,2"); + assertThat(Longs.join("", 1L, 2L, 3L)).isEqualTo("123"); } public void testLexicographicalComparator() { @@ -301,10 +301,10 @@ public void testLexicographicalComparator() { new long[] {}, new long[] {MIN_VALUE}, new long[] {MIN_VALUE, MIN_VALUE}, - new long[] {MIN_VALUE, (long) 1}, - new long[] {(long) 1}, - new long[] {(long) 1, MIN_VALUE}, - new long[] {MAX_VALUE, MAX_VALUE - (long) 1}, + new long[] {MIN_VALUE, 1L}, + new long[] {1L}, + new long[] {1L, MIN_VALUE}, + new long[] {MAX_VALUE, MAX_VALUE - 1L}, new long[] {MAX_VALUE, MAX_VALUE}, new long[] {MAX_VALUE, MAX_VALUE, MAX_VALUE}); @@ -312,10 +312,11 @@ public void testLexicographicalComparator() { Helpers.testComparator(comparator, ordered); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testLexicographicalComparatorSerializable() { Comparator comparator = Longs.lexicographicalComparator(); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); } public void testReverse() { @@ -329,13 +330,13 @@ public void testReverse() { private static void testReverse(long[] input, long[] expectedOutput) { input = Arrays.copyOf(input, input.length); Longs.reverse(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testReverse(long[] input, int fromIndex, int toIndex, long[] expectedOutput) { input = Arrays.copyOf(input, input.length); Longs.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testReverseIndexed() { @@ -347,6 +348,103 @@ public void testReverseIndexed() { testReverse(new long[] {-1, 1, -2, 2}, 1, 3, new long[] {-1, -2, 1, 2}); } + private static void testRotate(long[] input, int distance, long[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Longs.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + long[] input, int distance, int fromIndex, int toIndex, long[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Longs.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new long[] {}, -1, new long[] {}); + testRotate(new long[] {}, 0, new long[] {}); + testRotate(new long[] {}, 1, new long[] {}); + + testRotate(new long[] {1}, -2, new long[] {1}); + testRotate(new long[] {1}, -1, new long[] {1}); + testRotate(new long[] {1}, 0, new long[] {1}); + testRotate(new long[] {1}, 1, new long[] {1}); + testRotate(new long[] {1}, 2, new long[] {1}); + + testRotate(new long[] {1, 2}, -3, new long[] {2, 1}); + testRotate(new long[] {1, 2}, -1, new long[] {2, 1}); + testRotate(new long[] {1, 2}, -2, new long[] {1, 2}); + testRotate(new long[] {1, 2}, 0, new long[] {1, 2}); + testRotate(new long[] {1, 2}, 1, new long[] {2, 1}); + testRotate(new long[] {1, 2}, 2, new long[] {1, 2}); + testRotate(new long[] {1, 2}, 3, new long[] {2, 1}); + + testRotate(new long[] {1, 2, 3}, -5, new long[] {3, 1, 2}); + testRotate(new long[] {1, 2, 3}, -4, new long[] {2, 3, 1}); + testRotate(new long[] {1, 2, 3}, -3, new long[] {1, 2, 3}); + testRotate(new long[] {1, 2, 3}, -2, new long[] {3, 1, 2}); + testRotate(new long[] {1, 2, 3}, -1, new long[] {2, 3, 1}); + testRotate(new long[] {1, 2, 3}, 0, new long[] {1, 2, 3}); + testRotate(new long[] {1, 2, 3}, 1, new long[] {3, 1, 2}); + testRotate(new long[] {1, 2, 3}, 2, new long[] {2, 3, 1}); + testRotate(new long[] {1, 2, 3}, 3, new long[] {1, 2, 3}); + testRotate(new long[] {1, 2, 3}, 4, new long[] {3, 1, 2}); + testRotate(new long[] {1, 2, 3}, 5, new long[] {2, 3, 1}); + + testRotate(new long[] {1, 2, 3, 4}, -9, new long[] {2, 3, 4, 1}); + testRotate(new long[] {1, 2, 3, 4}, -5, new long[] {2, 3, 4, 1}); + testRotate(new long[] {1, 2, 3, 4}, -1, new long[] {2, 3, 4, 1}); + testRotate(new long[] {1, 2, 3, 4}, 0, new long[] {1, 2, 3, 4}); + testRotate(new long[] {1, 2, 3, 4}, 1, new long[] {4, 1, 2, 3}); + testRotate(new long[] {1, 2, 3, 4}, 5, new long[] {4, 1, 2, 3}); + testRotate(new long[] {1, 2, 3, 4}, 9, new long[] {4, 1, 2, 3}); + + testRotate(new long[] {1, 2, 3, 4, 5}, -6, new long[] {2, 3, 4, 5, 1}); + testRotate(new long[] {1, 2, 3, 4, 5}, -4, new long[] {5, 1, 2, 3, 4}); + testRotate(new long[] {1, 2, 3, 4, 5}, -3, new long[] {4, 5, 1, 2, 3}); + testRotate(new long[] {1, 2, 3, 4, 5}, -1, new long[] {2, 3, 4, 5, 1}); + testRotate(new long[] {1, 2, 3, 4, 5}, 0, new long[] {1, 2, 3, 4, 5}); + testRotate(new long[] {1, 2, 3, 4, 5}, 1, new long[] {5, 1, 2, 3, 4}); + testRotate(new long[] {1, 2, 3, 4, 5}, 3, new long[] {3, 4, 5, 1, 2}); + testRotate(new long[] {1, 2, 3, 4, 5}, 4, new long[] {2, 3, 4, 5, 1}); + testRotate(new long[] {1, 2, 3, 4, 5}, 6, new long[] {5, 1, 2, 3, 4}); + } + + public void testRotateIndexed() { + testRotate(new long[] {}, 0, 0, 0, new long[] {}); + + testRotate(new long[] {1}, 0, 0, 1, new long[] {1}); + testRotate(new long[] {1}, 1, 0, 1, new long[] {1}); + testRotate(new long[] {1}, 1, 1, 1, new long[] {1}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -6, 1, 6, new long[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -1, 1, 6, new long[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 0, 1, 6, new long[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 5, 1, 6, new long[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 14, 1, 6, new long[] {0, 2, 3, 4, 5, 1, 6}); + + // Rotate the first three elements + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -2, 0, 3, new long[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -1, 0, 3, new long[] {1, 2, 0, 3, 4, 5, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 0, 0, 3, new long[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 1, 0, 3, new long[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 2, 0, 3, new long[] {1, 2, 0, 3, 4, 5, 6}); + + // Rotate the last four elements + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -6, 3, 7, new long[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -5, 3, 7, new long[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -4, 3, 7, new long[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -3, 3, 7, new long[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -2, 3, 7, new long[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, -1, 3, 7, new long[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 0, 3, 7, new long[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 1, 3, 7, new long[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 2, 3, 7, new long[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new long[] {0, 1, 2, 3, 4, 5, 6}, 3, 3, 7, new long[] {0, 1, 2, 4, 5, 6, 3}); + } + public void testSortDescending() { testSortDescending(new long[] {}, new long[] {}); testSortDescending(new long[] {1}, new long[] {1}); @@ -358,14 +456,14 @@ public void testSortDescending() { private static void testSortDescending(long[] input, long[] expectedOutput) { input = Arrays.copyOf(input, input.length); Longs.sortDescending(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testSortDescending( long[] input, int fromIndex, int toIndex, long[] expectedOutput) { input = Arrays.copyOf(input, input.length); Longs.sortDescending(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testSortDescendingIndexed() { @@ -377,6 +475,7 @@ public void testSortDescendingIndexed() { testSortDescending(new long[] {-1, -2, 1, 2}, 1, 3, new long[] {-1, 1, -2, 2}); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testStringConverterSerialization() { SerializableTester.reserializeAndAssert(Longs.stringConverter()); @@ -385,17 +484,17 @@ public void testStringConverterSerialization() { public void testToArray() { // need explicit type parameter to avoid javac warning!? List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Longs.toArray(none))); + assertThat(Longs.toArray(none)).isEqualTo(EMPTY); - List one = Arrays.asList((long) 1); - assertTrue(Arrays.equals(ARRAY1, Longs.toArray(one))); + List one = Arrays.asList(1L); + assertThat(Longs.toArray(one)).isEqualTo(ARRAY1); - long[] array = {(long) 0, (long) 1, 0x0FF1C1AL}; + long[] array = {0L, 1L, 0x0FF1C1AL}; - List three = Arrays.asList((long) 0, (long) 1, 0x0FF1C1AL); - assertTrue(Arrays.equals(array, Longs.toArray(three))); + List three = Arrays.asList(0L, 1L, 0x0FF1C1AL); + assertThat(Longs.toArray(three)).isEqualTo(array); - assertTrue(Arrays.equals(array, Longs.toArray(Longs.asList(array)))); + assertThat(Longs.toArray(Longs.asList(array))).isEqualTo(array); } public void testToArray_threadSafe() { @@ -405,74 +504,74 @@ public void testToArray_threadSafe() { Collection misleadingSize = Helpers.misleadingSizeCollection(delta); misleadingSize.addAll(list); long[] arr = Longs.toArray(misleadingSize); - assertEquals(i, arr.length); + assertThat(arr).hasLength(i); for (int j = 0; j < i; j++) { - assertEquals(VALUES[j], arr[j]); + assertThat(arr[j]).isEqualTo(VALUES[j]); } } } } public void testToArray_withNull() { - List list = Arrays.asList((long) 0, (long) 1, null); - try { - Longs.toArray(list); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Long> list = Arrays.asList(0L, 1L, null); + assertThrows(NullPointerException.class, () -> Longs.toArray(list)); } public void testToArray_withConversion() { - long[] array = {(long) 0, (long) 1, (long) 2}; + long[] array = {0L, 1L, 2L}; List bytes = Arrays.asList((byte) 0, (byte) 1, (byte) 2); List shorts = Arrays.asList((short) 0, (short) 1, (short) 2); List ints = Arrays.asList(0, 1, 2); - List floats = Arrays.asList((float) 0, (float) 1, (float) 2); - List longs = Arrays.asList((long) 0, (long) 1, (long) 2); - List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); + List floats = Arrays.asList(0.0f, 1.0f, 2.0f); + List longs = Arrays.asList(0L, 1L, 2L); + List doubles = Arrays.asList(0.0, 1.0, 2.0); - assertTrue(Arrays.equals(array, Longs.toArray(bytes))); - assertTrue(Arrays.equals(array, Longs.toArray(shorts))); - assertTrue(Arrays.equals(array, Longs.toArray(ints))); - assertTrue(Arrays.equals(array, Longs.toArray(floats))); - assertTrue(Arrays.equals(array, Longs.toArray(longs))); - assertTrue(Arrays.equals(array, Longs.toArray(doubles))); + assertThat(Longs.toArray(bytes)).isEqualTo(array); + assertThat(Longs.toArray(shorts)).isEqualTo(array); + assertThat(Longs.toArray(ints)).isEqualTo(array); + assertThat(Longs.toArray(floats)).isEqualTo(array); + assertThat(Longs.toArray(longs)).isEqualTo(array); + assertThat(Longs.toArray(doubles)).isEqualTo(array); } + @J2ktIncompatible // b/239034072: Kotlin varargs copy parameter arrays. public void testAsList_isAView() { - long[] array = {(long) 0, (long) 1}; + long[] array = {0L, 1L}; List list = Longs.asList(array); - list.set(0, (long) 2); - assertTrue(Arrays.equals(new long[] {(long) 2, (long) 1}, array)); - array[1] = (long) 3; - assertEquals(Arrays.asList((long) 2, (long) 3), list); + list.set(0, 2L); + assertThat(array).isEqualTo(new long[] {2L, 1L}); + array[1] = 3L; + assertThat(list).containsExactly(2L, 3L).inOrder(); } public void testAsList_toArray_roundTrip() { - long[] array = {(long) 0, (long) 1, (long) 2}; + long[] array = {0L, 1L, 2L}; List list = Longs.asList(array); long[] newArray = Longs.toArray(list); // Make sure it returned a copy - list.set(0, (long) 4); - assertTrue(Arrays.equals(new long[] {(long) 0, (long) 1, (long) 2}, newArray)); - newArray[1] = (long) 5; - assertEquals((long) 1, (long) list.get(1)); + list.set(0, 4L); + assertThat(newArray).isEqualTo(new long[] {0L, 1L, 2L}); + newArray[1] = 5L; + assertThat((long) list.get(1)).isEqualTo(1L); } // This test stems from a real bug found by andrewk public void testAsList_subList_toArray_roundTrip() { - long[] array = {(long) 0, (long) 1, (long) 2, (long) 3}; + long[] array = {0L, 1L, 2L, 3L}; List list = Longs.asList(array); - assertTrue(Arrays.equals(new long[] {(long) 1, (long) 2}, Longs.toArray(list.subList(1, 3)))); - assertTrue(Arrays.equals(new long[] {}, Longs.toArray(list.subList(2, 2)))); + assertThat(Longs.toArray(list.subList(1, 3))).isEqualTo(new long[] {1L, 2L}); + assertThat(Longs.toArray(list.subList(2, 2))).isEqualTo(new long[] {}); } + // `primitives` can't depend on `collect`, so this is what the prod code has to return. + @SuppressWarnings("EmptyList") public void testAsListEmpty() { - assertSame(Collections.emptyList(), Longs.asList(EMPTY)); + assertThat(Longs.asList(EMPTY)).isSameInstanceAs(Collections.emptyList()); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Longs.class); @@ -480,40 +579,37 @@ public void testNulls() { public void testStringConverter_convert() { Converter converter = Longs.stringConverter(); - assertEquals((Long) 1L, converter.convert("1")); - assertEquals((Long) 0L, converter.convert("0")); - assertEquals((Long) (-1L), converter.convert("-1")); - assertEquals((Long) 255L, converter.convert("0xff")); - assertEquals((Long) 255L, converter.convert("0xFF")); - assertEquals((Long) (-255L), converter.convert("-0xFF")); - assertEquals((Long) 255L, converter.convert("#0000FF")); - assertEquals((Long) 438L, converter.convert("0666")); + assertThat(converter.convert("1")).isEqualTo(1L); + assertThat(converter.convert("0")).isEqualTo(0L); + assertThat(converter.convert("-1")).isEqualTo(-1L); + assertThat(converter.convert("0xff")).isEqualTo(255L); + assertThat(converter.convert("0xFF")).isEqualTo(255L); + assertThat(converter.convert("-0xFF")).isEqualTo(-255L); + assertThat(converter.convert("#0000FF")).isEqualTo(255L); + assertThat(converter.convert("0666")).isEqualTo(438L); } public void testStringConverter_convertError() { - try { - Longs.stringConverter().convert("notanumber"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> Longs.stringConverter().convert("notanumber")); } public void testStringConverter_nullConversions() { - assertNull(Longs.stringConverter().convert(null)); - assertNull(Longs.stringConverter().reverse().convert(null)); + assertThat(Longs.stringConverter().convert(null)).isNull(); + assertThat(Longs.stringConverter().reverse().convert(null)).isNull(); } public void testStringConverter_reverse() { Converter converter = Longs.stringConverter(); - assertEquals("1", converter.reverse().convert(1L)); - assertEquals("0", converter.reverse().convert(0L)); - assertEquals("-1", converter.reverse().convert(-1L)); - assertEquals("255", converter.reverse().convert(0xffL)); - assertEquals("255", converter.reverse().convert(0xFFL)); - assertEquals("-255", converter.reverse().convert(-0xFFL)); - assertEquals("438", converter.reverse().convert(0666L)); + assertThat(converter.reverse().convert(1L)).isEqualTo("1"); + assertThat(converter.reverse().convert(0L)).isEqualTo("0"); + assertThat(converter.reverse().convert(-1L)).isEqualTo("-1"); + assertThat(converter.reverse().convert(0xffL)).isEqualTo("255"); + assertThat(converter.reverse().convert(0xFFL)).isEqualTo("255"); + assertThat(converter.reverse().convert(-0xFFL)).isEqualTo("-255"); + assertThat(converter.reverse().convert(0666L)).isEqualTo("438"); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testStringConverter_nullPointerTester() throws Exception { NullPointerTester tester = new NullPointerTester(); @@ -529,23 +625,26 @@ public void testTryParse() { tryParseAndAssertEquals(-8900L, "-8900"); tryParseAndAssertEquals(MAX_VALUE, Long.toString(MAX_VALUE)); tryParseAndAssertEquals(MIN_VALUE, Long.toString(MIN_VALUE)); - assertNull(Longs.tryParse("")); - assertNull(Longs.tryParse("-")); - assertNull(Longs.tryParse("+1")); - assertNull(Longs.tryParse("999999999999999999999999")); - assertNull( - "Max long + 1", - Longs.tryParse(BigInteger.valueOf(MAX_VALUE).add(BigInteger.ONE).toString())); - assertNull( - "Max long * 10", - Longs.tryParse(BigInteger.valueOf(MAX_VALUE).multiply(BigInteger.TEN).toString())); - assertNull( - "Min long - 1", - Longs.tryParse(BigInteger.valueOf(MIN_VALUE).subtract(BigInteger.ONE).toString())); - assertNull( - "Min long * 10", - Longs.tryParse(BigInteger.valueOf(MIN_VALUE).multiply(BigInteger.TEN).toString())); - assertNull(Longs.tryParse("\u0662\u06f3")); + assertThat(Longs.tryParse("")).isNull(); + assertThat(Longs.tryParse("-")).isNull(); + assertThat(Longs.tryParse("+1")).isNull(); + assertThat(Longs.tryParse("999999999999999999999999")).isNull(); + assertThat(Longs.tryParse(" ")).isNull(); + assertThat(Longs.tryParse("1 ")).isNull(); + assertThat(Longs.tryParse(" 1")).isNull(); + assertWithMessage("Max long + 1") + .that(Longs.tryParse(BigInteger.valueOf(MAX_VALUE).add(BigInteger.ONE).toString())) + .isNull(); + assertWithMessage("Max long * 10") + .that(Longs.tryParse(BigInteger.valueOf(MAX_VALUE).multiply(BigInteger.TEN).toString())) + .isNull(); + assertWithMessage("Min long - 1") + .that(Longs.tryParse(BigInteger.valueOf(MIN_VALUE).subtract(BigInteger.ONE).toString())) + .isNull(); + assertWithMessage("Min long * 10") + .that(Longs.tryParse(BigInteger.valueOf(MIN_VALUE).multiply(BigInteger.TEN).toString())) + .isNull(); + assertThat(Longs.tryParse("\u0662\u06f3")).isNull(); } /** @@ -553,26 +652,32 @@ public void testTryParse() { * expected. */ private static void tryParseAndAssertEquals(Long expected, String value) { - assertEquals(expected, Longs.tryParse(value)); + assertThat(Longs.tryParse(value)).isEqualTo(expected); } public void testTryParse_radix() { for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { - radixEncodeParseAndAssertEquals((long) 0, radix); - radixEncodeParseAndAssertEquals((long) 8000, radix); - radixEncodeParseAndAssertEquals((long) -8000, radix); + radixEncodeParseAndAssertEquals(0L, radix); + radixEncodeParseAndAssertEquals(8000L, radix); + radixEncodeParseAndAssertEquals(-8000L, radix); radixEncodeParseAndAssertEquals(MAX_VALUE, radix); radixEncodeParseAndAssertEquals(MIN_VALUE, radix); - assertNull("Radix: " + radix, Longs.tryParse("999999999999999999999999", radix)); - assertNull( - "Radix: " + radix, - Longs.tryParse(BigInteger.valueOf(MAX_VALUE).add(BigInteger.ONE).toString(), radix)); - assertNull( - "Radix: " + radix, - Longs.tryParse(BigInteger.valueOf(MIN_VALUE).subtract(BigInteger.ONE).toString(), radix)); + assertWithMessage("Radix: " + radix) + .that(Longs.tryParse("999999999999999999999999", radix)) + .isNull(); + assertWithMessage("Radix: " + radix) + .that(Longs.tryParse(BigInteger.valueOf(MAX_VALUE).add(BigInteger.ONE).toString(), radix)) + .isNull(); + assertWithMessage("Radix: " + radix) + .that( + Longs.tryParse( + BigInteger.valueOf(MIN_VALUE).subtract(BigInteger.ONE).toString(), radix)) + .isNull(); } - assertNull("Hex string and dec parm", Longs.tryParse("FFFF", 10)); - assertEquals("Mixed hex case", 65535, Longs.tryParse("ffFF", 16).longValue()); + assertWithMessage("Hex string and dec parm").that(Longs.tryParse("FFFF", 10)).isNull(); + assertWithMessage("Mixed hex case") + .that(Longs.tryParse("ffFF", 16).longValue()) + .isEqualTo(65535); } /** @@ -580,31 +685,23 @@ public void testTryParse_radix() { * parse the result. Asserts the result is the same as what we started with. */ private static void radixEncodeParseAndAssertEquals(Long value, int radix) { - assertEquals("Radix: " + radix, value, Longs.tryParse(Long.toString(value, radix), radix)); + assertWithMessage("Radix: " + radix) + .that(Longs.tryParse(Long.toString(value, radix), radix)) + .isEqualTo(value); } public void testTryParse_radixTooBig() { - try { - Longs.tryParse("0", Character.MAX_RADIX + 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> Longs.tryParse("0", Character.MAX_RADIX + 1)); } public void testTryParse_radixTooSmall() { - try { - Longs.tryParse("0", Character.MIN_RADIX - 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> Longs.tryParse("0", Character.MIN_RADIX - 1)); } public void testTryParse_withNullGwt() { - assertNull(Longs.tryParse("null")); - try { - Longs.tryParse(null); - fail("Expected NPE"); - } catch (NullPointerException expected) { - } + assertThat(Longs.tryParse("null")).isNull(); + assertThrows(NullPointerException.class, () -> Longs.tryParse(null)); } } diff --git a/android/guava-tests/test/com/google/common/primitives/PackageSanityTests.java b/android/guava-tests/test/com/google/common/primitives/PackageSanityTests.java index 3f3e74539dac..eaa6ba09a51e 100644 --- a/android/guava-tests/test/com/google/common/primitives/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/primitives/PackageSanityTests.java @@ -17,6 +17,7 @@ package com.google.common.primitives; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** * Tests basic sanity for each class in the package. @@ -24,6 +25,7 @@ * @author Ben Yu */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests { public PackageSanityTests() { setDefault(String.class, "string"); diff --git a/android/guava-tests/test/com/google/common/primitives/PrimitivesTest.java b/android/guava-tests/test/com/google/common/primitives/PrimitivesTest.java index 1e09743654b5..f484dcf82693 100644 --- a/android/guava-tests/test/com/google/common/primitives/PrimitivesTest.java +++ b/android/guava-tests/test/com/google/common/primitives/PrimitivesTest.java @@ -16,38 +16,46 @@ package com.google.common.primitives; -import com.google.common.collect.ImmutableSet; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.NullPointerTester; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Primitives}. * * @author Kevin Bourrillion */ +@GwtCompatible +@NullUnmarked public class PrimitivesTest extends TestCase { public void testIsWrapperType() { - assertTrue(Primitives.isWrapperType(Void.class)); - assertFalse(Primitives.isWrapperType(void.class)); + assertThat(Primitives.isWrapperType(Void.class)).isTrue(); + assertThat(Primitives.isWrapperType(void.class)).isFalse(); } public void testWrap() { - assertSame(Integer.class, Primitives.wrap(int.class)); - assertSame(Integer.class, Primitives.wrap(Integer.class)); - assertSame(String.class, Primitives.wrap(String.class)); + assertThat(Primitives.wrap(int.class)).isSameInstanceAs(Integer.class); + assertThat(Primitives.wrap(Integer.class)).isSameInstanceAs(Integer.class); + assertThat(Primitives.wrap(String.class)).isSameInstanceAs(String.class); } public void testUnwrap() { - assertSame(int.class, Primitives.unwrap(Integer.class)); - assertSame(int.class, Primitives.unwrap(int.class)); - assertSame(String.class, Primitives.unwrap(String.class)); + assertThat(Primitives.unwrap(Integer.class)).isSameInstanceAs(int.class); + assertThat(Primitives.unwrap(int.class)).isSameInstanceAs(int.class); + assertThat(Primitives.unwrap(String.class)).isSameInstanceAs(String.class); } public void testAllPrimitiveTypes() { Set> primitives = Primitives.allPrimitiveTypes(); - assertEquals( - ImmutableSet.of( + assertThat(primitives) + .containsExactly( boolean.class, byte.class, char.class, @@ -56,20 +64,15 @@ public void testAllPrimitiveTypes() { int.class, long.class, short.class, - void.class), - primitives); + void.class); - try { - primitives.remove(boolean.class); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> primitives.remove(boolean.class)); } public void testAllWrapperTypes() { Set> wrappers = Primitives.allWrapperTypes(); - assertEquals( - ImmutableSet.of( + assertThat(wrappers) + .containsExactly( Boolean.class, Byte.class, Character.class, @@ -78,16 +81,13 @@ public void testAllWrapperTypes() { Integer.class, Long.class, Short.class, - Void.class), - wrappers); + Void.class); - try { - wrappers.remove(Boolean.class); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> wrappers.remove(Boolean.class)); } + @GwtIncompatible + @J2ktIncompatible public void testNullPointerExceptions() { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicStaticMethods(Primitives.class); diff --git a/android/guava-tests/test/com/google/common/primitives/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/primitives/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..acbf96b3a571 --- /dev/null +++ b/android/guava-tests/test/com/google/common/primitives/ReflectionFreeAssertThrows.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/primitives/ShortArrayAsListTest.java b/android/guava-tests/test/com/google/common/primitives/ShortArrayAsListTest.java index 9a1fada728d6..3f7d8e445b96 100644 --- a/android/guava-tests/test/com/google/common/primitives/ShortArrayAsListTest.java +++ b/android/guava-tests/test/com/google/common/primitives/ShortArrayAsListTest.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.ListTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; @@ -31,13 +32,16 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Test suite covering {@link Shorts#asList(short[])}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked +@AndroidIncompatible // test-suite builders public class ShortArrayAsListTest extends TestCase { private static List asList(Short[] values) { @@ -48,6 +52,7 @@ private static List asList(Short[] values) { return Shorts.asList(temp); } + @J2ktIncompatible @GwtIncompatible // suite public static Test suite() { List> builders = diff --git a/android/guava-tests/test/com/google/common/primitives/ShortsTest.java b/android/guava-tests/test/com/google/common/primitives/ShortsTest.java index 0816c6973c0d..47940702d343 100644 --- a/android/guava-tests/test/com/google/common/primitives/ShortsTest.java +++ b/android/guava-tests/test/com/google/common/primitives/ShortsTest.java @@ -16,8 +16,15 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.primitives.Shorts.max; +import static com.google.common.primitives.Shorts.min; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; @@ -29,14 +36,16 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link Shorts}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless +@NullMarked +@GwtCompatible public class ShortsTest extends TestCase { private static final short[] EMPTY = {}; private static final short[] ARRAY1 = {(short) 1}; @@ -47,15 +56,17 @@ public class ShortsTest extends TestCase { private static final short[] VALUES = {LEAST, (short) -1, (short) 0, (short) 1, GREATEST}; + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testHashCode() { for (short value : VALUES) { - assertEquals(((Short) value).hashCode(), Shorts.hashCode(value)); + assertThat(Shorts.hashCode(value)).isEqualTo(Short.hashCode(value)); } } public void testCheckedCast() { for (short value : VALUES) { - assertEquals(value, Shorts.checkedCast((long) value)); + assertThat(Shorts.checkedCast((long) value)).isEqualTo(value); } assertCastFails(GREATEST + 1L); assertCastFails(LEAST - 1L); @@ -65,12 +76,12 @@ public void testCheckedCast() { public void testSaturatedCast() { for (short value : VALUES) { - assertEquals(value, Shorts.saturatedCast((long) value)); + assertThat(Shorts.saturatedCast((long) value)).isEqualTo(value); } - assertEquals(GREATEST, Shorts.saturatedCast(GREATEST + 1L)); - assertEquals(LEAST, Shorts.saturatedCast(LEAST - 1L)); - assertEquals(GREATEST, Shorts.saturatedCast(Long.MAX_VALUE)); - assertEquals(LEAST, Shorts.saturatedCast(Long.MIN_VALUE)); + assertThat(Shorts.saturatedCast(GREATEST + 1L)).isEqualTo(GREATEST); + assertThat(Shorts.saturatedCast(LEAST - 1L)).isEqualTo(LEAST); + assertThat(Shorts.saturatedCast(Long.MAX_VALUE)).isEqualTo(GREATEST); + assertThat(Shorts.saturatedCast(Long.MIN_VALUE)).isEqualTo(LEAST); } private static void assertCastFails(long value) { @@ -78,192 +89,209 @@ private static void assertCastFails(long value) { Shorts.checkedCast(value); fail("Cast to short should have failed: " + value); } catch (IllegalArgumentException ex) { - assertTrue( - value + " not found in exception text: " + ex.getMessage(), - ex.getMessage().contains(String.valueOf(value))); + assertWithMessage(value + " not found in exception text: " + ex.getMessage()) + .that(ex.getMessage().contains(String.valueOf(value))) + .isTrue(); } } + // We need to test that our method behaves like the JDK method. + @SuppressWarnings("InlineMeInliner") public void testCompare() { for (short x : VALUES) { for (short y : VALUES) { - // Only compare the sign of the result of compareTo(). - int expected = Short.valueOf(x).compareTo(y); + // Only compare the sign of the result of compare(). + int expected = Short.compare(x, y); int actual = Shorts.compare(x, y); if (expected == 0) { - assertEquals(x + ", " + y, expected, actual); + assertWithMessage(x + ", " + y).that(actual).isEqualTo(expected); } else if (expected < 0) { - assertTrue( - x + ", " + y + " (expected: " + expected + ", actual" + actual + ")", actual < 0); + assertWithMessage(x + ", " + y + " (expected: " + expected + ", actual" + actual + ")") + .that(actual < 0) + .isTrue(); } else { - assertTrue( - x + ", " + y + " (expected: " + expected + ", actual" + actual + ")", actual > 0); + assertWithMessage(x + ", " + y + " (expected: " + expected + ", actual" + actual + ")") + .that(actual > 0) + .isTrue(); } } } } public void testContains() { - assertFalse(Shorts.contains(EMPTY, (short) 1)); - assertFalse(Shorts.contains(ARRAY1, (short) 2)); - assertFalse(Shorts.contains(ARRAY234, (short) 1)); - assertTrue(Shorts.contains(new short[] {(short) -1}, (short) -1)); - assertTrue(Shorts.contains(ARRAY234, (short) 2)); - assertTrue(Shorts.contains(ARRAY234, (short) 3)); - assertTrue(Shorts.contains(ARRAY234, (short) 4)); + assertThat(Shorts.contains(EMPTY, (short) 1)).isFalse(); + assertThat(Shorts.contains(ARRAY1, (short) 2)).isFalse(); + assertThat(Shorts.contains(ARRAY234, (short) 1)).isFalse(); + assertThat(Shorts.contains(new short[] {(short) -1}, (short) -1)).isTrue(); + assertThat(Shorts.contains(ARRAY234, (short) 2)).isTrue(); + assertThat(Shorts.contains(ARRAY234, (short) 3)).isTrue(); + assertThat(Shorts.contains(ARRAY234, (short) 4)).isTrue(); } public void testIndexOf() { - assertEquals(-1, Shorts.indexOf(EMPTY, (short) 1)); - assertEquals(-1, Shorts.indexOf(ARRAY1, (short) 2)); - assertEquals(-1, Shorts.indexOf(ARRAY234, (short) 1)); - assertEquals(0, Shorts.indexOf(new short[] {(short) -1}, (short) -1)); - assertEquals(0, Shorts.indexOf(ARRAY234, (short) 2)); - assertEquals(1, Shorts.indexOf(ARRAY234, (short) 3)); - assertEquals(2, Shorts.indexOf(ARRAY234, (short) 4)); - assertEquals( - 1, Shorts.indexOf(new short[] {(short) 2, (short) 3, (short) 2, (short) 3}, (short) 3)); + assertThat(Shorts.indexOf(EMPTY, (short) 1)).isEqualTo(-1); + assertThat(Shorts.indexOf(ARRAY1, (short) 2)).isEqualTo(-1); + assertThat(Shorts.indexOf(ARRAY234, (short) 1)).isEqualTo(-1); + assertThat(Shorts.indexOf(new short[] {(short) -1}, (short) -1)).isEqualTo(0); + assertThat(Shorts.indexOf(ARRAY234, (short) 2)).isEqualTo(0); + assertThat(Shorts.indexOf(ARRAY234, (short) 3)).isEqualTo(1); + assertThat(Shorts.indexOf(ARRAY234, (short) 4)).isEqualTo(2); + assertThat(Shorts.indexOf(new short[] {(short) 2, (short) 3, (short) 2, (short) 3}, (short) 3)) + .isEqualTo(1); } public void testIndexOf_arrayTarget() { - assertEquals(0, Shorts.indexOf(EMPTY, EMPTY)); - assertEquals(0, Shorts.indexOf(ARRAY234, EMPTY)); - assertEquals(-1, Shorts.indexOf(EMPTY, ARRAY234)); - assertEquals(-1, Shorts.indexOf(ARRAY234, ARRAY1)); - assertEquals(-1, Shorts.indexOf(ARRAY1, ARRAY234)); - assertEquals(0, Shorts.indexOf(ARRAY1, ARRAY1)); - assertEquals(0, Shorts.indexOf(ARRAY234, ARRAY234)); - assertEquals(0, Shorts.indexOf(ARRAY234, new short[] {(short) 2, (short) 3})); - assertEquals(1, Shorts.indexOf(ARRAY234, new short[] {(short) 3, (short) 4})); - assertEquals(1, Shorts.indexOf(ARRAY234, new short[] {(short) 3})); - assertEquals(2, Shorts.indexOf(ARRAY234, new short[] {(short) 4})); - assertEquals( - 1, - Shorts.indexOf( - new short[] {(short) 2, (short) 3, (short) 3, (short) 3, (short) 3}, - new short[] {(short) 3})); - assertEquals( - 2, - Shorts.indexOf( - new short[] { - (short) 2, (short) 3, (short) 2, (short) 3, (short) 4, (short) 2, (short) 3 - }, - new short[] {(short) 2, (short) 3, (short) 4})); - assertEquals( - 1, - Shorts.indexOf( - new short[] { - (short) 2, (short) 2, (short) 3, (short) 4, (short) 2, (short) 3, (short) 4 - }, - new short[] {(short) 2, (short) 3, (short) 4})); - assertEquals( - -1, - Shorts.indexOf( - new short[] {(short) 4, (short) 3, (short) 2}, - new short[] {(short) 2, (short) 3, (short) 4})); + assertThat(Shorts.indexOf(EMPTY, EMPTY)).isEqualTo(0); + assertThat(Shorts.indexOf(ARRAY234, EMPTY)).isEqualTo(0); + assertThat(Shorts.indexOf(EMPTY, ARRAY234)).isEqualTo(-1); + assertThat(Shorts.indexOf(ARRAY234, ARRAY1)).isEqualTo(-1); + assertThat(Shorts.indexOf(ARRAY1, ARRAY234)).isEqualTo(-1); + assertThat(Shorts.indexOf(ARRAY1, ARRAY1)).isEqualTo(0); + assertThat(Shorts.indexOf(ARRAY234, ARRAY234)).isEqualTo(0); + assertThat(Shorts.indexOf(ARRAY234, new short[] {(short) 2, (short) 3})).isEqualTo(0); + assertThat(Shorts.indexOf(ARRAY234, new short[] {(short) 3, (short) 4})).isEqualTo(1); + assertThat(Shorts.indexOf(ARRAY234, new short[] {(short) 3})).isEqualTo(1); + assertThat(Shorts.indexOf(ARRAY234, new short[] {(short) 4})).isEqualTo(2); + assertThat( + Shorts.indexOf( + new short[] {(short) 2, (short) 3, (short) 3, (short) 3, (short) 3}, + new short[] {(short) 3})) + .isEqualTo(1); + assertThat( + Shorts.indexOf( + new short[] { + (short) 2, (short) 3, (short) 2, (short) 3, (short) 4, (short) 2, (short) 3 + }, + new short[] {(short) 2, (short) 3, (short) 4})) + .isEqualTo(2); + assertThat( + Shorts.indexOf( + new short[] { + (short) 2, (short) 2, (short) 3, (short) 4, (short) 2, (short) 3, (short) 4 + }, + new short[] {(short) 2, (short) 3, (short) 4})) + .isEqualTo(1); + assertThat( + Shorts.indexOf( + new short[] {(short) 4, (short) 3, (short) 2}, + new short[] {(short) 2, (short) 3, (short) 4})) + .isEqualTo(-1); } public void testLastIndexOf() { - assertEquals(-1, Shorts.lastIndexOf(EMPTY, (short) 1)); - assertEquals(-1, Shorts.lastIndexOf(ARRAY1, (short) 2)); - assertEquals(-1, Shorts.lastIndexOf(ARRAY234, (short) 1)); - assertEquals(0, Shorts.lastIndexOf(new short[] {(short) -1}, (short) -1)); - assertEquals(0, Shorts.lastIndexOf(ARRAY234, (short) 2)); - assertEquals(1, Shorts.lastIndexOf(ARRAY234, (short) 3)); - assertEquals(2, Shorts.lastIndexOf(ARRAY234, (short) 4)); - assertEquals( - 3, Shorts.lastIndexOf(new short[] {(short) 2, (short) 3, (short) 2, (short) 3}, (short) 3)); + assertThat(Shorts.lastIndexOf(EMPTY, (short) 1)).isEqualTo(-1); + assertThat(Shorts.lastIndexOf(ARRAY1, (short) 2)).isEqualTo(-1); + assertThat(Shorts.lastIndexOf(ARRAY234, (short) 1)).isEqualTo(-1); + assertThat(Shorts.lastIndexOf(new short[] {(short) -1}, (short) -1)).isEqualTo(0); + assertThat(Shorts.lastIndexOf(ARRAY234, (short) 2)).isEqualTo(0); + assertThat(Shorts.lastIndexOf(ARRAY234, (short) 3)).isEqualTo(1); + assertThat(Shorts.lastIndexOf(ARRAY234, (short) 4)).isEqualTo(2); + assertThat( + Shorts.lastIndexOf(new short[] {(short) 2, (short) 3, (short) 2, (short) 3}, (short) 3)) + .isEqualTo(3); } @GwtIncompatible public void testMax_noArgs() { - try { - Shorts.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(LEAST, Shorts.max(LEAST)); - assertEquals(GREATEST, Shorts.max(GREATEST)); - assertEquals( - (short) 9, - Shorts.max((short) 8, (short) 6, (short) 7, (short) 5, (short) 3, (short) 0, (short) 9)); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat(max((short) 8, (short) 6, (short) 7, (short) 5, (short) 3, (short) 0, (short) 9)) + .isEqualTo((short) 9); } @GwtIncompatible public void testMin_noArgs() { - try { - Shorts.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, Shorts.min(LEAST)); - assertEquals(GREATEST, Shorts.min(GREATEST)); - assertEquals( - (short) 0, - Shorts.min((short) 8, (short) 6, (short) 7, (short) 5, (short) 3, (short) 0, (short) 9)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat(min((short) 8, (short) 6, (short) 7, (short) 5, (short) 3, (short) 0, (short) 9)) + .isEqualTo((short) 0); } public void testConstrainToRange() { - assertEquals((short) 1, Shorts.constrainToRange((short) 1, (short) 0, (short) 5)); - assertEquals((short) 1, Shorts.constrainToRange((short) 1, (short) 1, (short) 5)); - assertEquals((short) 3, Shorts.constrainToRange((short) 1, (short) 3, (short) 5)); - assertEquals((short) -1, Shorts.constrainToRange((short) 0, (short) -5, (short) -1)); - assertEquals((short) 2, Shorts.constrainToRange((short) 5, (short) 2, (short) 2)); + assertThat(Shorts.constrainToRange((short) 1, (short) 0, (short) 5)).isEqualTo((short) 1); + assertThat(Shorts.constrainToRange((short) 1, (short) 1, (short) 5)).isEqualTo((short) 1); + assertThat(Shorts.constrainToRange((short) 1, (short) 3, (short) 5)).isEqualTo((short) 3); + assertThat(Shorts.constrainToRange((short) 0, (short) -5, (short) -1)).isEqualTo((short) -1); + assertThat(Shorts.constrainToRange((short) 5, (short) 2, (short) 2)).isEqualTo((short) 2); + assertThrows( + IllegalArgumentException.class, + () -> Shorts.constrainToRange((short) 1, (short) 3, (short) 2)); + } + + public void testConcat() { + assertThat(Shorts.concat()).isEqualTo(EMPTY); + assertThat(Shorts.concat(EMPTY)).isEqualTo(EMPTY); + assertThat(Shorts.concat(EMPTY, EMPTY, EMPTY)).isEqualTo(EMPTY); + assertThat(Shorts.concat(ARRAY1)).isEqualTo(ARRAY1); + assertThat(Shorts.concat(ARRAY1)).isNotSameInstanceAs(ARRAY1); + assertThat(Shorts.concat(EMPTY, ARRAY1, EMPTY)).isEqualTo(ARRAY1); + assertThat(Shorts.concat(ARRAY1, ARRAY1, ARRAY1)) + .isEqualTo(new short[] {(short) 1, (short) 1, (short) 1}); + assertThat(Shorts.concat(ARRAY1, ARRAY234)) + .isEqualTo(new short[] {(short) 1, (short) 2, (short) 3, (short) 4}); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_negative() { + int dim1 = 1 << 16; + int dim2 = 1 << 15; + assertThat(dim1 * dim2).isLessThan(0); + testConcatOverflow(dim1, dim2); + } + + @GwtIncompatible // different overflow behavior; could probably be made to work by using ~~ + public void testConcat_overflow_nonNegative() { + int dim1 = 1 << 16; + int dim2 = 1 << 16; + assertThat(dim1 * dim2).isAtLeast(0); + testConcatOverflow(dim1, dim2); + } + + private static void testConcatOverflow(int arraysDim1, int arraysDim2) { + assertThat((long) arraysDim1 * arraysDim2).isNotEqualTo((long) (arraysDim1 * arraysDim2)); + + short[][] arrays = new short[arraysDim1][]; + // it's shared to avoid using too much memory in tests + short[] sharedArray = new short[arraysDim2]; + Arrays.fill(arrays, sharedArray); + try { - Shorts.constrainToRange((short) 1, (short) 3, (short) 2); + Shorts.concat(arrays); fail(); } catch (IllegalArgumentException expected) { } } - public void testConcat() { - assertTrue(Arrays.equals(EMPTY, Shorts.concat())); - assertTrue(Arrays.equals(EMPTY, Shorts.concat(EMPTY))); - assertTrue(Arrays.equals(EMPTY, Shorts.concat(EMPTY, EMPTY, EMPTY))); - assertTrue(Arrays.equals(ARRAY1, Shorts.concat(ARRAY1))); - assertNotSame(ARRAY1, Shorts.concat(ARRAY1)); - assertTrue(Arrays.equals(ARRAY1, Shorts.concat(EMPTY, ARRAY1, EMPTY))); - assertTrue( - Arrays.equals( - new short[] {(short) 1, (short) 1, (short) 1}, Shorts.concat(ARRAY1, ARRAY1, ARRAY1))); - assertTrue( - Arrays.equals( - new short[] {(short) 1, (short) 2, (short) 3, (short) 4}, - Shorts.concat(ARRAY1, ARRAY234))); - } - @GwtIncompatible // Shorts.toByteArray public void testToByteArray() { - assertTrue(Arrays.equals(new byte[] {0x23, 0x45}, Shorts.toByteArray((short) 0x2345))); - assertTrue( - Arrays.equals(new byte[] {(byte) 0xFE, (byte) 0xDC}, Shorts.toByteArray((short) 0xFEDC))); + assertThat(Shorts.toByteArray((short) 0x2345)).isEqualTo(new byte[] {0x23, 0x45}); + assertThat(Shorts.toByteArray((short) 0xFEDC)).isEqualTo(new byte[] {(byte) 0xFE, (byte) 0xDC}); } @GwtIncompatible // Shorts.fromByteArray public void testFromByteArray() { - assertEquals((short) 0x2345, Shorts.fromByteArray(new byte[] {0x23, 0x45})); - assertEquals((short) 0xFEDC, Shorts.fromByteArray(new byte[] {(byte) 0xFE, (byte) 0xDC})); + assertThat(Shorts.fromByteArray(new byte[] {0x23, 0x45})).isEqualTo((short) 0x2345); + assertThat(Shorts.fromByteArray(new byte[] {(byte) 0xFE, (byte) 0xDC})) + .isEqualTo((short) 0xFEDC); } @GwtIncompatible // Shorts.fromByteArray public void testFromByteArrayFails() { - try { - Shorts.fromByteArray(new byte[] {0x01}); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Shorts.fromByteArray(new byte[] {0x01})); } @GwtIncompatible // Shorts.fromBytes public void testFromBytes() { - assertEquals((short) 0x2345, Shorts.fromBytes((byte) 0x23, (byte) 0x45)); - assertEquals((short) 0xFEDC, Shorts.fromBytes((byte) 0xFE, (byte) 0xDC)); + assertThat(Shorts.fromBytes((byte) 0x23, (byte) 0x45)).isEqualTo((short) 0x2345); + assertThat(Shorts.fromBytes((byte) 0xFE, (byte) 0xDC)).isEqualTo((short) 0xFEDC); } @GwtIncompatible // Shorts.fromByteArray, Shorts.toByteArray @@ -274,41 +302,31 @@ public void testByteArrayRoundTrips() { // total overkill, but, it takes 0.1 sec so why not... for (int i = 0; i < 10000; i++) { short num = (short) r.nextInt(); - assertEquals(num, Shorts.fromByteArray(Shorts.toByteArray(num))); + assertThat(Shorts.fromByteArray(Shorts.toByteArray(num))).isEqualTo(num); r.nextBytes(b); - assertTrue(Arrays.equals(b, Shorts.toByteArray(Shorts.fromByteArray(b)))); + assertThat(Shorts.toByteArray(Shorts.fromByteArray(b))).isEqualTo(b); } } public void testEnsureCapacity() { - assertSame(EMPTY, Shorts.ensureCapacity(EMPTY, 0, 1)); - assertSame(ARRAY1, Shorts.ensureCapacity(ARRAY1, 0, 1)); - assertSame(ARRAY1, Shorts.ensureCapacity(ARRAY1, 1, 1)); - assertTrue( - Arrays.equals( - new short[] {(short) 1, (short) 0, (short) 0}, Shorts.ensureCapacity(ARRAY1, 2, 1))); + assertThat(Shorts.ensureCapacity(EMPTY, 0, 1)).isSameInstanceAs(EMPTY); + assertThat(Shorts.ensureCapacity(ARRAY1, 0, 1)).isSameInstanceAs(ARRAY1); + assertThat(Shorts.ensureCapacity(ARRAY1, 1, 1)).isSameInstanceAs(ARRAY1); + assertThat(Shorts.ensureCapacity(ARRAY1, 2, 1)) + .isEqualTo(new short[] {(short) 1, (short) 0, (short) 0}); } public void testEnsureCapacity_fail() { - try { - Shorts.ensureCapacity(ARRAY1, -1, 1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - // notice that this should even fail when no growth was needed - Shorts.ensureCapacity(ARRAY1, 1, -1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Shorts.ensureCapacity(ARRAY1, -1, 1)); + assertThrows(IllegalArgumentException.class, () -> Shorts.ensureCapacity(ARRAY1, 1, -1)); } public void testJoin() { - assertEquals("", Shorts.join(",", EMPTY)); - assertEquals("1", Shorts.join(",", ARRAY1)); - assertEquals("1,2", Shorts.join(",", (short) 1, (short) 2)); - assertEquals("123", Shorts.join("", (short) 1, (short) 2, (short) 3)); + assertThat(Shorts.join(",", EMPTY)).isEmpty(); + assertThat(Shorts.join(",", ARRAY1)).isEqualTo("1"); + assertThat(Shorts.join(",", (short) 1, (short) 2)).isEqualTo("1,2"); + assertThat(Shorts.join("", (short) 1, (short) 2, (short) 3)).isEqualTo("123"); } public void testLexicographicalComparator() { @@ -328,10 +346,11 @@ public void testLexicographicalComparator() { Helpers.testComparator(comparator, ordered); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testLexicographicalComparatorSerializable() { Comparator comparator = Shorts.lexicographicalComparator(); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); } public void testReverse() { @@ -345,14 +364,14 @@ public void testReverse() { private static void testReverse(short[] input, short[] expectedOutput) { input = Arrays.copyOf(input, input.length); Shorts.reverse(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testReverse( short[] input, int fromIndex, int toIndex, short[] expectedOutput) { input = Arrays.copyOf(input, input.length); Shorts.reverse(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testReverseIndexed() { @@ -364,6 +383,103 @@ public void testReverseIndexed() { testReverse(new short[] {-1, 1, -2, 2}, 1, 3, new short[] {-1, -2, 1, 2}); } + private static void testRotate(short[] input, int distance, short[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Shorts.rotate(input, distance); + assertThat(input).isEqualTo(expectedOutput); + } + + private static void testRotate( + short[] input, int distance, int fromIndex, int toIndex, short[] expectedOutput) { + input = Arrays.copyOf(input, input.length); + Shorts.rotate(input, distance, fromIndex, toIndex); + assertThat(input).isEqualTo(expectedOutput); + } + + public void testRotate() { + testRotate(new short[] {}, -1, new short[] {}); + testRotate(new short[] {}, 0, new short[] {}); + testRotate(new short[] {}, 1, new short[] {}); + + testRotate(new short[] {1}, -2, new short[] {1}); + testRotate(new short[] {1}, -1, new short[] {1}); + testRotate(new short[] {1}, 0, new short[] {1}); + testRotate(new short[] {1}, 1, new short[] {1}); + testRotate(new short[] {1}, 2, new short[] {1}); + + testRotate(new short[] {1, 2}, -3, new short[] {2, 1}); + testRotate(new short[] {1, 2}, -1, new short[] {2, 1}); + testRotate(new short[] {1, 2}, -2, new short[] {1, 2}); + testRotate(new short[] {1, 2}, 0, new short[] {1, 2}); + testRotate(new short[] {1, 2}, 1, new short[] {2, 1}); + testRotate(new short[] {1, 2}, 2, new short[] {1, 2}); + testRotate(new short[] {1, 2}, 3, new short[] {2, 1}); + + testRotate(new short[] {1, 2, 3}, -5, new short[] {3, 1, 2}); + testRotate(new short[] {1, 2, 3}, -4, new short[] {2, 3, 1}); + testRotate(new short[] {1, 2, 3}, -3, new short[] {1, 2, 3}); + testRotate(new short[] {1, 2, 3}, -2, new short[] {3, 1, 2}); + testRotate(new short[] {1, 2, 3}, -1, new short[] {2, 3, 1}); + testRotate(new short[] {1, 2, 3}, 0, new short[] {1, 2, 3}); + testRotate(new short[] {1, 2, 3}, 1, new short[] {3, 1, 2}); + testRotate(new short[] {1, 2, 3}, 2, new short[] {2, 3, 1}); + testRotate(new short[] {1, 2, 3}, 3, new short[] {1, 2, 3}); + testRotate(new short[] {1, 2, 3}, 4, new short[] {3, 1, 2}); + testRotate(new short[] {1, 2, 3}, 5, new short[] {2, 3, 1}); + + testRotate(new short[] {1, 2, 3, 4}, -9, new short[] {2, 3, 4, 1}); + testRotate(new short[] {1, 2, 3, 4}, -5, new short[] {2, 3, 4, 1}); + testRotate(new short[] {1, 2, 3, 4}, -1, new short[] {2, 3, 4, 1}); + testRotate(new short[] {1, 2, 3, 4}, 0, new short[] {1, 2, 3, 4}); + testRotate(new short[] {1, 2, 3, 4}, 1, new short[] {4, 1, 2, 3}); + testRotate(new short[] {1, 2, 3, 4}, 5, new short[] {4, 1, 2, 3}); + testRotate(new short[] {1, 2, 3, 4}, 9, new short[] {4, 1, 2, 3}); + + testRotate(new short[] {1, 2, 3, 4, 5}, -6, new short[] {2, 3, 4, 5, 1}); + testRotate(new short[] {1, 2, 3, 4, 5}, -4, new short[] {5, 1, 2, 3, 4}); + testRotate(new short[] {1, 2, 3, 4, 5}, -3, new short[] {4, 5, 1, 2, 3}); + testRotate(new short[] {1, 2, 3, 4, 5}, -1, new short[] {2, 3, 4, 5, 1}); + testRotate(new short[] {1, 2, 3, 4, 5}, 0, new short[] {1, 2, 3, 4, 5}); + testRotate(new short[] {1, 2, 3, 4, 5}, 1, new short[] {5, 1, 2, 3, 4}); + testRotate(new short[] {1, 2, 3, 4, 5}, 3, new short[] {3, 4, 5, 1, 2}); + testRotate(new short[] {1, 2, 3, 4, 5}, 4, new short[] {2, 3, 4, 5, 1}); + testRotate(new short[] {1, 2, 3, 4, 5}, 6, new short[] {5, 1, 2, 3, 4}); + } + + public void testRotateIndexed() { + testRotate(new short[] {}, 0, 0, 0, new short[] {}); + + testRotate(new short[] {1}, 0, 0, 1, new short[] {1}); + testRotate(new short[] {1}, 1, 0, 1, new short[] {1}); + testRotate(new short[] {1}, 1, 1, 1, new short[] {1}); + + // Rotate the central 5 elements, leaving the ends as-is + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -6, 1, 6, new short[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -1, 1, 6, new short[] {0, 2, 3, 4, 5, 1, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 0, 1, 6, new short[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 5, 1, 6, new short[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 14, 1, 6, new short[] {0, 2, 3, 4, 5, 1, 6}); + + // Rotate the first three elements + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -2, 0, 3, new short[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -1, 0, 3, new short[] {1, 2, 0, 3, 4, 5, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 0, 0, 3, new short[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 1, 0, 3, new short[] {2, 0, 1, 3, 4, 5, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 2, 0, 3, new short[] {1, 2, 0, 3, 4, 5, 6}); + + // Rotate the last four elements + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -6, 3, 7, new short[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -5, 3, 7, new short[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -4, 3, 7, new short[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -3, 3, 7, new short[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -2, 3, 7, new short[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, -1, 3, 7, new short[] {0, 1, 2, 4, 5, 6, 3}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 0, 3, 7, new short[] {0, 1, 2, 3, 4, 5, 6}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 1, 3, 7, new short[] {0, 1, 2, 6, 3, 4, 5}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 2, 3, 7, new short[] {0, 1, 2, 5, 6, 3, 4}); + testRotate(new short[] {0, 1, 2, 3, 4, 5, 6}, 3, 3, 7, new short[] {0, 1, 2, 4, 5, 6, 3}); + } + public void testSortDescending() { testSortDescending(new short[] {}, new short[] {}); testSortDescending(new short[] {1}, new short[] {1}); @@ -375,14 +491,14 @@ public void testSortDescending() { private static void testSortDescending(short[] input, short[] expectedOutput) { input = Arrays.copyOf(input, input.length); Shorts.sortDescending(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testSortDescending( short[] input, int fromIndex, int toIndex, short[] expectedOutput) { input = Arrays.copyOf(input, input.length); Shorts.sortDescending(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testSortDescendingIndexed() { @@ -394,6 +510,7 @@ public void testSortDescendingIndexed() { testSortDescending(new short[] {-1, -2, 1, 2}, 1, 3, new short[] {-1, 1, -2, 2}); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testStringConverterSerialization() { SerializableTester.reserializeAndAssert(Shorts.stringConverter()); @@ -402,17 +519,17 @@ public void testStringConverterSerialization() { public void testToArray() { // need explicit type parameter to avoid javac warning!? List none = Arrays.asList(); - assertTrue(Arrays.equals(EMPTY, Shorts.toArray(none))); + assertThat(Shorts.toArray(none)).isEqualTo(EMPTY); List one = Arrays.asList((short) 1); - assertTrue(Arrays.equals(ARRAY1, Shorts.toArray(one))); + assertThat(Shorts.toArray(one)).isEqualTo(ARRAY1); short[] array = {(short) 0, (short) 1, (short) 3}; List three = Arrays.asList((short) 0, (short) 1, (short) 3); - assertTrue(Arrays.equals(array, Shorts.toArray(three))); + assertThat(Shorts.toArray(three)).isEqualTo(array); - assertTrue(Arrays.equals(array, Shorts.toArray(Shorts.asList(array)))); + assertThat(Shorts.toArray(Shorts.asList(array))).isEqualTo(array); } public void testToArray_threadSafe() { @@ -422,21 +539,17 @@ public void testToArray_threadSafe() { Collection misleadingSize = Helpers.misleadingSizeCollection(delta); misleadingSize.addAll(list); short[] arr = Shorts.toArray(misleadingSize); - assertEquals(i, arr.length); + assertThat(arr).hasLength(i); for (int j = 0; j < i; j++) { - assertEquals(VALUES[j], arr[j]); + assertThat(arr[j]).isEqualTo(VALUES[j]); } } } } public void testToArray_withNull() { - List list = Arrays.asList((short) 0, (short) 1, null); - try { - Shorts.toArray(list); - fail(); - } catch (NullPointerException expected) { - } + List<@Nullable Short> list = Arrays.asList((short) 0, (short) 1, null); + assertThrows(NullPointerException.class, () -> Shorts.toArray(list)); } public void testToArray_withConversion() { @@ -445,25 +558,26 @@ public void testToArray_withConversion() { List bytes = Arrays.asList((byte) 0, (byte) 1, (byte) 2); List shorts = Arrays.asList((short) 0, (short) 1, (short) 2); List ints = Arrays.asList(0, 1, 2); - List floats = Arrays.asList((float) 0, (float) 1, (float) 2); - List longs = Arrays.asList((long) 0, (long) 1, (long) 2); - List doubles = Arrays.asList((double) 0, (double) 1, (double) 2); + List floats = Arrays.asList(0.0f, 1.0f, 2.0f); + List longs = Arrays.asList(0L, 1L, 2L); + List doubles = Arrays.asList(0.0, 1.0, 2.0); - assertTrue(Arrays.equals(array, Shorts.toArray(bytes))); - assertTrue(Arrays.equals(array, Shorts.toArray(shorts))); - assertTrue(Arrays.equals(array, Shorts.toArray(ints))); - assertTrue(Arrays.equals(array, Shorts.toArray(floats))); - assertTrue(Arrays.equals(array, Shorts.toArray(longs))); - assertTrue(Arrays.equals(array, Shorts.toArray(doubles))); + assertThat(Shorts.toArray(bytes)).isEqualTo(array); + assertThat(Shorts.toArray(shorts)).isEqualTo(array); + assertThat(Shorts.toArray(ints)).isEqualTo(array); + assertThat(Shorts.toArray(floats)).isEqualTo(array); + assertThat(Shorts.toArray(longs)).isEqualTo(array); + assertThat(Shorts.toArray(doubles)).isEqualTo(array); } + @J2ktIncompatible // b/239034072: Kotlin varargs copy parameter arrays. public void testAsList_isAView() { short[] array = {(short) 0, (short) 1}; List list = Shorts.asList(array); list.set(0, (short) 2); - assertTrue(Arrays.equals(new short[] {(short) 2, (short) 1}, array)); + assertThat(array).isEqualTo(new short[] {(short) 2, (short) 1}); array[1] = (short) 3; - assertEquals(Arrays.asList((short) 2, (short) 3), list); + assertThat(list).containsExactly((short) 2, (short) 3).inOrder(); } public void testAsList_toArray_roundTrip() { @@ -473,24 +587,26 @@ public void testAsList_toArray_roundTrip() { // Make sure it returned a copy list.set(0, (short) 4); - assertTrue(Arrays.equals(new short[] {(short) 0, (short) 1, (short) 2}, newArray)); + assertThat(newArray).isEqualTo(new short[] {(short) 0, (short) 1, (short) 2}); newArray[1] = (short) 5; - assertEquals((short) 1, (short) list.get(1)); + assertThat((short) list.get(1)).isEqualTo((short) 1); } // This test stems from a real bug found by andrewk public void testAsList_subList_toArray_roundTrip() { short[] array = {(short) 0, (short) 1, (short) 2, (short) 3}; List list = Shorts.asList(array); - assertTrue( - Arrays.equals(new short[] {(short) 1, (short) 2}, Shorts.toArray(list.subList(1, 3)))); - assertTrue(Arrays.equals(new short[] {}, Shorts.toArray(list.subList(2, 2)))); + assertThat(Shorts.toArray(list.subList(1, 3))).isEqualTo(new short[] {(short) 1, (short) 2}); + assertThat(Shorts.toArray(list.subList(2, 2))).isEqualTo(new short[] {}); } + // `primitives` can't depend on `collect`, so this is what the prod code has to return. + @SuppressWarnings("EmptyList") public void testAsListEmpty() { - assertSame(Collections.emptyList(), Shorts.asList(EMPTY)); + assertThat(Shorts.asList(EMPTY)).isSameInstanceAs(Collections.emptyList()); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(Shorts.class); @@ -498,40 +614,37 @@ public void testNulls() { public void testStringConverter_convert() { Converter converter = Shorts.stringConverter(); - assertEquals((Short) (short) 1, converter.convert("1")); - assertEquals((Short) (short) 0, converter.convert("0")); - assertEquals((Short) (short) (-1), converter.convert("-1")); - assertEquals((Short) (short) 255, converter.convert("0xff")); - assertEquals((Short) (short) 255, converter.convert("0xFF")); - assertEquals((Short) (short) (-255), converter.convert("-0xFF")); - assertEquals((Short) (short) 255, converter.convert("#0000FF")); - assertEquals((Short) (short) 438, converter.convert("0666")); + assertThat(converter.convert("1")).isEqualTo(1); + assertThat(converter.convert("0")).isEqualTo(0); + assertThat(converter.convert("-1")).isEqualTo(-1); + assertThat(converter.convert("0xff")).isEqualTo(255); + assertThat(converter.convert("0xFF")).isEqualTo(255); + assertThat(converter.convert("-0xFF")).isEqualTo(-255); + assertThat(converter.convert("#0000FF")).isEqualTo(255); + assertThat(converter.convert("0666")).isEqualTo(438); } public void testStringConverter_convertError() { - try { - Shorts.stringConverter().convert("notanumber"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> Shorts.stringConverter().convert("notanumber")); } public void testStringConverter_nullConversions() { - assertNull(Shorts.stringConverter().convert(null)); - assertNull(Shorts.stringConverter().reverse().convert(null)); + assertThat(Shorts.stringConverter().convert(null)).isNull(); + assertThat(Shorts.stringConverter().reverse().convert(null)).isNull(); } public void testStringConverter_reverse() { Converter converter = Shorts.stringConverter(); - assertEquals("1", converter.reverse().convert((short) 1)); - assertEquals("0", converter.reverse().convert((short) 0)); - assertEquals("-1", converter.reverse().convert((short) -1)); - assertEquals("255", converter.reverse().convert((short) 0xff)); - assertEquals("255", converter.reverse().convert((short) 0xFF)); - assertEquals("-255", converter.reverse().convert((short) -0xFF)); - assertEquals("438", converter.reverse().convert((short) 0666)); + assertThat(converter.reverse().convert((short) 1)).isEqualTo("1"); + assertThat(converter.reverse().convert((short) 0)).isEqualTo("0"); + assertThat(converter.reverse().convert((short) -1)).isEqualTo("-1"); + assertThat(converter.reverse().convert((short) 0xff)).isEqualTo("255"); + assertThat(converter.reverse().convert((short) 0xFF)).isEqualTo("255"); + assertThat(converter.reverse().convert((short) -0xFF)).isEqualTo("-255"); + assertThat(converter.reverse().convert((short) 0666)).isEqualTo("438"); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testStringConverter_nullPointerTester() throws Exception { NullPointerTester tester = new NullPointerTester(); diff --git a/android/guava-tests/test/com/google/common/primitives/SignedBytesTest.java b/android/guava-tests/test/com/google/common/primitives/SignedBytesTest.java index 86c2ce612bba..4b667b32a147 100644 --- a/android/guava-tests/test/com/google/common/primitives/SignedBytesTest.java +++ b/android/guava-tests/test/com/google/common/primitives/SignedBytesTest.java @@ -16,8 +16,15 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.primitives.SignedBytes.max; +import static com.google.common.primitives.SignedBytes.min; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; @@ -25,14 +32,15 @@ import java.util.Comparator; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Unit test for {@link SignedBytes}. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -@SuppressWarnings("cast") // redundant casts are intentional and harmless +@NullMarked +@GwtCompatible public class SignedBytesTest extends TestCase { private static final byte[] EMPTY = {}; private static final byte[] ARRAY1 = {(byte) 1}; @@ -44,7 +52,7 @@ public class SignedBytesTest extends TestCase { public void testCheckedCast() { for (byte value : VALUES) { - assertEquals(value, SignedBytes.checkedCast((long) value)); + assertThat(SignedBytes.checkedCast((long) value)).isEqualTo(value); } assertCastFails(GREATEST + 1L); assertCastFails(LEAST - 1L); @@ -54,12 +62,12 @@ public void testCheckedCast() { public void testSaturatedCast() { for (byte value : VALUES) { - assertEquals(value, SignedBytes.saturatedCast((long) value)); + assertThat(SignedBytes.saturatedCast((long) value)).isEqualTo(value); } - assertEquals(GREATEST, SignedBytes.saturatedCast(GREATEST + 1L)); - assertEquals(LEAST, SignedBytes.saturatedCast(LEAST - 1L)); - assertEquals(GREATEST, SignedBytes.saturatedCast(Long.MAX_VALUE)); - assertEquals(LEAST, SignedBytes.saturatedCast(Long.MIN_VALUE)); + assertThat(SignedBytes.saturatedCast(GREATEST + 1L)).isEqualTo(GREATEST); + assertThat(SignedBytes.saturatedCast(LEAST - 1L)).isEqualTo(LEAST); + assertThat(SignedBytes.saturatedCast(Long.MAX_VALUE)).isEqualTo(GREATEST); + assertThat(SignedBytes.saturatedCast(Long.MIN_VALUE)).isEqualTo(LEAST); } private static void assertCastFails(long value) { @@ -67,69 +75,62 @@ private static void assertCastFails(long value) { SignedBytes.checkedCast(value); fail("Cast to byte should have failed: " + value); } catch (IllegalArgumentException ex) { - assertTrue( - value + " not found in exception text: " + ex.getMessage(), - ex.getMessage().contains(String.valueOf(value))); + assertWithMessage(value + " not found in exception text: " + ex.getMessage()) + .that(ex.getMessage().contains(String.valueOf(value))) + .isTrue(); } } public void testCompare() { for (byte x : VALUES) { for (byte y : VALUES) { - // Only compare the sign of the result of compareTo(). - int expected = Byte.valueOf(x).compareTo(y); + // Only compare the sign of the result of compare(). + int expected = Byte.compare(x, y); int actual = SignedBytes.compare(x, y); if (expected == 0) { - assertEquals(x + ", " + y, expected, actual); + assertWithMessage(x + ", " + y).that(actual).isEqualTo(expected); } else if (expected < 0) { - assertTrue( - x + ", " + y + " (expected: " + expected + ", actual" + actual + ")", actual < 0); + assertWithMessage(x + ", " + y + " (expected: " + expected + ", actual" + actual + ")") + .that(actual < 0) + .isTrue(); } else { - assertTrue( - x + ", " + y + " (expected: " + expected + ", actual" + actual + ")", actual > 0); + assertWithMessage(x + ", " + y + " (expected: " + expected + ", actual" + actual + ")") + .that(actual > 0) + .isTrue(); } } } } public void testMax_noArgs() { - try { - SignedBytes.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(LEAST, SignedBytes.max(LEAST)); - assertEquals(GREATEST, SignedBytes.max(GREATEST)); - assertEquals( - (byte) 127, SignedBytes.max((byte) 0, (byte) -128, (byte) -1, (byte) 127, (byte) 1)); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat(max((byte) 0, (byte) -128, (byte) -1, (byte) 127, (byte) 1)).isEqualTo((byte) 127); } public void testMin_noArgs() { - try { - SignedBytes.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, SignedBytes.min(LEAST)); - assertEquals(GREATEST, SignedBytes.min(GREATEST)); - assertEquals( - (byte) -128, SignedBytes.min((byte) 0, (byte) -128, (byte) -1, (byte) 127, (byte) 1)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat(min((byte) 0, (byte) -128, (byte) -1, (byte) 127, (byte) 1)).isEqualTo((byte) -128); } public void testJoin() { - assertEquals("", SignedBytes.join(",", EMPTY)); - assertEquals("1", SignedBytes.join(",", ARRAY1)); - assertEquals("1,2", SignedBytes.join(",", (byte) 1, (byte) 2)); - assertEquals("123", SignedBytes.join("", (byte) 1, (byte) 2, (byte) 3)); - assertEquals("-128,-1", SignedBytes.join(",", (byte) -128, (byte) -1)); + assertThat(SignedBytes.join(",", EMPTY)).isEmpty(); + assertThat(SignedBytes.join(",", ARRAY1)).isEqualTo("1"); + assertThat(SignedBytes.join(",", (byte) 1, (byte) 2)).isEqualTo("1,2"); + assertThat(SignedBytes.join("", (byte) 1, (byte) 2, (byte) 3)).isEqualTo("123"); + assertThat(SignedBytes.join(",", (byte) -128, (byte) -1)).isEqualTo("-128,-1"); } + @J2ktIncompatible // b/285319375 public void testLexicographicalComparator() { List ordered = Arrays.asList( @@ -147,10 +148,11 @@ public void testLexicographicalComparator() { Helpers.testComparator(comparator, ordered); } + @J2ktIncompatible @GwtIncompatible // SerializableTester public void testLexicographicalComparatorSerializable() { Comparator comparator = SignedBytes.lexicographicalComparator(); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); } public void testSortDescending() { @@ -164,14 +166,14 @@ public void testSortDescending() { private static void testSortDescending(byte[] input, byte[] expectedOutput) { input = Arrays.copyOf(input, input.length); SignedBytes.sortDescending(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testSortDescending( byte[] input, int fromIndex, int toIndex, byte[] expectedOutput) { input = Arrays.copyOf(input, input.length); SignedBytes.sortDescending(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testSortDescendingIndexed() { @@ -183,6 +185,7 @@ public void testSortDescendingIndexed() { testSortDescending(new byte[] {-1, -2, 1, 2}, 1, 3, new byte[] {-1, 1, -2, 2}); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(SignedBytes.class); diff --git a/android/guava-tests/test/com/google/common/primitives/TestPlatform.java b/android/guava-tests/test/com/google/common/primitives/TestPlatform.java index 094c9d7fc09d..d06c4176786b 100644 --- a/android/guava-tests/test/com/google/common/primitives/TestPlatform.java +++ b/android/guava-tests/test/com/google/common/primitives/TestPlatform.java @@ -17,10 +17,14 @@ package com.google.common.primitives; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullUnmarked; -@GwtCompatible(emulated = true) -class TestPlatform { +@GwtCompatible +@NullUnmarked +final class TestPlatform { static int reduceIterationsIfGwt(int iterations) { return iterations; } + + private TestPlatform() {} } diff --git a/android/guava-tests/test/com/google/common/primitives/UnsignedBytesTest.java b/android/guava-tests/test/com/google/common/primitives/UnsignedBytesTest.java index 352c8f439e37..091d9f28a6bd 100644 --- a/android/guava-tests/test/com/google/common/primitives/UnsignedBytesTest.java +++ b/android/guava-tests/test/com/google/common/primitives/UnsignedBytesTest.java @@ -16,6 +16,14 @@ package com.google.common.primitives; +import static com.google.common.primitives.UnsignedBytes.max; +import static com.google.common.primitives.UnsignedBytes.min; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.lang.Byte.toUnsignedInt; +import static java.lang.Math.signum; +import static org.junit.Assert.assertThrows; + import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; @@ -24,6 +32,7 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link UnsignedBytes}. @@ -31,6 +40,7 @@ * @author Kevin Bourrillion * @author Louis Wasserman */ +@NullUnmarked public class UnsignedBytesTest extends TestCase { private static final byte LEAST = 0; private static final byte GREATEST = (byte) 255; @@ -38,18 +48,19 @@ public class UnsignedBytesTest extends TestCase { // Only in this class, VALUES must be strictly ascending private static final byte[] VALUES = {LEAST, 127, (byte) 128, (byte) 129, GREATEST}; + @SuppressWarnings("InlineMeInliner") // We need to test our method. public void testToInt() { - assertEquals(0, UnsignedBytes.toInt((byte) 0)); - assertEquals(1, UnsignedBytes.toInt((byte) 1)); - assertEquals(127, UnsignedBytes.toInt((byte) 127)); - assertEquals(128, UnsignedBytes.toInt((byte) -128)); - assertEquals(129, UnsignedBytes.toInt((byte) -127)); - assertEquals(255, UnsignedBytes.toInt((byte) -1)); + assertThat(UnsignedBytes.toInt((byte) 0)).isEqualTo(0); + assertThat(UnsignedBytes.toInt((byte) 1)).isEqualTo(1); + assertThat(UnsignedBytes.toInt((byte) 127)).isEqualTo(127); + assertThat(UnsignedBytes.toInt((byte) -128)).isEqualTo(128); + assertThat(UnsignedBytes.toInt((byte) -127)).isEqualTo(129); + assertThat(UnsignedBytes.toInt((byte) -1)).isEqualTo(255); } public void testCheckedCast() { for (byte value : VALUES) { - assertEquals(value, UnsignedBytes.checkedCast(UnsignedBytes.toInt(value))); + assertThat(UnsignedBytes.checkedCast(toUnsignedInt(value))).isEqualTo(value); } assertCastFails(256L); assertCastFails(-1L); @@ -59,12 +70,12 @@ public void testCheckedCast() { public void testSaturatedCast() { for (byte value : VALUES) { - assertEquals(value, UnsignedBytes.saturatedCast(UnsignedBytes.toInt(value))); + assertThat(UnsignedBytes.saturatedCast(toUnsignedInt(value))).isEqualTo(value); } - assertEquals(GREATEST, UnsignedBytes.saturatedCast(256L)); - assertEquals(LEAST, UnsignedBytes.saturatedCast(-1L)); - assertEquals(GREATEST, UnsignedBytes.saturatedCast(Long.MAX_VALUE)); - assertEquals(LEAST, UnsignedBytes.saturatedCast(Long.MIN_VALUE)); + assertThat(UnsignedBytes.saturatedCast(256L)).isEqualTo(GREATEST); + assertThat(UnsignedBytes.saturatedCast(-1L)).isEqualTo(LEAST); + assertThat(UnsignedBytes.saturatedCast(Long.MAX_VALUE)).isEqualTo(GREATEST); + assertThat(UnsignedBytes.saturatedCast(Long.MIN_VALUE)).isEqualTo(LEAST); } private static void assertCastFails(long value) { @@ -72,9 +83,9 @@ private static void assertCastFails(long value) { UnsignedBytes.checkedCast(value); fail("Cast to byte should have failed: " + value); } catch (IllegalArgumentException ex) { - assertTrue( - value + " not found in exception text: " + ex.getMessage(), - ex.getMessage().contains(String.valueOf(value))); + assertWithMessage(value + " not found in exception text: " + ex.getMessage()) + .that(ex.getMessage().contains(String.valueOf(value))) + .isTrue(); } } @@ -86,44 +97,32 @@ public void testCompare() { byte x = VALUES[i]; byte y = VALUES[j]; // note: spec requires only that the sign is the same - assertEquals( - x + ", " + y, - Math.signum(UnsignedBytes.compare(x, y)), - Math.signum(Ints.compare(i, j))); + assertWithMessage(x + ", " + y) + .that(signum(UnsignedBytes.compare(x, y))) + .isEqualTo(signum(Integer.compare(i, j))); } } } public void testMax_noArgs() { - try { - UnsignedBytes.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(LEAST, UnsignedBytes.max(LEAST)); - assertEquals(GREATEST, UnsignedBytes.max(GREATEST)); - assertEquals( - (byte) 255, UnsignedBytes.max((byte) 0, (byte) -128, (byte) -1, (byte) 127, (byte) 1)); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat(max((byte) 0, (byte) -128, (byte) -1, (byte) 127, (byte) 1)).isEqualTo((byte) 255); } public void testMin_noArgs() { - try { - UnsignedBytes.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, UnsignedBytes.min(LEAST)); - assertEquals(GREATEST, UnsignedBytes.min(GREATEST)); - assertEquals( - (byte) 0, UnsignedBytes.min((byte) 0, (byte) -128, (byte) -1, (byte) 127, (byte) 1)); - assertEquals( - (byte) 0, UnsignedBytes.min((byte) -1, (byte) 127, (byte) 1, (byte) -128, (byte) 0)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat(min((byte) 0, (byte) -128, (byte) -1, (byte) 127, (byte) 1)).isEqualTo((byte) 0); + assertThat(min((byte) -1, (byte) 127, (byte) 1, (byte) -128, (byte) 0)).isEqualTo((byte) 0); } private static void assertParseFails(String value) { @@ -145,7 +144,7 @@ private static void assertParseFails(String value, int radix) { public void testParseUnsignedByte() { // We can easily afford to test this exhaustively. for (int i = 0; i <= 0xff; i++) { - assertEquals((byte) i, UnsignedBytes.parseUnsignedByte(Integer.toString(i))); + assertThat(UnsignedBytes.parseUnsignedByte(Integer.toString(i))).isEqualTo((byte) i); } assertParseFails("1000"); assertParseFails("-1"); @@ -154,15 +153,16 @@ public void testParseUnsignedByte() { } public void testMaxValue() { - assertTrue( - UnsignedBytes.compare(UnsignedBytes.MAX_VALUE, (byte) (UnsignedBytes.MAX_VALUE + 1)) > 0); + assertThat(UnsignedBytes.compare(UnsignedBytes.MAX_VALUE, (byte) (UnsignedBytes.MAX_VALUE + 1))) + .isGreaterThan(0); } public void testParseUnsignedByteWithRadix() throws NumberFormatException { // We can easily afford to test this exhaustively. for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { for (int i = 0; i <= 0xff; i++) { - assertEquals((byte) i, UnsignedBytes.parseUnsignedByte(Integer.toString(i, radix), radix)); + assertThat(UnsignedBytes.parseUnsignedByte(Integer.toString(i, radix), radix)) + .isEqualTo((byte) i); } assertParseFails(Integer.toString(1000, radix), radix); assertParseFails(Integer.toString(-1, radix), radix); @@ -174,30 +174,22 @@ public void testParseUnsignedByteWithRadix() throws NumberFormatException { public void testParseUnsignedByteThrowsExceptionForInvalidRadix() { // Valid radix values are Character.MIN_RADIX to Character.MAX_RADIX, // inclusive. - try { - UnsignedBytes.parseUnsignedByte("0", Character.MIN_RADIX - 1); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows( + NumberFormatException.class, + () -> UnsignedBytes.parseUnsignedByte("0", Character.MIN_RADIX - 1)); - try { - UnsignedBytes.parseUnsignedByte("0", Character.MAX_RADIX + 1); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows( + NumberFormatException.class, + () -> UnsignedBytes.parseUnsignedByte("0", Character.MAX_RADIX + 1)); // The radix is used as an array index, so try a negative value. - try { - UnsignedBytes.parseUnsignedByte("0", -1); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedBytes.parseUnsignedByte("0", -1)); } public void testToString() { // We can easily afford to test this exhaustively. for (int i = 0; i <= 0xff; i++) { - assertEquals(Integer.toString(i), UnsignedBytes.toString((byte) i)); + assertThat(UnsignedBytes.toString((byte) i)).isEqualTo(Integer.toString(i)); } } @@ -205,17 +197,17 @@ public void testToStringWithRadix() { // We can easily afford to test this exhaustively. for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { for (int i = 0; i <= 0xff; i++) { - assertEquals(Integer.toString(i, radix), UnsignedBytes.toString((byte) i, radix)); + assertThat(UnsignedBytes.toString((byte) i, radix)).isEqualTo(Integer.toString(i, radix)); } } } public void testJoin() { - assertEquals("", UnsignedBytes.join(",", new byte[] {})); - assertEquals("1", UnsignedBytes.join(",", new byte[] {(byte) 1})); - assertEquals("1,2", UnsignedBytes.join(",", (byte) 1, (byte) 2)); - assertEquals("123", UnsignedBytes.join("", (byte) 1, (byte) 2, (byte) 3)); - assertEquals("128,255", UnsignedBytes.join(",", (byte) 128, (byte) -1)); + assertThat(UnsignedBytes.join(",", new byte[] {})).isEmpty(); + assertThat(UnsignedBytes.join(",", new byte[] {(byte) 1})).isEqualTo("1"); + assertThat(UnsignedBytes.join(",", (byte) 1, (byte) 2)).isEqualTo("1,2"); + assertThat(UnsignedBytes.join("", (byte) 1, (byte) 2, (byte) 3)).isEqualTo("123"); + assertThat(UnsignedBytes.join(",", (byte) 128, (byte) -1)).isEqualTo("128,255"); } private static String unsafeComparatorClassName() { @@ -234,7 +226,7 @@ private static boolean unsafeComparatorAvailable() { * * A note on exception types: * - * Android API level 10 throws ExceptionInInitializerError the first time and + * Android API level 23 throws ExceptionInInitializerError the first time and * ClassNotFoundException thereafter. * * Android API level 26 and JVM8 both let our Error propagate directly the first time and @@ -251,12 +243,14 @@ private static boolean unsafeComparatorAvailable() { public void testLexicographicalComparatorChoice() throws Exception { Comparator defaultComparator = UnsignedBytes.lexicographicalComparator(); - assertNotNull(defaultComparator); - assertSame(defaultComparator, UnsignedBytes.lexicographicalComparator()); + assertThat(defaultComparator).isNotNull(); + assertThat(UnsignedBytes.lexicographicalComparator()).isSameInstanceAs(defaultComparator); if (unsafeComparatorAvailable()) { - assertSame(defaultComparator.getClass(), Class.forName(unsafeComparatorClassName())); + assertThat(Class.forName(unsafeComparatorClassName())) + .isSameInstanceAs(defaultComparator.getClass()); } else { - assertSame(defaultComparator, UnsignedBytes.lexicographicalComparatorJavaImpl()); + assertThat(UnsignedBytes.lexicographicalComparatorJavaImpl()) + .isSameInstanceAs(defaultComparator); } } @@ -273,36 +267,51 @@ public void testLexicographicalComparator() { new byte[] {GREATEST, GREATEST}, new byte[] {GREATEST, GREATEST, GREATEST}); - // The Unsafe implementation if it's available. Otherwise, the Java implementation. + // The VarHandle, Unsafe, or Java implementation. Comparator comparator = UnsignedBytes.lexicographicalComparator(); Helpers.testComparator(comparator, ordered); - assertSame(comparator, SerializableTester.reserialize(comparator)); + assertThat(SerializableTester.reserialize(comparator)).isSameInstanceAs(comparator); // The Java implementation. Comparator javaImpl = UnsignedBytes.lexicographicalComparatorJavaImpl(); Helpers.testComparator(javaImpl, ordered); - assertSame(javaImpl, SerializableTester.reserialize(javaImpl)); + assertThat(SerializableTester.reserialize(javaImpl)).isSameInstanceAs(javaImpl); + } + + public void testLexicographicalComparatorLongPseudorandomInputs() { + Comparator comparator1 = UnsignedBytes.lexicographicalComparator(); + Comparator comparator2 = UnsignedBytes.lexicographicalComparatorJavaImpl(); + Random rnd = new Random(714958103); + for (int trial = 0; trial < 100; trial++) { + byte[] left = new byte[1 + rnd.nextInt(32)]; + rnd.nextBytes(left); + byte[] right = left.clone(); + assertThat(comparator1.compare(left, right)).isEqualTo(0); + assertThat(comparator2.compare(left, right)).isEqualTo(0); + int i = rnd.nextInt(left.length); + left[i] ^= (byte) (1 + rnd.nextInt(255)); + assertThat(signum(comparator1.compare(left, right))) + .isEqualTo(signum(UnsignedBytes.compare(left[i], right[i]))); + assertThat(signum(comparator2.compare(left, right))) + .isEqualTo(signum(UnsignedBytes.compare(left[i], right[i]))); + } } - @SuppressWarnings("unchecked") - public void testLexicographicalComparatorLongInputs() { - Random rnd = new Random(); - for (Comparator comparator : - Arrays.asList( - UnsignedBytes.lexicographicalComparator(), - UnsignedBytes.lexicographicalComparatorJavaImpl())) { - for (int trials = 10; trials-- > 0; ) { - byte[] left = new byte[1 + rnd.nextInt(32)]; - rnd.nextBytes(left); - byte[] right = left.clone(); - assertTrue(comparator.compare(left, right) == 0); - int i = rnd.nextInt(left.length); - left[i] ^= (byte) (1 + rnd.nextInt(255)); - assertTrue(comparator.compare(left, right) != 0); - assertEquals( - comparator.compare(left, right) > 0, UnsignedBytes.compare(left[i], right[i]) > 0); - } - } + public void testLexicographicalComparatorLongHandwrittenInputs() { + Comparator comparator1 = UnsignedBytes.lexicographicalComparator(); + Comparator comparator2 = UnsignedBytes.lexicographicalComparatorJavaImpl(); + + /* + * These arrays are set up to test that the comparator compares bytes within a word in the + * correct order—in order words, that it doesn't mix up big-endian and little-endian. The first + * array has a smaller element at one index, and then the second array has a smaller element at + * the next. + */ + byte[] a0 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 99, 15, 16, 17}; + byte[] b0 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99, 14, 15, 16, 17}; + + assertThat(comparator1.compare(a0, b0)).isLessThan(0); + assertThat(comparator2.compare(a0, b0)).isLessThan(0); } public void testSort() { @@ -315,13 +324,13 @@ public void testSort() { static void testSort(byte[] input, byte[] expected) { input = Arrays.copyOf(input, input.length); UnsignedBytes.sort(input); - assertTrue(Arrays.equals(expected, input)); + assertThat(input).isEqualTo(expected); } static void testSort(byte[] input, int from, int to, byte[] expected) { input = Arrays.copyOf(input, input.length); UnsignedBytes.sort(input, from, to); - assertTrue(Arrays.equals(expected, input)); + assertThat(input).isEqualTo(expected); } public void testSortIndexed() { @@ -344,14 +353,14 @@ public void testSortDescending() { private static void testSortDescending(byte[] input, byte[] expectedOutput) { input = Arrays.copyOf(input, input.length); UnsignedBytes.sortDescending(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testSortDescending( byte[] input, int fromIndex, int toIndex, byte[] expectedOutput) { input = Arrays.copyOf(input, input.length); UnsignedBytes.sortDescending(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testSortDescendingIndexed() { diff --git a/android/guava-tests/test/com/google/common/primitives/UnsignedIntegerTest.java b/android/guava-tests/test/com/google/common/primitives/UnsignedIntegerTest.java index 04bf27d05581..3d93cfad16e3 100644 --- a/android/guava-tests/test/com/google/common/primitives/UnsignedIntegerTest.java +++ b/android/guava-tests/test/com/google/common/primitives/UnsignedIntegerTest.java @@ -14,21 +14,28 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableSet; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.math.BigInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code UnsignedInteger}. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class UnsignedIntegerTest extends TestCase { private static final ImmutableSet TEST_INTS; private static final ImmutableSet TEST_LONGS; @@ -58,16 +65,18 @@ private static int force32(int value) { public void testFromIntBitsAndIntValueAreInverses() { for (int value : TEST_INTS) { - assertEquals( - UnsignedInts.toString(value), value, UnsignedInteger.fromIntBits(value).intValue()); + assertWithMessage(UnsignedInts.toString(value)) + .that(UnsignedInteger.fromIntBits(value).intValue()) + .isEqualTo(value); } } public void testFromIntBitsLongValue() { for (int value : TEST_INTS) { long expected = value & 0xffffffffL; - assertEquals( - UnsignedInts.toString(value), expected, UnsignedInteger.fromIntBits(value).longValue()); + assertWithMessage(UnsignedInts.toString(value)) + .that(UnsignedInteger.fromIntBits(value).longValue()) + .isEqualTo(expected); } } @@ -77,10 +86,10 @@ public void testValueOfLong() { for (long value : TEST_LONGS) { boolean expectSuccess = value >= min && value <= max; try { - assertEquals(value, UnsignedInteger.valueOf(value).longValue()); - assertTrue(expectSuccess); + assertThat(UnsignedInteger.valueOf(value).longValue()).isEqualTo(value); + assertThat(expectSuccess).isTrue(); } catch (IllegalArgumentException e) { - assertFalse(expectSuccess); + assertThat(expectSuccess).isFalse(); } } } @@ -91,10 +100,10 @@ public void testValueOfBigInteger() { for (long value : TEST_LONGS) { boolean expectSuccess = value >= min && value <= max; try { - assertEquals(value, UnsignedInteger.valueOf(BigInteger.valueOf(value)).longValue()); - assertTrue(expectSuccess); + assertThat(UnsignedInteger.valueOf(BigInteger.valueOf(value)).longValue()).isEqualTo(value); + assertThat(expectSuccess).isTrue(); } catch (IllegalArgumentException e) { - assertFalse(expectSuccess); + assertThat(expectSuccess).isFalse(); } } } @@ -102,16 +111,17 @@ public void testValueOfBigInteger() { public void testToString() { for (int value : TEST_INTS) { UnsignedInteger unsignedValue = UnsignedInteger.fromIntBits(value); - assertEquals(unsignedValue.bigIntegerValue().toString(), unsignedValue.toString()); + assertThat(unsignedValue.toString()).isEqualTo(unsignedValue.bigIntegerValue().toString()); } } + @J2ktIncompatible @GwtIncompatible // too slow public void testToStringRadix() { for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { for (int l : TEST_INTS) { UnsignedInteger value = UnsignedInteger.fromIntBits(l); - assertEquals(value.bigIntegerValue().toString(radix), value.toString(radix)); + assertThat(value.toString(radix)).isEqualTo(value.bigIntegerValue().toString(radix)); } } } @@ -121,7 +131,7 @@ public void testToStringRadixQuick() { for (int radix : radices) { for (int l : TEST_INTS) { UnsignedInteger value = UnsignedInteger.fromIntBits(l); - assertEquals(value.bigIntegerValue().toString(radix), value.toString(radix)); + assertThat(value.toString(radix)).isEqualTo(value.bigIntegerValue().toString(radix)); } } } @@ -129,14 +139,16 @@ public void testToStringRadixQuick() { public void testFloatValue() { for (int value : TEST_INTS) { UnsignedInteger unsignedValue = UnsignedInteger.fromIntBits(value); - assertEquals(unsignedValue.bigIntegerValue().floatValue(), unsignedValue.floatValue()); + assertThat(unsignedValue.floatValue()) + .isEqualTo(unsignedValue.bigIntegerValue().floatValue()); } } public void testDoubleValue() { for (int value : TEST_INTS) { UnsignedInteger unsignedValue = UnsignedInteger.fromIntBits(value); - assertEquals(unsignedValue.bigIntegerValue().doubleValue(), unsignedValue.doubleValue()); + assertThat(unsignedValue.doubleValue()) + .isEqualTo(unsignedValue.bigIntegerValue().doubleValue()); } } @@ -147,7 +159,7 @@ public void testPlus() { UnsignedInteger bUnsigned = UnsignedInteger.fromIntBits(b); int expected = aUnsigned.bigIntegerValue().add(bUnsigned.bigIntegerValue()).intValue(); UnsignedInteger unsignedSum = aUnsigned.plus(bUnsigned); - assertEquals(expected, unsignedSum.intValue()); + assertThat(unsignedSum.intValue()).isEqualTo(expected); } } } @@ -160,11 +172,12 @@ public void testMinus() { int expected = force32(aUnsigned.bigIntegerValue().subtract(bUnsigned.bigIntegerValue()).intValue()); UnsignedInteger unsignedSub = aUnsigned.minus(bUnsigned); - assertEquals(expected, unsignedSub.intValue()); + assertThat(unsignedSub.intValue()).isEqualTo(expected); } } } + @J2ktIncompatible @GwtIncompatible // multiply public void testTimes() { for (int a : TEST_INTS) { @@ -174,7 +187,9 @@ public void testTimes() { int expected = force32(aUnsigned.bigIntegerValue().multiply(bUnsigned.bigIntegerValue()).intValue()); UnsignedInteger unsignedMul = aUnsigned.times(bUnsigned); - assertEquals(aUnsigned + " * " + bUnsigned, expected, unsignedMul.intValue()); + assertWithMessage(aUnsigned + " * " + bUnsigned) + .that(unsignedMul.intValue()) + .isEqualTo(expected); } } } @@ -187,7 +202,7 @@ public void testDividedBy() { UnsignedInteger bUnsigned = UnsignedInteger.fromIntBits(b); int expected = aUnsigned.bigIntegerValue().divide(bUnsigned.bigIntegerValue()).intValue(); UnsignedInteger unsignedDiv = aUnsigned.dividedBy(bUnsigned); - assertEquals(expected, unsignedDiv.intValue()); + assertThat(unsignedDiv.intValue()).isEqualTo(expected); } } } @@ -195,11 +210,11 @@ public void testDividedBy() { public void testDivideByZeroThrows() { for (int a : TEST_INTS) { - try { - UnsignedInteger unused = UnsignedInteger.fromIntBits(a).dividedBy(UnsignedInteger.ZERO); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, + () -> { + UnsignedInteger unused = UnsignedInteger.fromIntBits(a).dividedBy(UnsignedInteger.ZERO); + }); } } @@ -211,7 +226,7 @@ public void testMod() { UnsignedInteger bUnsigned = UnsignedInteger.fromIntBits(b); int expected = aUnsigned.bigIntegerValue().mod(bUnsigned.bigIntegerValue()).intValue(); UnsignedInteger unsignedRem = aUnsigned.mod(bUnsigned); - assertEquals(expected, unsignedRem.intValue()); + assertThat(unsignedRem.intValue()).isEqualTo(expected); } } } @@ -219,11 +234,9 @@ public void testMod() { public void testModByZero() { for (int a : TEST_INTS) { - try { - UnsignedInteger.fromIntBits(a).mod(UnsignedInteger.ZERO); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, + () -> UnsignedInteger.fromIntBits(a).mod(UnsignedInteger.ZERO)); } } @@ -232,13 +245,13 @@ public void testCompare() { for (int b : TEST_INTS) { UnsignedInteger aUnsigned = UnsignedInteger.fromIntBits(a); UnsignedInteger bUnsigned = UnsignedInteger.fromIntBits(b); - assertEquals( - aUnsigned.bigIntegerValue().compareTo(bUnsigned.bigIntegerValue()), - aUnsigned.compareTo(bUnsigned)); + assertThat(aUnsigned.compareTo(bUnsigned)) + .isEqualTo(aUnsigned.bigIntegerValue().compareTo(bUnsigned.bigIntegerValue())); } } } + @J2ktIncompatible @GwtIncompatible // too slow public void testEquals() { EqualsTester equalsTester = new EqualsTester(); @@ -257,17 +270,19 @@ public void testIntValue() { for (int a : TEST_INTS) { UnsignedInteger aUnsigned = UnsignedInteger.fromIntBits(a); int intValue = aUnsigned.bigIntegerValue().intValue(); - assertEquals(intValue, aUnsigned.intValue()); + assertThat(aUnsigned.intValue()).isEqualTo(intValue); } } - @GwtIncompatible // serialization - public void testSerialization() { + @GwtIncompatible + @J2ktIncompatible + public void testSerialization() { for (int a : TEST_INTS) { SerializableTester.reserializeAndAssert(UnsignedInteger.fromIntBits(a)); } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(UnsignedInteger.class); diff --git a/android/guava-tests/test/com/google/common/primitives/UnsignedIntsTest.java b/android/guava-tests/test/com/google/common/primitives/UnsignedIntsTest.java index 0d0b3800879e..5bcb1b03e704 100644 --- a/android/guava-tests/test/com/google/common/primitives/UnsignedIntsTest.java +++ b/android/guava-tests/test/com/google/common/primitives/UnsignedIntsTest.java @@ -14,10 +14,14 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.primitives.UnsignedInts.max; +import static com.google.common.primitives.UnsignedInts.min; import static com.google.common.truth.Truth.assertThat; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; import java.util.Arrays; @@ -25,13 +29,15 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for UnsignedInts * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class UnsignedIntsTest extends TestCase { private static final long[] UNSIGNED_INTS = { 0L, @@ -52,7 +58,7 @@ public class UnsignedIntsTest extends TestCase { public void testCheckedCast() { for (long value : UNSIGNED_INTS) { - assertEquals(value, UnsignedInts.toLong(UnsignedInts.checkedCast(value))); + assertThat(UnsignedInts.toLong(UnsignedInts.checkedCast(value))).isEqualTo(value); } assertCastFails(1L << 32); assertCastFails(-1L); @@ -65,80 +71,72 @@ private static void assertCastFails(long value) { UnsignedInts.checkedCast(value); fail("Cast to int should have failed: " + value); } catch (IllegalArgumentException ex) { - assertThat(ex.getMessage()).contains(String.valueOf(value)); + assertThat(ex).hasMessageThat().contains(String.valueOf(value)); } } public void testSaturatedCast() { for (long value : UNSIGNED_INTS) { - assertEquals(value, UnsignedInts.toLong(UnsignedInts.saturatedCast(value))); + assertThat(UnsignedInts.toLong(UnsignedInts.saturatedCast(value))).isEqualTo(value); } - assertEquals(GREATEST, UnsignedInts.saturatedCast(1L << 32)); - assertEquals(LEAST, UnsignedInts.saturatedCast(-1L)); - assertEquals(GREATEST, UnsignedInts.saturatedCast(Long.MAX_VALUE)); - assertEquals(LEAST, UnsignedInts.saturatedCast(Long.MIN_VALUE)); + assertThat(UnsignedInts.saturatedCast(1L << 32)).isEqualTo(GREATEST); + assertThat(UnsignedInts.saturatedCast(-1L)).isEqualTo(LEAST); + assertThat(UnsignedInts.saturatedCast(Long.MAX_VALUE)).isEqualTo(GREATEST); + assertThat(UnsignedInts.saturatedCast(Long.MIN_VALUE)).isEqualTo(LEAST); } public void testToLong() { for (long a : UNSIGNED_INTS) { - assertEquals(a, UnsignedInts.toLong((int) a)); + assertThat(UnsignedInts.toLong((int) a)).isEqualTo(a); } } public void testCompare() { for (long a : UNSIGNED_INTS) { for (long b : UNSIGNED_INTS) { - int cmpAsLongs = Longs.compare(a, b); + int cmpAsLongs = Long.compare(a, b); int cmpAsUInt = UnsignedInts.compare((int) a, (int) b); - assertEquals(Integer.signum(cmpAsLongs), Integer.signum(cmpAsUInt)); + assertThat(Integer.signum(cmpAsUInt)).isEqualTo(Integer.signum(cmpAsLongs)); } } } public void testMax_noArgs() { - try { - UnsignedInts.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(LEAST, UnsignedInts.max(LEAST)); - assertEquals(GREATEST, UnsignedInts.max(GREATEST)); - assertEquals( - (int) 0xff1a618bL, - UnsignedInts.max( - (int) 8L, - (int) 6L, - (int) 7L, - (int) 0x12345678L, - (int) 0x5a4316b8L, - (int) 0xff1a618bL, - (int) 0L)); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat( + max( + (int) 8L, + (int) 6L, + (int) 7L, + (int) 0x12345678L, + (int) 0x5a4316b8L, + (int) 0xff1a618bL, + (int) 0L)) + .isEqualTo((int) 0xff1a618bL); } public void testMin_noArgs() { - try { - UnsignedInts.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, UnsignedInts.min(LEAST)); - assertEquals(GREATEST, UnsignedInts.min(GREATEST)); - assertEquals( - (int) 0L, - UnsignedInts.min( - (int) 8L, - (int) 6L, - (int) 7L, - (int) 0x12345678L, - (int) 0x5a4316b8L, - (int) 0xff1a618bL, - (int) 0L)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat( + min( + (int) 8L, + (int) 6L, + (int) 7L, + (int) 0x12345678L, + (int) 0x5a4316b8L, + (int) 0xff1a618bL, + (int) 0L)) + .isEqualTo((int) 0L); } public void testLexicographicalComparator() { @@ -168,13 +166,13 @@ public void testSort() { static void testSort(int[] input, int[] expected) { input = Arrays.copyOf(input, input.length); UnsignedInts.sort(input); - assertTrue(Arrays.equals(expected, input)); + assertThat(input).isEqualTo(expected); } static void testSort(int[] input, int from, int to, int[] expected) { input = Arrays.copyOf(input, input.length); UnsignedInts.sort(input, from, to); - assertTrue(Arrays.equals(expected, input)); + assertThat(input).isEqualTo(expected); } public void testSortIndexed() { @@ -196,14 +194,14 @@ public void testSortDescending() { private static void testSortDescending(int[] input, int[] expectedOutput) { input = Arrays.copyOf(input, input.length); UnsignedInts.sortDescending(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testSortDescending( int[] input, int fromIndex, int toIndex, int[] expectedOutput) { input = Arrays.copyOf(input, input.length); UnsignedInts.sortDescending(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testSortDescendingIndexed() { @@ -223,10 +221,10 @@ public void testDivide() { for (long a : UNSIGNED_INTS) { for (long b : UNSIGNED_INTS) { try { - assertEquals((int) (a / b), UnsignedInts.divide((int) a, (int) b)); - assertFalse(b == 0); + assertThat(UnsignedInts.divide((int) a, (int) b)).isEqualTo((int) (a / b)); + assertThat(b).isNotEqualTo(0); } catch (ArithmeticException e) { - assertEquals(0, b); + assertThat(b).isEqualTo(0); } } } @@ -236,10 +234,10 @@ public void testRemainder() { for (long a : UNSIGNED_INTS) { for (long b : UNSIGNED_INTS) { try { - assertEquals((int) (a % b), UnsignedInts.remainder((int) a, (int) b)); - assertFalse(b == 0); + assertThat(UnsignedInts.remainder((int) a, (int) b)).isEqualTo((int) (a % b)); + assertThat(b).isNotEqualTo(0); } catch (ArithmeticException e) { - assertEquals(0, b); + assertThat(b).isEqualTo(0); } } } @@ -253,67 +251,74 @@ public void testDivideRemainderEuclideanProperty() { int dividend = r.nextInt(); int divisor = r.nextInt(); // Test that the Euclidean property is preserved: - assertTrue( - dividend + assertThat( + dividend - (divisor * UnsignedInts.divide(dividend, divisor) - + UnsignedInts.remainder(dividend, divisor)) - == 0); + + UnsignedInts.remainder(dividend, divisor))) + .isEqualTo(0); } } public void testParseInt() { for (long a : UNSIGNED_INTS) { - assertEquals((int) a, UnsignedInts.parseUnsignedInt(Long.toString(a))); + assertThat(UnsignedInts.parseUnsignedInt(Long.toString(a))).isEqualTo((int) a); } } public void testParseIntFail() { - try { - UnsignedInts.parseUnsignedInt(Long.toString(1L << 32)); - fail("Expected NumberFormatException"); - } catch (NumberFormatException expected) { - } + assertThrows( + NumberFormatException.class, () -> UnsignedInts.parseUnsignedInt(Long.toString(1L << 32))); } public void testParseIntWithRadix() { for (long a : UNSIGNED_INTS) { for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { - assertEquals((int) a, UnsignedInts.parseUnsignedInt(Long.toString(a, radix), radix)); + assertThat(UnsignedInts.parseUnsignedInt(Long.toString(a, radix), radix)) + .isEqualTo((int) a); } } } public void testParseIntWithRadixLimits() { // loops through all legal radix values. - for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + for (int r = Character.MIN_RADIX; r <= Character.MAX_RADIX; r++) { + int radix = r; // tests can successfully parse a number string with this radix. String maxAsString = Long.toString((1L << 32) - 1, radix); - assertEquals(-1, UnsignedInts.parseUnsignedInt(maxAsString, radix)); - - try { - // tests that we get exception whre an overflow would occur. - long overflow = 1L << 32; - String overflowAsString = Long.toString(overflow, radix); - UnsignedInts.parseUnsignedInt(overflowAsString, radix); - fail(); - } catch (NumberFormatException expected) { - } + assertThat(UnsignedInts.parseUnsignedInt(maxAsString, radix)).isEqualTo(-1); + + assertThrows( + NumberFormatException.class, + () -> { + long overflow = 1L << 32; + String overflowAsString = Long.toString(overflow, radix); + UnsignedInts.parseUnsignedInt(overflowAsString, radix); + }); } } public void testParseIntThrowsExceptionForInvalidRadix() { // Valid radix values are Character.MIN_RADIX to Character.MAX_RADIX, // inclusive. + // + // Note: According to the spec, a NumberFormatException is thrown for a number that is not + // parseable, but the spec doesn't seem to say which exception is thrown for an invalid radix. + // In contrast to the JVM, Kotlin native throws an Illegal argument exception in this case + // (which seems to make more sense). try { UnsignedInts.parseUnsignedInt("0", Character.MIN_RADIX - 1); fail(); } catch (NumberFormatException expected) { + } catch (IllegalArgumentException expected) { + // Kotlin native, see above } try { UnsignedInts.parseUnsignedInt("0", Character.MAX_RADIX + 1); fail(); } catch (NumberFormatException expected) { + } catch (IllegalArgumentException expected) { + // Kotlin native, see above } // The radix is used as an array index, so try a negative value. @@ -321,68 +326,54 @@ public void testParseIntThrowsExceptionForInvalidRadix() { UnsignedInts.parseUnsignedInt("0", -1); fail(); } catch (NumberFormatException expected) { + } catch (IllegalArgumentException expected) { + // Kotlin native, see above } } public void testDecodeInt() { - assertEquals(0xffffffff, UnsignedInts.decode("0xffffffff")); - assertEquals(01234567, UnsignedInts.decode("01234567")); // octal - assertEquals(0x12345678, UnsignedInts.decode("#12345678")); - assertEquals(76543210, UnsignedInts.decode("76543210")); - assertEquals(0x13579135, UnsignedInts.decode("0x13579135")); - assertEquals(0x13579135, UnsignedInts.decode("0X13579135")); - assertEquals(0, UnsignedInts.decode("0")); + assertThat(UnsignedInts.decode("0xffffffff")).isEqualTo(0xffffffff); + assertThat(UnsignedInts.decode("01234567")).isEqualTo(01234567); // octal + assertThat(UnsignedInts.decode("#12345678")).isEqualTo(0x12345678); + assertThat(UnsignedInts.decode("76543210")).isEqualTo(76543210); + assertThat(UnsignedInts.decode("0x13579135")).isEqualTo(0x13579135); + assertThat(UnsignedInts.decode("0X13579135")).isEqualTo(0x13579135); + assertThat(UnsignedInts.decode("0")).isEqualTo(0); } public void testDecodeIntFails() { - try { - // One more than maximum value - UnsignedInts.decode("0xfffffffff"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedInts.decode("0xfffffffff")); - try { - UnsignedInts.decode("-5"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedInts.decode("-5")); - try { - UnsignedInts.decode("-0x5"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedInts.decode("-0x5")); - try { - UnsignedInts.decode("-05"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedInts.decode("-05")); } public void testToString() { int[] bases = {2, 5, 7, 8, 10, 16}; for (long a : UNSIGNED_INTS) { for (int base : bases) { - assertEquals(UnsignedInts.toString((int) a, base), Long.toString(a, base)); + assertThat(Long.toString(a, base)).isEqualTo(UnsignedInts.toString((int) a, base)); } } } public void testJoin() { - assertEquals("", join()); - assertEquals("1", join(1)); - assertEquals("1,2", join(1, 2)); - assertEquals("4294967295,2147483648", join(-1, Integer.MIN_VALUE)); + assertThat(join()).isEmpty(); + assertThat(join(1)).isEqualTo("1"); + assertThat(join(1, 2)).isEqualTo("1,2"); + assertThat(join(-1, Integer.MIN_VALUE)).isEqualTo("4294967295,2147483648"); - assertEquals("123", UnsignedInts.join("", 1, 2, 3)); + assertThat(UnsignedInts.join("", 1, 2, 3)).isEqualTo("123"); } private static String join(int... values) { return UnsignedInts.join(",", values); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(UnsignedInts.class); diff --git a/android/guava-tests/test/com/google/common/primitives/UnsignedLongTest.java b/android/guava-tests/test/com/google/common/primitives/UnsignedLongTest.java index cfa2862573aa..3893d66ab4bc 100644 --- a/android/guava-tests/test/com/google/common/primitives/UnsignedLongTest.java +++ b/android/guava-tests/test/com/google/common/primitives/UnsignedLongTest.java @@ -14,21 +14,28 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableSet; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import java.math.BigInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@code UnsignedLong}. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class UnsignedLongTest extends TestCase { private static final ImmutableSet TEST_LONGS; private static final ImmutableSet TEST_BIG_INTEGERS; @@ -69,8 +76,9 @@ public class UnsignedLongTest extends TestCase { public void testAsUnsignedAndLongValueAreInverses() { for (long value : TEST_LONGS) { - assertEquals( - UnsignedLongs.toString(value), value, UnsignedLong.fromLongBits(value).longValue()); + assertWithMessage(UnsignedLongs.toString(value)) + .that(UnsignedLong.fromLongBits(value).longValue()) + .isEqualTo(value); } } @@ -80,10 +88,9 @@ public void testAsUnsignedBigIntegerValue() { (value >= 0) ? BigInteger.valueOf(value) : BigInteger.valueOf(value).add(BigInteger.ZERO.setBit(64)); - assertEquals( - UnsignedLongs.toString(value), - expected, - UnsignedLong.fromLongBits(value).bigIntegerValue()); + assertWithMessage(UnsignedLongs.toString(value)) + .that(UnsignedLong.fromLongBits(value).bigIntegerValue()) + .isEqualTo(expected); } } @@ -91,10 +98,10 @@ public void testValueOfLong() { for (long value : TEST_LONGS) { boolean expectSuccess = value >= 0; try { - assertEquals(value, UnsignedLong.valueOf(value).longValue()); - assertTrue(expectSuccess); + assertThat(UnsignedLong.valueOf(value).longValue()).isEqualTo(value); + assertThat(expectSuccess).isTrue(); } catch (IllegalArgumentException e) { - assertFalse(expectSuccess); + assertThat(expectSuccess).isFalse(); } } } @@ -105,10 +112,10 @@ public void testValueOfBigInteger() { for (BigInteger big : TEST_BIG_INTEGERS) { boolean expectSuccess = big.compareTo(min) >= 0 && big.compareTo(max) <= 0; try { - assertEquals(big, UnsignedLong.valueOf(big).bigIntegerValue()); - assertTrue(expectSuccess); + assertThat(UnsignedLong.valueOf(big).bigIntegerValue()).isEqualTo(big); + assertThat(expectSuccess).isTrue(); } catch (IllegalArgumentException e) { - assertFalse(expectSuccess); + assertThat(expectSuccess).isFalse(); } } } @@ -116,7 +123,7 @@ public void testValueOfBigInteger() { public void testToString() { for (long value : TEST_LONGS) { UnsignedLong unsignedValue = UnsignedLong.fromLongBits(value); - assertEquals(unsignedValue.bigIntegerValue().toString(), unsignedValue.toString()); + assertThat(unsignedValue.toString()).isEqualTo(unsignedValue.bigIntegerValue().toString()); } } @@ -125,7 +132,7 @@ public void testToStringRadix() { for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { for (long l : TEST_LONGS) { UnsignedLong value = UnsignedLong.fromLongBits(l); - assertEquals(value.bigIntegerValue().toString(radix), value.toString(radix)); + assertThat(value.toString(radix)).isEqualTo(value.bigIntegerValue().toString(radix)); } } } @@ -135,7 +142,7 @@ public void testToStringRadixQuick() { for (int radix : radices) { for (long l : TEST_LONGS) { UnsignedLong value = UnsignedLong.fromLongBits(l); - assertEquals(value.bigIntegerValue().toString(radix), value.toString(radix)); + assertThat(value.toString(radix)).isEqualTo(value.bigIntegerValue().toString(radix)); } } } @@ -144,22 +151,18 @@ public void testToStringRadixQuick() { public void testFloatValue() { for (long value : TEST_LONGS) { UnsignedLong unsignedValue = UnsignedLong.fromLongBits(value); - assertEquals( - "Float value of " + unsignedValue, - unsignedValue.bigIntegerValue().floatValue(), - unsignedValue.floatValue(), - 0.0f); + assertWithMessage("Float value of " + unsignedValue) + .that(unsignedValue.floatValue()) + .isEqualTo(unsignedValue.bigIntegerValue().floatValue()); } } public void testDoubleValue() { for (long value : TEST_LONGS) { UnsignedLong unsignedValue = UnsignedLong.fromLongBits(value); - assertEquals( - "Double value of " + unsignedValue, - unsignedValue.bigIntegerValue().doubleValue(), - unsignedValue.doubleValue(), - 0.0); + assertWithMessage("Double value of " + unsignedValue) + .that(unsignedValue.doubleValue()) + .isEqualTo(unsignedValue.bigIntegerValue().doubleValue()); } } @@ -170,7 +173,7 @@ public void testPlus() { UnsignedLong bUnsigned = UnsignedLong.fromLongBits(b); long expected = aUnsigned.bigIntegerValue().add(bUnsigned.bigIntegerValue()).longValue(); UnsignedLong unsignedSum = aUnsigned.plus(bUnsigned); - assertEquals(expected, unsignedSum.longValue()); + assertThat(unsignedSum.longValue()).isEqualTo(expected); } } } @@ -183,7 +186,7 @@ public void testMinus() { long expected = aUnsigned.bigIntegerValue().subtract(bUnsigned.bigIntegerValue()).longValue(); UnsignedLong unsignedSub = aUnsigned.minus(bUnsigned); - assertEquals(expected, unsignedSub.longValue()); + assertThat(unsignedSub.longValue()).isEqualTo(expected); } } } @@ -196,7 +199,7 @@ public void testTimes() { long expected = aUnsigned.bigIntegerValue().multiply(bUnsigned.bigIntegerValue()).longValue(); UnsignedLong unsignedMul = aUnsigned.times(bUnsigned); - assertEquals(expected, unsignedMul.longValue()); + assertThat(unsignedMul.longValue()).isEqualTo(expected); } } } @@ -210,7 +213,7 @@ public void testDividedBy() { long expected = aUnsigned.bigIntegerValue().divide(bUnsigned.bigIntegerValue()).longValue(); UnsignedLong unsignedDiv = aUnsigned.dividedBy(bUnsigned); - assertEquals(expected, unsignedDiv.longValue()); + assertThat(unsignedDiv.longValue()).isEqualTo(expected); } } } @@ -218,11 +221,9 @@ public void testDividedBy() { public void testDivideByZeroThrows() { for (long a : TEST_LONGS) { - try { - UnsignedLong.fromLongBits(a).dividedBy(UnsignedLong.ZERO); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, + () -> UnsignedLong.fromLongBits(a).dividedBy(UnsignedLong.ZERO)); } } @@ -235,7 +236,7 @@ public void testMod() { long expected = aUnsigned.bigIntegerValue().remainder(bUnsigned.bigIntegerValue()).longValue(); UnsignedLong unsignedRem = aUnsigned.mod(bUnsigned); - assertEquals(expected, unsignedRem.longValue()); + assertThat(unsignedRem.longValue()).isEqualTo(expected); } } } @@ -243,11 +244,8 @@ public void testMod() { public void testModByZero() { for (long a : TEST_LONGS) { - try { - UnsignedLong.fromLongBits(a).mod(UnsignedLong.ZERO); - fail("Expected ArithmeticException"); - } catch (ArithmeticException expected) { - } + assertThrows( + ArithmeticException.class, () -> UnsignedLong.fromLongBits(a).mod(UnsignedLong.ZERO)); } } @@ -256,9 +254,8 @@ public void testCompare() { for (long b : TEST_LONGS) { UnsignedLong aUnsigned = UnsignedLong.fromLongBits(a); UnsignedLong bUnsigned = UnsignedLong.fromLongBits(b); - assertEquals( - aUnsigned.bigIntegerValue().compareTo(bUnsigned.bigIntegerValue()), - aUnsigned.compareTo(bUnsigned)); + assertThat(aUnsigned.compareTo(bUnsigned)) + .isEqualTo(aUnsigned.bigIntegerValue().compareTo(bUnsigned.bigIntegerValue())); } } } @@ -282,17 +279,19 @@ public void testIntValue() { for (long a : TEST_LONGS) { UnsignedLong aUnsigned = UnsignedLong.fromLongBits(a); int intValue = aUnsigned.bigIntegerValue().intValue(); - assertEquals(intValue, aUnsigned.intValue()); + assertThat(aUnsigned.intValue()).isEqualTo(intValue); } } - @GwtIncompatible // serialization - public void testSerialization() { + @GwtIncompatible + @J2ktIncompatible + public void testSerialization() { for (long a : TEST_LONGS) { SerializableTester.reserializeAndAssert(UnsignedLong.fromLongBits(a)); } } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(UnsignedLong.class); diff --git a/android/guava-tests/test/com/google/common/primitives/UnsignedLongsTest.java b/android/guava-tests/test/com/google/common/primitives/UnsignedLongsTest.java index a8b799577ce7..83f40e647826 100644 --- a/android/guava-tests/test/com/google/common/primitives/UnsignedLongsTest.java +++ b/android/guava-tests/test/com/google/common/primitives/UnsignedLongsTest.java @@ -14,10 +14,15 @@ package com.google.common.primitives; +import static com.google.common.primitives.ReflectionFreeAssertThrows.assertThrows; +import static com.google.common.primitives.UnsignedLongs.max; +import static com.google.common.primitives.UnsignedLongs.min; +import static com.google.common.truth.Truth.assertThat; import static java.math.BigInteger.ONE; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.testing.Helpers; import com.google.common.testing.NullPointerTester; import java.math.BigInteger; @@ -26,6 +31,7 @@ import java.util.List; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for UnsignedLongs @@ -33,64 +39,53 @@ * @author Brian Milch * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class UnsignedLongsTest extends TestCase { private static final long LEAST = 0L; private static final long GREATEST = 0xffffffffffffffffL; public void testCompare() { // max value - assertTrue(UnsignedLongs.compare(0, 0xffffffffffffffffL) < 0); - assertTrue(UnsignedLongs.compare(0xffffffffffffffffL, 0) > 0); + assertThat(UnsignedLongs.compare(0, 0xffffffffffffffffL)).isLessThan(0); + assertThat(UnsignedLongs.compare(0xffffffffffffffffL, 0)).isGreaterThan(0); // both with high bit set - assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0xffffffffffffffffL) < 0); - assertTrue(UnsignedLongs.compare(0xffffffffffffffffL, 0xff1a618b7f65ea12L) > 0); + assertThat(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0xffffffffffffffffL)).isLessThan(0); + assertThat(UnsignedLongs.compare(0xffffffffffffffffL, 0xff1a618b7f65ea12L)).isGreaterThan(0); // one with high bit set - assertTrue(UnsignedLongs.compare(0x5a4316b8c153ac4dL, 0xff1a618b7f65ea12L) < 0); - assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0x5a4316b8c153ac4dL) > 0); + assertThat(UnsignedLongs.compare(0x5a4316b8c153ac4dL, 0xff1a618b7f65ea12L)).isLessThan(0); + assertThat(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0x5a4316b8c153ac4dL)).isGreaterThan(0); // neither with high bit set - assertTrue(UnsignedLongs.compare(0x5a4316b8c153ac4dL, 0x6cf78a4b139a4e2aL) < 0); - assertTrue(UnsignedLongs.compare(0x6cf78a4b139a4e2aL, 0x5a4316b8c153ac4dL) > 0); + assertThat(UnsignedLongs.compare(0x5a4316b8c153ac4dL, 0x6cf78a4b139a4e2aL)).isLessThan(0); + assertThat(UnsignedLongs.compare(0x6cf78a4b139a4e2aL, 0x5a4316b8c153ac4dL)).isGreaterThan(0); // same value - assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0xff1a618b7f65ea12L) == 0); + assertThat(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0xff1a618b7f65ea12L)).isEqualTo(0); } public void testMax_noArgs() { - try { - UnsignedLongs.max(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> max()); } public void testMax() { - assertEquals(LEAST, UnsignedLongs.max(LEAST)); - assertEquals(GREATEST, UnsignedLongs.max(GREATEST)); - assertEquals( - 0xff1a618b7f65ea12L, - UnsignedLongs.max( - 0x5a4316b8c153ac4dL, 8L, 100L, 0L, 0x6cf78a4b139a4e2aL, 0xff1a618b7f65ea12L)); + assertThat(max(LEAST)).isEqualTo(LEAST); + assertThat(max(GREATEST)).isEqualTo(GREATEST); + assertThat(max(0x5a4316b8c153ac4dL, 8L, 100L, 0L, 0x6cf78a4b139a4e2aL, 0xff1a618b7f65ea12L)) + .isEqualTo(0xff1a618b7f65ea12L); } public void testMin_noArgs() { - try { - UnsignedLongs.min(); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> min()); } public void testMin() { - assertEquals(LEAST, UnsignedLongs.min(LEAST)); - assertEquals(GREATEST, UnsignedLongs.min(GREATEST)); - assertEquals( - 0L, - UnsignedLongs.min( - 0x5a4316b8c153ac4dL, 8L, 100L, 0L, 0x6cf78a4b139a4e2aL, 0xff1a618b7f65ea12L)); + assertThat(min(LEAST)).isEqualTo(LEAST); + assertThat(min(GREATEST)).isEqualTo(GREATEST); + assertThat(min(0x5a4316b8c153ac4dL, 8L, 100L, 0L, 0x6cf78a4b139a4e2aL, 0xff1a618b7f65ea12L)) + .isEqualTo(0L); } public void testLexicographicalComparator() { @@ -99,10 +94,10 @@ public void testLexicographicalComparator() { new long[] {}, new long[] {LEAST}, new long[] {LEAST, LEAST}, - new long[] {LEAST, (long) 1}, - new long[] {(long) 1}, - new long[] {(long) 1, LEAST}, - new long[] {GREATEST, GREATEST - (long) 1}, + new long[] {LEAST, 1L}, + new long[] {1L}, + new long[] {1L, LEAST}, + new long[] {GREATEST, GREATEST - 1L}, new long[] {GREATEST, GREATEST}, new long[] {GREATEST, GREATEST, GREATEST}); @@ -120,13 +115,13 @@ public void testSort() { static void testSort(long[] input, long[] expected) { input = Arrays.copyOf(input, input.length); UnsignedLongs.sort(input); - assertTrue(Arrays.equals(expected, input)); + assertThat(input).isEqualTo(expected); } static void testSort(long[] input, int from, int to, long[] expected) { input = Arrays.copyOf(input, input.length); UnsignedLongs.sort(input, from, to); - assertTrue(Arrays.equals(expected, input)); + assertThat(input).isEqualTo(expected); } public void testSortIndexed() { @@ -149,14 +144,14 @@ public void testSortDescending() { private static void testSortDescending(long[] input, long[] expectedOutput) { input = Arrays.copyOf(input, input.length); UnsignedLongs.sortDescending(input); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } private static void testSortDescending( long[] input, int fromIndex, int toIndex, long[] expectedOutput) { input = Arrays.copyOf(input, input.length); UnsignedLongs.sortDescending(input, fromIndex, toIndex); - assertTrue(Arrays.equals(expectedOutput, input)); + assertThat(input).isEqualTo(expectedOutput); } public void testSortDescendingIndexed() { @@ -173,24 +168,24 @@ public void testSortDescendingIndexed() { } public void testDivide() { - assertEquals(2, UnsignedLongs.divide(14, 5)); - assertEquals(0, UnsignedLongs.divide(0, 50)); - assertEquals(1, UnsignedLongs.divide(0xfffffffffffffffeL, 0xfffffffffffffffdL)); - assertEquals(0, UnsignedLongs.divide(0xfffffffffffffffdL, 0xfffffffffffffffeL)); - assertEquals(281479271743488L, UnsignedLongs.divide(0xfffffffffffffffeL, 65535)); - assertEquals(0x7fffffffffffffffL, UnsignedLongs.divide(0xfffffffffffffffeL, 2)); - assertEquals(3689348814741910322L, UnsignedLongs.divide(0xfffffffffffffffeL, 5)); + assertThat(UnsignedLongs.divide(14, 5)).isEqualTo(2); + assertThat(UnsignedLongs.divide(0, 50)).isEqualTo(0); + assertThat(UnsignedLongs.divide(0xfffffffffffffffeL, 0xfffffffffffffffdL)).isEqualTo(1); + assertThat(UnsignedLongs.divide(0xfffffffffffffffdL, 0xfffffffffffffffeL)).isEqualTo(0); + assertThat(UnsignedLongs.divide(0xfffffffffffffffeL, 65535)).isEqualTo(281479271743488L); + assertThat(UnsignedLongs.divide(0xfffffffffffffffeL, 2)).isEqualTo(0x7fffffffffffffffL); + assertThat(UnsignedLongs.divide(0xfffffffffffffffeL, 5)).isEqualTo(3689348814741910322L); } public void testRemainder() { - assertEquals(4, UnsignedLongs.remainder(14, 5)); - assertEquals(0, UnsignedLongs.remainder(0, 50)); - assertEquals(1, UnsignedLongs.remainder(0xfffffffffffffffeL, 0xfffffffffffffffdL)); - assertEquals( - 0xfffffffffffffffdL, UnsignedLongs.remainder(0xfffffffffffffffdL, 0xfffffffffffffffeL)); - assertEquals(65534L, UnsignedLongs.remainder(0xfffffffffffffffeL, 65535)); - assertEquals(0, UnsignedLongs.remainder(0xfffffffffffffffeL, 2)); - assertEquals(4, UnsignedLongs.remainder(0xfffffffffffffffeL, 5)); + assertThat(UnsignedLongs.remainder(14, 5)).isEqualTo(4); + assertThat(UnsignedLongs.remainder(0, 50)).isEqualTo(0); + assertThat(UnsignedLongs.remainder(0xfffffffffffffffeL, 0xfffffffffffffffdL)).isEqualTo(1); + assertThat(UnsignedLongs.remainder(0xfffffffffffffffdL, 0xfffffffffffffffeL)) + .isEqualTo(0xfffffffffffffffdL); + assertThat(UnsignedLongs.remainder(0xfffffffffffffffeL, 65535)).isEqualTo(65534L); + assertThat(UnsignedLongs.remainder(0xfffffffffffffffeL, 2)).isEqualTo(0); + assertThat(UnsignedLongs.remainder(0xfffffffffffffffeL, 5)).isEqualTo(4); } @GwtIncompatible // Too slow in GWT (~3min fully optimized) @@ -201,126 +196,98 @@ public void testDivideRemainderEuclideanProperty() { long dividend = r.nextLong(); long divisor = r.nextLong(); // Test that the Euclidean property is preserved: - assertEquals( - 0, - dividend - - (divisor * UnsignedLongs.divide(dividend, divisor) - + UnsignedLongs.remainder(dividend, divisor))); + assertThat( + dividend + - (divisor * UnsignedLongs.divide(dividend, divisor) + + UnsignedLongs.remainder(dividend, divisor))) + .isEqualTo(0); } } public void testParseLong() { - assertEquals(0xffffffffffffffffL, UnsignedLongs.parseUnsignedLong("18446744073709551615")); - assertEquals(0x7fffffffffffffffL, UnsignedLongs.parseUnsignedLong("9223372036854775807")); - assertEquals(0xff1a618b7f65ea12L, UnsignedLongs.parseUnsignedLong("18382112080831834642")); - assertEquals(0x5a4316b8c153ac4dL, UnsignedLongs.parseUnsignedLong("6504067269626408013")); - assertEquals(0x6cf78a4b139a4e2aL, UnsignedLongs.parseUnsignedLong("7851896530399809066")); + assertThat(UnsignedLongs.parseUnsignedLong("18446744073709551615")) + .isEqualTo(0xffffffffffffffffL); + assertThat(UnsignedLongs.parseUnsignedLong("9223372036854775807")) + .isEqualTo(0x7fffffffffffffffL); + assertThat(UnsignedLongs.parseUnsignedLong("18382112080831834642")) + .isEqualTo(0xff1a618b7f65ea12L); + assertThat(UnsignedLongs.parseUnsignedLong("6504067269626408013")) + .isEqualTo(0x5a4316b8c153ac4dL); + assertThat(UnsignedLongs.parseUnsignedLong("7851896530399809066")) + .isEqualTo(0x6cf78a4b139a4e2aL); } public void testParseLongEmptyString() { - try { - UnsignedLongs.parseUnsignedLong(""); - fail("NumberFormatException should have been raised."); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedLongs.parseUnsignedLong("")); } public void testParseLongFails() { - try { - // One more than maximum value - UnsignedLongs.parseUnsignedLong("18446744073709551616"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows( + NumberFormatException.class, () -> UnsignedLongs.parseUnsignedLong("18446744073709551616")); } public void testDecodeLong() { - assertEquals(0xffffffffffffffffL, UnsignedLongs.decode("0xffffffffffffffff")); - assertEquals(01234567, UnsignedLongs.decode("01234567")); // octal - assertEquals(0x1234567890abcdefL, UnsignedLongs.decode("#1234567890abcdef")); - assertEquals(987654321012345678L, UnsignedLongs.decode("987654321012345678")); - assertEquals(0x135791357913579L, UnsignedLongs.decode("0x135791357913579")); - assertEquals(0x135791357913579L, UnsignedLongs.decode("0X135791357913579")); - assertEquals(0L, UnsignedLongs.decode("0")); + assertThat(UnsignedLongs.decode("0xffffffffffffffff")).isEqualTo(0xffffffffffffffffL); + assertThat(UnsignedLongs.decode("01234567")).isEqualTo(01234567); // octal + assertThat(UnsignedLongs.decode("#1234567890abcdef")).isEqualTo(0x1234567890abcdefL); + assertThat(UnsignedLongs.decode("987654321012345678")).isEqualTo(987654321012345678L); + assertThat(UnsignedLongs.decode("0x135791357913579")).isEqualTo(0x135791357913579L); + assertThat(UnsignedLongs.decode("0X135791357913579")).isEqualTo(0x135791357913579L); + assertThat(UnsignedLongs.decode("0")).isEqualTo(0L); } public void testDecodeLongFails() { - try { - // One more than maximum value - UnsignedLongs.decode("0xfffffffffffffffff"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedLongs.decode("0xfffffffffffffffff")); - try { - UnsignedLongs.decode("-5"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedLongs.decode("-5")); - try { - UnsignedLongs.decode("-0x5"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedLongs.decode("-0x5")); - try { - UnsignedLongs.decode("-05"); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedLongs.decode("-05")); } public void testParseLongWithRadix() { - assertEquals(0xffffffffffffffffL, UnsignedLongs.parseUnsignedLong("ffffffffffffffff", 16)); - assertEquals(0x1234567890abcdefL, UnsignedLongs.parseUnsignedLong("1234567890abcdef", 16)); + assertThat(UnsignedLongs.parseUnsignedLong("ffffffffffffffff", 16)) + .isEqualTo(0xffffffffffffffffL); + assertThat(UnsignedLongs.parseUnsignedLong("1234567890abcdef", 16)) + .isEqualTo(0x1234567890abcdefL); } public void testParseLongWithRadixLimits() { BigInteger max = BigInteger.ZERO.setBit(64).subtract(ONE); // loops through all legal radix values. - for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + for (int r = Character.MIN_RADIX; r <= Character.MAX_RADIX; r++) { + int radix = r; // tests can successfully parse a number string with this radix. String maxAsString = max.toString(radix); - assertEquals(max.longValue(), UnsignedLongs.parseUnsignedLong(maxAsString, radix)); - - try { - // tests that we get exception whre an overflow would occur. - BigInteger overflow = max.add(ONE); - String overflowAsString = overflow.toString(radix); - UnsignedLongs.parseUnsignedLong(overflowAsString, radix); - fail(); - } catch (NumberFormatException expected) { - } + assertThat(UnsignedLongs.parseUnsignedLong(maxAsString, radix)).isEqualTo(max.longValue()); + + assertThrows( + NumberFormatException.class, + () -> { + BigInteger overflow = max.add(ONE); + String overflowAsString = overflow.toString(radix); + UnsignedLongs.parseUnsignedLong(overflowAsString, radix); + }); } - try { - UnsignedLongs.parseUnsignedLong("1234567890abcdef1", 16); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows( + NumberFormatException.class, + () -> UnsignedLongs.parseUnsignedLong("1234567890abcdef1", 16)); } public void testParseLongThrowsExceptionForInvalidRadix() { // Valid radix values are Character.MIN_RADIX to Character.MAX_RADIX, inclusive. - try { - UnsignedLongs.parseUnsignedLong("0", Character.MIN_RADIX - 1); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows( + NumberFormatException.class, + () -> UnsignedLongs.parseUnsignedLong("0", Character.MIN_RADIX - 1)); - try { - UnsignedLongs.parseUnsignedLong("0", Character.MAX_RADIX + 1); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows( + NumberFormatException.class, + () -> UnsignedLongs.parseUnsignedLong("0", Character.MAX_RADIX + 1)); // The radix is used as an array index, so try a negative value. - try { - UnsignedLongs.parseUnsignedLong("0", -1); - fail(); - } catch (NumberFormatException expected) { - } + assertThrows(NumberFormatException.class, () -> UnsignedLongs.parseUnsignedLong("0", -1)); } public void testToString() { @@ -337,22 +304,23 @@ public void testToString() { for (String x : tests) { BigInteger xValue = new BigInteger(x, 16); long xLong = xValue.longValue(); // signed - assertEquals(xValue.toString(base), UnsignedLongs.toString(xLong, base)); + assertThat(UnsignedLongs.toString(xLong, base)).isEqualTo(xValue.toString(base)); } } } public void testJoin() { - assertEquals("", UnsignedLongs.join(",")); - assertEquals("1", UnsignedLongs.join(",", 1)); - assertEquals("1,2", UnsignedLongs.join(",", 1, 2)); - assertEquals( - "18446744073709551615,9223372036854775808", UnsignedLongs.join(",", -1, Long.MIN_VALUE)); - assertEquals("123", UnsignedLongs.join("", 1, 2, 3)); - assertEquals( - "184467440737095516159223372036854775808", UnsignedLongs.join("", -1, Long.MIN_VALUE)); + assertThat(UnsignedLongs.join(",")).isEmpty(); + assertThat(UnsignedLongs.join(",", 1)).isEqualTo("1"); + assertThat(UnsignedLongs.join(",", 1, 2)).isEqualTo("1,2"); + assertThat(UnsignedLongs.join(",", -1, Long.MIN_VALUE)) + .isEqualTo("18446744073709551615,9223372036854775808"); + assertThat(UnsignedLongs.join("", 1, 2, 3)).isEqualTo("123"); + assertThat(UnsignedLongs.join("", -1, Long.MIN_VALUE)) + .isEqualTo("184467440737095516159223372036854775808"); } + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { new NullPointerTester().testAllPublicStaticMethods(UnsignedLongs.class); diff --git a/android/guava-tests/test/com/google/common/reflect/AbstractInvocationHandlerTest.java b/android/guava-tests/test/com/google/common/reflect/AbstractInvocationHandlerTest.java index 56b20bdfb22a..5684dda5c6e8 100644 --- a/android/guava-tests/test/com/google/common/reflect/AbstractInvocationHandlerTest.java +++ b/android/guava-tests/test/com/google/common/reflect/AbstractInvocationHandlerTest.java @@ -26,12 +26,15 @@ import java.lang.reflect.Proxy; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link AbstractInvocationHandler}. * * @author Ben Yu */ +@NullUnmarked public class AbstractInvocationHandlerTest extends TestCase { private static final ImmutableList LIST1 = ImmutableList.of("one", "two"); @@ -52,10 +55,6 @@ interface A {} interface B {} public void testEquals() { - class AB implements A, B {} - class BA implements B, A {} - AB ab = new AB(); - BA ba = new BA(); new EqualsTester() .addEqualityGroup(newDelegatingList(LIST1)) // Actually, this violates List#equals contract. @@ -136,7 +135,7 @@ private static class DelegatingInvocationHandlerWithEquals extends DelegatingInv } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof DelegatingInvocationHandlerWithEquals) { DelegatingInvocationHandlerWithEquals that = (DelegatingInvocationHandlerWithEquals) obj; return delegate.equals(that.delegate); diff --git a/android/guava-tests/test/com/google/common/reflect/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/reflect/AndroidIncompatible.java index 7dae25d1c8cd..010e1a178416 100644 --- a/android/guava-tests/test/com/google/common/reflect/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/reflect/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/reflect/ClassPathTest.java b/android/guava-tests/test/com/google/common/reflect/ClassPathTest.java index d199d1f90cb0..653e523dfbe9 100644 --- a/android/guava-tests/test/com/google/common/reflect/ClassPathTest.java +++ b/android/guava-tests/test/com/google/common/reflect/ClassPathTest.java @@ -15,10 +15,11 @@ */ package com.google.common.reflect; -import static com.google.common.base.Charsets.US_ASCII; import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH; +import static com.google.common.base.StandardSystemProperty.OS_NAME; import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.US_ASCII; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -33,24 +34,23 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; -import java.io.FilePermission; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; -import java.security.Permission; -import java.security.PermissionCollection; import java.util.jar.Attributes; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.logging.Logger; import java.util.zip.ZipEntry; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; import org.junit.Test; /** Functional tests of {@link ClassPath}. */ +@NullUnmarked public class ClassPathTest extends TestCase { private static final Logger log = Logger.getLogger(ClassPathTest.class.getName()); private static final File FILE = new File("."); @@ -73,7 +73,7 @@ public void testClassPathEntries_emptyURLClassLoader_noParent() { } @AndroidIncompatible // Android forbids null parent ClassLoader - public void testClassPathEntries_URLClassLoader_noParent() throws Exception { + public void testClassPathEntries_urlClassLoader_noParent() throws Exception { URL url1 = new URL("file:/a"); URL url2 = new URL("file:/b"); URLClassLoader classloader = new URLClassLoader(new URL[] {url1, url2}, null); @@ -82,7 +82,7 @@ public void testClassPathEntries_URLClassLoader_noParent() throws Exception { } @AndroidIncompatible // Android forbids null parent ClassLoader - public void testClassPathEntries_URLClassLoader_withParent() throws Exception { + public void testClassPathEntries_urlClassLoader_withParent() throws Exception { URL url1 = new URL("file:/a"); URL url2 = new URL("file:/b"); URLClassLoader parent = new URLClassLoader(new URL[] {url1}, null); @@ -134,7 +134,7 @@ public void testClassPathEntries_notURLClassLoader_withGrandParent() throws Exce @AndroidIncompatible // Android forbids null parent ClassLoader // https://github.com/google/guava/issues/2152 - public void testClassPathEntries_URLClassLoader_pathWithSpace() throws Exception { + public void testClassPathEntries_urlClassLoader_pathWithSpace() throws Exception { URL url = new URL("file:///c:/Documents and Settings/"); URLClassLoader classloader = new URLClassLoader(new URL[] {url}, null); assertThat(ClassPath.getClassPathEntries(classloader)) @@ -143,7 +143,7 @@ public void testClassPathEntries_URLClassLoader_pathWithSpace() throws Exception @AndroidIncompatible // Android forbids null parent ClassLoader // https://github.com/google/guava/issues/2152 - public void testClassPathEntries_URLClassLoader_pathWithEscapedSpace() throws Exception { + public void testClassPathEntries_urlClassLoader_pathWithEscapedSpace() throws Exception { URL url = new URL("file:///c:/Documents%20and%20Settings/"); URLClassLoader classloader = new URLClassLoader(new URL[] {url}, null); assertThat(ClassPath.getClassPathEntries(classloader)) @@ -160,7 +160,7 @@ public void testToFile() throws Exception { // https://github.com/google/guava/issues/2152 @AndroidIncompatible // works in newer Android versions but fails at the version we test with - public void testToFile_AndroidIncompatible() throws Exception { + public void testToFile_androidIncompatible() throws Exception { assertThat(ClassPath.toFile(new URL("file:///c:\\Documents ~ Settings, or not\\11-12 12:05"))) .isEqualTo(new File("/c:\\Documents ~ Settings, or not\\11-12 12:05")); assertThat(ClassPath.toFile(new URL("file:///C:\\Program Files\\Apache Software Foundation"))) @@ -178,6 +178,7 @@ public void testJarFileWithSpaces() throws Exception { assertThat(ClassPath.from(classloader).getTopLevelClasses()).isNotEmpty(); } + @AndroidIncompatible // ClassPath is documented as not supporting Android public void testScan_classPathCycle() throws IOException { File jarFile = File.createTempFile("with_circular_class_path", ".jar"); @@ -201,6 +202,7 @@ public void testScanFromFile_fileNotExists() throws IOException { .isEmpty(); } + @AndroidIncompatible // ClassPath is documented as not supporting Android public void testScanFromFile_notJarFile() throws IOException { ClassLoader classLoader = ClassPathTest.class.getClassLoader(); @@ -213,6 +215,9 @@ public void testScanFromFile_notJarFile() throws IOException { } public void testGetClassPathEntry() throws MalformedURLException, URISyntaxException { + if (isWindows()) { + return; // TODO: b/136041958 - We need to account for drive letters in the path. + } assertEquals( new File("/usr/test/dep.jar").toURI(), ClassPath.getClassPathEntry(new File("/home/build/outer.jar"), "file:/usr/test/dep.jar") @@ -283,6 +288,9 @@ public void testGetClassPathFromManifest_jarInCurrentDirectory() throws IOExcept } public void testGetClassPathFromManifest_absoluteDirectory() throws IOException { + if (isWindows()) { + return; // TODO: b/136041958 - We need to account for drive letters in the path. + } File jarFile = new File("base/some.jar"); Manifest manifest = manifestClasspath("file:/with/absolute/dir"); assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest)) @@ -290,6 +298,9 @@ public void testGetClassPathFromManifest_absoluteDirectory() throws IOException } public void testGetClassPathFromManifest_absoluteJar() throws IOException { + if (isWindows()) { + return; // TODO: b/136041958 - We need to account for drive letters in the path. + } File jarFile = new File("base/some.jar"); Manifest manifest = manifestClasspath("file:/with/absolute.jar"); assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest)) @@ -297,6 +308,9 @@ public void testGetClassPathFromManifest_absoluteJar() throws IOException { } public void testGetClassPathFromManifest_multiplePaths() throws IOException { + if (isWindows()) { + return; // TODO: b/136041958 - We need to account for drive letters in the path. + } File jarFile = new File("base/some.jar"); Manifest manifest = manifestClasspath("file:/with/absolute.jar relative.jar relative/dir"); assertThat(ClassPath.getClassPathFromManifest(jarFile, manifest)) @@ -352,7 +366,11 @@ public void testGetPackageName() { // Test that ResourceInfo.urls() returns identical content to ClassLoader.getResources() + @AndroidIncompatible public void testGetClassPathUrls() throws Exception { + if (isWindows()) { + return; // TODO: b/136041958 - We need to account for drive letters in the path. + } String oldPathSeparator = PATH_SEPARATOR.value(); String oldClassPath = JAVA_CLASS_PATH.value(); System.setProperty(PATH_SEPARATOR.key(), ":"); @@ -386,10 +404,6 @@ public void testGetClassPathUrls() throws Exception { } } - private static boolean contentEquals(URL left, URL right) throws IOException { - return Resources.asByteSource(left).contentEquals(Resources.asByteSource(right)); - } - private static class Nested {} @@ -399,6 +413,7 @@ public void testNulls() throws IOException { .testAllPublicInstanceMethods(ClassPath.from(getClass().getClassLoader())); } + @AndroidIncompatible // ClassPath is documented as not supporting Android public void testLocationsFrom_idempotentScan() throws IOException { ImmutableSet locations = @@ -429,56 +444,13 @@ public void testLocationEquals() { .testEquals(); } + @AndroidIncompatible // ClassPath is documented as not supporting Android public void testScanAllResources() throws IOException { assertThat(scanResourceNames(ClassLoader.getSystemClassLoader())) .contains("com/google/common/reflect/ClassPathTest.class"); } - - public void testExistsThrowsSecurityException() throws IOException, URISyntaxException { - SecurityManager oldSecurityManager = System.getSecurityManager(); - try { - doTestExistsThrowsSecurityException(); - } finally { - System.setSecurityManager(oldSecurityManager); - } - } - - private void doTestExistsThrowsSecurityException() throws IOException, URISyntaxException { - File file = null; - // In Java 9, Logger may read the TZ database. Only disallow reading the class path URLs. - final PermissionCollection readClassPathFiles = - new FilePermission("", "read").newPermissionCollection(); - for (URL url : ClassPath.parseJavaClassPath()) { - if (url.getProtocol().equalsIgnoreCase("file")) { - file = new File(url.toURI()); - readClassPathFiles.add(new FilePermission(file.getAbsolutePath(), "read")); - } - } - assertThat(file).isNotNull(); - SecurityManager disallowFilesSecurityManager = - new SecurityManager() { - @Override - public void checkPermission(Permission p) { - if (readClassPathFiles.implies(p)) { - throw new SecurityException("Disallowed: " + p); - } - } - }; - System.setSecurityManager(disallowFilesSecurityManager); - try { - file.exists(); - fail("Did not get expected SecurityException"); - } catch (SecurityException expected) { - } - ClassPath classPath = ClassPath.from(getClass().getClassLoader()); - // ClassPath may contain resources from the boot class loader; just not from the class path. - for (ResourceInfo resource : classPath.getResources()) { - assertThat(resource.getResourceName()).doesNotContain("com/google/common/reflect/"); - } - } - private static ClassPath.ClassInfo findClass( Iterable classes, Class cls) { for (ClassPath.ClassInfo classInfo : classes) { @@ -518,7 +490,7 @@ private static void writeSelfReferencingJarFile(File jarFile, String... entries) Closer closer = Closer.create(); try { FileOutputStream fileOut = closer.register(new FileOutputStream(jarFile)); - JarOutputStream jarOut = closer.register(new JarOutputStream(fileOut)); + JarOutputStream jarOut = closer.register(new JarOutputStream(fileOut, manifest)); for (String entry : entries) { jarOut.putNextEntry(new ZipEntry(entry)); Resources.copy(ClassPathTest.class.getResource(entry), jarOut); @@ -543,6 +515,10 @@ private static File fullpath(String path) { } private static URL makeJarUrlWithName(String name) throws IOException { + /* + * TODO: cpovirk - Use java.nio.file.Files.createTempDirectory instead of + * c.g.c.io.Files.createTempDir? + */ File fullPath = new File(Files.createTempDir(), name); File jarFile = pickAnyJarFile(); Files.copy(jarFile, fullPath); @@ -568,4 +544,8 @@ private static ImmutableSet scanResourceNames(ClassLoader loader) throws } return builder.build(); } + + private static boolean isWindows() { + return OS_NAME.value().startsWith("Windows"); + } } diff --git a/android/guava-tests/test/com/google/common/reflect/ElementTest.java b/android/guava-tests/test/com/google/common/reflect/ElementTest.java deleted file mode 100644 index abe63ba56163..000000000000 --- a/android/guava-tests/test/com/google/common/reflect/ElementTest.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2012 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.reflect; - -import com.google.common.testing.EqualsTester; -import com.google.common.testing.NullPointerTester; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.Constructor; -import junit.framework.TestCase; - -/** - * Unit tests of {@link Element}. - * - * @author Ben Yu - */ -public class ElementTest extends TestCase { - - public void testPrivateField() throws Exception { - Element element = A.field("privateField"); - assertTrue(element.isPrivate()); - assertFalse(element.isAbstract()); - assertFalse(element.isPackagePrivate()); - assertFalse(element.isProtected()); - assertFalse(element.isPublic()); - assertFalse(element.isFinal()); - assertFalse(element.isStatic()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testPackagePrivateField() throws Exception { - Element element = A.field("packagePrivateField"); - assertFalse(element.isPrivate()); - assertTrue(element.isPackagePrivate()); - assertFalse(element.isProtected()); - assertFalse(element.isPublic()); - assertFalse(element.isFinal()); - assertFalse(element.isStatic()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testProtectedField() throws Exception { - Element element = A.field("protectedField"); - assertFalse(element.isPrivate()); - assertFalse(element.isPackagePrivate()); - assertTrue(element.isProtected()); - assertFalse(element.isPublic()); - assertFalse(element.isFinal()); - assertFalse(element.isStatic()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testPublicField() throws Exception { - Element element = A.field("publicField"); - assertFalse(element.isPrivate()); - assertFalse(element.isPackagePrivate()); - assertFalse(element.isProtected()); - assertTrue(element.isPublic()); - assertFalse(element.isFinal()); - assertFalse(element.isStatic()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testFinalField() throws Exception { - Element element = A.field("finalField"); - assertTrue(element.isFinal()); - assertFalse(element.isStatic()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testStaticField() throws Exception { - Element element = A.field("staticField"); - assertTrue(element.isStatic()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testVolatileField() throws Exception { - Element element = A.field("volatileField"); - assertTrue(element.isVolatile()); - } - - public void testTransientField() throws Exception { - Element element = A.field("transientField"); - assertTrue(element.isTransient()); - } - - public void testConstructor() throws Exception { - Element element = A.constructor(); - assertTrue(element.isPublic()); - assertFalse(element.isPackagePrivate()); - assertFalse(element.isAbstract()); - assertFalse(element.isStatic()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testAbstractMethod() throws Exception { - Element element = A.method("abstractMethod"); - assertTrue(element.isPackagePrivate()); - assertTrue(element.isAbstract()); - assertFalse(element.isFinal()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testOverridableMethod() throws Exception { - Element element = A.method("overridableMethod"); - assertTrue(element.isPackagePrivate()); - assertFalse(element.isAbstract()); - assertFalse(element.isFinal()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testPrivateMethod() throws Exception { - Element element = A.method("privateMethod"); - assertFalse(element.isAbstract()); - assertTrue(element.isPrivate()); - assertFalse(element.isPackagePrivate()); - assertFalse(element.isPublic()); - assertFalse(element.isProtected()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testProtectedMethod() throws Exception { - Element element = A.method("protectedMethod"); - assertFalse(element.isAbstract()); - assertFalse(element.isPrivate()); - assertFalse(element.isPackagePrivate()); - assertFalse(element.isFinal()); - assertFalse(element.isPublic()); - assertTrue(element.isProtected()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testFinalMethod() throws Exception { - Element element = A.method("publicFinalMethod"); - assertFalse(element.isAbstract()); - assertFalse(element.isPrivate()); - assertTrue(element.isFinal()); - assertTrue(element.isPublic()); - assertTrue(element.isAnnotationPresent(Tested.class)); - } - - public void testNativeMethod() throws Exception { - Element element = A.method("nativeMethod"); - assertTrue(element.isNative()); - assertTrue(element.isPackagePrivate()); - } - - public void testSynchronizedMethod() throws Exception { - Element element = A.method("synchronizedMethod"); - assertTrue(element.isSynchronized()); - } - - public void testUnannotatedMethod() throws Exception { - Element element = A.method("notAnnotatedMethod"); - assertFalse(element.isAnnotationPresent(Tested.class)); - } - - public void testEquals() throws Exception { - new EqualsTester() - .addEqualityGroup(A.field("privateField"), A.field("privateField")) - .addEqualityGroup(A.field("publicField")) - .addEqualityGroup(A.constructor(), A.constructor()) - .addEqualityGroup(A.method("privateMethod"), A.method("privateMethod")) - .addEqualityGroup(A.method("publicFinalMethod")) - .testEquals(); - } - - public void testNulls() { - new NullPointerTester().testAllPublicStaticMethods(Element.class); - } - - @Retention(RetentionPolicy.RUNTIME) - private @interface Tested {} - - private abstract static class A { - @Tested private boolean privateField; - @Tested int packagePrivateField; - @Tested protected int protectedField; - @Tested public String publicField; - @Tested private static Iterable staticField; - @Tested private final Object finalField; - private volatile char volatileField; - private transient long transientField; - - @Tested - public A(Object finalField) { - this.finalField = finalField; - } - - @Tested - abstract void abstractMethod(); - - @Tested - void overridableMethod() {} - - @Tested - protected void protectedMethod() {} - - @Tested - private void privateMethod() {} - - @Tested - public final void publicFinalMethod() {} - - void notAnnotatedMethod() {} - - static Element field(String name) throws Exception { - Element element = new Element(A.class.getDeclaredField(name)); - assertEquals(name, element.getName()); - assertEquals(A.class, element.getDeclaringClass()); - return element; - } - - static Element constructor() throws Exception { - Constructor constructor = A.class.getDeclaredConstructor(Object.class); - Element element = new Element(constructor); - assertEquals(constructor.getName(), element.getName()); - assertEquals(A.class, element.getDeclaringClass()); - return element; - } - - static Element method(String name, Class... parameterTypes) throws Exception { - Element element = new Element(A.class.getDeclaredMethod(name, parameterTypes)); - assertEquals(name, element.getName()); - assertEquals(A.class, element.getDeclaringClass()); - return element; - } - - native void nativeMethod(); - - synchronized void synchronizedMethod() {} - } -} diff --git a/android/guava-tests/test/com/google/common/reflect/ImmutableTypeToInstanceMapTest.java b/android/guava-tests/test/com/google/common/reflect/ImmutableTypeToInstanceMapTest.java index 064c4b5c1533..56493b56e483 100644 --- a/android/guava-tests/test/com/google/common/reflect/ImmutableTypeToInstanceMapTest.java +++ b/android/guava-tests/test/com/google/common/reflect/ImmutableTypeToInstanceMapTest.java @@ -18,6 +18,7 @@ import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.testing.MapTestSuiteBuilder; @@ -32,12 +33,14 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link ImmutableTypeToInstanceMap}. * * @author Ben Yu */ +@NullUnmarked public class ImmutableTypeToInstanceMapTest extends TestCase { @AndroidIncompatible // problem with suite builders on Android @@ -51,13 +54,13 @@ public static Test suite() { // Other tests will verify what real, warning-free usage looks like // but here we have to do some serious fudging @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) public Map create(Object... elements) { ImmutableTypeToInstanceMap.Builder builder = ImmutableTypeToInstanceMap.builder(); for (Object object : elements) { - Entry entry = (Entry) object; - builder.put(entry.getKey(), entry.getValue()); + Entry entry = (Entry) object; + builder.put((TypeToken) entry.getKey(), entry.getValue()); } return (Map) builder.build(); } @@ -102,7 +105,8 @@ public void testParameterizedType() { public void testGenericArrayType() { @SuppressWarnings("unchecked") // Trying to test generic array - ImmutableList[] array = new ImmutableList[] {ImmutableList.of(1)}; + ImmutableList[] array = + (ImmutableList[]) new ImmutableList[] {ImmutableList.of(1)}; TypeToken[]> type = new TypeToken[]>() {}; ImmutableTypeToInstanceMap[]> map = ImmutableTypeToInstanceMap.[]>builder().put(type, array).build(); @@ -121,33 +125,29 @@ public void testWildcardType() { public void testGetInstance_containsTypeVariable() { ImmutableTypeToInstanceMap> map = ImmutableTypeToInstanceMap.of(); - try { - map.getInstance(this.anyIterableType()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> map.getInstance(this.anyIterableType())); } public void testPut_containsTypeVariable() { ImmutableTypeToInstanceMap.Builder> builder = ImmutableTypeToInstanceMap.builder(); - try { - builder.put(this.anyIterableType(), ImmutableList.of(1)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> builder.put(this.anyIterableType(), ImmutableList.of(1))); } private TypeToken> anyIterableType() { return new TypeToken>() {}; } + @SuppressWarnings("rawtypes") // TODO(cpovirk): Can we at least use Class in some places? abstract static class TestTypeToInstanceMapGenerator implements TestMapGenerator { @Override - public TypeToken[] createKeyArray(int length) { - return new TypeToken[length]; + public TypeToken[] createKeyArray(int length) { + return new TypeToken[length]; } @Override @@ -172,7 +172,7 @@ private static Entry entry(TypeToken k, Object v) { @Override @SuppressWarnings("unchecked") public Entry[] createArray(int length) { - return new Entry[length]; + return (Entry[]) new Entry[length]; } @Override diff --git a/android/guava-tests/test/com/google/common/reflect/InvokableTest.java b/android/guava-tests/test/com/google/common/reflect/InvokableTest.java index 816aed3326ee..389028582761 100644 --- a/android/guava-tests/test/com/google/common/reflect/InvokableTest.java +++ b/android/guava-tests/test/com/google/common/reflect/InvokableTest.java @@ -17,20 +17,27 @@ package com.google.common.reflect; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; +import com.google.errorprone.annotations.Keep; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.TypeVariable; import java.util.Collections; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit tests for {@link Invokable}. @@ -38,7 +45,180 @@ * @author Ben Yu */ @AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations? +@NullUnmarked public class InvokableTest extends TestCase { + // Historically Invokable inherited from java.lang.reflect.AccessibleObject. That's no longer the + // case, but we do check that its API still has the same public methods. We exclude some methods + // that were added in Java 9 and that people probably weren't calling via Invokable, namely + // `boolean canAccess(Object)`. + public void testApiCompatibleWithAccessibleObject() { + ImmutableSet invokableMethods = + publicMethodSignatures(Invokable.class, ImmutableSet.of()); + ImmutableSet accessibleObjectMethods = + publicMethodSignatures(AccessibleObject.class, ImmutableSet.of("canAccess")); + assertThat(invokableMethods).containsAtLeastElementsIn(accessibleObjectMethods); + ImmutableSet genericDeclarationMethods = + publicMethodSignatures(GenericDeclaration.class, ImmutableSet.of()); + assertThat(invokableMethods).containsAtLeastElementsIn(genericDeclarationMethods); + } + + private static ImmutableSet publicMethodSignatures( + Class c, ImmutableSet ignore) { + ImmutableSet.Builder methods = ImmutableSet.builder(); + for (Method method : c.getMethods()) { + if (Modifier.isStatic(method.getModifiers()) || ignore.contains(method.getName())) { + continue; + } + StringBuilder signature = + new StringBuilder() + .append(typeName(method.getReturnType())) + .append(" ") + .append(method.getName()) + .append("("); + String sep = ""; + for (Class param : method.getParameterTypes()) { + signature.append(sep).append(typeName(param)); + sep = ", "; + } + methods.add(signature.append(")").toString()); + } + return methods.build(); + } + + private static String typeName(Class type) { + return type.isArray() ? typeName(type.getComponentType()) + "[]" : type.getName(); + } + + public void testConstructor() throws Exception { + Invokable invokable = A.constructor(); + assertTrue(invokable.isPublic()); + assertFalse(invokable.isPackagePrivate()); + assertFalse(invokable.isAbstract()); + assertFalse(invokable.isStatic()); + assertTrue(invokable.isAnnotationPresent(Tested.class)); + } + + public void testAbstractMethod() throws Exception { + Invokable invokable = A.method("abstractMethod"); + assertTrue(invokable.isPackagePrivate()); + assertTrue(invokable.isAbstract()); + assertFalse(invokable.isFinal()); + assertTrue(invokable.isAnnotationPresent(Tested.class)); + } + + public void testOverridableMethod() throws Exception { + Invokable invokable = A.method("overridableMethod"); + assertTrue(invokable.isPackagePrivate()); + assertFalse(invokable.isAbstract()); + assertFalse(invokable.isFinal()); + assertTrue(invokable.isAnnotationPresent(Tested.class)); + } + + public void testPrivateMethod() throws Exception { + Invokable invokable = A.method("privateMethod"); + assertFalse(invokable.isAbstract()); + assertTrue(invokable.isPrivate()); + assertFalse(invokable.isPackagePrivate()); + assertFalse(invokable.isPublic()); + assertFalse(invokable.isProtected()); + assertTrue(invokable.isAnnotationPresent(Tested.class)); + } + + public void testProtectedMethod() throws Exception { + Invokable invokable = A.method("protectedMethod"); + assertFalse(invokable.isAbstract()); + assertFalse(invokable.isPrivate()); + assertFalse(invokable.isPackagePrivate()); + assertFalse(invokable.isFinal()); + assertFalse(invokable.isPublic()); + assertTrue(invokable.isProtected()); + assertTrue(invokable.isAnnotationPresent(Tested.class)); + } + + public void testFinalMethod() throws Exception { + Invokable invokable = A.method("publicFinalMethod"); + assertFalse(invokable.isAbstract()); + assertFalse(invokable.isPrivate()); + assertTrue(invokable.isFinal()); + assertTrue(invokable.isPublic()); + assertTrue(invokable.isAnnotationPresent(Tested.class)); + } + + public void testNativeMethod() throws Exception { + Invokable invokable = A.method("nativeMethod"); + assertTrue(invokable.isNative()); + assertTrue(invokable.isPackagePrivate()); + } + + public void testSynchronizedMethod() throws Exception { + Invokable invokable = A.method("synchronizedMethod"); + assertTrue(invokable.isSynchronized()); + } + + public void testUnannotatedMethod() throws Exception { + Invokable invokable = A.method("notAnnotatedMethod"); + assertFalse(invokable.isAnnotationPresent(Tested.class)); + } + + @Retention(RetentionPolicy.RUNTIME) + @Keep + private @interface Tested {} + + private abstract static class A { + @Tested private boolean privateField; + @Tested int packagePrivateField; + @Tested protected int protectedField; + @Tested public String publicField; + @Tested private static Iterable staticField; + @Tested private final Object finalField; + private volatile char volatileField; + private transient long transientField; + + @Keep + @Tested + public A(Object finalField) { + this.finalField = finalField; + } + + @Tested + abstract void abstractMethod(); + + @Keep + @Tested + void overridableMethod() {} + + @Tested + protected void protectedMethod() {} + + @Tested + private void privateMethod() {} + + @Keep + @Tested + public final void publicFinalMethod() {} + + void notAnnotatedMethod() {} + + static Invokable constructor() throws Exception { + Constructor constructor = A.class.getDeclaredConstructor(Object.class); + Invokable invokable = Invokable.from(constructor); + assertEquals(constructor.getName(), invokable.getName()); + assertEquals(A.class, invokable.getDeclaringClass()); + return invokable; + } + + static Invokable method(String name, Class... parameterTypes) throws Exception { + Invokable invokable = + Invokable.from(A.class.getDeclaredMethod(name, parameterTypes)); + assertEquals(name, invokable.getName()); + assertEquals(A.class, invokable.getDeclaringClass()); + return invokable; + } + + native void nativeMethod(); + + synchronized void synchronizedMethod() {} + } public void testConstructor_returnType() throws Exception { assertEquals(Prepender.class, Prepender.constructor().getReturnType().getType()); @@ -50,7 +230,6 @@ WithConstructorAndTypeParameter() {} } public void testConstructor_returnType_hasTypeParameter() throws Exception { - @SuppressWarnings("rawtypes") // Foo.class for Foo is always raw type Class type = WithConstructorAndTypeParameter.class; @SuppressWarnings("rawtypes") // Foo.class Constructor constructor = type.getDeclaredConstructor(); @@ -74,7 +253,7 @@ public void testConstructor_exceptionTypes() throws Exception { public void testConstructor_typeParameters() throws Exception { TypeVariable[] variables = Prepender.constructor().getTypeParameters(); assertThat(variables).hasLength(1); - assertEquals("A", variables[0].getName()); + assertEquals("T", variables[0].getName()); } public void testConstructor_parameters() throws Exception { @@ -108,11 +287,7 @@ public void testConstructor_returning() throws Exception { public void testConstructor_invalidReturning() throws Exception { Invokable delegate = Prepender.constructor(String.class, int.class); - try { - delegate.returning(SubPrepender.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> delegate.returning(SubPrepender.class)); } public void testStaticMethod_returnType() throws Exception { @@ -175,11 +350,9 @@ public void testStaticMethod_returningRawType() throws Exception { public void testStaticMethod_invalidReturning() throws Exception { Invokable delegate = Prepender.method("prepend", String.class, Iterable.class); - try { - delegate.returning(new TypeToken>() {}); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> delegate.returning(new TypeToken>() {})); } public void testInstanceMethod_returnType() throws Exception { @@ -237,11 +410,9 @@ public void testInstanceMethod_returningRawType() throws Exception { public void testInstanceMethod_invalidReturning() throws Exception { Invokable delegate = Prepender.method("prepend", Iterable.class); - try { - delegate.returning(new TypeToken>() {}); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> delegate.returning(new TypeToken>() {})); } public void testPrivateInstanceMethod_isOverridable() throws Exception { @@ -276,7 +447,7 @@ public void testStaticFinalMethod_isFinal() throws Exception { static class Foo {} - public void testConstructor_isOverridablel() throws Exception { + public void testConstructor_isOverridable() throws Exception { Invokable delegate = Invokable.from(Foo.class.getDeclaredConstructor()); assertFalse(delegate.isOverridable()); assertFalse(delegate.isVarArgs()); @@ -330,7 +501,7 @@ public void testNestedInnerClassDefaultConstructor() { private class InnerWithOneParameterConstructor { @SuppressWarnings("unused") // called by reflection - public InnerWithOneParameterConstructor(String s) {} + InnerWithOneParameterConstructor(String s) {} } public void testInnerClassWithOneParameterConstructor() { @@ -343,7 +514,7 @@ public void testInnerClassWithOneParameterConstructor() { private class InnerWithAnnotatedConstructorParameter { @SuppressWarnings("unused") // called by reflection - InnerWithAnnotatedConstructorParameter(@NullableDecl String s) {} + InnerWithAnnotatedConstructorParameter(@Nullable String s) {} } public void testInnerClassWithAnnotatedConstructorParameter() { @@ -369,8 +540,8 @@ public void testInnerClassWithGenericConstructorParameter() { } public void testAnonymousClassDefaultConstructor() { - final int i = 1; - final String s = "hello world"; + int i = 1; + String s = "hello world"; Class anonymous = new Runnable() { @Override @@ -393,8 +564,8 @@ abstract class Base { } public void testLocalClassDefaultConstructor() { - final int i = 1; - final String s = "hello world"; + int i = 1; + String s = "hello world"; class LocalWithDefaultConstructor implements Runnable { @Override public void run() { @@ -410,8 +581,8 @@ public void testStaticAnonymousClassDefaultConstructor() throws Exception { } private static void doTestStaticAnonymousClassDefaultConstructor() { - final int i = 1; - final String s = "hello world"; + int i = 1; + String s = "hello world"; Class anonymous = new Runnable() { @Override @@ -424,13 +595,13 @@ public void run() { } public void testAnonymousClassInConstructor() { - new AnonymousClassInConstructor(); + AnonymousClassInConstructor unused = new AnonymousClassInConstructor(); } private static class AnonymousClassInConstructor { AnonymousClassInConstructor() { - final int i = 1; - final String s = "hello world"; + int i = 1; + String s = "hello world"; Class anonymous = new Runnable() { @Override @@ -444,7 +615,7 @@ public void run() { } public void testLocalClassInInstanceInitializer() { - new LocalClassInInstanceInitializer(); + LocalClassInInstanceInitializer unused = new LocalClassInInstanceInitializer(); } private static class LocalClassInInstanceInitializer { @@ -456,7 +627,7 @@ class Local {} } public void testLocalClassInStaticInitializer() { - new LocalClassInStaticInitializer(); + LocalClassInStaticInitializer unused = new LocalClassInStaticInitializer(); } private static class LocalClassInStaticInitializer { @@ -467,8 +638,9 @@ class Local {} } } - public void testLocalClassWithSeeminglyHiddenThisInStaticInitializer_BUG() { - new LocalClassWithSeeminglyHiddenThisInStaticInitializer(); + public void testLocalClassWithSeeminglyHiddenThisInStaticInitializer_bug() { + LocalClassWithSeeminglyHiddenThisInStaticInitializer unused = + new LocalClassWithSeeminglyHiddenThisInStaticInitializer(); } /** @@ -488,11 +660,11 @@ class Local { } public void testLocalClassWithOneParameterConstructor() throws Exception { - final int i = 1; - final String s = "hello world"; + int i = 1; + String s = "hello world"; class LocalWithOneParameterConstructor { @SuppressWarnings("unused") // called by reflection - public LocalWithOneParameterConstructor(String x) { + LocalWithOneParameterConstructor(String x) { System.out.println(s + i); } } @@ -506,7 +678,7 @@ public LocalWithOneParameterConstructor(String x) { public void testLocalClassWithAnnotatedConstructorParameter() throws Exception { class LocalWithAnnotatedConstructorParameter { @SuppressWarnings("unused") // called by reflection - LocalWithAnnotatedConstructorParameter(@NullableDecl String s) {} + LocalWithAnnotatedConstructorParameter(@Nullable String s) {} } Constructor constructor = LocalWithAnnotatedConstructorParameter.class.getDeclaredConstructors()[0]; @@ -530,6 +702,9 @@ class LocalWithGenericConstructorParameter { public void testEquals() throws Exception { new EqualsTester() + .addEqualityGroup(A.constructor(), A.constructor()) + .addEqualityGroup(A.method("privateMethod"), A.method("privateMethod")) + .addEqualityGroup(A.method("publicFinalMethod")) .addEqualityGroup(Prepender.constructor(), Prepender.constructor()) .addEqualityGroup(Prepender.constructor(String.class, int.class)) .addEqualityGroup(Prepender.method("privateMethod"), Prepender.method("privateMethod")) @@ -552,7 +727,7 @@ private static class Prepender { private final String prefix; private final int times; - Prepender(@NotBlank String prefix, int times) throws NullPointerException { + Prepender(@NotBlank @Nullable String prefix, int times) throws NullPointerException { this.prefix = prefix; this.times = times; } @@ -562,7 +737,7 @@ private static class Prepender { } // just for testing - private Prepender() { + private Prepender() { this(null, 0); } @@ -605,7 +780,7 @@ private void privateVarArgsMethod(String... varargs) {} private static class SubPrepender extends Prepender { @SuppressWarnings("unused") // needed to satisfy compiler, never called - public SubPrepender() throws NullPointerException { + SubPrepender() throws NullPointerException { throw new AssertionError(); } } diff --git a/android/guava-tests/test/com/google/common/reflect/MutableTypeToInstanceMapTest.java b/android/guava-tests/test/com/google/common/reflect/MutableTypeToInstanceMapTest.java index cea81e3a2832..f034b3812731 100644 --- a/android/guava-tests/test/com/google/common/reflect/MutableTypeToInstanceMapTest.java +++ b/android/guava-tests/test/com/google/common/reflect/MutableTypeToInstanceMapTest.java @@ -18,6 +18,7 @@ import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -31,12 +32,14 @@ import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Unit test of {@link MutableTypeToInstanceMap}. * * @author Ben Yu */ +@NullUnmarked public class MutableTypeToInstanceMapTest extends TestCase { @AndroidIncompatible // problem with suite builders on Android @@ -50,7 +53,7 @@ public static Test suite() { // Other tests will verify what real, warning-free usage looks like // but here we have to do some serious fudging @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) public Map create(Object... elements) { MutableTypeToInstanceMap map = new MutableTypeToInstanceMap<>(); for (Object object : elements) { @@ -81,62 +84,47 @@ protected void setUp() throws Exception { } public void testPutThrows() { - try { - map.put(TypeToken.of(Integer.class), new Integer(5)); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, + () -> map.put(TypeToken.of(Integer.class), Integer.valueOf(5))); } public void testPutAllThrows() { - try { - map.putAll(ImmutableMap.of(TypeToken.of(Integer.class), new Integer(5))); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, + () -> map.putAll(ImmutableMap.of(TypeToken.of(Integer.class), Integer.valueOf(5)))); } public void testEntrySetMutationThrows() { map.putInstance(String.class, "test"); assertEquals(TypeToken.of(String.class), map.entrySet().iterator().next().getKey()); assertEquals("test", map.entrySet().iterator().next().getValue()); - try { - map.entrySet().iterator().next().setValue(1); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows( + UnsupportedOperationException.class, () -> map.entrySet().iterator().next().setValue(1)); } public void testEntrySetToArrayMutationThrows() { map.putInstance(String.class, "test"); @SuppressWarnings("unchecked") // Should get a CCE later if cast is wrong - Entry entry = (Entry) map.entrySet().toArray()[0]; + Entry entry = (Entry) map.entrySet().toArray()[0]; assertEquals(TypeToken.of(String.class), entry.getKey()); assertEquals("test", entry.getValue()); - try { - entry.setValue(1); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> entry.setValue(1)); } public void testEntrySetToTypedArrayMutationThrows() { map.putInstance(String.class, "test"); @SuppressWarnings("unchecked") // Should get a CCE later if cast is wrong - Entry entry = map.entrySet().toArray(new Entry[0])[0]; + Entry entry = (Entry) map.entrySet().toArray(new Entry[0])[0]; assertEquals(TypeToken.of(String.class), entry.getKey()); assertEquals("test", entry.getValue()); - try { - entry.setValue(1); - fail(); - } catch (UnsupportedOperationException expected) { - } + assertThrows(UnsupportedOperationException.class, () -> entry.setValue(1)); } public void testPutAndGetInstance() { - assertNull(map.putInstance(Integer.class, new Integer(5))); + assertThat(map.putInstance(Integer.class, Integer.valueOf(5))).isNull(); - Integer oldValue = map.putInstance(Integer.class, new Integer(7)); + Integer oldValue = map.putInstance(Integer.class, Integer.valueOf(7)); assertEquals(5, (int) oldValue); Integer newValue = map.getInstance(Integer.class); @@ -147,30 +135,28 @@ public void testPutAndGetInstance() { } public void testNull() { - try { - map.putInstance((TypeToken) null, new Integer(1)); - fail(); - } catch (NullPointerException expected) { - } + assertThrows( + NullPointerException.class, + () -> map.putInstance((TypeToken) null, Integer.valueOf(1))); map.putInstance(Integer.class, null); assertTrue(map.containsKey(TypeToken.of(Integer.class))); assertTrue(map.entrySet().contains(immutableEntry(TypeToken.of(Integer.class), null))); - assertNull(map.get(TypeToken.of(Integer.class))); - assertNull(map.getInstance(Integer.class)); + assertThat(map.get(TypeToken.of(Integer.class))).isNull(); + assertThat(map.getInstance(Integer.class)).isNull(); map.putInstance(Long.class, null); assertTrue(map.containsKey(TypeToken.of(Long.class))); assertTrue(map.entrySet().contains(immutableEntry(TypeToken.of(Long.class), null))); - assertNull(map.get(TypeToken.of(Long.class))); - assertNull(map.getInstance(Long.class)); + assertThat(map.get(TypeToken.of(Long.class))).isNull(); + assertThat(map.getInstance(Long.class)).isNull(); } public void testPrimitiveAndWrapper() { - assertNull(map.getInstance(int.class)); - assertNull(map.getInstance(Integer.class)); + assertThat(map.getInstance(int.class)).isNull(); + assertThat(map.getInstance(Integer.class)).isNull(); - assertNull(map.putInstance(int.class, 0)); - assertNull(map.putInstance(Integer.class, 1)); + assertThat(map.putInstance(int.class, 0)).isNull(); + assertThat(map.putInstance(Integer.class, 1)).isNull(); assertEquals(2, map.size()); assertEquals(0, (int) map.getInstance(int.class)); @@ -179,8 +165,8 @@ public void testPrimitiveAndWrapper() { assertEquals(0, (int) map.putInstance(int.class, null)); assertEquals(1, (int) map.putInstance(Integer.class, null)); - assertNull(map.getInstance(int.class)); - assertNull(map.getInstance(Integer.class)); + assertThat(map.getInstance(int.class)).isNull(); + assertThat(map.getInstance(Integer.class)).isNull(); assertEquals(2, map.size()); } @@ -193,7 +179,8 @@ public void testParameterizedType() { public void testGenericArrayType() { @SuppressWarnings("unchecked") // Trying to test generic array - ImmutableList[] array = new ImmutableList[] {ImmutableList.of(1)}; + ImmutableList[] array = + (ImmutableList[]) new ImmutableList[] {ImmutableList.of(1)}; TypeToken[]> type = new TypeToken[]>() {}; map.putInstance(type, array); assertEquals(1, map.size()); @@ -208,19 +195,14 @@ public void testWildcardType() { } public void testGetInstance_withTypeVariable() { - try { - map.getInstance(this.anyIterableType()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> map.getInstance(this.anyIterableType())); } public void testPutInstance_withTypeVariable() { - try { - map.putInstance(this.anyIterableType(), ImmutableList.of(1)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> map.putInstance(this.anyIterableType(), ImmutableList.of(1))); } private TypeToken> anyIterableType() { diff --git a/android/guava-tests/test/com/google/common/reflect/PackageSanityTests.java b/android/guava-tests/test/com/google/common/reflect/PackageSanityTests.java index ba11fe80c778..9d3a4a2cf3b6 100644 --- a/android/guava-tests/test/com/google/common/reflect/PackageSanityTests.java +++ b/android/guava-tests/test/com/google/common/reflect/PackageSanityTests.java @@ -17,7 +17,9 @@ package com.google.common.reflect; import com.google.common.testing.AbstractPackageSanityTests; +import org.jspecify.annotations.NullUnmarked; /** Tests nulls for the entire package. */ +@NullUnmarked public class PackageSanityTests extends AbstractPackageSanityTests {} diff --git a/android/guava-tests/test/com/google/common/reflect/ParameterTest.java b/android/guava-tests/test/com/google/common/reflect/ParameterTest.java index 6e0500a9ce08..890ae32a0313 100644 --- a/android/guava-tests/test/com/google/common/reflect/ParameterTest.java +++ b/android/guava-tests/test/com/google/common/reflect/ParameterTest.java @@ -20,15 +20,27 @@ import com.google.common.testing.NullPointerTester; import java.lang.reflect.Method; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Parameter}. * * @author Ben Yu */ +@NullUnmarked public class ParameterTest extends TestCase { public void testNulls() { + try { + Class.forName("java.lang.reflect.AnnotatedType"); + } catch (ClassNotFoundException runningInAndroidVm) { + /* + * Parameter declares a method that returns AnnotatedType, which isn't available on Android. + * This would cause NullPointerTester, which calls Class.getDeclaredMethods, to throw + * NoClassDefFoundError. + */ + return; + } for (Method method : ParameterTest.class.getDeclaredMethods()) { for (Parameter param : Invokable.from(method).getParameters()) { new NullPointerTester().testAllPublicInstanceMethods(param); diff --git a/android/guava-tests/test/com/google/common/reflect/ReflectionTest.java b/android/guava-tests/test/com/google/common/reflect/ReflectionTest.java index 2885f895661f..6a0a7ce34b18 100644 --- a/android/guava-tests/test/com/google/common/reflect/ReflectionTest.java +++ b/android/guava-tests/test/com/google/common/reflect/ReflectionTest.java @@ -16,13 +16,17 @@ package com.google.common.reflect; +import static org.junit.Assert.assertThrows; + import com.google.common.testing.NullPointerTester; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Tests for {@link Reflection} */ +@NullUnmarked public class ReflectionTest extends TestCase { public void testGetPackageName() throws Exception { @@ -39,11 +43,8 @@ public void testNewProxy() throws Exception { } public void testNewProxyCantWorkOnAClass() throws Exception { - try { - Reflection.newProxy(Object.class, X_RETURNER); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> Reflection.newProxy(Object.class, X_RETURNER)); } private static final InvocationHandler X_RETURNER = diff --git a/android/guava-tests/test/com/google/common/reflect/SubtypeTester.java b/android/guava-tests/test/com/google/common/reflect/SubtypeTester.java index 3eec668433fb..11fbb0cfdfa0 100644 --- a/android/guava-tests/test/com/google/common/reflect/SubtypeTester.java +++ b/android/guava-tests/test/com/google/common/reflect/SubtypeTester.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import com.google.errorprone.annotations.Keep; import com.google.errorprone.annotations.RequiredModifiers; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -30,6 +31,8 @@ import java.util.Arrays; import java.util.Comparator; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tester of subtyping relationships between two types. @@ -40,7 +43,7 @@ *

    These declaration methods rely on Java static type checking to make sure what we want to * assert as subtypes are really subtypes according to javac. For example: * - *

    {@code
    + * {@snippet :
      * class MySubtypeTests extends SubtypeTester {
      *   @TestSubtype(suppressGetSubtype = true, suppressGetSupertype = true)
      *   public  Iterable listIsSubtypeOfIterable(List list) {
    @@ -56,7 +59,7 @@
      * public void testMySubtypes() throws Exception {
      *   new MySubtypeTests().testAllDeclarations();
      * }
    - * }
    + * } * * The calls to {@link #isSubtype} and {@link #notSubtype} tells the framework what assertions need * to be made. @@ -64,12 +67,14 @@ *

    The declaration methods must be public. */ @AndroidIncompatible // only used by android incompatible tests. +@NullUnmarked abstract class SubtypeTester implements Cloneable { /** Annotates a public method that declares subtype assertion. */ @RequiredModifiers(Modifier.PUBLIC) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) + @Keep @interface TestSubtype { /** Suppresses the assertion on {@link TypeToken#getSubtype}. */ boolean suppressGetSubtype() default false; @@ -78,7 +83,7 @@ abstract class SubtypeTester implements Cloneable { boolean suppressGetSupertype() default false; } - private Method method = null; + private @Nullable Method method = null; /** Call this in a {@link TestSubtype} public method asserting subtype relationship. */ final T isSubtype(T sub) { @@ -105,7 +110,7 @@ final T isSubtype(T sub) { * Call this in a {@link TestSubtype} public method asserting that subtype relationship does not * hold. */ - final X notSubtype(@SuppressWarnings("unused") Object sub) { + final @Nullable X notSubtype(@SuppressWarnings("unused") Object sub) { Type returnType = method.getGenericReturnType(); Type paramType = getOnlyParameterType(); TestSubtype spec = method.getAnnotation(TestSubtype.class); diff --git a/android/guava-tests/test/com/google/common/reflect/TypeParameterTest.java b/android/guava-tests/test/com/google/common/reflect/TypeParameterTest.java index b83e48596c0a..e1d2cb021c50 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypeParameterTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypeParameterTest.java @@ -16,17 +16,21 @@ package com.google.common.reflect; +import static org.junit.Assert.assertThrows; + import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import java.lang.reflect.Method; import java.lang.reflect.TypeVariable; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link TypeParameter}. * * @author Ben Yu */ +@NullUnmarked public class TypeParameterTest extends TestCase { public void testCaptureTypeParameter() throws Exception { @@ -38,11 +42,7 @@ public void testCaptureTypeParameter() throws Exception { } public void testConcreteTypeRejected() { - try { - new TypeParameter() {}; - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> new TypeParameter() {}); } public void testEquals() throws Exception { diff --git a/android/guava-tests/test/com/google/common/reflect/TypeResolverTest.java b/android/guava-tests/test/com/google/common/reflect/TypeResolverTest.java index e970a8d99a36..464eebdd466c 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypeResolverTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypeResolverTest.java @@ -16,11 +16,14 @@ package com.google.common.reflect; +import static org.junit.Assert.assertThrows; + import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests of {@link TypeResolver}. @@ -28,6 +31,7 @@ * @author Ben Yu */ @AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations? +@NullUnmarked public class TypeResolverTest extends TestCase { public void testWhere_noMapping() { @@ -80,11 +84,7 @@ public void testWhere_wildcardSelfMapping() { public void testWhere_duplicateMapping() { Type t = aTypeVariable(); TypeResolver resolver = new TypeResolver().where(t, String.class); - try { - resolver.where(t, String.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> resolver.where(t, String.class)); } public > void testWhere_recursiveMapping() { @@ -153,87 +153,77 @@ public void testWhere_wildcardTypeMapping() { } public void testWhere_incompatibleGenericArrayMapping() { - try { - new TypeResolver().where(new TypeCapture() {}.capture(), String.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> new TypeResolver().where(new TypeCapture() {}.capture(), String.class)); } public void testWhere_incompatibleParameterizedTypeMapping() { - try { - new TypeResolver().where(new TypeCapture>() {}.capture(), List.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> new TypeResolver().where(new TypeCapture>() {}.capture(), List.class)); } public void testWhere_impossibleParameterizedTypeMapping() { - try { - new TypeResolver() - .where( - new TypeCapture>() {}.capture(), - new TypeCapture>() {}.capture()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + new TypeResolver() + .where( + new TypeCapture>() {}.capture(), + new TypeCapture>() {}.capture())); } public void testWhere_incompatibleWildcardUpperBound() { - try { - new TypeResolver() - .where( - new TypeCapture>() {}.capture(), - new TypeCapture>() {}.capture()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + new TypeResolver() + .where( + new TypeCapture>() {}.capture(), + new TypeCapture>() {}.capture())); } public void testWhere_incompatibleWildcardLowerBound() { - try { - new TypeResolver() - .where( - new TypeCapture>() {}.capture(), - new TypeCapture>() {}.capture()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + new TypeResolver() + .where( + new TypeCapture>() {}.capture(), + new TypeCapture>() {}.capture())); } public void testWhere_incompatibleWildcardBounds() { - try { - new TypeResolver() - .where( - new TypeCapture>() {}.capture(), - new TypeCapture>() {}.capture()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + new TypeResolver() + .where( + new TypeCapture>() {}.capture(), + new TypeCapture>() {}.capture())); } public void testWhere_wrongOrder() { - try { - new TypeResolver().where(String.class, aTypeVariable()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> new TypeResolver().where(String.class, aTypeVariable())); } public void testWhere_mapFromConcreteParameterizedType() { - try { - new TypeResolver().where(new TypeCapture>() {}.capture(), aTypeVariable()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + new TypeResolver() + .where(new TypeCapture>() {}.capture(), aTypeVariable())); } public void testWhere_mapFromConcreteGenericArrayType() { - try { - new TypeResolver().where(new TypeCapture>() {}.capture(), aTypeVariable()); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + new TypeResolver() + .where(new TypeCapture>() {}.capture(), aTypeVariable())); } public void testWhere_actualArgHasWildcard() { diff --git a/android/guava-tests/test/com/google/common/reflect/TypeTokenResolutionTest.java b/android/guava-tests/test/com/google/common/reflect/TypeTokenResolutionTest.java index 69e59ff18895..0e9b3a94a6f3 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypeTokenResolutionTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypeTokenResolutionTest.java @@ -17,9 +17,11 @@ package com.google.common.reflect; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.base.Predicate; import com.google.common.base.Supplier; +import com.google.errorprone.annotations.Keep; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -29,6 +31,7 @@ import java.util.List; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link TypeToken} and {@link TypeResolver}. @@ -36,6 +39,7 @@ * @author Ben Yu */ @AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations? +@NullUnmarked public class TypeTokenResolutionTest extends TestCase { private static class Foo { @@ -178,10 +182,12 @@ public void testResolveNestedClass() { assertEquals(String.class, new Owner.Nested() {}.getTypeArgument()); } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public void testResolveInnerClass() { assertEquals(String.class, new Owner().new Inner() {}.getTypeArgument()); } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public void testResolveOwnerClass() { assertEquals(Integer.class, new Owner().new Inner() {}.getOwnerType()); } @@ -219,9 +225,7 @@ public void testCyclicMapping() { } private static class ParameterizedOuter { - - @SuppressWarnings("unused") // used by reflection - public Inner field; + @Keep public Inner field; class Inner {} } @@ -246,14 +250,10 @@ public void testResolveType() { TypeToken.of(StringIterable.class) .resolveType(Iterable.class.getTypeParameters()[0]) .getType()); - try { - TypeToken.of(this.getClass()).resolveType(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> TypeToken.of(this.getClass()).resolveType(null)); } - public void testConextIsParameterizedType() throws Exception { + public void testContextIsParameterizedType() throws Exception { class Context { @SuppressWarnings("unused") // used by reflection Map returningMap() { @@ -359,7 +359,7 @@ Type getType() { } public void testLocalClassInsideStaticMethod() { - assertNotNull(staticMethodWithLocalClass()); + assertThat(staticMethodWithLocalClass()).isNotNull(); } public void testLocalClassInsideNonStaticMethod() { @@ -368,7 +368,7 @@ Type getType() { return new TypeToken(getClass()) {}.getType(); } } - assertNotNull(new MyLocalClass().getType()); + assertThat(new MyLocalClass().getType()).isNotNull(); } private static Type staticMethodWithAnonymousClass() { @@ -380,16 +380,17 @@ Type getType() { } public void testAnonymousClassInsideStaticMethod() { - assertNotNull(staticMethodWithAnonymousClass()); + assertThat(staticMethodWithAnonymousClass()).isNotNull(); } public void testAnonymousClassInsideNonStaticMethod() { - assertNotNull( - new Object() { - Type getType() { - return new TypeToken() {}.getType(); - } - }.getType()); + assertThat( + new Object() { + Type getType() { + return new TypeToken() {}.getType(); + } + }.getType()) + .isNotNull(); } public void testStaticContext() { @@ -417,20 +418,20 @@ public void testResolveToGenericArrayType() { private abstract class WithGenericBound { - @SuppressWarnings("unused") + @Keep public void withTypeVariable(List list) {} - @SuppressWarnings("unused") + @Keep public > void withRecursiveBound(List list) {} - @SuppressWarnings("unused") + @Keep public , V extends List> void withMutualRecursiveBound( List> list) {} - @SuppressWarnings("unused") + @Keep void withWildcardLowerBound(List list) {} - @SuppressWarnings("unused") + @Keep void withWildcardUpperBound(List list) {} Type getTargetType(String methodName) throws Exception { diff --git a/android/guava-tests/test/com/google/common/reflect/TypeTokenSubtypeTest.java b/android/guava-tests/test/com/google/common/reflect/TypeTokenSubtypeTest.java index 0dd2318e9776..542d77f61df5 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypeTokenSubtypeTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypeTokenSubtypeTest.java @@ -17,13 +17,16 @@ package com.google.common.reflect; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import java.io.Serializable; import java.util.Comparator; import java.util.List; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; @AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations? +@NullUnmarked public class TypeTokenSubtypeTest extends TestCase { public void testOwnerTypeSubtypes() throws Exception { @@ -39,38 +42,41 @@ public void testWildcardSubtypes() throws Exception { * recursively bounded. */ public void testRecursiveWildcardSubtypeBug() throws Exception { - try { - new RecursiveTypeBoundBugExample<>().testAllDeclarations(); - fail(); - } catch (Exception e) { - assertThat(e).hasCauseThat().isInstanceOf(AssertionError.class); - } + Exception e = + assertThrows( + Exception.class, () -> new RecursiveTypeBoundBugExample<>().testAllDeclarations()); + assertThat(e).hasCauseThat().isInstanceOf(AssertionError.class); } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public void testSubtypeOfInnerClass_nonStaticAnonymousClass() { TypeToken supertype = new TypeToken.Shop>() {}; Class subclass = new Mall().new Shop() {}.getClass(); assertTrue(TypeToken.of(subclass).isSubtypeOf(supertype)); } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public void testSubtypeOfInnerClass_nonStaticAnonymousClass_typeParameterOfOwnerTypeNotMatch() { TypeToken supertype = new TypeToken.Shop>() {}; Class subclass = new Mall().new Shop() {}.getClass(); assertFalse(TypeToken.of(subclass).isSubtypeOf(supertype)); } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public void testSubtypeOfInnerClass_nonStaticAnonymousClass_typeParameterOfInnerTypeNotMatch() { TypeToken supertype = new TypeToken.Shop>() {}; Class subclass = new Mall().new Shop() {}.getClass(); assertFalse(TypeToken.of(subclass).isSubtypeOf(supertype)); } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public static void testSubtypeOfInnerClass_staticAnonymousClass() { TypeToken supertype = new TypeToken.Shop>() {}; Class subclass = new Mall().new Shop() {}.getClass(); assertTrue(TypeToken.of(subclass).isSubtypeOf(supertype)); } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public static void testSubtypeOfStaticAnonymousClass() { Class superclass = new Mall().new Shop() {}.getClass(); assertTrue(TypeToken.of(superclass).isSubtypeOf(superclass)); @@ -79,6 +85,7 @@ public static void testSubtypeOfStaticAnonymousClass() { .isSubtypeOf(superclass)); } + @SuppressWarnings("RestrictedApiChecker") // crashes under JDK8, which EP no longer supports public void testSubtypeOfNonStaticAnonymousClass() { Class superclass = new Mall().new Shop() {}.getClass(); assertTrue(TypeToken.of(superclass).isSubtypeOf(superclass)); @@ -90,11 +97,7 @@ public void testSubtypeOfNonStaticAnonymousClass() { public void testGetSubtypeOf_impossibleWildcard() { TypeToken> numberList = new TypeToken>() {}; abstract class StringList implements List {} - try { - numberList.getSubtype(StringList.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> numberList.getSubtype(StringList.class)); } private static class OwnerTypeSubtypingTests extends SubtypeTester { @@ -231,7 +234,7 @@ private static class RecursiveTypeBoundBugExample> ifYouUseTheTypeVariableOnTheClassAndItIsRecursive( List>> arg) { - return notSubtype(arg); // isSubtype() currently incorectly considers it a subtype. + return notSubtype(arg); // isSubtype() currently incorrectly considers it a subtype. } } diff --git a/android/guava-tests/test/com/google/common/reflect/TypeTokenTest.java b/android/guava-tests/test/com/google/common/reflect/TypeTokenTest.java index 171ef49fdb80..26ea5dd500b6 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypeTokenTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypeTokenTest.java @@ -17,18 +17,19 @@ package com.google.common.reflect; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; import com.google.common.primitives.Primitives; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; import com.google.common.truth.IterableSubject; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.Keep; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -41,9 +42,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test cases for {@link TypeToken}. @@ -52,6 +55,7 @@ * @author Ben Yu */ @AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations? +@NullUnmarked public class TypeTokenTest extends TestCase { private abstract static class StringList implements List {} @@ -64,11 +68,18 @@ public void testValueEqualityNotInstanceEquality() { assertEquals(a, b); } + @SuppressWarnings("TestExceptionChecker") // see comment below public void testVariableTypeTokenNotAllowed() { + /* + * We'd use assertThrows here, but that causes no exception to be thrown under Java 8, + * presumably because the ThrowingRunnable lambda triggers some kind of bug in Java 8's + * reflection implementation. + */ try { new TypeToken() {}; fail(); } catch (IllegalStateException expected) { + // Type variables aren't allowed. } } @@ -378,9 +389,9 @@ public void testAssertSubtypeBeforeSupertype_duplicate() { } public void testGetGenericSuperclass_noSuperclass() { - assertNull(new TypeToken() {}.getGenericSuperclass()); + assertThat(new TypeToken() {}.getGenericSuperclass()).isNull(); assertEquals(TypeToken.of(Object.class), new TypeToken() {}.getGenericSuperclass()); - assertNull(new TypeToken>() {}.getGenericSuperclass()); + assertThat(new TypeToken>() {}.getGenericSuperclass()).isNull(); assertEquals( TypeToken.of(Object.class), new TypeToken[]>() {}.getGenericSuperclass()); } @@ -402,7 +413,7 @@ public void testGetGenericSuperclass_typeVariable_unbounded() { assertEquals(TypeToken.of(Object.class), new TypeToken() {}.getGenericSuperclass()); } - public & CharSequence> + public & Serializable> void testGetGenericSuperclass_typeVariable_boundIsClass() { assertEquals( new TypeToken>() {}, @@ -410,7 +421,7 @@ void testGetGenericSuperclass_typeVariable_boundIsClass() { assertEquals(TypeToken.of(Object.class), new TypeToken() {}.getGenericSuperclass()); } - public & CharSequence> + public & Serializable> void testGetGenericSuperclass_typeVariable_boundIsFBoundedClass() { assertEquals( new TypeToken>() {}, @@ -418,13 +429,13 @@ void testGetGenericSuperclass_typeVariable_boundIsFBoundedClass() { assertEquals(TypeToken.of(Object.class), new TypeToken() {}.getGenericSuperclass()); } - public & CharSequence> + public & Serializable> void testGetGenericSuperclass_typeVariable_boundIsInterface() { - assertNull(TypeToken.of(new TypeCapture() {}.capture()).getGenericSuperclass()); + assertThat(TypeToken.of(new TypeCapture() {}.capture()).getGenericSuperclass()).isNull(); assertEquals(TypeToken.of(Object.class), new TypeToken() {}.getGenericSuperclass()); } - public & CharSequence, T1 extends T> + public & Serializable, T1 extends T> void testGetGenericSuperclass_typeVariable_boundIsTypeVariableAndClass() { assertEquals( TypeToken.of(new TypeCapture() {}.capture()), @@ -432,9 +443,9 @@ void testGetGenericSuperclass_typeVariable_boundIsTypeVariableAndClass() { assertEquals(TypeToken.of(Object.class), new TypeToken() {}.getGenericSuperclass()); } - public & CharSequence, T1 extends T> + public & Serializable, T1 extends T> void testGetGenericSuperclass_typeVariable_boundIsTypeVariableAndInterface() { - assertNull(TypeToken.of(new TypeCapture() {}.capture()).getGenericSuperclass()); + assertThat(TypeToken.of(new TypeCapture() {}.capture()).getGenericSuperclass()).isNull(); assertEquals(TypeToken.of(Object.class), new TypeToken() {}.getGenericSuperclass()); } @@ -460,7 +471,7 @@ public void testGetGenericSuperclass_wildcard_boundIsClass() { } public void testGetGenericSuperclass_wildcard_boundIsInterface() { - assertNull(TypeToken.of(Types.subtypeOf(CharSequence.class)).getGenericSuperclass()); + assertThat(TypeToken.of(Types.subtypeOf(CharSequence.class)).getGenericSuperclass()).isNull(); assertEquals( new TypeToken() {}, TypeToken.of(Types.subtypeOf(CharSequence[].class)).getGenericSuperclass()); @@ -540,7 +551,7 @@ public void testGetGenericInterfaces_noInterface() { } public void testGetGenericInterfaces_withInterfaces() { - Map, Type> interfaceMap = Maps.newHashMap(); + Map, Type> interfaceMap = new HashMap<>(); for (TypeToken interfaceType : new TypeToken>() {}.getGenericInterfaces()) { interfaceMap.put(interfaceType.getRawType(), interfaceType.getType()); @@ -574,9 +585,9 @@ private abstract static class Third extends Second {} private abstract static class Fourth extends Third {} - private static class ConcreteIS extends Fourth {} + private static class ConcreteIntegerString extends Fourth {} - private static class ConcreteSI extends Fourth {} + private static class ConcreteStringInteger extends Fourth {} public void testAssignableClassToClass() { @SuppressWarnings("rawtypes") // To test TypeToken @@ -749,8 +760,8 @@ public void testAssignableClassToType() { assertFalse(tokenL.isSupertypeOf(List.class)); TypeToken> tokenF = new TypeToken>() {}; - assertTrue(tokenF.isSupertypeOf(ConcreteIS.class)); - assertFalse(tokenF.isSupertypeOf(ConcreteSI.class)); + assertTrue(tokenF.isSupertypeOf(ConcreteIntegerString.class)); + assertFalse(tokenF.isSupertypeOf(ConcreteStringInteger.class)); } public void testAssignableClassToArrayType() { @@ -765,8 +776,8 @@ public void testAssignableParameterizedTypeToType() { assertFalse(tokenL.isSupertypeOf(IntegerList.class.getGenericInterfaces()[0])); TypeToken> tokenF = new TypeToken>() {}; - assertTrue(tokenF.isSupertypeOf(ConcreteIS.class.getGenericSuperclass())); - assertFalse(tokenF.isSupertypeOf(ConcreteSI.class.getGenericSuperclass())); + assertTrue(tokenF.isSupertypeOf(ConcreteIntegerString.class.getGenericSuperclass())); + assertFalse(tokenF.isSupertypeOf(ConcreteStringInteger.class.getGenericSuperclass())); } public void testGenericArrayTypeToArrayType() { @@ -788,8 +799,8 @@ public void testAssignableTokenToType() { assertFalse(tokenF.isSupertypeOf(new TypeToken>() {})); assertTrue(tokenF.isSupertypeOf(new TypeToken>() {})); assertFalse(tokenF.isSupertypeOf(new TypeToken>() {})); - assertTrue(tokenF.isSupertypeOf(new TypeToken() {})); - assertFalse(tokenF.isSupertypeOf(new TypeToken() {})); + assertTrue(tokenF.isSupertypeOf(new TypeToken() {})); + assertFalse(tokenF.isSupertypeOf(new TypeToken() {})); } public void testAssignableWithWildcards() { @@ -996,12 +1007,12 @@ public void testGetComponentType_arrayClasses() { assertEquals(long.class, TypeToken.of(long[].class).getComponentType().getType()); assertEquals(float.class, TypeToken.of(float[].class).getComponentType().getType()); assertEquals(double.class, TypeToken.of(double[].class).getComponentType().getType()); - assertNull(TypeToken.of(Object.class).getComponentType()); - assertNull(TypeToken.of(void.class).getComponentType()); + assertThat(TypeToken.of(Object.class).getComponentType()).isNull(); + assertThat(TypeToken.of(void.class).getComponentType()).isNull(); } public void testGetComponentType_genericArrayClasses() { - assertNull(TypeToken.of(new TypeCapture() {}.capture()).getComponentType()); + assertThat(TypeToken.of(new TypeCapture() {}.capture()).getComponentType()).isNull(); assertEquals( TypeToken.of(new TypeCapture() {}.capture()), new TypeToken() {}.getComponentType()); @@ -1018,8 +1029,8 @@ public void testGetComponentType_wildcardType() { TypeToken.of(Types.subtypeOf(Object[].class)).getComponentType().getType())); assertEquals( int.class, TypeToken.of(Types.subtypeOf(int[].class)).getComponentType().getType()); - assertNull(TypeToken.of(Types.subtypeOf(Object.class)).getComponentType()); - assertNull(TypeToken.of(Types.supertypeOf(Object[].class)).getComponentType()); + assertThat(TypeToken.of(Types.subtypeOf(Object.class)).getComponentType()).isNull(); + assertThat(TypeToken.of(Types.supertypeOf(Object[].class)).getComponentType()).isNull(); } private interface NumberList {} @@ -1058,10 +1069,9 @@ public void testToGenericType_staticMemberClass() throws Exception { assertThat(parameterizedType.getOwnerType()).isEqualTo(javacReturnType.getOwnerType()); } - public static GenericClass getStaticAnonymousClass(final T value) { + public static GenericClass getStaticAnonymousClass(T value) { return new GenericClass() { - @SuppressWarnings("unused") - public T innerValue = value; + @Keep public final T innerValue = value; }; } @@ -1101,7 +1111,7 @@ public void testGetSupertype_withoutTypeVariable() { } public void testGetSupertype_chained() { - @SuppressWarnings("unchecked") // StringListIterable extensd ListIterable + @SuppressWarnings("unchecked") // StringListIterable extends ListIterable TypeToken> listIterableType = (TypeToken>) TypeToken.of(StringListIterable.class).getSupertype(ListIterable.class); @@ -1147,11 +1157,9 @@ public void testGetSupertype_fromRawClass() { @SuppressWarnings({"rawtypes", "unchecked"}) // purpose is to test raw type public void testGetSupertype_notSupertype() { - try { - new TypeToken>() {}.getSupertype((Class) String.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> new TypeToken>() {}.getSupertype((Class) String.class)); } public void testGetSupertype_fromArray() { @@ -1235,11 +1243,7 @@ public void testGetSubtype_fromWildcard_lowerBoundNotSupertype() { TypeToken> type = (TypeToken>) TypeToken.of(Types.supertypeOf(new TypeToken>() {}.getType())); - try { - type.getSubtype(List.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> type.getSubtype(List.class)); } public void testGetSubtype_fromWildcard_upperBounded() { @@ -1247,18 +1251,21 @@ public void testGetSubtype_fromWildcard_upperBounded() { TypeToken> type = (TypeToken>) TypeToken.of(Types.subtypeOf(new TypeToken>() {}.getType())); - try { - type.getSubtype(Iterable.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> type.getSubtype(Iterable.class)); } + @SuppressWarnings("TestExceptionChecker") // see comment below public > void testGetSubtype_fromTypeVariable() { + /* + * We'd use assertThrows here, but that causes capture() to return null under Java 8, presumably + * because the ThrowingRunnable lambda triggers some kind of bug in Java 8's reflection + * implementation. + */ try { TypeToken.of(new TypeCapture() {}.capture()).getSubtype(List.class); fail(); } catch (IllegalArgumentException expected) { + // Type variables aren't allowed. } } @@ -1377,7 +1384,9 @@ public void testGetSubtype_genericSubtypeOfGenericTypeWithFewerParameters() { } public void testGetSubtype_genericSubtypeOfRawTypeWithFewerTypeParameters() { + @SuppressWarnings("rawtypes") // test of raw types TypeToken supertype = new TypeToken() {}; + @SuppressWarnings("rawtypes") // test of raw types TypeToken subtype = new TypeToken() {}; assertTrue(subtype.isSubtypeOf(supertype)); Class actualSubtype = (Class) supertype.getSubtype(subtype.getRawType()).getType(); @@ -1433,8 +1442,7 @@ class Sub2> extends BaseWithTypeVar> {} public void testGetSubtype_subtypeSameAsDeclaringType() throws Exception { class Bar {} class SubBar extends Bar { - @SuppressWarnings("unused") - Bar delegate; + @Keep Bar delegate; TypeToken> fieldTypeAsSubBar() { return new TypeToken>() {}; @@ -1450,22 +1458,24 @@ TypeToken> fieldTypeAsSubBar() { @SuppressWarnings("unchecked") // To construct TypeToken with TypeToken.of() public void testWhere_circleRejected() { TypeToken> type = new TypeToken>() {}; - try { - type.where( - new TypeParameter() {}, - (TypeToken) TypeToken.of(new TypeCapture() {}.capture())); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + type.where( + new TypeParameter() {}, + (TypeToken) TypeToken.of(new TypeCapture() {}.capture()))); } + @SuppressWarnings("JUnitIncompatibleType") public void testWhere() { assertEquals(new TypeToken>() {}, mapOf(String.class, Integer.class)); + // Type inference is doomed here: int.class is the same as Integer.class, so this is comparing + // TypeToken and TypeToken. assertEquals(new TypeToken() {}, arrayOf(int.class)); assertEquals(int[].class, arrayOf(int.class).getRawType()); } - @SuppressWarnings("unused") // used by reflection + @Keep private static class Holder { List[] matrix; @@ -1499,7 +1509,7 @@ public void testWildcardCaptured_wildcardWithImplicitBound() throws Exception { assertThat(parameters).hasSize(1); TypeToken parameterType = parameters.get(0).getType(); Type[] typeArgs = ((ParameterizedType) parameterType.getType()).getActualTypeArguments(); - assertThat(typeArgs).asList().hasSize(1); + assertThat(typeArgs).hasLength(1); TypeVariable captured = (TypeVariable) typeArgs[0]; assertThat(captured.getBounds()).asList().containsExactly(Object.class); assertThat(new TypeToken>() {}.isSupertypeOf(parameterType)).isTrue(); @@ -1512,15 +1522,14 @@ public void testWildcardCaptured_wildcardWithExplicitBound() throws Exception { assertThat(parameters).hasSize(1); TypeToken parameterType = parameters.get(0).getType(); Type[] typeArgs = ((ParameterizedType) parameterType.getType()).getActualTypeArguments(); - assertThat(typeArgs).asList().hasSize(1); + assertThat(typeArgs).hasLength(1); TypeVariable captured = (TypeVariable) typeArgs[0]; assertThat(captured.getBounds()).asList().containsExactly(Number.class); assertThat(new TypeToken>() {}.isSupertypeOf(parameterType)).isTrue(); } private static class Counter { - @SuppressWarnings("unused") // used by reflection - List counts; + @Keep List counts; } public void testWildcardCaptured_typeVariableDeclaresTypeBound_wildcardHasNoExplicitUpperBound() @@ -1529,7 +1538,7 @@ public void testWildcardCaptured_typeVariableDeclaresTypeBound_wildcardHasNoExpl TypeToken fieldType = type.resolveType(Counter.class.getDeclaredField("counts").getGenericType()); Type[] typeArgs = ((ParameterizedType) fieldType.getType()).getActualTypeArguments(); - assertThat(typeArgs).asList().hasSize(1); + assertThat(typeArgs).hasLength(1); TypeVariable captured = (TypeVariable) typeArgs[0]; assertThat(captured.getBounds()).asList().containsExactly(Number.class); assertThat(new TypeToken>() {}.isSupertypeOf(fieldType)).isTrue(); @@ -1542,7 +1551,7 @@ public void testWildcardCaptured_typeVariableDeclaresTypeBound_wildcardHasExplic TypeToken fieldType = type.resolveType(Counter.class.getDeclaredField("counts").getGenericType()); Type[] typeArgs = ((ParameterizedType) fieldType.getType()).getActualTypeArguments(); - assertThat(typeArgs).asList().hasSize(1); + assertThat(typeArgs).hasLength(1); TypeVariable captured = (TypeVariable) typeArgs[0]; assertThat(captured.getBounds()).asList().containsExactly(Number.class); assertThat(new TypeToken>() {}.isSupertypeOf(fieldType)).isTrue(); @@ -1556,7 +1565,7 @@ public void testWildcardCaptured_typeVariableDeclaresTypeBound_wildcardAddsNewUp TypeToken fieldType = type.resolveType(Counter.class.getDeclaredField("counts").getGenericType()); Type[] typeArgs = ((ParameterizedType) fieldType.getType()).getActualTypeArguments(); - assertThat(typeArgs).asList().hasSize(1); + assertThat(typeArgs).hasLength(1); TypeVariable captured = (TypeVariable) typeArgs[0]; assertThat(captured.getBounds()).asList().contains(Number.class); assertThat(captured.getBounds()) @@ -1587,11 +1596,7 @@ public void testMethod_getOwnerType() throws NoSuchMethodException { public void testMethod_notDeclaredByType() throws NoSuchMethodException { Method sizeMethod = Map.class.getMethod("size"); - try { - TypeToken.of(List.class).method(sizeMethod); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> TypeToken.of(List.class).method(sizeMethod)); } public void testMethod_declaredBySuperclass() throws Exception { @@ -1654,20 +1659,14 @@ public void testConstructor_getOwnerType() throws NoSuchMethodException { public void testConstructor_notDeclaredByType() throws NoSuchMethodException { Constructor constructor = String.class.getConstructor(); - try { - TypeToken.of(Object.class).constructor(constructor); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> TypeToken.of(Object.class).constructor(constructor)); } public void testConstructor_declaredBySuperclass() throws NoSuchMethodException { Constructor constructor = Object.class.getConstructor(); - try { - TypeToken.of(String.class).constructor(constructor); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> TypeToken.of(String.class).constructor(constructor)); } public void testConstructor_equals() throws NoSuchMethodException { @@ -1684,7 +1683,7 @@ public void testConstructor_equals() throws NoSuchMethodException { } private static class Container { - @SuppressWarnings("unused") + @Keep public Container(T data) {} } @@ -1699,7 +1698,7 @@ public > void testConstructor_parameterTypes() } private static class CannotConstruct { - @SuppressWarnings("unused") + @Keep public CannotConstruct() throws E {} } @@ -1793,7 +1792,7 @@ private abstract static class RawTypeConsistencyTester & CharS abstract > void acceptT2(T2 t2); - static void verifyConsitentRawType() { + static void verifyConsistentRawType() { for (Method method : RawTypeConsistencyTester.class.getDeclaredMethods()) { assertEquals( method.getReturnType(), TypeToken.of(method.getGenericReturnType()).getRawType()); @@ -1807,7 +1806,7 @@ static void verifyConsitentRawType() { } public void testRawTypes() { - RawTypeConsistencyTester.verifyConsitentRawType(); + RawTypeConsistencyTester.verifyConsistentRawType(); assertEquals(Object.class, TypeToken.of(Types.subtypeOf(Object.class)).getRawType()); assertEquals( CharSequence.class, TypeToken.of(Types.subtypeOf(CharSequence.class)).getRawType()); @@ -1839,19 +1838,13 @@ public , B extends A> void testSerializable reserialize(new TypeToken>() {}); reserialize(new IKnowMyType>() {}.type()); reserialize(TypeToken.of(new TypeCapture() {}.capture()).getTypes().rawTypes()); - try { - SerializableTester.reserialize(TypeToken.of(new TypeCapture() {}.capture())); - fail(); - } catch (RuntimeException expected) { - } + assertThrows( + RuntimeException.class, + () -> SerializableTester.reserialize(TypeToken.of(new TypeCapture() {}.capture()))); } public void testSerializable_typeVariableNotSupported() { - try { - new ITryToSerializeMyTypeVariable().go(); - fail(); - } catch (RuntimeException expected) { - } + assertThrows(RuntimeException.class, () -> new ITryToSerializeMyTypeVariable().go()); } private static class ITryToSerializeMyTypeVariable { @@ -1889,7 +1882,7 @@ private abstract class SubInner extends BaseInner {} } } - // For Guava bug http://code.google.com/p/guava-libraries/issues/detail?id=1025 + // For Guava bug https://github.com/google/guava/issues/1025 public void testDespiteGenericSignatureFormatError() { ImmutableSet unused = ImmutableSet.copyOf( diff --git a/android/guava-tests/test/com/google/common/reflect/TypeVisitorTest.java b/android/guava-tests/test/com/google/common/reflect/TypeVisitorTest.java index 4cb53cfee481..d466067c8970 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypeVisitorTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypeVisitorTest.java @@ -24,12 +24,14 @@ import java.util.ArrayList; import java.util.EnumSet; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests of {@link TypeVisitor}. * * @author Ben Yu */ +@NullUnmarked public class TypeVisitorTest extends TestCase { public void testVisitNull() { diff --git a/android/guava-tests/test/com/google/common/reflect/TypesTest.java b/android/guava-tests/test/com/google/common/reflect/TypesTest.java index 436b2bbffe65..70f0e0c1876c 100644 --- a/android/guava-tests/test/com/google/common/reflect/TypesTest.java +++ b/android/guava-tests/test/com/google/common/reflect/TypesTest.java @@ -18,12 +18,14 @@ import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; +import static org.junit.Assert.assertThrows; import com.google.common.collect.Lists; import com.google.common.testing.EqualsTester; import com.google.common.testing.NullPointerTester; import com.google.common.testing.NullPointerTester.Visibility; import com.google.common.testing.SerializableTester; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericDeclaration; import java.lang.reflect.ParameterizedType; @@ -36,6 +38,7 @@ import java.util.Map; import java.util.Map.Entry; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Types}. @@ -43,6 +46,7 @@ * @author Ben Yu */ @AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations? +@NullUnmarked public class TypesTest extends TestCase { public void testNewParameterizedType_ownerTypeImplied() throws Exception { ParameterizedType jvmType = @@ -81,10 +85,10 @@ class LocalClass {} } public void testNewParameterizedType_staticLocalClass() { - doTestNewParameterizedType_staticLocalClass(); + doTestNewParameterizedTypeStaticLocalClass(); } - private static void doTestNewParameterizedType_staticLocalClass() { + private static void doTestNewParameterizedTypeStaticLocalClass() { class LocalClass {} Type jvmType = new LocalClass() {}.getClass().getGenericSuperclass(); Type ourType = Types.newParameterizedType(LocalClass.class, String.class); @@ -117,11 +121,9 @@ public void testNewParameterizedType_serializable() { } public void testNewParameterizedType_ownerMismatch() { - try { - Types.newParameterizedTypeWithOwner(Number.class, List.class, String.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Types.newParameterizedTypeWithOwner(Number.class, List.class, String.class)); } public void testNewParameterizedType_ownerMissing() { @@ -131,25 +133,22 @@ public void testNewParameterizedType_ownerMissing() { } public void testNewParameterizedType_invalidTypeParameters() { - try { - Types.newParameterizedTypeWithOwner(Map.class, Entry.class, String.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Types.newParameterizedTypeWithOwner(Map.class, Entry.class, String.class)); } public void testNewParameterizedType_primitiveTypeParameters() { - try { - Types.newParameterizedTypeWithOwner(Map.class, Entry.class, int.class, int.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Types.newParameterizedTypeWithOwner(Map.class, Entry.class, int.class, int.class)); } public void testNewArrayType() { Type jvmType1 = new TypeCapture[]>() {}.capture(); GenericArrayType ourType1 = (GenericArrayType) Types.newArrayType(Types.newParameterizedType(List.class, String.class)); + @SuppressWarnings("rawtypes") // test of raw types Type jvmType2 = new TypeCapture() {}.capture(); Type ourType2 = Types.newArrayType(List.class); new EqualsTester() @@ -234,11 +233,7 @@ public void testNewWildcardType() throws Exception { } public void testNewWildcardType_primitiveTypeBound() { - try { - Types.subtypeOf(int.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> Types.subtypeOf(int.class)); } public void testNewWildcardType_serializable() { @@ -265,7 +260,16 @@ private static class WithTypeVariable { @SuppressWarnings("unused") void withoutBound(List list) {} - @SuppressWarnings("unused") + @SuppressWarnings({ + "unused", + /* + * Since reflection can't tell the difference between and , it doesn't + * make a ton of sense to have a separate tests for each. But having tests for each doesn't + * really hurt anything, and maybe it will serve a purpose in a future in which Java has a + * built-in nullness feature? + */ + "ExtendsObject", + }) void withObjectBound(List list) {} @SuppressWarnings("unused") @@ -301,19 +305,15 @@ public void testNewTypeVariable() throws Exception { } public void testNewTypeVariable_primitiveTypeBound() { - try { - Types.newArtificialTypeVariable(List.class, "E", int.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Types.newArtificialTypeVariable(List.class, "E", int.class)); } public void testNewTypeVariable_serializable() throws Exception { - try { - SerializableTester.reserialize(Types.newArtificialTypeVariable(List.class, "E")); - fail(); - } catch (RuntimeException expected) { - } + assertThrows( + RuntimeException.class, + () -> SerializableTester.reserialize(Types.newArtificialTypeVariable(List.class, "E"))); } private static TypeVariable withBounds( @@ -325,6 +325,7 @@ private static TypeVariable withBounds( private static class TypeVariableEqualsTester { private final EqualsTester tester = new EqualsTester(); + @CanIgnoreReturnValue TypeVariableEqualsTester addEqualityGroup(Type jvmType, Type... types) { if (Types.NativeTypeVariableEquals.NATIVE_TYPE_VARIABLE_ONLY) { tester.addEqualityGroup(jvmType); @@ -372,11 +373,9 @@ public void testNewParameterizedTypeImmutability() { } public void testNewParameterizedTypeWithWrongNumberOfTypeArguments() { - try { - Types.newParameterizedType(Map.class, String.class, Integer.class, Long.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> Types.newParameterizedType(Map.class, String.class, Integer.class, Long.class)); } public void testToString() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractAbstractFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractAbstractFutureTest.java index f49b96245db4..f9a7ab18c65f 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractAbstractFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractAbstractFutureTest.java @@ -20,6 +20,7 @@ import static com.google.common.util.concurrent.Futures.getDone; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.util.concurrent.Runnables.doNothing; import static com.google.common.util.concurrent.TestPlatform.getDoneFromTimeoutOverload; import static com.google.common.util.concurrent.TestPlatform.verifyGetOnPendingFuture; @@ -29,17 +30,21 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.util.concurrent.AbstractFutureTest.TimedWaiterThread; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Base class for tests for emulated {@link AbstractFuture} that allow subclasses to swap in a * different "source Future" for {@link AbstractFuture#setFuture} calls. */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked abstract class AbstractAbstractFutureTest extends TestCase { private TestedFuture future; private AbstractFuture delegate; @@ -109,38 +114,26 @@ public void testSetFutureDelegateLaterSuccessful() throws Exception { } public void testSetFutureDelegateAlreadyCancelled() throws Exception { - delegate.cancel( - false - /** mayInterruptIfRunning */ - ); + delegate.cancel(/* mayInterruptIfRunning= */ false); assertThat(future.setFuture(delegate)).isTrue(); assertCancelled(future, false); } public void testSetFutureDelegateLaterCancelled() throws Exception { assertThat(future.setFuture(delegate)).isTrue(); - delegate.cancel( - false - /** mayInterruptIfRunning */ - ); + delegate.cancel(/* mayInterruptIfRunning= */ false); assertCancelled(future, false); } public void testSetFutureDelegateAlreadyInterrupted() throws Exception { - delegate.cancel( - true - /** mayInterruptIfRunning */ - ); + delegate.cancel(/* mayInterruptIfRunning= */ true); assertThat(future.setFuture(delegate)).isTrue(); assertCancelled(future, /* expectWasInterrupted= */ false); } public void testSetFutureDelegateLaterInterrupted() throws Exception { assertThat(future.setFuture(delegate)).isTrue(); - delegate.cancel( - true - /** mayInterruptIfRunning */ - ); + delegate.cancel(/* mayInterruptIfRunning= */ true); assertCancelled(future, /* expectWasInterrupted= */ false); } @@ -329,28 +322,16 @@ public void run() { } public void testNullListener() { - try { - future.addListener(null, directExecutor()); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> future.addListener(null, directExecutor())); } public void testNullExecutor() { - try { - future.addListener(doNothing(), null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> future.addListener(doNothing(), null)); } public void testNullTimeUnit() throws Exception { future.set(1); - try { - future.get(0, null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> future.get(0, null)); } public void testNegativeTimeout() throws Exception { @@ -358,8 +339,8 @@ public void testNegativeTimeout() throws Exception { assertEquals(1, future.get(-1, SECONDS).intValue()); } + @J2ktIncompatible @GwtIncompatible // threads - public void testOverflowTimeout() throws Exception { // First, sanity check that naive multiplication would really overflow to a negative number: long nanosPerSecond = NANOSECONDS.convert(1, SECONDS); @@ -374,17 +355,14 @@ public void testOverflowTimeout() throws Exception { waiter.join(); } + @J2ktIncompatible // TODO(b/324550390): Enable public void testSetNull() throws Exception { future.set(null); assertSuccessful(future, null); } public void testSetExceptionNull() throws Exception { - try { - future.setException(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> future.setException(null)); assertThat(future.isDone()).isFalse(); assertThat(future.set(1)).isTrue(); @@ -392,11 +370,7 @@ public void testSetExceptionNull() throws Exception { } public void testSetFutureNull() throws Exception { - try { - future.setFuture(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> future.setFuture(null)); assertThat(future.isDone()).isFalse(); assertThat(future.set(1)).isTrue(); @@ -444,7 +418,8 @@ private static void assertPending(AbstractFuture future) { verifyTimedGetOnPendingFuture(future); } - private static void assertSuccessful(AbstractFuture future, Integer expectedResult) + private static void assertSuccessful( + AbstractFuture future, @Nullable Integer expectedResult) throws InterruptedException, TimeoutException, ExecutionException { assertDone(future); assertThat(future.isCancelled()).isFalse(); @@ -462,7 +437,7 @@ private static void assertFailed(AbstractFuture future, Throwable expec getDone(future); fail(); } catch (ExecutionException e) { - assertThat(e.getCause()).isSameInstanceAs(expectedException); + assertThat(e).hasCauseThat().isSameInstanceAs(expectedException); } try { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractChainedListenableFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractChainedListenableFutureTest.java index 85c4e14a5dce..261c14637992 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractChainedListenableFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractChainedListenableFutureTest.java @@ -17,11 +17,13 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.util.concurrent.testing.MockFutureListener; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for any listenable future that chains other listenable futures. Unit tests need only @@ -29,6 +31,7 @@ * * @author Nishant Thakkar */ +@NullUnmarked public abstract class AbstractChainedListenableFutureTest extends TestCase { protected static final int EXCEPTION_DATA = -1; protected static final int VALID_INPUT_DATA = 1; @@ -49,11 +52,7 @@ protected void setUp() throws Exception { public void testFutureGetBeforeCallback() throws Exception { // Verify that get throws a timeout exception before the callback is called. - try { - resultFuture.get(1L, TimeUnit.MILLISECONDS); - fail("The data is not yet ready, so a TimeoutException is expected"); - } catch (TimeoutException expected) { - } + assertThrows(TimeoutException.class, () -> resultFuture.get(1L, MILLISECONDS)); } public void testFutureGetThrowsWrappedException() throws Exception { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractClosingFutureTest.java similarity index 89% rename from android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureTest.java rename to android/guava-tests/test/com/google/common/util/concurrent/AbstractClosingFutureTest.java index f5f12db824ca..fe25a2f59f8e 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractClosingFutureTest.java @@ -20,16 +20,19 @@ import static com.google.common.collect.Lists.asList; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.util.concurrent.ClosingFuture.withoutCloser; import static com.google.common.util.concurrent.Futures.immediateCancelledFuture; import static com.google.common.util.concurrent.Futures.immediateFailedFuture; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; import static com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination; import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; import static java.util.Arrays.asList; import static java.util.concurrent.Executors.newSingleThreadExecutor; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -73,6 +76,7 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; import org.mockito.Mockito; /** @@ -81,7 +85,8 @@ * ClosingFuture#finishToValueAndCloser(ValueAndCloserConsumer, Executor)} paths to complete a * {@link ClosingFuture} pipeline. */ -public abstract class ClosingFutureTest extends TestCase { +@NullUnmarked +public abstract class AbstractClosingFutureTest extends TestCase { // TODO(dpb): Use Expect once that supports JUnit 3, or we can use JUnit 4. final List failures = new ArrayList<>(); final StandardSubjectBuilder expect = @@ -93,8 +98,7 @@ public void fail(AssertionError failure) { } }); - final ListeningExecutorService executor = - MoreExecutors.listeningDecorator(newSingleThreadExecutor()); + final ListeningExecutorService executor = listeningDecorator(newSingleThreadExecutor()); final ExecutorService closingExecutor = newSingleThreadExecutor(); final TestCloseable closeable1 = new TestCloseable("closeable1"); @@ -338,6 +342,23 @@ public ClosingFuture call(DeferredCloser closer) throws Exception { assertClosed(closeable1, closeable2); } + public void testAutoCloseable() throws Exception { + AutoCloseable autoCloseable = closeable1::close; + ClosingFuture closingFuture = + ClosingFuture.submit( + new ClosingCallable() { + @Override + public String call(DeferredCloser closer) throws Exception { + closer.eventuallyClose(autoCloseable, closingExecutor); + return "foo"; + } + }, + executor); + assertThat(getFinalValue(closingFuture)).isEqualTo("foo"); + waitUntilClosed(closingFuture); + assertClosed(closeable1); + } + public void testStatusFuture() throws Exception { ClosingFuture closingFuture = ClosingFuture.submit( @@ -675,7 +696,7 @@ public TestCloseable call(DeferredCloser closer) throws Exception { }, executor) .transformAsync( - ClosingFuture.withoutCloser( + withoutCloser( new AsyncFunction() { @Override public ListenableFuture apply(TestCloseable v) throws Exception { @@ -691,10 +712,10 @@ public ListenableFuture apply(TestCloseable v) throws Exception { } public void testWhenAllComplete_call() throws Exception { - final ClosingFuture input1 = ClosingFuture.from(immediateFuture("value1")); - final ClosingFuture input2Failed = failedClosingFuture(); - final ClosingFuture nonInput = ClosingFuture.from(immediateFuture("value3")); - final AtomicReference capturedPeeker = new AtomicReference<>(); + ClosingFuture input1 = ClosingFuture.from(immediateFuture("value1")); + ClosingFuture input2Failed = failedClosingFuture(); + ClosingFuture nonInput = ClosingFuture.from(immediateFuture("value3")); + AtomicReference capturedPeeker = new AtomicReference<>(); ClosingFuture closingFuture = ClosingFuture.whenAllComplete(ImmutableList.of(input1, input2Failed)) .call( @@ -722,11 +743,7 @@ public TestCloseable call(DeferredCloser closer, Peeker peeker) throws Exception waitUntilClosed(closingFuture); assertStillOpen(closeable2); assertClosed(closeable1); - try { - capturedPeeker.get().getDone(input1); - fail("Peeker should not be able to peek except during call."); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> capturedPeeker.get().getDone(input1)); } public void testWhenAllComplete_call_cancelledPipeline() throws Exception { @@ -776,10 +793,10 @@ public Object call(DeferredCloser closer, Peeker peeker) throws Exception { } public void testWhenAllComplete_callAsync() throws Exception { - final ClosingFuture input1 = ClosingFuture.from(immediateFuture("value1")); - final ClosingFuture input2Failed = failedClosingFuture(); - final ClosingFuture nonInput = ClosingFuture.from(immediateFuture("value3")); - final AtomicReference capturedPeeker = new AtomicReference<>(); + ClosingFuture input1 = ClosingFuture.from(immediateFuture("value1")); + ClosingFuture input2Failed = failedClosingFuture(); + ClosingFuture nonInput = ClosingFuture.from(immediateFuture("value3")); + AtomicReference capturedPeeker = new AtomicReference<>(); ClosingFuture closingFuture = ClosingFuture.whenAllComplete(ImmutableList.of(input1, input2Failed)) .callAsync( @@ -808,11 +825,7 @@ public ClosingFuture call(DeferredCloser closer, Peeker peeker) assertThat(getFinalValue(closingFuture)).isSameInstanceAs(closeable2); waitUntilClosed(closingFuture); assertClosed(closeable1, closeable2); - try { - capturedPeeker.get().getDone(input1); - fail("Peeker should not be able to peek except during call."); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> capturedPeeker.get().getDone(input1)); } public void testWhenAllComplete_callAsync_cancelledPipeline() throws Exception { @@ -1480,7 +1493,7 @@ public void testCatchingAsync_preventsFurtherOperations() { ClosingFuture unused = closingFuture.catchingAsync( Exception.class, - ClosingFuture.withoutCloser( + withoutCloser( new AsyncFunction() { @Override public ListenableFuture apply(Exception x) throws Exception { @@ -1616,7 +1629,7 @@ public Closeable call(DeferredCloser closer) throws Exception { /** * Marks the given step final and waits for it to fail. Expects the failure exception to match - * {@link ClosingFutureTest#exception}. + * {@link AbstractClosingFutureTest#exception}. */ abstract void assertFinallyFailsWithException(ClosingFuture closingFuture); @@ -1628,191 +1641,6 @@ void waitUntilClosed(ClosingFuture closingFuture) { assertTrue(awaitUninterruptibly(closingFuture.whenClosedCountDown(), 1, SECONDS)); } - /** Tests for {@link ClosingFuture} that exercise {@link ClosingFuture#finishToFuture()}. */ - - public static class FinishToFutureTest extends ClosingFutureTest { - - public void testFinishToFuture_throwsIfCalledTwice() throws Exception { - ClosingFuture closingFuture = - ClosingFuture.submit( - new ClosingCallable() { - @Override - public Closeable call(DeferredCloser closer) throws Exception { - return closer.eventuallyClose(mockCloseable, executor); - } - }, - executor); - FluentFuture unused = closingFuture.finishToFuture(); - try { - FluentFuture unused2 = closingFuture.finishToFuture(); - fail("should have thrown"); - } catch (IllegalStateException expected) { - } - } - - public void testFinishToFuture_throwsAfterCallingFinishToValueAndCloser() throws Exception { - ClosingFuture closingFuture = - ClosingFuture.submit( - new ClosingCallable() { - @Override - public Closeable call(DeferredCloser closer) throws Exception { - return closer.eventuallyClose(mockCloseable, executor); - } - }, - executor); - closingFuture.finishToValueAndCloser(new NoOpValueAndCloserConsumer<>(), directExecutor()); - try { - FluentFuture unused = closingFuture.finishToFuture(); - fail("should have thrown"); - } catch (IllegalStateException expected) { - } - } - - public void testFinishToFuture_preventsFurtherDerivation() { - ClosingFuture closingFuture = ClosingFuture.from(immediateFuture("value1")); - FluentFuture unused = closingFuture.finishToFuture(); - assertDerivingThrowsIllegalStateException(closingFuture); - } - - @Override - T getFinalValue(ClosingFuture closingFuture) throws ExecutionException { - return getUninterruptibly(closingFuture.finishToFuture()); - } - - @Override - void assertFinallyFailsWithException(ClosingFuture closingFuture) { - assertThatFutureFailsWithException(closingFuture.finishToFuture()); - } - - @Override - void assertBecomesCanceled(ClosingFuture closingFuture) throws ExecutionException { - assertThatFutureBecomesCancelled(closingFuture.finishToFuture()); - } - - @Override - void cancelFinalStepAndWait(ClosingFuture closingFuture) { - assertThat(closingFuture.finishToFuture().cancel(false)).isTrue(); - waitUntilClosed(closingFuture); - futureCancelled.countDown(); - } - } - - /** - * Tests for {@link ClosingFuture} that exercise {@link - * ClosingFuture#finishToValueAndCloser(ValueAndCloserConsumer, Executor)}. - */ - - public static class FinishToValueAndCloserTest extends ClosingFutureTest { - - private final ExecutorService finishToValueAndCloserExecutor = newSingleThreadExecutor(); - private volatile ValueAndCloser valueAndCloser; - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - assertWithMessage("finishToValueAndCloserExecutor was shut down") - .that(shutdownAndAwaitTermination(finishToValueAndCloserExecutor, 10, SECONDS)) - .isTrue(); - } - - public void testFinishToValueAndCloser_throwsIfCalledTwice() throws Exception { - ClosingFuture closingFuture = - ClosingFuture.submit( - new ClosingCallable() { - @Override - public Closeable call(DeferredCloser closer) throws Exception { - return closer.eventuallyClose(mockCloseable, executor); - } - }, - executor); - closingFuture.finishToValueAndCloser( - new NoOpValueAndCloserConsumer<>(), finishToValueAndCloserExecutor); - try { - closingFuture.finishToValueAndCloser( - new NoOpValueAndCloserConsumer<>(), finishToValueAndCloserExecutor); - fail("should have thrown"); - } catch (IllegalStateException expected) { - } - } - - public void testFinishToValueAndCloser_throwsAfterCallingFinishToFuture() throws Exception { - ClosingFuture closingFuture = - ClosingFuture.submit( - new ClosingCallable() { - @Override - public Closeable call(DeferredCloser closer) throws Exception { - return closer.eventuallyClose(mockCloseable, executor); - } - }, - executor); - FluentFuture unused = closingFuture.finishToFuture(); - try { - closingFuture.finishToValueAndCloser( - new NoOpValueAndCloserConsumer<>(), finishToValueAndCloserExecutor); - fail("should have thrown"); - } catch (IllegalStateException expected) { - } - } - - @Override - T getFinalValue(ClosingFuture closingFuture) throws ExecutionException { - return finishToValueAndCloser(closingFuture).get(); - } - - @Override - void assertFinallyFailsWithException(ClosingFuture closingFuture) { - assertThatFutureFailsWithException(closingFuture.statusFuture()); - ValueAndCloser valueAndCloser = finishToValueAndCloser(closingFuture); - try { - valueAndCloser.get(); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(exception); - } - valueAndCloser.closeAsync(); - } - - @Override - void assertBecomesCanceled(ClosingFuture closingFuture) throws ExecutionException { - assertThatFutureBecomesCancelled(closingFuture.statusFuture()); - } - - @Override - void waitUntilClosed(ClosingFuture closingFuture) { - if (valueAndCloser != null) { - valueAndCloser.closeAsync(); - } - super.waitUntilClosed(closingFuture); - } - - @Override - void cancelFinalStepAndWait(ClosingFuture closingFuture) { - assertThat(closingFuture.cancel(false)).isTrue(); - ValueAndCloser unused = finishToValueAndCloser(closingFuture); - waitUntilClosed(closingFuture); - futureCancelled.countDown(); - } - - private ValueAndCloser finishToValueAndCloser(ClosingFuture closingFuture) { - final CountDownLatch valueAndCloserSet = new CountDownLatch(1); - closingFuture.finishToValueAndCloser( - new ValueAndCloserConsumer() { - @Override - public void accept(ValueAndCloser valueAndCloser) { - FinishToValueAndCloserTest.this.valueAndCloser = valueAndCloser; - valueAndCloserSet.countDown(); - } - }, - finishToValueAndCloserExecutor); - assertWithMessage("valueAndCloser was set") - .that(awaitUninterruptibly(valueAndCloserSet, 10, SECONDS)) - .isTrue(); - @SuppressWarnings("unchecked") - ValueAndCloser valueAndCloserWithType = (ValueAndCloser) valueAndCloser; - return valueAndCloserWithType; - } - } - void assertThatFutureFailsWithException(Future future) { try { getUninterruptibly(future); @@ -1822,7 +1650,7 @@ void assertThatFutureFailsWithException(Future future) { } } - private static void assertThatFutureBecomesCancelled(Future future) throws ExecutionException { + static void assertThatFutureBecomesCancelled(Future future) throws ExecutionException { try { getUninterruptibly(future); fail("Expected future to be canceled: " + future); @@ -1900,59 +1728,71 @@ static final class Waiter { private final CountDownLatch returned = new CountDownLatch(1); private Object proxy; + @SuppressWarnings("unchecked") // proxy for a generic class Callable waitFor(Callable callable) { return waitFor(callable, Callable.class); } + @SuppressWarnings("unchecked") // proxy for a generic class ClosingCallable waitFor(ClosingCallable closingCallable) { return waitFor(closingCallable, ClosingCallable.class); } + @SuppressWarnings("unchecked") // proxy for a generic class AsyncClosingCallable waitFor(AsyncClosingCallable asyncClosingCallable) { return waitFor(asyncClosingCallable, AsyncClosingCallable.class); } + @SuppressWarnings("unchecked") // proxy for a generic class ClosingFunction waitFor(ClosingFunction closingFunction) { return waitFor(closingFunction, ClosingFunction.class); } + @SuppressWarnings("unchecked") // proxy for a generic class AsyncClosingFunction waitFor(AsyncClosingFunction asyncClosingFunction) { return waitFor(asyncClosingFunction, AsyncClosingFunction.class); } + @SuppressWarnings("unchecked") // proxy for a generic class CombiningCallable waitFor(CombiningCallable combiningCallable) { return waitFor(combiningCallable, CombiningCallable.class); } + @SuppressWarnings("unchecked") // proxy for a generic class AsyncCombiningCallable waitFor(AsyncCombiningCallable asyncCombiningCallable) { return waitFor(asyncCombiningCallable, AsyncCombiningCallable.class); } + @SuppressWarnings("unchecked") // proxy for a generic class ClosingFunction2 waitFor(ClosingFunction2 closingFunction2) { return waitFor(closingFunction2, ClosingFunction2.class); } + @SuppressWarnings("unchecked") // proxy for a generic class AsyncClosingFunction2 waitFor( AsyncClosingFunction2 asyncClosingFunction2) { return waitFor(asyncClosingFunction2, AsyncClosingFunction2.class); } + @SuppressWarnings("unchecked") // proxy for a generic class ClosingFunction3 waitFor( ClosingFunction3 closingFunction3) { return waitFor(closingFunction3, ClosingFunction3.class); } + @SuppressWarnings("unchecked") // proxy for a generic class ClosingFunction4 waitFor( ClosingFunction4 closingFunction4) { return waitFor(closingFunction4, ClosingFunction4.class); } + @SuppressWarnings("unchecked") // proxy for a generic class ClosingFunction5 waitFor( ClosingFunction5 closingFunction5) { return waitFor(closingFunction5, ClosingFunction5.class); } - T waitFor(final T delegate, final Class type) { + T waitFor(T delegate, Class type) { checkState(proxy == null); T proxyObject = Reflection.newProxy( @@ -1989,7 +1829,7 @@ void awaitReturned() { } } - private static final class NoOpValueAndCloserConsumer implements ValueAndCloserConsumer { + static final class NoOpValueAndCloserConsumer implements ValueAndCloserConsumer { @Override public void accept(ValueAndCloser valueAndCloser) {} } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractExecutionThreadServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractExecutionThreadServiceTest.java index 557fc0b5f2fe..c1a6a2b384cd 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractExecutionThreadServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractExecutionThreadServiceTest.java @@ -17,6 +17,10 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.testing.TearDown; import com.google.common.testing.TearDownStack; @@ -25,17 +29,17 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link AbstractExecutionThreadService}. * * @author Jesse Wilson */ +@NullUnmarked public class AbstractExecutionThreadServiceTest extends TestCase { private final TearDownStack tearDownStack = new TearDownStack(true); @@ -63,12 +67,11 @@ public void uncaughtException(Thread thread, Throwable e) { @Override protected final void tearDown() { tearDownStack.runTearDown(); - assertNull( - "exceptions should not be propagated to uncaught exception handlers", - thrownByExecutionThread); + assertWithMessage("exceptions should not be propagated to uncaught exception handlers") + .that(thrownByExecutionThread) + .isNull(); } - public void testServiceStartStop() throws Exception { WaitOnRunService service = new WaitOnRunService(); assertFalse(service.startUpCalled); @@ -85,7 +88,6 @@ public void testServiceStartStop() throws Exception { executionThread.join(); } - public void testServiceStopIdempotence() throws Exception { WaitOnRunService service = new WaitOnRunService(); @@ -102,7 +104,6 @@ public void testServiceStopIdempotence() throws Exception { executionThread.join(); } - public void testServiceExitingOnItsOwn() throws Exception { WaitOnRunService service = new WaitOnRunService(); service.expectedShutdownState = Service.State.RUNNING; @@ -173,18 +174,14 @@ protected Executor executor() { } } - public void testServiceThrowOnStartUp() throws Exception { ThrowOnStartUpService service = new ThrowOnStartUpService(); assertFalse(service.startUpCalled); service.startAsync(); - try { - service.awaitRunning(); - fail(); - } catch (IllegalStateException expected) { - assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); - } + IllegalStateException expected = + assertThrows(IllegalStateException.class, () -> service.awaitRunning()); + assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); executionThread.join(); assertTrue(service.startUpCalled); @@ -212,40 +209,32 @@ protected Executor executor() { } } - public void testServiceThrowOnRun() throws Exception { ThrowOnRunService service = new ThrowOnRunService(); service.startAsync(); - try { - service.awaitTerminated(); - fail(); - } catch (IllegalStateException expected) { - executionThread.join(); - assertThat(expected).hasCauseThat().isEqualTo(service.failureCause()); - assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); - } + IllegalStateException expected = + assertThrows(IllegalStateException.class, () -> service.awaitTerminated()); + executionThread.join(); + assertThat(expected).hasCauseThat().isEqualTo(service.failureCause()); + assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); assertTrue(service.shutDownCalled); assertEquals(Service.State.FAILED, service.state()); } - public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception { ThrowOnRunService service = new ThrowOnRunService(); service.throwOnShutDown = true; service.startAsync(); - try { - service.awaitTerminated(); - fail(); - } catch (IllegalStateException expected) { - executionThread.join(); - assertThat(expected).hasCauseThat().isEqualTo(service.failureCause()); - assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); - } - + IllegalStateException expected = + assertThrows(IllegalStateException.class, () -> service.awaitTerminated()); + executionThread.join(); + assertThat(expected).hasCauseThat().isEqualTo(service.failureCause()); + assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); assertTrue(service.shutDownCalled); assertEquals(Service.State.FAILED, service.state()); + assertThat(expected.getCause().getSuppressed()[0]).hasMessageThat().isEqualTo("double kaboom!"); } private class ThrowOnRunService extends AbstractExecutionThreadService { @@ -271,7 +260,6 @@ protected Executor executor() { } } - public void testServiceThrowOnShutDown() throws Exception { ThrowOnShutDown service = new ThrowOnShutDown(); @@ -310,12 +298,10 @@ protected Executor executor() { public void testServiceTimeoutOnStartUp() throws Exception { TimeoutOnStartUp service = new TimeoutOnStartUp(); - try { - service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS); - fail(); - } catch (TimeoutException e) { - assertThat(e.getMessage()).contains(Service.State.STARTING.toString()); - } + TimeoutException e = + assertThrows( + TimeoutException.class, () -> service.startAsync().awaitRunning(1, MILLISECONDS)); + assertThat(e).hasMessageThat().contains(Service.State.STARTING.toString()); } private class TimeoutOnStartUp extends AbstractExecutionThreadService { @@ -331,9 +317,8 @@ public void execute(Runnable command) {} protected void run() throws Exception {} } - public void testStopWhileStarting_runNotCalled() throws Exception { - final CountDownLatch started = new CountDownLatch(1); + CountDownLatch started = new CountDownLatch(1); FakeService service = new FakeService() { @Override @@ -361,7 +346,6 @@ public void testStop_noStart() { assertEquals(0, service.shutdownCalled); } - public void testDefaultService() throws InterruptedException { WaitOnRunService service = new WaitOnRunService(); service.startAsync().awaitRunning(); @@ -386,19 +370,17 @@ protected String serviceName() { return "Foo"; } }; - try { - service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS); - fail("Expected timeout"); - } catch (TimeoutException e) { - assertThat(e) - .hasMessageThat() - .isEqualTo("Timed out waiting for Foo [STARTING] to reach the RUNNING state."); - } + TimeoutException e = + assertThrows( + TimeoutException.class, () -> service.startAsync().awaitRunning(1, MILLISECONDS)); + assertThat(e) + .hasMessageThat() + .isEqualTo("Timed out waiting for Foo [STARTING] to reach the RUNNING state."); } private class FakeService extends AbstractExecutionThreadService implements TearDown { - private final ExecutorService executor = Executors.newSingleThreadExecutor(); + private final ExecutorService executor = newSingleThreadExecutor(); FakeService() { tearDownStack.addTearDown(this); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureBenchmarks.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureBenchmarks.java index bf70aac25e72..1dd4986b6a3c 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureBenchmarks.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureBenchmarks.java @@ -25,9 +25,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.AbstractQueuedSynchronizer; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** Utilities for the AbstractFutureBenchmarks */ +@NullUnmarked final class AbstractFutureBenchmarks { private AbstractFutureBenchmarks() {} @@ -85,6 +87,7 @@ Facade newFacade() { abstract Facade newFacade(); } + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. static void awaitWaiting(Thread t) { while (true) { Thread.State state = t.getState(); @@ -218,7 +221,7 @@ public void addListener(Runnable listener, Executor exec) { * @return true if the state was successfully changed. */ @CanIgnoreReturnValue - protected boolean set(@NullableDecl V value) { + protected boolean set(@Nullable V value) { boolean result = sync.set(value); if (result) { executionList.execute(); @@ -360,7 +363,7 @@ boolean wasInterrupted() { } /** Transition to the COMPLETED state and set the value. */ - boolean set(@NullableDecl V v) { + boolean set(@Nullable V v) { return complete(v, null, COMPLETED); } @@ -384,7 +387,7 @@ boolean cancel(boolean interrupt) { * @param t the exception to set as the result of the computation. * @param finalState the state to transition to. */ - private boolean complete(@NullableDecl V v, @NullableDecl Throwable t, int finalState) { + private boolean complete(@Nullable V v, @Nullable Throwable t, int finalState) { boolean doCompletion = compareAndSetState(RUNNING, COMPLETING); if (doCompletion) { // If this thread successfully transitioned to COMPLETING, set the value @@ -405,8 +408,8 @@ private boolean complete(@NullableDecl V v, @NullableDecl Throwable t, int final } } - static final CancellationException cancellationExceptionWithCause( - @NullableDecl String message, @NullableDecl Throwable cause) { + static CancellationException cancellationExceptionWithCause( + @Nullable String message, @Nullable Throwable cause) { CancellationException exception = new CancellationException(message); exception.initCause(cause); return exception; diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java index 6c921b5b67d5..16148b64aacd 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java @@ -17,7 +17,9 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import com.google.errorprone.annotations.concurrent.GuardedBy; import java.lang.reflect.Method; import java.net.URLClassLoader; import java.util.HashMap; @@ -26,11 +28,13 @@ import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import javax.annotation.concurrent.GuardedBy; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Tests for {@link AbstractFuture} with the cancellation cause system property set */ +@AndroidIncompatible // custom classloading +@NullUnmarked public class AbstractFutureCancellationCauseTest extends TestCase { private ClassLoader oldClassLoader; @@ -46,7 +50,7 @@ protected void setUp() throws Exception { // cause system property. This allows us to run with both settings of the property in one jvm // without resorting to even crazier hacks to reset static final boolean fields. System.setProperty("guava.concurrent.generate_cancellation_cause", "true"); - final String concurrentPackage = SettableFuture.class.getPackage().getName(); + String concurrentPackage = SettableFuture.class.getPackage().getName(); classReloader = new URLClassLoader(ClassPathUtil.getClassPathUrls()) { @GuardedBy("loadedClasses") @@ -87,13 +91,9 @@ public void testCancel_notDoneNoInterrupt() throws Exception { assertTrue(future.cancel(false)); assertTrue(future.isCancelled()); assertTrue(future.isDone()); - assertNull(tryInternalFastPathGetFailure(future)); - try { - future.get(); - fail("Expected CancellationException"); - } catch (CancellationException e) { - assertNotNull(e.getCause()); - } + assertThat(tryInternalFastPathGetFailure(future)).isNull(); + CancellationException e = assertThrows(CancellationException.class, () -> future.get()); + assertThat(e.getCause()).isNotNull(); } public void testCancel_notDoneInterrupt() throws Exception { @@ -101,13 +101,9 @@ public void testCancel_notDoneInterrupt() throws Exception { assertTrue(future.cancel(true)); assertTrue(future.isCancelled()); assertTrue(future.isDone()); - assertNull(tryInternalFastPathGetFailure(future)); - try { - future.get(); - fail("Expected CancellationException"); - } catch (CancellationException e) { - assertNotNull(e.getCause()); - } + assertThat(tryInternalFastPathGetFailure(future)).isNull(); + CancellationException e = assertThrows(CancellationException.class, () -> future.get()); + assertThat(e.getCause()).isNotNull(); } public void testSetFuture_misbehavingFutureDoesNotThrow() throws Exception { @@ -150,13 +146,9 @@ public void addListener(Runnable runnable, Executor executor) { "setFuture", future.getClass().getClassLoader().loadClass(ListenableFuture.class.getName())) .invoke(future, badFuture); - try { - future.get(); - fail(); - } catch (CancellationException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(IllegalArgumentException.class); - assertThat(expected).hasCauseThat().hasMessageThat().contains(badFuture.toString()); - } + CancellationException expected = assertThrows(CancellationException.class, () -> future.get()); + assertThat(expected).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(expected).hasCauseThat().hasMessageThat().contains(badFuture.toString()); } private Future newFutureInstance() throws Exception { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureDefaultAtomicHelperTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureDefaultAtomicHelperTest.java new file mode 100644 index 000000000000..ac612b0fd34e --- /dev/null +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureDefaultAtomicHelperTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static com.google.common.truth.Truth.assertThat; + +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +/** + * Tests that {@link AbstractFutureState} uses the expected {@code AtomicHelper} implementation. + * + *

    We have more thorough testing of {@code AtomicHelper} implementations in {@link + * AbstractFutureFallbackAtomicHelperTest}. The advantage to this test is that it can run under + * Android. + */ +@NullUnmarked +public class AbstractFutureDefaultAtomicHelperTest extends TestCase { + public void testUsingExpectedAtomicHelper() throws Exception { + if (isAndroid()) { + assertThat(AbstractFutureState.atomicHelperTypeForTest()).isEqualTo("UnsafeAtomicHelper"); + } else { + assertThat(AbstractFutureState.atomicHelperTypeForTest()) + .isEqualTo("AtomicReferenceFieldUpdaterAtomicHelper"); + } + } + + private static boolean isJava8() { + return JAVA_SPECIFICATION_VERSION.value().equals("1.8"); + } + + private static boolean isAndroid() { + return System.getProperty("java.runtime.name", "").contains("Android"); + } +} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureFallbackAtomicHelperTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureFallbackAtomicHelperTest.java index 208cf24831e1..731e534ea0a1 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureFallbackAtomicHelperTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureFallbackAtomicHelperTest.java @@ -14,8 +14,10 @@ package com.google.common.util.concurrent; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.ImmutableSet; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URLClassLoader; @@ -23,6 +25,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests our AtomicHelper fallback strategies in AbstractFuture. @@ -30,40 +33,45 @@ *

    On different platforms AbstractFuture uses different strategies for its core synchronization * primitives. The strategies are all implemented as subtypes of AtomicHelper and the strategy is * selected in the static initializer of AbstractFuture. This is convenient and performant but - * introduces some testing difficulties. This test exercises the two fallback strategies in abstract - * future. - * - *

      - *
    • SafeAtomicHelper: uses AtomicReferenceFieldsUpdaters to implement synchronization - *
    • SynchronizedHelper: uses {@code synchronized} blocks for synchronization - *
    + * introduces some testing difficulties. This test exercises the fallback strategies. * - * To force selection of our fallback strategies we load {@link AbstractFuture} (and all of {@code - * com.google.common.util.concurrent}) in degenerate class loaders which make certain platform - * classes unavailable. Then we construct a test suite so we can run the normal AbstractFutureTest - * test methods in these degenerate classloaders. + *

    To force selection of our fallback strategies, we load {@link AbstractFuture} (and all of + * {@code com.google.common.util.concurrent}) in degenerate class loaders which make certain + * platform classes unavailable. Then we construct a test suite so we can run the normal + * AbstractFutureTest test methods in these degenerate classloaders. */ +@NullUnmarked public class AbstractFutureFallbackAtomicHelperTest extends TestCase { // stash these in static fields to avoid loading them over and over again (speeds up test // execution significantly) /** - * This classloader disallows {@link sun.misc.Unsafe}, which will prevent us from selecting our - * preferred strategy {@code UnsafeAtomicHelper}. + * This classloader disallows {@link java.lang.invoke.VarHandle}, which will prevent us from + * selecting the {@code VarHandleAtomicHelper} strategy. + */ + private static final ClassLoader NO_VAR_HANDLE = + getClassLoader(ImmutableSet.of("java.lang.invoke.VarHandle")); + + /** + * This classloader disallows {@link java.lang.invoke.VarHandle} and {@link sun.misc.Unsafe}, + * which will prevent us from selecting the {@code UnsafeAtomicHelper} strategy. */ private static final ClassLoader NO_UNSAFE = - getClassLoader(ImmutableSet.of(sun.misc.Unsafe.class.getName())); + getClassLoader(ImmutableSet.of("java.lang.invoke.VarHandle", "sun.misc.Unsafe")); /** - * This classloader disallows {@link sun.misc.Unsafe} and {@link AtomicReferenceFieldUpdater}, - * which will prevent us from selecting our {@code SafeAtomicHelper} strategy. + * This classloader disallows {@link java.lang.invoke.VarHandle}, {@link sun.misc.Unsafe} and + * {@link AtomicReferenceFieldUpdater}, which will prevent us from selecting the {@code + * AtomicReferenceFieldUpdaterAtomicHelper} strategy. */ private static final ClassLoader NO_ATOMIC_REFERENCE_FIELD_UPDATER = getClassLoader( ImmutableSet.of( - sun.misc.Unsafe.class.getName(), AtomicReferenceFieldUpdater.class.getName())); + "java.lang.invoke.VarHandle", + "sun.misc.Unsafe", + AtomicReferenceFieldUpdater.class.getName())); public static TestSuite suite() { // we create a test suite containing a test for every AbstractFutureTest test method and we @@ -81,46 +89,78 @@ public static TestSuite suite() { @Override public void runTest() throws Exception { - // First ensure that our classloaders are initializing the correct helper versions - checkHelperVersion(getClass().getClassLoader(), "UnsafeAtomicHelper"); - checkHelperVersion(NO_UNSAFE, "SafeAtomicHelper"); + /* + * Note that we do not run this test under Android at the moment. For Android testing, see + * AbstractFutureDefaultAtomicHelperTest. + */ + + // First, ensure that our classloaders are initializing the correct helper versions: + + checkHelperVersion(getClass().getClassLoader(), "AtomicReferenceFieldUpdaterAtomicHelper"); + /* + * Since we use AtomicReferenceFieldUpdaterAtomicHelper by default, we'll "obviously" use it + * even when Unsafe isn't available. But it's nice to have a check here to make sure that + * nothing somehow goes wrong as the JDK restricts access to Unsafe. + */ + checkHelperVersion(NO_UNSAFE, "AtomicReferenceFieldUpdaterAtomicHelper"); + /* + * SynchronizedHelper is meant for Android, but our best way to test it is under the JVM. + * + * SynchronizedHelper also ends up getting used under the JVM during + * AggregateFutureStateFallbackAtomicHelperTest, as discussed in a comment in + * AggregateFutureState. + * + * Here, we check that we're able to force AbstractFutureState to select SynchronizedHelper, and + * below, we actually run the AbstractFutureTest methods under that scenario. + */ checkHelperVersion(NO_ATOMIC_REFERENCE_FIELD_UPDATER, "SynchronizedHelper"); - // Run the corresponding AbstractFutureTest test method in a new classloader that disallows - // certain core jdk classes. + // Then, run the actual tests under each alternative classloader: + + /* + * We don't need to test further under NO_UNSAFE: We verified that it selects + * AtomicReferenceFieldUpdaterAtomicHelper, which is the default, which means that it's used + * when we run AbstractFutureTest itself. + */ + + /* + * We don't test UnsafeAtomicHelper here, since guava-android doesn't provide a way to use it + * under the JVM. (We could arrange for one if we really wanted, but that will break once the + * JDK further restricts access to Unsafe.) But we have coverage under an Android emulator, + * which uses UnsafeAtomicHelper when it runs AbstractFutureTest itself. + */ + + runTestMethod(NO_ATOMIC_REFERENCE_FIELD_UPDATER); + // TODO(lukes): assert that the logs are full of errors + } + + /** + * Runs the corresponding {@link AbstractFutureTest} test method in a new classloader that + * disallows certain core JDK classes. + */ + private void runTestMethod(ClassLoader classLoader) throws Exception { ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(NO_UNSAFE); - try { - runTestMethod(NO_UNSAFE); - } finally { - Thread.currentThread().setContextClassLoader(oldClassLoader); - } - Thread.currentThread().setContextClassLoader(NO_ATOMIC_REFERENCE_FIELD_UPDATER); + Thread.currentThread().setContextClassLoader(classLoader); try { - runTestMethod(NO_ATOMIC_REFERENCE_FIELD_UPDATER); - // TODO(lukes): assert that the logs are full of errors + Class test = classLoader.loadClass(AbstractFutureTest.class.getName()); + test.getMethod(getName()).invoke(test.getDeclaredConstructor().newInstance()); } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } } - private void runTestMethod(ClassLoader classLoader) throws Exception { - Class test = classLoader.loadClass(AbstractFutureTest.class.getName()); - test.getMethod(getName()).invoke(test.getDeclaredConstructor().newInstance()); - } - private void checkHelperVersion(ClassLoader classLoader, String expectedHelperClassName) throws Exception { // Make sure we are actually running with the expected helper implementation - Class abstractFutureClass = classLoader.loadClass(AbstractFuture.class.getName()); - Field helperField = abstractFutureClass.getDeclaredField("ATOMIC_HELPER"); - helperField.setAccessible(true); - assertEquals(expectedHelperClassName, helperField.get(null).getClass().getSimpleName()); + Class abstractFutureStateClass = classLoader.loadClass(AbstractFutureState.class.getName()); + Method helperMethod = abstractFutureStateClass.getDeclaredMethod("atomicHelperTypeForTest"); + helperMethod.setAccessible(true); + assertThat(helperMethod.invoke(null)).isEqualTo(expectedHelperClassName); } - private static ClassLoader getClassLoader(final Set disallowedClassNames) { - final String concurrentPackage = SettableFuture.class.getPackage().getName(); + private static ClassLoader getClassLoader(Set disallowedClassNames) { + String concurrentPackage = SettableFuture.class.getPackage().getName(); ClassLoader classLoader = AbstractFutureFallbackAtomicHelperTest.class.getClassLoader(); // we delegate to the current classloader so both loaders agree on classes like TestCase return new URLClassLoader(ClassPathUtil.getClassPathUrls(), classLoader) { @@ -140,4 +180,8 @@ public Class loadClass(String name) throws ClassNotFoundException { } }; } + + private static boolean isJava8() { + return JAVA_SPECIFICATION_VERSION.value().equals("1.8"); + } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java index e55a31e725de..8a858d33a0b0 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java @@ -16,13 +16,28 @@ package com.google.common.util.concurrent; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static com.google.common.base.StandardSystemProperty.OS_NAME; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.util.concurrent.Futures.immediateCancelledFuture; +import static com.google.common.util.concurrent.Futures.immediateFailedFuture; +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.SneakyThrows.sneakyThrow; +import static java.util.concurrent.Executors.callable; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Iterables; import com.google.common.collect.Range; import com.google.common.collect.Sets; +import com.google.common.primitives.Ints; import com.google.common.util.concurrent.internal.InternalFutureFailureAccess; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +51,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -46,16 +60,18 @@ import java.util.concurrent.locks.LockSupport; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Tests for {@link AbstractFuture}. * * @author Brian Stoler */ - +@NullUnmarked public class AbstractFutureTest extends TestCase { public void testSuccess() throws ExecutionException, InterruptedException { - final Object value = new Object(); + Object value = new Object(); assertSame( value, new AbstractFuture() { @@ -66,7 +82,7 @@ public void testSuccess() throws ExecutionException, InterruptedException { } public void testException() throws InterruptedException { - final Throwable failure = new Throwable(); + Throwable failure = new Throwable(); AbstractFuture future = new AbstractFuture() { { @@ -94,13 +110,8 @@ public void testCancel_notDoneNoInterrupt() throws Exception { assertTrue(future.isDone()); assertFalse(future.wasInterrupted()); assertFalse(future.interruptTaskWasCalled); - try { - future.get(); - fail("Expected CancellationException"); - } catch (CancellationException e) { - // See AbstractFutureCancellationCauseTest for how to set causes - assertThat(e).hasCauseThat().isNull(); - } + CancellationException e = assertThrows(CancellationException.class, () -> future.get()); + assertThat(e).hasCauseThat().isNull(); } public void testCancel_notDoneInterrupt() throws Exception { @@ -110,13 +121,8 @@ public void testCancel_notDoneInterrupt() throws Exception { assertTrue(future.isDone()); assertTrue(future.wasInterrupted()); assertTrue(future.interruptTaskWasCalled); - try { - future.get(); - fail("Expected CancellationException"); - } catch (CancellationException e) { - // See AbstractFutureCancellationCauseTest for how to set causes - assertThat(e).hasCauseThat().isNull(); - } + CancellationException e = assertThrows(CancellationException.class, () -> future.get()); + assertThat(e).hasCauseThat().isNull(); } public void testCancel_done() throws Exception { @@ -138,11 +144,11 @@ public void testGetWithTimeoutDoneFuture() throws Exception { set("foo"); } }; - assertEquals("foo", future.get(0, TimeUnit.SECONDS)); + assertEquals("foo", future.get(0, SECONDS)); } public void testEvilFuture_setFuture() throws Exception { - final RuntimeException exception = new RuntimeException("you didn't say the magic word!"); + RuntimeException exception = new RuntimeException("you didn't say the magic word!"); AbstractFuture evilFuture = new AbstractFuture() { @Override @@ -153,16 +159,12 @@ public void addListener(Runnable r, Executor e) { AbstractFuture normalFuture = new AbstractFuture() {}; normalFuture.setFuture(evilFuture); assertTrue(normalFuture.isDone()); - try { - normalFuture.get(); - fail(); - } catch (ExecutionException e) { - assertThat(e).hasCauseThat().isSameInstanceAs(exception); - } + ExecutionException e = assertThrows(ExecutionException.class, () -> normalFuture.get()); + assertThat(e).hasCauseThat().isSameInstanceAs(exception); } public void testRemoveWaiter_interruption() throws Exception { - final AbstractFuture future = new AbstractFuture() {}; + AbstractFuture future = new AbstractFuture() {}; WaiterThread waiter1 = new WaiterThread(future); waiter1.start(); waiter1.awaitWaiting(); @@ -186,7 +188,7 @@ public void testRemoveWaiter_interruption() throws Exception { } public void testRemoveWaiter_polling() throws Exception { - final AbstractFuture future = new AbstractFuture() {}; + AbstractFuture future = new AbstractFuture() {}; WaiterThread waiter = new WaiterThread(future); waiter.start(); waiter.awaitWaiting(); @@ -263,13 +265,9 @@ public String pendingToString() { assertThat(testFuture.toString()) .matches( "[^\\[]+\\[status=PENDING, info=\\[cause=\\[Because this test isn't done\\]\\]\\]"); - try { - testFuture.get(1, TimeUnit.NANOSECONDS); - fail(); - } catch (TimeoutException e) { - assertThat(e.getMessage()).contains("1 nanoseconds"); - assertThat(e.getMessage()).contains("Because this test isn't done"); - } + TimeoutException e = assertThrows(TimeoutException.class, () -> testFuture.get(1, NANOSECONDS)); + assertThat(e).hasMessageThat().contains("1 nanoseconds"); + assertThat(e).hasMessageThat().contains("Because this test isn't done"); } public void testToString_completesDuringToString() throws Exception { @@ -291,30 +289,36 @@ public String pendingToString() { * get() call. As measurements of time are prone to flakiness, it tries to assert based on ranges * derived from observing how much time actually passed for various operations. */ - @SuppressWarnings({"DeprecatedThreadMethods", "ThreadPriorityCheck"}) + @SuppressWarnings("ThreadPriorityCheck") + @AndroidIncompatible // Thread.suspend public void testToString_delayedTimeout() throws Exception { - TimedWaiterThread thread = - new TimedWaiterThread(new AbstractFuture() {}, 2, TimeUnit.SECONDS); + Integer javaVersion = Ints.tryParse(JAVA_SPECIFICATION_VERSION.value()); + // Parsing to an integer might fail because Java 8 returns "1.8" instead of "8." + // We can continue if it's 1.8, and we can continue if it's an integer in [9, 20). + if (javaVersion != null && javaVersion >= 20) { + // TODO(b/261217224, b/361604053): Make this test work under newer JDKs. + return; + } + TimedWaiterThread thread = new TimedWaiterThread(new AbstractFuture() {}, 2, SECONDS); thread.start(); thread.awaitWaiting(); - thread.suspend(); + Thread.class.getMethod("suspend").invoke(thread); // Sleep for enough time to add 1500 milliseconds of overwait to the get() call. - long toWaitMillis = 3500 - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - thread.startTime); + long toWaitMillis = 3500 - NANOSECONDS.toMillis(System.nanoTime() - thread.startTime); Thread.sleep(toWaitMillis); thread.setPriority(Thread.MAX_PRIORITY); - thread.resume(); + Thread.class.getMethod("resume").invoke(thread); thread.join(); // It's possible to race and suspend the thread just before the park call actually takes effect, // causing the thread to be suspended for 3.5 seconds, and then park itself for 2 seconds after // being resumed. To avoid a flake in this scenario, calculate how long that thread actually // waited and assert based on that time. Empirically, the race where the thread ends up waiting // for 5.5 seconds happens about 2% of the time. - boolean longWait = TimeUnit.NANOSECONDS.toSeconds(thread.timeSpentBlocked) >= 5; + boolean longWait = NANOSECONDS.toSeconds(thread.timeSpentBlocked) >= 5; // Count how long it actually took to return; we'll accept any number between the expected delay // and the approximate actual delay, to be robust to variance in thread scheduling. char overWaitNanosFirstDigit = - Long.toString( - thread.timeSpentBlocked - TimeUnit.MILLISECONDS.toNanos(longWait ? 5000 : 3000)) + Long.toString(thread.timeSpentBlocked - MILLISECONDS.toNanos(longWait ? 5000 : 3000)) .charAt(0); if (overWaitNanosFirstDigit < '4') { overWaitNanosFirstDigit = '9'; @@ -352,12 +356,11 @@ public String pendingToString() { } public void testToString_cancelled() throws Exception { - assertThat(Futures.immediateCancelledFuture().toString()) - .matches("[^\\[]+\\[status=CANCELLED\\]"); + assertThat(immediateCancelledFuture().toString()).matches("[^\\[]+\\[status=CANCELLED\\]"); } public void testToString_failed() { - assertThat(Futures.immediateFailedFuture(new RuntimeException("foo")).toString()) + assertThat(immediateFailedFuture(new RuntimeException("foo")).toString()) .matches("[^\\[]+\\[status=FAILURE, cause=\\[java.lang.RuntimeException: foo\\]\\]"); } @@ -375,10 +378,10 @@ public String pendingToString() { } public void testCompletionFinishesWithDone() { - ExecutorService executor = Executors.newFixedThreadPool(10); + ExecutorService executor = newFixedThreadPool(10); for (int i = 0; i < 50000; i++) { - final AbstractFuture future = new AbstractFuture() {}; - final AtomicReference errorMessage = Atomics.newReference(); + AbstractFuture future = new AbstractFuture() {}; + AtomicReference errorMessage = Atomics.newReference(); executor.execute( new Runnable() { @Override @@ -415,7 +418,7 @@ public void run() { // Ignore, we just wanted to block. } String error = errorMessage.get(); - assertNull(error, error); + assertWithMessage(error).that(error).isNull(); } executor.shutdown(); } @@ -426,19 +429,22 @@ public void run() { */ public void testFutureBash() { - final CyclicBarrier barrier = + if (isWindows()) { + return; // TODO: b/136041958 - Running very slowly on Windows CI. + } + CyclicBarrier barrier = new CyclicBarrier( 6 // for the setter threads + 50 // for the listeners + 50 // for the blocking get threads, + 1); // for the main thread - final ExecutorService executor = Executors.newFixedThreadPool(barrier.getParties()); - final AtomicReference> currentFuture = Atomics.newReference(); - final AtomicInteger numSuccessfulSetCalls = new AtomicInteger(); - Callable completeSuccessfullyRunnable = - new Callable() { + ExecutorService executor = newFixedThreadPool(barrier.getParties()); + AtomicReference> currentFuture = Atomics.newReference(); + AtomicInteger numSuccessfulSetCalls = new AtomicInteger(); + Callable<@Nullable Void> completeSuccessfullyRunnable = + new Callable<@Nullable Void>() { @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().set("set")) { numSuccessfulSetCalls.incrementAndGet(); } @@ -446,12 +452,12 @@ public Void call() { return null; } }; - Callable completeExceptionallyRunnable = - new Callable() { - Exception failureCause = new Exception("setException"); + Callable<@Nullable Void> completeExceptionallyRunnable = + new Callable<@Nullable Void>() { + final Exception failureCause = new Exception("setException"); @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().setException(failureCause)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -459,10 +465,10 @@ public Void call() { return null; } }; - Callable cancelRunnable = - new Callable() { + Callable<@Nullable Void> cancelRunnable = + new Callable<@Nullable Void>() { @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().cancel(true)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -470,12 +476,12 @@ public Void call() { return null; } }; - Callable setFutureCompleteSuccessfullyRunnable = - new Callable() { - ListenableFuture future = Futures.immediateFuture("setFuture"); + Callable<@Nullable Void> setFutureCompleteSuccessfullyRunnable = + new Callable<@Nullable Void>() { + final ListenableFuture future = immediateFuture("setFuture"); @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().setFuture(future)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -483,13 +489,12 @@ public Void call() { return null; } }; - Callable setFutureCompleteExceptionallyRunnable = - new Callable() { - ListenableFuture future = - Futures.immediateFailedFuture(new Exception("setFuture")); + Callable<@Nullable Void> setFutureCompleteExceptionallyRunnable = + new Callable<@Nullable Void>() { + final ListenableFuture future = immediateFailedFuture(new Exception("setFuture")); @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().setFuture(future)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -497,12 +502,12 @@ public Void call() { return null; } }; - Callable setFutureCancelRunnable = - new Callable() { - ListenableFuture future = Futures.immediateCancelledFuture(); + Callable<@Nullable Void> setFutureCancelRunnable = + new Callable<@Nullable Void>() { + final ListenableFuture future = immediateCancelledFuture(); @Override - public Void call() { + public @Nullable Void call() { if (currentFuture.get().setFuture(future)) { numSuccessfulSetCalls.incrementAndGet(); } @@ -510,7 +515,7 @@ public Void call() { return null; } }; - final Set finalResults = Collections.synchronizedSet(Sets.newIdentityHashSet()); + Set finalResults = Collections.synchronizedSet(Sets.newIdentityHashSet()); Runnable collectResultsRunnable = new Runnable() { @Override @@ -534,7 +539,7 @@ public void run() { Future future = currentFuture.get(); while (true) { try { - String result = Uninterruptibles.getUninterruptibly(future, 0, TimeUnit.SECONDS); + String result = Uninterruptibles.getUninterruptibly(future, 0, SECONDS); finalResults.add(result); break; } catch (ExecutionException e) { @@ -559,15 +564,14 @@ public void run() { allTasks.add(setFutureCancelRunnable); for (int k = 0; k < 50; k++) { // For each listener we add a task that submits it to the executor directly for the blocking - // get usecase and another task that adds it as a listener to the future to exercise both + // get use case and another task that adds it as a listener to the future to exercise both // racing addListener calls and addListener calls completing after the future completes. - final Runnable listener = - k % 2 == 0 ? collectResultsRunnable : collectResultsTimedGetRunnable; - allTasks.add(Executors.callable(listener)); + Runnable listener = k % 2 == 0 ? collectResultsRunnable : collectResultsTimedGetRunnable; + allTasks.add(callable(listener)); allTasks.add( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { currentFuture.get().addListener(listener, executor); return null; } @@ -576,7 +580,7 @@ public Void call() throws Exception { assertEquals(allTasks.size() + 1, barrier.getParties()); for (int i = 0; i < 1000; i++) { Collections.shuffle(allTasks); - final AbstractFuture future = new AbstractFuture() {}; + AbstractFuture future = new AbstractFuture() {}; currentFuture.set(future); for (Callable task : allTasks) { @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored @@ -607,19 +611,22 @@ public Void call() throws Exception { // setFuture and cancel() interact in more complicated ways than the other setters. public void testSetFutureCancelBash() { - final int size = 50; - final CyclicBarrier barrier = + if (isWindows()) { + return; // TODO: b/136041958 - Running very slowly on Windows CI. + } + int size = 50; + CyclicBarrier barrier = new CyclicBarrier( 2 // for the setter threads + size // for the listeners + size // for the get threads, + 1); // for the main thread - final ExecutorService executor = Executors.newFixedThreadPool(barrier.getParties()); - final AtomicReference> currentFuture = Atomics.newReference(); - final AtomicReference> setFutureFuture = Atomics.newReference(); - final AtomicBoolean setFutureSetSuccess = new AtomicBoolean(); - final AtomicBoolean setFutureCompletionSuccess = new AtomicBoolean(); - final AtomicBoolean cancellationSuccess = new AtomicBoolean(); + ExecutorService executor = newFixedThreadPool(barrier.getParties()); + AtomicReference> currentFuture = Atomics.newReference(); + AtomicReference> setFutureFuture = Atomics.newReference(); + AtomicBoolean setFutureSetSuccess = new AtomicBoolean(); + AtomicBoolean setFutureCompletionSuccess = new AtomicBoolean(); + AtomicBoolean cancellationSuccess = new AtomicBoolean(); Runnable cancelRunnable = new Runnable() { @Override @@ -638,7 +645,7 @@ public void run() { awaitUnchecked(barrier); } }; - final Set finalResults = Collections.synchronizedSet(Sets.newIdentityHashSet()); + Set finalResults = Collections.synchronizedSet(Sets.newIdentityHashSet()); Runnable collectResultsRunnable = new Runnable() { @Override @@ -662,7 +669,7 @@ public void run() { Future future = currentFuture.get(); while (true) { try { - String result = Uninterruptibles.getUninterruptibly(future, 0, TimeUnit.SECONDS); + String result = Uninterruptibles.getUninterruptibly(future, 0, SECONDS); finalResults.add(result); break; } catch (ExecutionException e) { @@ -683,10 +690,9 @@ public void run() { allTasks.add(setFutureCompleteSuccessfullyRunnable); for (int k = 0; k < size; k++) { // For each listener we add a task that submits it to the executor directly for the blocking - // get usecase and another task that adds it as a listener to the future to exercise both + // get use case and another task that adds it as a listener to the future to exercise both // racing addListener calls and addListener calls completing after the future completes. - final Runnable listener = - k % 2 == 0 ? collectResultsRunnable : collectResultsTimedGetRunnable; + Runnable listener = k % 2 == 0 ? collectResultsRunnable : collectResultsTimedGetRunnable; allTasks.add(listener); allTasks.add( new Runnable() { @@ -699,8 +705,8 @@ public void run() { assertEquals(allTasks.size() + 1, barrier.getParties()); // sanity check for (int i = 0; i < 1000; i++) { Collections.shuffle(allTasks); - final AbstractFuture future = new AbstractFuture() {}; - final AbstractFuture setFuture = new AbstractFuture() {}; + AbstractFuture future = new AbstractFuture() {}; + AbstractFuture setFuture = new AbstractFuture() {}; currentFuture.set(future); setFutureFuture.set(setFuture); for (Runnable task : allTasks) { @@ -742,37 +748,37 @@ public void run() { // Test to ensure that when calling setFuture with a done future only setFuture or cancel can // return true. public void testSetFutureCancelBash_withDoneFuture() { - final CyclicBarrier barrier = + CyclicBarrier barrier = new CyclicBarrier( 2 // for the setter threads + 1 // for the blocking get thread, + 1); // for the main thread - final ExecutorService executor = Executors.newFixedThreadPool(barrier.getParties()); - final AtomicReference> currentFuture = Atomics.newReference(); - final AtomicBoolean setFutureSuccess = new AtomicBoolean(); - final AtomicBoolean cancellationSuccess = new AtomicBoolean(); - Callable cancelRunnable = - new Callable() { + ExecutorService executor = newFixedThreadPool(barrier.getParties()); + AtomicReference> currentFuture = Atomics.newReference(); + AtomicBoolean setFutureSuccess = new AtomicBoolean(); + AtomicBoolean cancellationSuccess = new AtomicBoolean(); + Callable<@Nullable Void> cancelRunnable = + new Callable<@Nullable Void>() { @Override - public Void call() { + public @Nullable Void call() { cancellationSuccess.set(currentFuture.get().cancel(true)); awaitUnchecked(barrier); return null; } }; - Callable setFutureCompleteSuccessfullyRunnable = - new Callable() { - final ListenableFuture future = Futures.immediateFuture("hello"); + Callable<@Nullable Void> setFutureCompleteSuccessfullyRunnable = + new Callable<@Nullable Void>() { + final ListenableFuture future = immediateFuture("hello"); @Override - public Void call() { + public @Nullable Void call() { setFutureSuccess.set(currentFuture.get().setFuture(future)); awaitUnchecked(barrier); return null; } }; - final Set finalResults = Collections.synchronizedSet(Sets.newIdentityHashSet()); - final Runnable collectResultsRunnable = + Set finalResults = Collections.synchronizedSet(Sets.newIdentityHashSet()); + Runnable collectResultsRunnable = new Runnable() { @Override public void run() { @@ -791,11 +797,11 @@ public void run() { List> allTasks = new ArrayList<>(); allTasks.add(cancelRunnable); allTasks.add(setFutureCompleteSuccessfullyRunnable); - allTasks.add(Executors.callable(collectResultsRunnable)); + allTasks.add(callable(collectResultsRunnable)); assertEquals(allTasks.size() + 1, barrier.getParties()); // sanity check for (int i = 0; i < 1000; i++) { Collections.shuffle(allTasks); - final AbstractFuture future = new AbstractFuture() {}; + AbstractFuture future = new AbstractFuture() {}; currentFuture.set(future); for (Callable task : allTasks) { @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored @@ -839,8 +845,9 @@ public void testSetFuture_stackOverflow() { // Verify that StackOverflowError in a long chain of SetFuture doesn't cause the entire toString // call to fail + @J2ktIncompatible @GwtIncompatible - @AndroidIncompatible + @AndroidIncompatible // b/391667564: crashes from stack overflows public void testSetFutureToString_stackOverflow() { SettableFuture orig = SettableFuture.create(); SettableFuture prev = orig; @@ -981,8 +988,9 @@ public String toString() { + " java.lang.NullPointerException]]"); } + @AndroidIncompatible // b/391667564: crashes from stack overflows public void testSetIndirectSelf_toString() { - final SettableFuture orig = SettableFuture.create(); + SettableFuture orig = SettableFuture.create(); // unlike the above this indirection defeats the trivial cycle detection and causes a SOE orig.setFuture( new ForwardingListenableFuture() { @@ -1002,7 +1010,7 @@ public void testListenersExecuteImmediately_fromAfterDone() { new AbstractFuture() { @Override protected void afterDone() { - final AtomicBoolean ranImmediately = new AtomicBoolean(); + AtomicBoolean ranImmediately = new AtomicBoolean(); addListener( new Runnable() { @Override @@ -1010,7 +1018,7 @@ public void run() { ranImmediately.set(true); } }, - MoreExecutors.directExecutor()); + directExecutor()); assertThat(ranImmediately.get()).isTrue(); } }; @@ -1020,13 +1028,13 @@ public void run() { // Regression test for a case where we would fail to execute listeners immediately on done futures // this would be observable from a waiter that was just unblocked. public void testListenersExecuteImmediately_afterWaiterWakesUp() throws Exception { - final AbstractFuture f = + AbstractFuture f = new AbstractFuture() { @Override protected void afterDone() { // this simply delays executing listeners try { - Thread.sleep(TimeUnit.SECONDS.toMillis(10)); + Thread.sleep(SECONDS.toMillis(10)); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); // preserve status } @@ -1041,7 +1049,7 @@ public void run() { }; t.start(); f.get(); - final AtomicBoolean ranImmediately = new AtomicBoolean(); + AtomicBoolean ranImmediately = new AtomicBoolean(); f.addListener( new Runnable() { @Override @@ -1049,57 +1057,65 @@ public void run() { ranImmediately.set(true); } }, - MoreExecutors.directExecutor()); + directExecutor()); assertThat(ranImmediately.get()).isTrue(); t.interrupt(); t.join(); } - public void testTrustedGetFailure_Completed() { + public void testCatchesUndeclaredThrowableFromListener() { + AbstractFuture f = new AbstractFuture() {}; + f.set("foo"); + f.addListener(() -> sneakyThrow(new SomeCheckedException()), directExecutor()); + } + + private static final class SomeCheckedException extends Exception {} + + public void testTrustedGetFailure_completed() { SettableFuture future = SettableFuture.create(); future.set("261"); assertThat(future.tryInternalFastPathGetFailure()).isNull(); } - public void testTrustedGetFailure_Failed() { + public void testTrustedGetFailure_failed() { SettableFuture future = SettableFuture.create(); Throwable failure = new Throwable(); future.setException(failure); assertThat(future.tryInternalFastPathGetFailure()).isEqualTo(failure); } - public void testTrustedGetFailure_NotCompleted() { + public void testTrustedGetFailure_notCompleted() { SettableFuture future = SettableFuture.create(); assertThat(future.isDone()).isFalse(); assertThat(future.tryInternalFastPathGetFailure()).isNull(); } - public void testTrustedGetFailure_CanceledNoCause() { + public void testTrustedGetFailure_canceledNoCause() { SettableFuture future = SettableFuture.create(); future.cancel(false); assertThat(future.tryInternalFastPathGetFailure()).isNull(); } - public void testGetFailure_Completed() { + public void testGetFailure_completed() { AbstractFuture future = new AbstractFuture() {}; future.set("261"); assertThat(future.tryInternalFastPathGetFailure()).isNull(); } - public void testGetFailure_Failed() { + public void testGetFailure_failed() { AbstractFuture future = new AbstractFuture() {}; - final Throwable failure = new Throwable(); + Throwable failure = new Throwable(); future.setException(failure); assertThat(future.tryInternalFastPathGetFailure()).isNull(); } - public void testGetFailure_NotCompleted() { + public void testGetFailure_notCompleted() { AbstractFuture future = new AbstractFuture() {}; assertThat(future.isDone()).isFalse(); assertThat(future.tryInternalFastPathGetFailure()).isNull(); } - public void testGetFailure_CanceledNoCause() { + public void testGetFailure_canceledNoCause() { AbstractFuture future = new AbstractFuture() {}; future.cancel(false); assertThat(future.tryInternalFastPathGetFailure()).isNull(); @@ -1107,7 +1123,7 @@ public void testGetFailure_CanceledNoCause() { public void testForwardExceptionFastPath() throws Exception { class FailFuture extends InternalFutureFailureAccess implements ListenableFuture { - Throwable failure; + final Throwable failure; FailFuture(Throwable throwable) { failure = throwable; @@ -1150,19 +1166,15 @@ public void addListener(Runnable listener, Executor executor) { } } - final RuntimeException exception = new RuntimeException("you still didn't say the magic word!"); + RuntimeException exception = new RuntimeException("you still didn't say the magic word!"); SettableFuture normalFuture = SettableFuture.create(); normalFuture.setFuture(new FailFuture(exception)); assertTrue(normalFuture.isDone()); - try { - normalFuture.get(); - fail(); - } catch (ExecutionException e) { - assertSame(exception, e.getCause()); - } + ExecutionException e = assertThrows(ExecutionException.class, () -> normalFuture.get()); + assertSame(exception, e.getCause()); } - private static void awaitUnchecked(final CyclicBarrier barrier) { + private static void awaitUnchecked(CyclicBarrier barrier) { try { barrier.await(); } catch (Exception e) { @@ -1189,24 +1201,18 @@ private static int findStackFrame(ExecutionException e, String clazz, String met return i; } } - AssertionFailedError failure = - new AssertionFailedError( - "Expected element " + clazz + "." + method + " not found in stack trace"); - failure.initCause(e); - throw failure; + throw new AssertionError( + "Expected element " + clazz + "." + method + " not found in stack trace", e); } private ExecutionException getExpectingExecutionException(AbstractFuture future) throws InterruptedException { try { String got = future.get(); - fail("Expected exception but got " + got); + throw new AssertionError("Expected exception but got " + got); } catch (ExecutionException e) { return e; } - - // unreachable, but compiler doesn't know that fail() always throws - return null; } private static final class WaiterThread extends Thread { @@ -1225,6 +1231,7 @@ public void run() { } } + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. void awaitWaiting() { while (!isBlocked()) { if (getState() == State.TERMINATED) { @@ -1266,6 +1273,7 @@ public void run() { } } + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. void awaitWaiting() { while (!isBlocked()) { if (getState() == State.TERMINATED) { @@ -1292,7 +1300,7 @@ private PollingThread(AbstractFuture future) { public void run() { while (true) { try { - future.get(0, TimeUnit.SECONDS); + future.get(0, SECONDS); return; } catch (InterruptedException | ExecutionException e) { return; @@ -1318,4 +1326,8 @@ protected void interruptTask() { interruptTaskWasCalled = true; } } + + private static boolean isWindows() { + return OS_NAME.value().startsWith("Windows"); + } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractIdleServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractIdleServiceTest.java index 7cad8b0fa712..fa5fb5e0e0e1 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractIdleServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractIdleServiceTest.java @@ -18,13 +18,15 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertThrows; -import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link AbstractIdleService}. @@ -32,67 +34,8 @@ * @author Chris Nokleberg * @author Ben Yu */ +@NullUnmarked public class AbstractIdleServiceTest extends TestCase { - - // Functional tests using real thread. We only verify publicly visible state. - // Interaction assertions are done by the single-threaded unit tests. - - public static class FunctionalTest extends TestCase { - - private static class DefaultService extends AbstractIdleService { - @Override - protected void startUp() throws Exception {} - - @Override - protected void shutDown() throws Exception {} - } - - public void testServiceStartStop() throws Exception { - AbstractIdleService service = new DefaultService(); - service.startAsync().awaitRunning(); - assertEquals(Service.State.RUNNING, service.state()); - service.stopAsync().awaitTerminated(); - assertEquals(Service.State.TERMINATED, service.state()); - } - - public void testStart_failed() throws Exception { - final Exception exception = new Exception("deliberate"); - AbstractIdleService service = - new DefaultService() { - @Override - protected void startUp() throws Exception { - throw exception; - } - }; - try { - service.startAsync().awaitRunning(); - fail(); - } catch (RuntimeException e) { - assertThat(e).hasCauseThat().isSameInstanceAs(exception); - } - assertEquals(Service.State.FAILED, service.state()); - } - - public void testStop_failed() throws Exception { - final Exception exception = new Exception("deliberate"); - AbstractIdleService service = - new DefaultService() { - @Override - protected void shutDown() throws Exception { - throw exception; - } - }; - service.startAsync().awaitRunning(); - try { - service.stopAsync().awaitTerminated(); - fail(); - } catch (RuntimeException e) { - assertThat(e).hasCauseThat().isSameInstanceAs(exception); - } - assertEquals(Service.State.FAILED, service.state()); - } - } - public void testStart() { TestService service = new TestService(); assertEquals(0, service.startUpCalled); @@ -103,7 +46,7 @@ public void testStart() { } public void testStart_failed() { - final Exception exception = new Exception("deliberate"); + Exception exception = new Exception("deliberate"); TestService service = new TestService() { @Override @@ -113,12 +56,9 @@ protected void startUp() throws Exception { } }; assertEquals(0, service.startUpCalled); - try { - service.startAsync().awaitRunning(); - fail(); - } catch (RuntimeException e) { - assertThat(e).hasCauseThat().isSameInstanceAs(exception); - } + RuntimeException e = + assertThrows(RuntimeException.class, () -> service.startAsync().awaitRunning()); + assertThat(e).hasCauseThat().isSameInstanceAs(exception); assertEquals(1, service.startUpCalled); assertEquals(Service.State.FAILED, service.state()); assertThat(service.transitionStates).containsExactly(Service.State.STARTING); @@ -148,7 +88,7 @@ public void testStop_afterStart() { } public void testStop_failed() { - final Exception exception = new Exception("deliberate"); + Exception exception = new Exception("deliberate"); TestService service = new TestService() { @Override @@ -160,12 +100,9 @@ protected void shutDown() throws Exception { service.startAsync().awaitRunning(); assertEquals(1, service.startUpCalled); assertEquals(0, service.shutDownCalled); - try { - service.stopAsync().awaitTerminated(); - fail(); - } catch (RuntimeException e) { - assertThat(e).hasCauseThat().isSameInstanceAs(exception); - } + RuntimeException e = + assertThrows(RuntimeException.class, () -> service.stopAsync().awaitTerminated()); + assertThat(e).hasCauseThat().isSameInstanceAs(exception); assertEquals(1, service.startUpCalled); assertEquals(1, service.shutDownCalled); assertEquals(Service.State.FAILED, service.state()); @@ -200,20 +137,18 @@ protected String serviceName() { return "Foo"; } }; - try { - service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS); - fail("Expected timeout"); - } catch (TimeoutException e) { - assertThat(e) - .hasMessageThat() - .isEqualTo("Timed out waiting for Foo [STARTING] to reach the RUNNING state."); - } + TimeoutException e = + assertThrows( + TimeoutException.class, () -> service.startAsync().awaitRunning(1, MILLISECONDS)); + assertThat(e) + .hasMessageThat() + .isEqualTo("Timed out waiting for Foo [STARTING] to reach the RUNNING state."); } private static class TestService extends AbstractIdleService { int startUpCalled = 0; int shutDownCalled = 0; - final List transitionStates = Lists.newArrayList(); + final List transitionStates = new ArrayList<>(); @Override protected void startUp() throws Exception { @@ -237,4 +172,54 @@ protected Executor executor() { return directExecutor(); } } + + // Functional tests using real thread. We only verify publicly visible state. + // Interaction assertions are done by the single-threaded unit tests. + + private static class DefaultService extends AbstractIdleService { + @Override + protected void startUp() throws Exception {} + + @Override + protected void shutDown() throws Exception {} + } + + public void testFunctionalServiceStartStop() { + AbstractIdleService service = new DefaultService(); + service.startAsync().awaitRunning(); + assertEquals(Service.State.RUNNING, service.state()); + service.stopAsync().awaitTerminated(); + assertEquals(Service.State.TERMINATED, service.state()); + } + + public void testFunctionalStart_failed() { + Exception exception = new Exception("deliberate"); + AbstractIdleService service = + new DefaultService() { + @Override + protected void startUp() throws Exception { + throw exception; + } + }; + RuntimeException e = + assertThrows(RuntimeException.class, () -> service.startAsync().awaitRunning()); + assertThat(e).hasCauseThat().isSameInstanceAs(exception); + assertEquals(Service.State.FAILED, service.state()); + } + + public void testFunctionalStop_failed() { + Exception exception = new Exception("deliberate"); + AbstractIdleService service = + new DefaultService() { + @Override + protected void shutDown() throws Exception { + throw exception; + } + }; + service.startAsync().awaitRunning(); + RuntimeException e = + assertThrows(RuntimeException.class, () -> service.stopAsync().awaitTerminated()); + assertThat(e).hasCauseThat().isSameInstanceAs(exception); + assertEquals(Service.State.FAILED, service.state()); + } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractListeningExecutorServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractListeningExecutorServiceTest.java index 139581f7c1dd..bd2c95b0e520 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractListeningExecutorServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractListeningExecutorServiceTest.java @@ -23,12 +23,14 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link AbstractListeningExecutorService}. * * @author Colin Decker */ +@NullUnmarked public class AbstractListeningExecutorServiceTest extends TestCase { public void testSubmit() throws Exception { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractScheduledServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractScheduledServiceTest.java index 158520b932ed..3093854a830d 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractScheduledServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractScheduledServiceTest.java @@ -19,8 +19,13 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.AbstractScheduledService.Scheduler.newFixedDelaySchedule; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.concurrent.Executors.newScheduledThreadPool; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; +import com.google.common.util.concurrent.AbstractScheduledService.Cancellable; import com.google.common.util.concurrent.AbstractScheduledService.Scheduler; import com.google.common.util.concurrent.Service.State; import com.google.common.util.concurrent.testing.TestingExecutors; @@ -28,7 +33,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.Executors; +import java.util.concurrent.Delayed; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -39,17 +44,19 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit test for {@link AbstractScheduledService}. * * @author Luke Sandberg */ - +@NullUnmarked public class AbstractScheduledServiceTest extends TestCase { - volatile Scheduler configuration = newFixedDelaySchedule(0, 10, TimeUnit.MILLISECONDS); - volatile ScheduledFuture future = null; + volatile Scheduler configuration = newFixedDelaySchedule(0, 10, MILLISECONDS); + volatile @Nullable ScheduledFuture future = null; volatile boolean atFixedRateCalled = false; volatile boolean withFixedDelayCalled = false; @@ -93,11 +100,7 @@ public void testFailOnExceptionFromRun() throws Exception { service.startAsync().awaitRunning(); service.runFirstBarrier.await(); service.runSecondBarrier.await(); - try { - future.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> future.get()); // An execution exception holds a runtime exception (from throwables.propagate) that holds our // original exception. assertEquals(service.runException, service.failureCause()); @@ -107,19 +110,16 @@ public void testFailOnExceptionFromRun() throws Exception { public void testFailOnExceptionFromStartUp() { TestService service = new TestService(); service.startUpException = new Exception(); - try { - service.startAsync().awaitRunning(); - fail(); - } catch (IllegalStateException e) { - assertEquals(service.startUpException, e.getCause()); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.startAsync().awaitRunning()); + assertThat(e).hasCauseThat().isEqualTo(service.startUpException); assertEquals(0, service.numberOfTimesRunCalled.get()); assertEquals(Service.State.FAILED, service.state()); } public void testFailOnErrorFromStartUpListener() throws InterruptedException { - final Error error = new Error(); - final CountDownLatch latch = new CountDownLatch(1); + Error error = new Error(); + CountDownLatch latch = new CountDownLatch(1); TestService service = new TestService(); service.addListener( new Service.Listener() { @@ -150,12 +150,9 @@ public void testFailOnExceptionFromShutDown() throws Exception { service.runFirstBarrier.await(); service.stopAsync(); service.runSecondBarrier.await(); - try { - service.awaitTerminated(); - fail(); - } catch (IllegalStateException e) { - assertEquals(service.shutDownException, e.getCause()); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.awaitTerminated()); + assertThat(e).hasCauseThat().isEqualTo(service.shutDownException); assertEquals(Service.State.FAILED, service.state()); } @@ -192,7 +189,7 @@ public void testExecutorOnlyCalledOnce() throws Exception { } public void testDefaultExecutorIsShutdownWhenServiceIsStopped() throws Exception { - final AtomicReference executor = Atomics.newReference(); + AtomicReference executor = Atomics.newReference(); AbstractScheduledService service = new AbstractScheduledService() { @Override @@ -206,7 +203,7 @@ protected ScheduledExecutorService executor() { @Override protected Scheduler scheduler() { - return newFixedDelaySchedule(0, 1, TimeUnit.MILLISECONDS); + return newFixedDelaySchedule(0, 1, MILLISECONDS); } }; @@ -215,11 +212,11 @@ protected Scheduler scheduler() { service.awaitRunning(); service.stopAsync(); service.awaitTerminated(); - assertTrue(executor.get().awaitTermination(100, TimeUnit.MILLISECONDS)); + assertTrue(executor.get().awaitTermination(100, MILLISECONDS)); } public void testDefaultExecutorIsShutdownWhenServiceFails() throws Exception { - final AtomicReference executor = Atomics.newReference(); + AtomicReference executor = Atomics.newReference(); AbstractScheduledService service = new AbstractScheduledService() { @Override @@ -238,17 +235,13 @@ protected ScheduledExecutorService executor() { @Override protected Scheduler scheduler() { - return newFixedDelaySchedule(0, 1, TimeUnit.MILLISECONDS); + return newFixedDelaySchedule(0, 1, MILLISECONDS); } }; - try { - service.startAsync().awaitRunning(); - fail("Expected service to fail during startup"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> service.startAsync().awaitRunning()); - assertTrue(executor.get().awaitTermination(100, TimeUnit.MILLISECONDS)); + assertTrue(executor.get().awaitTermination(100, MILLISECONDS)); } public void testSchedulerOnlyCalledOnce() throws Exception { @@ -275,7 +268,7 @@ public void testTimeout() { new AbstractScheduledService() { @Override protected Scheduler scheduler() { - return Scheduler.newFixedDelaySchedule(0, 1, TimeUnit.NANOSECONDS); + return Scheduler.newFixedDelaySchedule(0, 1, NANOSECONDS); } @Override @@ -291,28 +284,26 @@ protected String serviceName() { return "Foo"; } }; - try { - service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS); - fail("Expected timeout"); - } catch (TimeoutException e) { - assertThat(e) - .hasMessageThat() - .isEqualTo("Timed out waiting for Foo [STARTING] to reach the RUNNING state."); - } + TimeoutException e = + assertThrows( + TimeoutException.class, () -> service.startAsync().awaitRunning(1, MILLISECONDS)); + assertThat(e) + .hasMessageThat() + .isEqualTo("Timed out waiting for Foo [STARTING] to reach the RUNNING state."); } private class TestService extends AbstractScheduledService { - CyclicBarrier runFirstBarrier = new CyclicBarrier(2); - CyclicBarrier runSecondBarrier = new CyclicBarrier(2); + final CyclicBarrier runFirstBarrier = new CyclicBarrier(2); + final CyclicBarrier runSecondBarrier = new CyclicBarrier(2); volatile boolean startUpCalled = false; volatile boolean shutDownCalled = false; - AtomicInteger numberOfTimesRunCalled = new AtomicInteger(0); - AtomicInteger numberOfTimesExecutorCalled = new AtomicInteger(0); - AtomicInteger numberOfTimesSchedulerCalled = new AtomicInteger(0); - volatile Exception runException = null; - volatile Exception startUpException = null; - volatile Exception shutDownException = null; + final AtomicInteger numberOfTimesRunCalled = new AtomicInteger(0); + final AtomicInteger numberOfTimesExecutorCalled = new AtomicInteger(0); + final AtomicInteger numberOfTimesSchedulerCalled = new AtomicInteger(0); + volatile @Nullable Exception runException = null; + volatile @Nullable Exception startUpException = null; + volatile @Nullable Exception shutDownException = null; @Override protected void runOneIteration() throws Exception { @@ -361,306 +352,308 @@ protected Scheduler scheduler() { } } - public static class SchedulerTest extends TestCase { - // These constants are arbitrary and just used to make sure that the correct method is called - // with the correct parameters. - private static final int initialDelay = 10; - private static final int delay = 20; - private static final TimeUnit unit = TimeUnit.MILLISECONDS; + // Tests for Scheduler: - // Unique runnable object used for comparison. - final Runnable testRunnable = - new Runnable() { - @Override - public void run() {} - }; - boolean called = false; - - private void assertSingleCallWithCorrectParameters( - Runnable command, long initialDelay, long delay, TimeUnit unit) { - assertFalse(called); // only called once. - called = true; - assertEquals(SchedulerTest.initialDelay, initialDelay); - assertEquals(SchedulerTest.delay, delay); - assertEquals(SchedulerTest.unit, unit); - assertEquals(testRunnable, command); - } + // These constants are arbitrary and just used to make sure that the correct method is called + // with the correct parameters. + private static final int INITIAL_DELAY = 10; + private static final int DELAY = 20; + private static final TimeUnit UNIT = MILLISECONDS; - public void testFixedRateSchedule() { - Scheduler schedule = Scheduler.newFixedRateSchedule(initialDelay, delay, unit); - Future unused = - schedule.schedule( - null, - new ScheduledThreadPoolExecutor(1) { - @Override - public ScheduledFuture scheduleAtFixedRate( - Runnable command, long initialDelay, long period, TimeUnit unit) { - assertSingleCallWithCorrectParameters(command, initialDelay, delay, unit); - return null; - } - }, - testRunnable); - assertTrue(called); - } + // Unique runnable object used for comparison. + final Runnable testRunnable = + new Runnable() { + @Override + public void run() {} + }; + boolean called = false; + + private void assertSingleCallWithCorrectParameters( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + assertFalse(called); // only called once. + called = true; + assertEquals(INITIAL_DELAY, initialDelay); + assertEquals(DELAY, delay); + assertEquals(UNIT, unit); + assertEquals(testRunnable, command); + } - public void testFixedDelaySchedule() { - Scheduler schedule = newFixedDelaySchedule(initialDelay, delay, unit); - Future unused = - schedule.schedule( - null, - new ScheduledThreadPoolExecutor(10) { - @Override - public ScheduledFuture scheduleWithFixedDelay( - Runnable command, long initialDelay, long delay, TimeUnit unit) { - assertSingleCallWithCorrectParameters(command, initialDelay, delay, unit); - return null; - } - }, - testRunnable); - assertTrue(called); - } + public void testFixedRateSchedule() { + Scheduler schedule = Scheduler.newFixedRateSchedule(INITIAL_DELAY, DELAY, UNIT); + Cancellable unused = + schedule.schedule( + null, + new ScheduledThreadPoolExecutor(1) { + @Override + public ScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + assertSingleCallWithCorrectParameters(command, initialDelay, period, unit); + return new ThrowingScheduledFuture<>(); + } + }, + testRunnable); + assertTrue(called); + } + public void testFixedDelaySchedule() { + Scheduler schedule = newFixedDelaySchedule(INITIAL_DELAY, DELAY, UNIT); + Cancellable unused = + schedule.schedule( + null, + new ScheduledThreadPoolExecutor(10) { + @Override + public ScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + assertSingleCallWithCorrectParameters(command, initialDelay, delay, unit); + return new ThrowingScheduledFuture<>(); + } + }, + testRunnable); + assertTrue(called); + } - public void testFixedDelayScheduleFarFuturePotentiallyOverflowingScheduleIsNeverReached() - throws Exception { - TestAbstractScheduledCustomService service = - new TestAbstractScheduledCustomService() { - @Override - protected Scheduler scheduler() { - return newFixedDelaySchedule(Long.MAX_VALUE, Long.MAX_VALUE, SECONDS); - } - }; - service.startAsync().awaitRunning(); - try { - service.firstBarrier.await(5, SECONDS); - fail(); - } catch (TimeoutException expected) { - } - assertEquals(0, service.numIterations.get()); - service.stopAsync(); - service.awaitTerminated(); + private static final class ThrowingScheduledFuture extends ForwardingFuture + implements ScheduledFuture { + @Override + protected Future delegate() { + throw new UnsupportedOperationException("test should not care about this"); } - - public void testCustomSchedulerFarFuturePotentiallyOverflowingScheduleIsNeverReached() - throws Exception { - TestAbstractScheduledCustomService service = - new TestAbstractScheduledCustomService() { - @Override - protected Scheduler scheduler() { - return new AbstractScheduledService.CustomScheduler() { - @Override - protected Schedule getNextSchedule() throws Exception { - return new Schedule(Long.MAX_VALUE, SECONDS); - } - }; - } - }; - service.startAsync().awaitRunning(); - try { - service.firstBarrier.await(5, SECONDS); - fail(); - } catch (TimeoutException expected) { - } - assertEquals(0, service.numIterations.get()); - service.stopAsync(); - service.awaitTerminated(); + @Override + public long getDelay(TimeUnit unit) { + throw new UnsupportedOperationException("test should not care about this"); } - private class TestCustomScheduler extends AbstractScheduledService.CustomScheduler { - public AtomicInteger scheduleCounter = new AtomicInteger(0); - - @Override - protected Schedule getNextSchedule() throws Exception { - scheduleCounter.incrementAndGet(); - return new Schedule(0, TimeUnit.SECONDS); - } + @Override + public int compareTo(Delayed other) { + throw new UnsupportedOperationException("test should not care about this"); } + } + public void testFixedDelayScheduleFarFuturePotentiallyOverflowingScheduleIsNeverReached() + throws Exception { + TestAbstractScheduledCustomService service = + new TestAbstractScheduledCustomService() { + @Override + protected Scheduler scheduler() { + return newFixedDelaySchedule(Long.MAX_VALUE, Long.MAX_VALUE, SECONDS); + } + }; + service.startAsync().awaitRunning(); + assertThrows(TimeoutException.class, () -> service.firstBarrier.await(5, SECONDS)); + assertEquals(0, service.numIterations.get()); + service.stopAsync(); + service.awaitTerminated(); + } - public void testCustomSchedule_startStop() throws Exception { - final CyclicBarrier firstBarrier = new CyclicBarrier(2); - final CyclicBarrier secondBarrier = new CyclicBarrier(2); - final AtomicBoolean shouldWait = new AtomicBoolean(true); - Runnable task = - new Runnable() { - @Override - public void run() { - try { - if (shouldWait.get()) { - firstBarrier.await(); - secondBarrier.await(); - } - } catch (Exception e) { - throw new RuntimeException(e); + public void testCustomSchedulerFarFuturePotentiallyOverflowingScheduleIsNeverReached() + throws Exception { + TestAbstractScheduledCustomService service = + new TestAbstractScheduledCustomService() { + @Override + protected Scheduler scheduler() { + return new AbstractScheduledService.CustomScheduler() { + @Override + protected Schedule getNextSchedule() throws Exception { + return new Schedule(Long.MAX_VALUE, SECONDS); } - } - }; - TestCustomScheduler scheduler = new TestCustomScheduler(); - Future future = scheduler.schedule(null, Executors.newScheduledThreadPool(10), task); - firstBarrier.await(); - assertEquals(1, scheduler.scheduleCounter.get()); - secondBarrier.await(); - firstBarrier.await(); - assertEquals(2, scheduler.scheduleCounter.get()); - shouldWait.set(false); - secondBarrier.await(); - future.cancel(false); - } + }; + } + }; + service.startAsync().awaitRunning(); + assertThrows(TimeoutException.class, () -> service.firstBarrier.await(5, SECONDS)); + assertEquals(0, service.numIterations.get()); + service.stopAsync(); + service.awaitTerminated(); + } + private static class TestCustomScheduler extends AbstractScheduledService.CustomScheduler { + private final AtomicInteger scheduleCounter = new AtomicInteger(0); - public void testCustomSchedulerServiceStop() throws Exception { - TestAbstractScheduledCustomService service = new TestAbstractScheduledCustomService(); - service.startAsync().awaitRunning(); - service.firstBarrier.await(); - assertEquals(1, service.numIterations.get()); - service.stopAsync(); - service.secondBarrier.await(); - service.awaitTerminated(); - // Sleep for a while just to ensure that our task wasn't called again. - Thread.sleep(unit.toMillis(3 * delay)); - assertEquals(1, service.numIterations.get()); + @Override + protected Schedule getNextSchedule() throws Exception { + scheduleCounter.incrementAndGet(); + return new Schedule(0, SECONDS); } + } - - public void testCustomScheduler_deadlock() throws InterruptedException, BrokenBarrierException { - final CyclicBarrier inGetNextSchedule = new CyclicBarrier(2); - // This will flakily deadlock, so run it multiple times to increase the flake likelihood - for (int i = 0; i < 1000; i++) { - Service service = - new AbstractScheduledService() { - @Override - protected void runOneIteration() {} - - @Override - protected Scheduler scheduler() { - return new CustomScheduler() { - @Override - protected Schedule getNextSchedule() throws Exception { - if (state() != State.STARTING) { - inGetNextSchedule.await(); - Thread.yield(); - throw new RuntimeException("boom"); - } - return new Schedule(0, TimeUnit.NANOSECONDS); - } - }; + public void testCustomSchedule_startStop() throws Exception { + CyclicBarrier firstBarrier = new CyclicBarrier(2); + CyclicBarrier secondBarrier = new CyclicBarrier(2); + AtomicBoolean shouldWait = new AtomicBoolean(true); + Runnable task = + new Runnable() { + @Override + public void run() { + try { + if (shouldWait.get()) { + firstBarrier.await(); + secondBarrier.await(); } - }; - service.startAsync().awaitRunning(); - inGetNextSchedule.await(); - service.stopAsync(); - } - } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + TestCustomScheduler scheduler = new TestCustomScheduler(); + Cancellable future = scheduler.schedule(null, newScheduledThreadPool(10), task); + firstBarrier.await(); + assertEquals(1, scheduler.scheduleCounter.get()); + secondBarrier.await(); + firstBarrier.await(); + assertEquals(2, scheduler.scheduleCounter.get()); + shouldWait.set(false); + secondBarrier.await(); + future.cancel(false); + } + + public void testCustomSchedulerServiceStop() throws Exception { + TestAbstractScheduledCustomService service = new TestAbstractScheduledCustomService(); + service.startAsync().awaitRunning(); + service.firstBarrier.await(); + assertEquals(1, service.numIterations.get()); + service.stopAsync(); + service.secondBarrier.await(); + service.awaitTerminated(); + // Sleep for a while just to ensure that our task wasn't called again. + Thread.sleep(UNIT.toMillis(3 * DELAY)); + assertEquals(1, service.numIterations.get()); + } + public void testCustomScheduler_deadlock() throws InterruptedException, BrokenBarrierException { + CyclicBarrier inGetNextSchedule = new CyclicBarrier(2); + // This will flakily deadlock, so run it multiple times to increase the flake likelihood + for (int i = 0; i < 1000; i++) { + Service service = + new AbstractScheduledService() { + @Override + protected void runOneIteration() {} - public void testBig() throws Exception { - TestAbstractScheduledCustomService service = - new TestAbstractScheduledCustomService() { @Override protected Scheduler scheduler() { - return new AbstractScheduledService.CustomScheduler() { + return new CustomScheduler() { @Override + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races protected Schedule getNextSchedule() throws Exception { - // Explicitly yield to increase the probability of a pathological scheduling. - Thread.yield(); - return new Schedule(0, TimeUnit.SECONDS); + if (state() != State.STARTING) { + inGetNextSchedule.await(); + Thread.yield(); + throw new RuntimeException("boom"); + } + return new Schedule(0, NANOSECONDS); } }; } }; - service.useBarriers = false; service.startAsync().awaitRunning(); - Thread.sleep(50); - service.useBarriers = true; - service.firstBarrier.await(); - int numIterations = service.numIterations.get(); + inGetNextSchedule.await(); service.stopAsync(); - service.secondBarrier.await(); - service.awaitTerminated(); - assertEquals(numIterations, service.numIterations.get()); } + } - private static class TestAbstractScheduledCustomService extends AbstractScheduledService { - final AtomicInteger numIterations = new AtomicInteger(0); - volatile boolean useBarriers = true; - final CyclicBarrier firstBarrier = new CyclicBarrier(2); - final CyclicBarrier secondBarrier = new CyclicBarrier(2); - - @Override - protected void runOneIteration() throws Exception { - numIterations.incrementAndGet(); - if (useBarriers) { - firstBarrier.await(); - secondBarrier.await(); - } - } - - @Override - protected ScheduledExecutorService executor() { - // use a bunch of threads so that weird overlapping schedules are more likely to happen. - return Executors.newScheduledThreadPool(10); - } - - @Override - protected Scheduler scheduler() { - return new CustomScheduler() { + public void testBig() throws Exception { + TestAbstractScheduledCustomService service = + new TestAbstractScheduledCustomService() { @Override - protected Schedule getNextSchedule() throws Exception { - return new Schedule(delay, unit); + protected Scheduler scheduler() { + return new AbstractScheduledService.CustomScheduler() { + @Override + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races + protected Schedule getNextSchedule() throws Exception { + // Explicitly yield to increase the probability of a pathological scheduling. + Thread.yield(); + return new Schedule(0, SECONDS); + } + }; } }; + service.useBarriers = false; + service.startAsync().awaitRunning(); + Thread.sleep(50); + service.useBarriers = true; + service.firstBarrier.await(); + int numIterations = service.numIterations.get(); + service.stopAsync(); + service.secondBarrier.await(); + service.awaitTerminated(); + assertEquals(numIterations, service.numIterations.get()); + } + + private static class TestAbstractScheduledCustomService extends AbstractScheduledService { + final AtomicInteger numIterations = new AtomicInteger(0); + volatile boolean useBarriers = true; + final CyclicBarrier firstBarrier = new CyclicBarrier(2); + final CyclicBarrier secondBarrier = new CyclicBarrier(2); + + @Override + protected void runOneIteration() throws Exception { + numIterations.incrementAndGet(); + if (useBarriers) { + firstBarrier.await(); + secondBarrier.await(); } } + @Override + protected ScheduledExecutorService executor() { + // use a bunch of threads so that weird overlapping schedules are more likely to happen. + return newScheduledThreadPool(10); + } - public void testCustomSchedulerFailure() throws Exception { - TestFailingCustomScheduledService service = new TestFailingCustomScheduledService(); - service.startAsync().awaitRunning(); - for (int i = 1; i < 4; i++) { - service.firstBarrier.await(); - assertEquals(i, service.numIterations.get()); - service.secondBarrier.await(); - } - Thread.sleep(1000); - try { - service.stopAsync().awaitTerminated(100, TimeUnit.SECONDS); - fail(); - } catch (IllegalStateException e) { - assertEquals(State.FAILED, service.state()); - } + @Override + protected Scheduler scheduler() { + return new CustomScheduler() { + @Override + protected Schedule getNextSchedule() throws Exception { + return new Schedule(DELAY, UNIT); + } + }; } + } - private static class TestFailingCustomScheduledService extends AbstractScheduledService { - final AtomicInteger numIterations = new AtomicInteger(0); - final CyclicBarrier firstBarrier = new CyclicBarrier(2); - final CyclicBarrier secondBarrier = new CyclicBarrier(2); + public void testCustomSchedulerFailure() throws Exception { + TestFailingCustomScheduledService service = new TestFailingCustomScheduledService(); + service.startAsync().awaitRunning(); + for (int i = 1; i < 4; i++) { + service.firstBarrier.await(); + assertEquals(i, service.numIterations.get()); + service.secondBarrier.await(); + } + Thread.sleep(1000); + assertThrows( + IllegalStateException.class, () -> service.stopAsync().awaitTerminated(100, SECONDS)); + assertEquals(State.FAILED, service.state()); + } - @Override - protected void runOneIteration() throws Exception { - numIterations.incrementAndGet(); - firstBarrier.await(); - secondBarrier.await(); - } + private static class TestFailingCustomScheduledService extends AbstractScheduledService { + final AtomicInteger numIterations = new AtomicInteger(0); + final CyclicBarrier firstBarrier = new CyclicBarrier(2); + final CyclicBarrier secondBarrier = new CyclicBarrier(2); - @Override - protected ScheduledExecutorService executor() { - // use a bunch of threads so that weird overlapping schedules are more likely to happen. - return Executors.newScheduledThreadPool(10); - } + @Override + protected void runOneIteration() throws Exception { + numIterations.incrementAndGet(); + firstBarrier.await(); + secondBarrier.await(); + } - @Override - protected Scheduler scheduler() { - return new CustomScheduler() { - @Override - protected Schedule getNextSchedule() throws Exception { - if (numIterations.get() > 2) { - throw new IllegalStateException("Failed"); - } - return new Schedule(delay, unit); + @Override + protected ScheduledExecutorService executor() { + // use a bunch of threads so that weird overlapping schedules are more likely to happen. + return newScheduledThreadPool(10); + } + + @Override + protected Scheduler scheduler() { + return new CustomScheduler() { + @Override + protected Schedule getNextSchedule() throws Exception { + if (numIterations.get() > 2) { + throw new IllegalStateException("Failed"); } - }; - } + return new Schedule(DELAY, UNIT); + } + }; } } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractServiceTest.java index e3c22a8673df..72997c759765 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractServiceTest.java @@ -19,27 +19,30 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.lang.Thread.currentThread; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.Service.Listener; import com.google.common.util.concurrent.Service.State; import com.google.errorprone.annotations.concurrent.GuardedBy; import java.lang.Thread.UncaughtExceptionHandler; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link AbstractService}. * * @author Jesse Wilson */ +@NullUnmarked public class AbstractServiceTest extends TestCase { private static final long LONG_TIMEOUT_MILLIS = 10000; @@ -234,7 +237,7 @@ public void testManualServiceStopWhileStarting() throws Exception { */ public void testManualServiceStopMultipleTimesWhileStarting() throws Exception { ManualSwitchedService service = new ManualSwitchedService(); - final AtomicInteger stoppingCount = new AtomicInteger(); + AtomicInteger stoppingCount = new AtomicInteger(); service.addListener( new Listener() { @Override @@ -330,9 +333,8 @@ protected void doStop() { } } - public void testAwaitTerminated() throws Exception { - final NoOpService service = new NoOpService(); + NoOpService service = new NoOpService(); Thread waiter = new Thread() { @Override @@ -348,10 +350,9 @@ public void run() { assertFalse(waiter.isAlive()); } - - public void testAwaitTerminated_FailedService() throws Exception { - final ManualSwitchedService service = new ManualSwitchedService(); - final AtomicReference exception = Atomics.newReference(); + public void testAwaitTerminated_failedService() throws Exception { + ManualSwitchedService service = new ManualSwitchedService(); + AtomicReference exception = Atomics.newReference(); Thread waiter = new Thread() { @Override @@ -376,7 +377,6 @@ public void run() { assertThat(exception.get()).hasCauseThat().isEqualTo(EXCEPTION); } - public void testThreadedServiceStartAndWaitStopAndWait() throws Throwable { ThreadedService service = new ThreadedService(); RecordingListener listener = RecordingListener.record(service); @@ -394,7 +394,6 @@ public void testThreadedServiceStartAndWaitStopAndWait() throws Throwable { listener.getStateHistory()); } - public void testThreadedServiceStopIdempotence() throws Throwable { ThreadedService service = new ThreadedService(); @@ -410,7 +409,6 @@ public void testThreadedServiceStopIdempotence() throws Throwable { throwIfSet(thrownByExecutionThread); } - public void testThreadedServiceStopIdempotenceAfterWait() throws Throwable { ThreadedService service = new ThreadedService(); @@ -428,7 +426,6 @@ public void testThreadedServiceStopIdempotenceAfterWait() throws Throwable { throwIfSet(thrownByExecutionThread); } - public void testThreadedServiceStopIdempotenceDoubleWait() throws Throwable { ThreadedService service = new ThreadedService(); @@ -455,12 +452,9 @@ public void testManualServiceFailureIdempotence() { service.notifyFailed(new Exception("1")); service.notifyFailed(new Exception("2")); assertThat(service.failureCause()).hasMessageThat().isEqualTo("1"); - try { - service.awaitRunning(); - fail(); - } catch (IllegalStateException e) { - assertThat(e).hasCauseThat().hasMessageThat().isEqualTo("1"); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.awaitRunning()); + assertThat(e).hasCauseThat().hasMessageThat().isEqualTo("1"); } private class ThreadedService extends AbstractService { @@ -537,11 +531,7 @@ public void testStopUnstartedService() throws Exception { service.stopAsync(); assertEquals(State.TERMINATED, service.state()); - try { - service.startAsync(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> service.startAsync()); assertEquals(State.TERMINATED, Iterables.getOnlyElement(listener.getStateHistory())); } @@ -549,13 +539,10 @@ public void testFailingServiceStartAndWait() throws Exception { StartFailingService service = new StartFailingService(); RecordingListener listener = RecordingListener.record(service); - try { - service.startAsync().awaitRunning(); - fail(); - } catch (IllegalStateException e) { - assertEquals(EXCEPTION, service.failureCause()); - assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.startAsync().awaitRunning()); + assertEquals(EXCEPTION, service.failureCause()); + assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory()); } @@ -564,13 +551,10 @@ public void testFailingServiceStopAndWait_stopFailing() throws Exception { RecordingListener listener = RecordingListener.record(service); service.startAsync().awaitRunning(); - try { - service.stopAsync().awaitTerminated(); - fail(); - } catch (IllegalStateException e) { - assertEquals(EXCEPTION, service.failureCause()); - assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.stopAsync().awaitTerminated()); + assertEquals(EXCEPTION, service.failureCause()); + assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); assertEquals( ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED), listener.getStateHistory()); @@ -581,13 +565,10 @@ public void testFailingServiceStopAndWait_runFailing() throws Exception { RecordingListener listener = RecordingListener.record(service); service.startAsync(); - try { - service.awaitRunning(); - fail(); - } catch (IllegalStateException e) { - assertEquals(EXCEPTION, service.failureCause()); - assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.awaitRunning()); + assertEquals(EXCEPTION, service.failureCause()); + assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); assertEquals( ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), listener.getStateHistory()); } @@ -596,13 +577,10 @@ public void testThrowingServiceStartAndWait() throws Exception { StartThrowingService service = new StartThrowingService(); RecordingListener listener = RecordingListener.record(service); - try { - service.startAsync().awaitRunning(); - fail(); - } catch (IllegalStateException e) { - assertEquals(service.exception, service.failureCause()); - assertThat(e).hasCauseThat().isEqualTo(service.exception); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.startAsync().awaitRunning()); + assertEquals(service.exception, service.failureCause()); + assertThat(e).hasCauseThat().isEqualTo(service.exception); assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory()); } @@ -611,13 +589,10 @@ public void testThrowingServiceStopAndWait_stopThrowing() throws Exception { RecordingListener listener = RecordingListener.record(service); service.startAsync().awaitRunning(); - try { - service.stopAsync().awaitTerminated(); - fail(); - } catch (IllegalStateException e) { - assertEquals(service.exception, service.failureCause()); - assertThat(e).hasCauseThat().isEqualTo(service.exception); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.stopAsync().awaitTerminated()); + assertEquals(service.exception, service.failureCause()); + assertThat(e).hasCauseThat().isEqualTo(service.exception); assertEquals( ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED), listener.getStateHistory()); @@ -628,42 +603,27 @@ public void testThrowingServiceStopAndWait_runThrowing() throws Exception { RecordingListener listener = RecordingListener.record(service); service.startAsync(); - try { - service.awaitTerminated(); - fail(); - } catch (IllegalStateException e) { - assertEquals(service.exception, service.failureCause()); - assertThat(e).hasCauseThat().isEqualTo(service.exception); - } + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.awaitTerminated()); + assertEquals(service.exception, service.failureCause()); + assertThat(e).hasCauseThat().isEqualTo(service.exception); assertEquals( ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), listener.getStateHistory()); } public void testFailureCause_throwsIfNotFailed() { StopFailingService service = new StopFailingService(); - try { - service.failureCause(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> service.failureCause()); service.startAsync().awaitRunning(); - try { - service.failureCause(); - fail(); - } catch (IllegalStateException expected) { - } - try { - service.stopAsync().awaitTerminated(); - fail(); - } catch (IllegalStateException e) { - assertEquals(EXCEPTION, service.failureCause()); - assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); - } + assertThrows(IllegalStateException.class, () -> service.failureCause()); + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> service.stopAsync().awaitTerminated()); + assertEquals(EXCEPTION, service.failureCause()); + assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); } - public void testAddListenerAfterFailureDoesntCauseDeadlock() throws InterruptedException { - final StartFailingService service = new StartFailingService(); + StartFailingService service = new StartFailingService(); service.startAsync(); assertEquals(State.FAILED, service.state()); service.addListener(new RecordingListener(service), directExecutor()); @@ -681,9 +641,8 @@ public void run() { assertFalse(thread + " is deadlocked", thread.isAlive()); } - public void testListenerDoesntDeadlockOnStartAndWaitFromRunning() throws Exception { - final NoOpThreadedService service = new NoOpThreadedService(); + NoOpThreadedService service = new NoOpThreadedService(); service.addListener( new Listener() { @Override @@ -692,13 +651,12 @@ public void running() { } }, directExecutor()); - service.startAsync().awaitRunning(LONG_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + service.startAsync().awaitRunning(LONG_TIMEOUT_MILLIS, MILLISECONDS); service.stopAsync(); } - public void testListenerDoesntDeadlockOnStopAndWaitFromTerminated() throws Exception { - final NoOpThreadedService service = new NoOpThreadedService(); + NoOpThreadedService service = new NoOpThreadedService(); service.addListener( new Listener() { @Override @@ -832,7 +790,7 @@ static RecordingListener record(Service service) { } @GuardedBy("this") - final List stateHistory = Lists.newArrayList(); + final List stateHistory = new ArrayList<>(); final CountDownLatch completionLatch = new CountDownLatch(1); @@ -921,40 +879,24 @@ public synchronized void failed(State from, Throwable failure) { public void testNotifyStartedWhenNotStarting() { AbstractService service = new DefaultService(); - try { - service.notifyStarted(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> service.notifyStarted()); } public void testNotifyStoppedWhenNotRunning() { AbstractService service = new DefaultService(); - try { - service.notifyStopped(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> service.notifyStopped()); } public void testNotifyFailedWhenNotStarted() { AbstractService service = new DefaultService(); - try { - service.notifyFailed(new Exception()); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> service.notifyFailed(new Exception())); } public void testNotifyFailedWhenTerminated() { NoOpService service = new NoOpService(); service.startAsync().awaitRunning(); service.stopAsync().awaitTerminated(); - try { - service.notifyFailed(new Exception()); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> service.notifyFailed(new Exception())); } private static class DefaultService extends AbstractService { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AggregateFutureStateDefaultAtomicHelperTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AggregateFutureStateDefaultAtomicHelperTest.java new file mode 100644 index 000000000000..7992ce0db756 --- /dev/null +++ b/android/guava-tests/test/com/google/common/util/concurrent/AggregateFutureStateDefaultAtomicHelperTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.truth.Truth.assertThat; + +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +/** + * Tests that {@link AggregateFutureState} uses the expected {@code AtomicHelper} implementation. + * + *

    We have more thorough testing of {@code AtomicHelper} implementations in {@link + * AggregateFutureStateFallbackAtomicHelperTest}. The advantage to this test is that it can run + * under Android. + */ +@NullUnmarked +public class AggregateFutureStateDefaultAtomicHelperTest extends TestCase { + public void testUsingExpectedAtomicHelper() throws Exception { + assertThat(AggregateFutureState.atomicHelperTypeForTest()).isEqualTo("SafeAtomicHelper"); + } +} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AggregateFutureStateFallbackAtomicHelperTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AggregateFutureStateFallbackAtomicHelperTest.java index fec93946f794..76144db03e2c 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AggregateFutureStateFallbackAtomicHelperTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AggregateFutureStateFallbackAtomicHelperTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; /** * Tests our AtomicHelper fallback strategy in AggregateFutureState. @@ -40,16 +41,18 @@ * * * To force selection of our fallback strategies we load {@link AggregateFutureState} (and all of - * {@code com.google.common.util.concurrent} in degenerate class loaders which make certain platform - * classes unavailable. Then we construct a test suite so we can run the normal FuturesTest test - * methods in these degenerate classloaders. + * {@code com.google.common.util.concurrent}) in degenerate class loaders which make certain + * platform classes unavailable. Then we construct a test suite so we can run the normal FuturesTest + * test methods in these degenerate classloaders. */ +@NullUnmarked public class AggregateFutureStateFallbackAtomicHelperTest extends TestCase { /** - * This classloader disallows AtomicReferenceFieldUpdater and AtomicIntegerFieldUpdate which will - * prevent us from selecting our {@code SafeAtomicHelper} strategy. + * This classloader disallows {@code AtomicReferenceFieldUpdater} and {@code + * AtomicIntegerFieldUpdater}, which will prevent us from selecting the {@code SafeAtomicHelper} + * strategy. * *

    Stashing this in a static field avoids loading it over and over again and speeds up test * execution significantly. @@ -66,7 +69,13 @@ public static TestSuite suite() { // corresponding method on FuturesTest in the correct classloader. TestSuite suite = new TestSuite(AggregateFutureStateFallbackAtomicHelperTest.class.getName()); for (Method method : FuturesTest.class.getDeclaredMethods()) { - if (Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("test")) { + if (Modifier.isPublic(method.getModifiers()) + && method.getName().startsWith("test") + /* + * When we block access to AtomicReferenceFieldUpdater, we can't even reflect on + * AbstractFuture, since it declares methods that use that type in their signatures. + */ + && !method.getName().equals("testFutures_nullChecks")) { suite.addTest( TestSuite.createTest( AggregateFutureStateFallbackAtomicHelperTest.class, method.getName())); @@ -77,41 +86,52 @@ public static TestSuite suite() { @Override public void runTest() throws Exception { - // First ensure that our classloaders are initializing the correct helper versions + /* + * Note that we do not run this test under Android at the moment. For Android testing, see + * AggregateFutureStateDefaultAtomicHelperTest. + */ + + // First, ensure that our classloaders are initializing the correct helper versions: + checkHelperVersion(getClass().getClassLoader(), "SafeAtomicHelper"); checkHelperVersion(NO_ATOMIC_FIELD_UPDATER, "SynchronizedAtomicHelper"); - // Run the corresponding FuturesTest test method in a new classloader that disallows - // certain core jdk classes. + // Then, run the actual tests under each alternative classloader: + + runTestMethod(NO_ATOMIC_FIELD_UPDATER); + // TODO(lukes): assert that the logs are full of errors + } + + /** + * Runs the corresponding {@link FuturesTest} test method in a new classloader that disallows + * certain core JDK classes. + */ + private void runTestMethod(ClassLoader classLoader) throws Exception { ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(NO_ATOMIC_FIELD_UPDATER); + Thread.currentThread().setContextClassLoader(classLoader); try { - runTestMethod(NO_ATOMIC_FIELD_UPDATER); - // TODO(lukes): assert that the logs are full of errors + Class test = classLoader.loadClass(FuturesTest.class.getName()); + Object testInstance = test.getDeclaredConstructor().newInstance(); + test.getMethod("setUp").invoke(testInstance); + test.getMethod(getName()).invoke(testInstance); + test.getMethod("tearDown").invoke(testInstance); } finally { Thread.currentThread().setContextClassLoader(oldClassLoader); } } - private void runTestMethod(ClassLoader classLoader) throws Exception { - Class test = classLoader.loadClass(FuturesTest.class.getName()); - Object testInstance = test.getDeclaredConstructor().newInstance(); - test.getMethod("setUp").invoke(testInstance); - test.getMethod(getName()).invoke(testInstance); - test.getMethod("tearDown").invoke(testInstance); - } - private void checkHelperVersion(ClassLoader classLoader, String expectedHelperClassName) throws Exception { // Make sure we are actually running with the expected helper implementation - Class abstractFutureClass = classLoader.loadClass(AggregateFutureState.class.getName()); - Field helperField = abstractFutureClass.getDeclaredField("ATOMIC_HELPER"); + Class aggregateFutureStateClass = + classLoader.loadClass(AggregateFutureState.class.getName()); + Field helperField = aggregateFutureStateClass.getDeclaredField("ATOMIC_HELPER"); helperField.setAccessible(true); assertEquals(expectedHelperClassName, helperField.get(null).getClass().getSimpleName()); } - private static ClassLoader getClassLoader(final Set blocklist) { - final String concurrentPackage = SettableFuture.class.getPackage().getName(); + private static ClassLoader getClassLoader(Set blocklist) { + String concurrentPackage = SettableFuture.class.getPackage().getName(); ClassLoader classLoader = AggregateFutureStateFallbackAtomicHelperTest.class.getClassLoader(); // we delegate to the current classloader so both loaders agree on classes like TestCase return new URLClassLoader(ClassPathUtil.getClassPathUrls(), classLoader) { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AndroidIncompatible.java b/android/guava-tests/test/com/google/common/util/concurrent/AndroidIncompatible.java index b18a1535e84c..6f6dcd239665 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AndroidIncompatible.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AndroidIncompatible.java @@ -30,7 +30,7 @@ /** * Signifies that a test should not be run under Android. This annotation is respected only by our * Google-internal Android suite generators. Note that those generators also suppress any test - * annotated with MediumTest or LargeTest. + * annotated with LargeTest. * *

    For more discussion, see {@linkplain com.google.common.base.AndroidIncompatible the * documentation on another copy of this annotation}. diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java index 70c186d345da..07e4c329d881 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleArrayTest.java @@ -13,9 +13,16 @@ package com.google.common.util.concurrent; +import static org.junit.Assert.assertThrows; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.testing.NullPointerTester; import java.util.Arrays; +import org.jspecify.annotations.NullUnmarked; /** Unit test for {@link AtomicDoubleArray}. */ +@NullUnmarked public class AtomicDoubleArrayTest extends JSR166TestCase { private static final double[] VALUES = { @@ -48,6 +55,14 @@ static void assertBitEquals(double x, double y) { assertEquals(Double.doubleToRawLongBits(x), Double.doubleToRawLongBits(y)); } + @J2ktIncompatible + @GwtIncompatible // NullPointerTester + public void testNulls() { + new NullPointerTester().testAllPublicStaticMethods(AtomicDoubleArray.class); + new NullPointerTester().testAllPublicConstructors(AtomicDoubleArray.class); + new NullPointerTester().testAllPublicInstanceMethods(new AtomicDoubleArray(1)); + } + /** constructor creates array of given size with all elements zero */ public void testConstructor() { AtomicDoubleArray aa = new AtomicDoubleArray(SIZE); @@ -59,11 +74,7 @@ public void testConstructor() { /** constructor with null array throws NPE */ public void testConstructor2NPE() { double[] a = null; - try { - new AtomicDoubleArray(a); - fail(); - } catch (NullPointerException success) { - } + assertThrows(NullPointerException.class, () -> new AtomicDoubleArray(a)); } /** constructor with array is of same size and has all elements */ @@ -79,63 +90,27 @@ public void testConstructor2() { public void testConstructorEmptyArray() { AtomicDoubleArray aa = new AtomicDoubleArray(new double[0]); assertEquals(0, aa.length()); - try { - aa.get(0); - fail(); - } catch (IndexOutOfBoundsException success) { - } + assertThrows(IndexOutOfBoundsException.class, () -> aa.get(0)); } /** constructor with length zero has size 0 and contains no elements */ public void testConstructorZeroLength() { AtomicDoubleArray aa = new AtomicDoubleArray(0); assertEquals(0, aa.length()); - try { - aa.get(0); - fail(); - } catch (IndexOutOfBoundsException success) { - } + assertThrows(IndexOutOfBoundsException.class, () -> aa.get(0)); } /** get and set for out of bound indices throw IndexOutOfBoundsException */ public void testIndexing() { AtomicDoubleArray aa = new AtomicDoubleArray(SIZE); for (int index : new int[] {-1, SIZE}) { - try { - aa.get(index); - fail(); - } catch (IndexOutOfBoundsException success) { - } - try { - aa.set(index, 1.0); - fail(); - } catch (IndexOutOfBoundsException success) { - } - try { - aa.lazySet(index, 1.0); - fail(); - } catch (IndexOutOfBoundsException success) { - } - try { - aa.compareAndSet(index, 1.0, 2.0); - fail(); - } catch (IndexOutOfBoundsException success) { - } - try { - aa.weakCompareAndSet(index, 1.0, 2.0); - fail(); - } catch (IndexOutOfBoundsException success) { - } - try { - aa.getAndAdd(index, 1.0); - fail(); - } catch (IndexOutOfBoundsException success) { - } - try { - aa.addAndGet(index, 1.0); - fail(); - } catch (IndexOutOfBoundsException success) { - } + assertThrows(IndexOutOfBoundsException.class, () -> aa.get(index)); + assertThrows(IndexOutOfBoundsException.class, () -> aa.set(index, 1.0)); + assertThrows(IndexOutOfBoundsException.class, () -> aa.lazySet(index, 1.0)); + assertThrows(IndexOutOfBoundsException.class, () -> aa.compareAndSet(index, 1.0, 2.0)); + assertThrows(IndexOutOfBoundsException.class, () -> aa.weakCompareAndSet(index, 1.0, 2.0)); + assertThrows(IndexOutOfBoundsException.class, () -> aa.getAndAdd(index, 1.0)); + assertThrows(IndexOutOfBoundsException.class, () -> aa.addAndGet(index, 1.0)); } } @@ -181,13 +156,14 @@ public void testCompareAndSet() { } /** compareAndSet in one thread enables another waiting for value to succeed */ - public void testCompareAndSetInMultipleThreads() throws InterruptedException { - final AtomicDoubleArray a = new AtomicDoubleArray(1); + AtomicDoubleArray a = new AtomicDoubleArray(1); a.set(0, 1.0); Thread t = newStartedThread( new CheckedRunnable() { + @Override + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races public void realRun() { while (!a.compareAndSet(0, 2.0, 3.0)) { Thread.yield(); @@ -210,7 +186,8 @@ public void testWeakCompareAndSet() { assertBitEquals(prev, aa.get(i)); assertFalse(aa.weakCompareAndSet(i, unused, x)); assertBitEquals(prev, aa.get(i)); - while (!aa.weakCompareAndSet(i, prev, x)) {; + while (!aa.weakCompareAndSet(i, prev, x)) { + ; } assertBitEquals(x, aa.get(i)); prev = x; @@ -270,6 +247,8 @@ class Counter extends CheckedRunnable { aa = a; } + @SuppressWarnings("DoubleAtLeastJUnit") // causes timeouts under Android + @Override public void realRun() { for (; ; ) { boolean done = true; @@ -294,9 +273,8 @@ public void realRun() { * Multiple threads using same array of counters successfully update a number of times equal to * total count */ - public void testCountingInMultipleThreads() throws InterruptedException { - final AtomicDoubleArray aa = new AtomicDoubleArray(SIZE); + AtomicDoubleArray aa = new AtomicDoubleArray(SIZE); for (int i = 0; i < SIZE; i++) { aa.set(i, (double) COUNTDOWN); } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java index fe68e009952f..c220b9ece348 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AtomicDoubleTest.java @@ -13,8 +13,12 @@ package com.google.common.util.concurrent; +import static com.google.common.truth.Truth.assertThat; + +import org.jspecify.annotations.NullUnmarked; /** Unit test for {@link AtomicDouble}. */ +@NullUnmarked public class AtomicDoubleTest extends JSR166TestCase { private static final double[] VALUES = { @@ -97,12 +101,13 @@ public void testCompareAndSet() { } /** compareAndSet in one thread enables another waiting for value to succeed */ - public void testCompareAndSetInMultipleThreads() throws Exception { - final AtomicDouble at = new AtomicDouble(1.0); + AtomicDouble at = new AtomicDouble(1.0); Thread t = newStartedThread( new CheckedRunnable() { + @Override + @SuppressWarnings("ThreadPriorityCheck") // doing our best to test for races public void realRun() { while (!at.compareAndSet(2.0, 3.0)) { Thread.yield(); @@ -124,7 +129,8 @@ public void testWeakCompareAndSet() { assertBitEquals(prev, at.get()); assertFalse(at.weakCompareAndSet(unused, x)); assertBitEquals(prev, at.get()); - while (!at.weakCompareAndSet(prev, x)) {; + while (!at.weakCompareAndSet(prev, x)) { + ; } assertBitEquals(x, at.get()); prev = x; @@ -225,7 +231,7 @@ public void testFloatValue() { /** doubleValue returns current value. */ public void testDoubleValue() { AtomicDouble at = new AtomicDouble(); - assertEquals(0.0d, at.doubleValue()); + assertThat(at.doubleValue()).isEqualTo(0.0d); for (double x : VALUES) { at.set(x); assertBitEquals(x, at.doubleValue()); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AtomicLongMapBasherTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AtomicLongMapBasherTest.java index 4765825855ba..17cb068ccbab 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AtomicLongMapBasherTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AtomicLongMapBasherTest.java @@ -16,44 +16,48 @@ package com.google.common.util.concurrent; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.TimeUnit.SECONDS; + import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.util.ArrayList; import java.util.Random; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Basher test for {@link AtomicLongMap}. * * @author mike nonemacher */ +@J2ktIncompatible // threads @GwtIncompatible // threads - +@NullUnmarked public class AtomicLongMapBasherTest extends TestCase { private final Random random = new Random(301); - public void testModify_basher() throws InterruptedException { + public void testModify_basher() throws Exception { int nTasks = 3000; int nThreads = 100; - final int getsPerTask = 1000; - final int deltaRange = 10000; - final String key = "key"; + int getsPerTask = 1000; + int deltaRange = 10000; + String key = "key"; - final AtomicLong sum = new AtomicLong(); - final AtomicLongMap map = AtomicLongMap.create(); + AtomicLongMap map = AtomicLongMap.create(); - ExecutorService threadPool = Executors.newFixedThreadPool(nThreads); + ExecutorService threadPool = newFixedThreadPool(nThreads); + ArrayList> futures = new ArrayList<>(); for (int i = 0; i < nTasks; i++) { - @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored - Future possiblyIgnoredError = + futures.add( threadPool.submit( - new Runnable() { + new Callable() { @Override - public void run() { - int threadSum = 0; + public Long call() { + long threadSum = 0; for (int j = 0; j < getsPerTask; j++) { long delta = random.nextInt(deltaRange); int behavior = random.nextInt(10); @@ -106,14 +110,16 @@ public void run() { throw new AssertionError(); } } - sum.addAndGet(threadSum); + return threadSum; } - }); + })); } - threadPool.shutdown(); - assertTrue(threadPool.awaitTermination(300, TimeUnit.SECONDS)); - - assertEquals(sum.get(), map.get(key)); + assertTrue(threadPool.awaitTermination(300, SECONDS)); + long sum = 0; + for (Future f : futures) { + sum += f.get(); + } + assertEquals(sum, map.get(key)); } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AtomicLongMapTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AtomicLongMapTest.java index 587a4ec21d72..d9e6ca164885 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AtomicLongMapTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AtomicLongMapTest.java @@ -18,27 +18,31 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Sets; import com.google.common.testing.NullPointerTester; import com.google.common.testing.SerializableTester; +import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link AtomicLongMap}. * * @author mike nonemacher */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class AtomicLongMapTest extends TestCase { private static final int ITERATIONS = 100; private static final int MAX_ADDEND = 100; private final Random random = new Random(301); + @J2ktIncompatible @GwtIncompatible // NullPointerTester public void testNulls() { NullPointerTester tester = new NullPointerTester(); @@ -52,7 +56,7 @@ public void testCreate_map() { Map in = ImmutableMap.of("1", 1L, "2", 2L, "3", 3L); AtomicLongMap map = AtomicLongMap.create(in); assertFalse(map.isEmpty()); - assertSame(3, map.size()); + assertEquals(3, map.size()); assertTrue(map.containsKey("1")); assertTrue(map.containsKey("2")); assertTrue(map.containsKey("3")); @@ -302,7 +306,7 @@ public void testPutAll() { Map in = ImmutableMap.of("1", 1L, "2", 2L, "3", 3L); AtomicLongMap map = AtomicLongMap.create(); assertTrue(map.isEmpty()); - assertSame(0, map.size()); + assertEquals(0, map.size()); assertFalse(map.containsKey("1")); assertFalse(map.containsKey("2")); assertFalse(map.containsKey("3")); @@ -312,7 +316,7 @@ public void testPutAll() { map.putAll(in); assertFalse(map.isEmpty()); - assertSame(3, map.size()); + assertEquals(3, map.size()); assertTrue(map.containsKey("1")); assertTrue(map.containsKey("2")); assertTrue(map.containsKey("3")); @@ -516,7 +520,7 @@ public void testRemoveValue_zero() { public void testRemoveZeros() { AtomicLongMap map = AtomicLongMap.create(); - Set nonZeroKeys = Sets.newHashSet(); + Set nonZeroKeys = new HashSet<>(); for (int i = 0; i < ITERATIONS; i++) { Object key = new Object(); long value = i % 2; diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AtomicsTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AtomicsTest.java index b903e6ce8623..1cb9fd8c4c00 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/AtomicsTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/AtomicsTest.java @@ -16,15 +16,19 @@ package com.google.common.util.concurrent; +import static org.junit.Assert.assertThrows; + import com.google.common.testing.NullPointerTester; import java.util.concurrent.atomic.AtomicReferenceArray; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link Atomics}. * * @author Kurt Alfred Kluever */ +@NullUnmarked public class AtomicsTest extends TestCase { private static final Object OBJECT = new Object(); @@ -44,19 +48,11 @@ public void testNewReferenceArray_withLength() throws Exception { for (int i = 0; i < length; ++i) { assertEquals(null, refArray.get(i)); } - try { - refArray.get(length); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> refArray.get(length)); } public void testNewReferenceArray_withNegativeLength() throws Exception { - try { - Atomics.newReferenceArray(-1); - fail(); - } catch (NegativeArraySizeException expected) { - } + assertThrows(NegativeArraySizeException.class, () -> Atomics.newReferenceArray(-1)); } public void testNewReferenceArray_withStringArray() throws Exception { @@ -65,19 +61,11 @@ public void testNewReferenceArray_withStringArray() throws Exception { for (int i = 0; i < array.length; ++i) { assertEquals(array[i], refArray.get(i)); } - try { - refArray.get(array.length); - fail(); - } catch (IndexOutOfBoundsException expected) { - } + assertThrows(IndexOutOfBoundsException.class, () -> refArray.get(array.length)); } public void testNewReferenceArray_withNullArray() throws Exception { - try { - Atomics.newReferenceArray(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> Atomics.newReferenceArray(null)); } public void testNullPointers() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/CallablesTest.java b/android/guava-tests/test/com/google/common/util/concurrent/CallablesTest.java index 5bc92dc20633..bddd038c8b14 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/CallablesTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/CallablesTest.java @@ -17,26 +17,33 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; +import static com.google.common.util.concurrent.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import java.security.Permission; +import com.google.common.util.concurrent.TestExceptions.SomeCheckedException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Unit tests for {@link Callables}. * * @author Isaac Shum */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class CallablesTest extends TestCase { + @J2ktIncompatible // TODO(b/324550390): Enable public void testReturning() throws Exception { - assertNull(Callables.returning(null).call()); + assertThat(Callables.returning(null).call()).isNull(); Object value = new Object(); Callable callable = Callables.returning(value); @@ -45,9 +52,10 @@ public void testReturning() throws Exception { assertSame(value, callable.call()); } + @J2ktIncompatible @GwtIncompatible public void testAsAsyncCallable() throws Exception { - final String expected = "MyCallableString"; + String expected = "MyCallableString"; Callable callable = new Callable() { @Override @@ -57,15 +65,16 @@ public String call() throws Exception { }; AsyncCallable asyncCallable = - Callables.asAsyncCallable(callable, MoreExecutors.newDirectExecutorService()); + Callables.asAsyncCallable(callable, newDirectExecutorService()); ListenableFuture future = asyncCallable.call(); assertSame(expected, future.get()); } + @J2ktIncompatible @GwtIncompatible public void testAsAsyncCallable_exception() throws Exception { - final Exception expected = new IllegalArgumentException(); + Exception expected = new IllegalArgumentException(); Callable callable = new Callable() { @Override @@ -75,25 +84,22 @@ public String call() throws Exception { }; AsyncCallable asyncCallable = - Callables.asAsyncCallable(callable, MoreExecutors.newDirectExecutorService()); + Callables.asAsyncCallable(callable, newDirectExecutorService()); ListenableFuture future = asyncCallable.call(); - try { - future.get(); - fail("Expected exception to be thrown"); - } catch (ExecutionException e) { - assertThat(e).hasCauseThat().isSameInstanceAs(expected); - } + ExecutionException e = assertThrows(ExecutionException.class, () -> future.get()); + assertThat(e).hasCauseThat().isSameInstanceAs(expected); } + @J2ktIncompatible @GwtIncompatible // threads public void testRenaming() throws Exception { String oldName = Thread.currentThread().getName(); - final Supplier newName = Suppliers.ofInstance("MyCrazyThreadName"); - Callable callable = - new Callable() { + Supplier newName = Suppliers.ofInstance("MyCrazyThreadName"); + Callable<@Nullable Void> callable = + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { assertEquals(Thread.currentThread().getName(), newName.get()); return null; } @@ -102,57 +108,21 @@ public Void call() throws Exception { assertEquals(oldName, Thread.currentThread().getName()); } + @J2ktIncompatible @GwtIncompatible // threads public void testRenaming_exceptionalReturn() throws Exception { String oldName = Thread.currentThread().getName(); - final Supplier newName = Suppliers.ofInstance("MyCrazyThreadName"); - class MyException extends Exception {} - Callable callable = - new Callable() { + Supplier newName = Suppliers.ofInstance("MyCrazyThreadName"); + Callable<@Nullable Void> callable = + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { assertEquals(Thread.currentThread().getName(), newName.get()); - throw new MyException(); + throw new SomeCheckedException(); } }; - try { - Callables.threadRenaming(callable, newName).call(); - fail(); - } catch (MyException expected) { - } + assertThrows( + SomeCheckedException.class, () -> Callables.threadRenaming(callable, newName).call()); assertEquals(oldName, Thread.currentThread().getName()); } - - @GwtIncompatible // threads - - public void testRenaming_noPermissions() throws Exception { - System.setSecurityManager( - new SecurityManager() { - @Override - public void checkAccess(Thread t) { - throw new SecurityException(); - } - - @Override - public void checkPermission(Permission perm) { - // Do nothing so we can clear the security manager at the end - } - }); - try { - final String oldName = Thread.currentThread().getName(); - Supplier newName = Suppliers.ofInstance("MyCrazyThreadName"); - Callable callable = - new Callable() { - @Override - public Void call() throws Exception { - assertEquals(Thread.currentThread().getName(), oldName); - return null; - } - }; - Callables.threadRenaming(callable, newName).call(); - assertEquals(oldName, Thread.currentThread().getName()); - } finally { - System.setSecurityManager(null); - } - } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ClassPathUtil.java b/android/guava-tests/test/com/google/common/util/concurrent/ClassPathUtil.java index a36aa34e4e56..16e88a5df94c 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ClassPathUtil.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ClassPathUtil.java @@ -23,9 +23,11 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import org.jspecify.annotations.NullUnmarked; // TODO(b/65488446): Make this a public API. /** Utility method to parse the system class path. */ +@NullUnmarked final class ClassPathUtil { private ClassPathUtil() {} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureFinishToFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureFinishToFutureTest.java new file mode 100644 index 000000000000..c42357cac0b2 --- /dev/null +++ b/android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureFinishToFutureTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static org.junit.Assert.assertThrows; + +import com.google.common.util.concurrent.ClosingFuture.ClosingCallable; +import com.google.common.util.concurrent.ClosingFuture.DeferredCloser; +import java.io.Closeable; +import java.util.concurrent.ExecutionException; +import org.jspecify.annotations.NullUnmarked; + +/** Tests for {@link ClosingFuture} that exercise {@link ClosingFuture#finishToFuture()}. */ +@NullUnmarked +public class ClosingFutureFinishToFutureTest extends AbstractClosingFutureTest { + public void testFinishToFuture_throwsIfCalledTwice() throws Exception { + ClosingFuture closingFuture = + ClosingFuture.submit( + new ClosingCallable() { + @Override + public Closeable call(DeferredCloser closer) throws Exception { + return closer.eventuallyClose(mockCloseable, executor); + } + }, + executor); + FluentFuture unused = closingFuture.finishToFuture(); + assertThrows( + IllegalStateException.class, + () -> { + FluentFuture unused2 = closingFuture.finishToFuture(); + }); + } + + public void testFinishToFuture_throwsAfterCallingFinishToValueAndCloser() throws Exception { + ClosingFuture closingFuture = + ClosingFuture.submit( + new ClosingCallable() { + @Override + public Closeable call(DeferredCloser closer) throws Exception { + return closer.eventuallyClose(mockCloseable, executor); + } + }, + executor); + closingFuture.finishToValueAndCloser(new NoOpValueAndCloserConsumer<>(), directExecutor()); + assertThrows( + IllegalStateException.class, + () -> { + FluentFuture unused = closingFuture.finishToFuture(); + }); + } + + public void testFinishToFuture_preventsFurtherDerivation() { + ClosingFuture closingFuture = ClosingFuture.from(immediateFuture("value1")); + FluentFuture unused = closingFuture.finishToFuture(); + assertDerivingThrowsIllegalStateException(closingFuture); + } + + @Override + T getFinalValue(ClosingFuture closingFuture) throws ExecutionException { + return getUninterruptibly(closingFuture.finishToFuture()); + } + + @Override + void assertFinallyFailsWithException(ClosingFuture closingFuture) { + assertThatFutureFailsWithException(closingFuture.finishToFuture()); + } + + @Override + void assertBecomesCanceled(ClosingFuture closingFuture) throws ExecutionException { + assertThatFutureBecomesCancelled(closingFuture.finishToFuture()); + } + + @Override + void cancelFinalStepAndWait(ClosingFuture closingFuture) { + assertThat(closingFuture.finishToFuture().cancel(false)).isTrue(); + waitUntilClosed(closingFuture); + futureCancelled.countDown(); + } +} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureFinishToValueAndCloserTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureFinishToValueAndCloserTest.java new file mode 100644 index 000000000000..3f5e27f9b249 --- /dev/null +++ b/android/guava-tests/test/com/google/common/util/concurrent/ClosingFutureFinishToValueAndCloserTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination; +import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; + +import com.google.common.util.concurrent.ClosingFuture.ClosingCallable; +import com.google.common.util.concurrent.ClosingFuture.DeferredCloser; +import com.google.common.util.concurrent.ClosingFuture.ValueAndCloser; +import com.google.common.util.concurrent.ClosingFuture.ValueAndCloserConsumer; +import java.io.Closeable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import org.jspecify.annotations.NullUnmarked; + +/** + * Tests for {@link ClosingFuture} that exercise {@link + * ClosingFuture#finishToValueAndCloser(ValueAndCloserConsumer, Executor)}. + */ +@NullUnmarked +public class ClosingFutureFinishToValueAndCloserTest extends AbstractClosingFutureTest { + private final ExecutorService finishToValueAndCloserExecutor = newSingleThreadExecutor(); + private volatile ValueAndCloser valueAndCloser; + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + assertWithMessage("finishToValueAndCloserExecutor was shut down") + .that(shutdownAndAwaitTermination(finishToValueAndCloserExecutor, 10, SECONDS)) + .isTrue(); + } + + public void testFinishToValueAndCloser_throwsIfCalledTwice() throws Exception { + ClosingFuture closingFuture = + ClosingFuture.submit( + new ClosingCallable() { + @Override + public Closeable call(DeferredCloser closer) throws Exception { + return closer.eventuallyClose(mockCloseable, executor); + } + }, + executor); + closingFuture.finishToValueAndCloser( + new NoOpValueAndCloserConsumer<>(), finishToValueAndCloserExecutor); + assertThrows( + IllegalStateException.class, + () -> + closingFuture.finishToValueAndCloser( + new NoOpValueAndCloserConsumer<>(), finishToValueAndCloserExecutor)); + } + + public void testFinishToValueAndCloser_throwsAfterCallingFinishToFuture() throws Exception { + ClosingFuture closingFuture = + ClosingFuture.submit( + new ClosingCallable() { + @Override + public Closeable call(DeferredCloser closer) throws Exception { + return closer.eventuallyClose(mockCloseable, executor); + } + }, + executor); + FluentFuture unused = closingFuture.finishToFuture(); + assertThrows( + IllegalStateException.class, + () -> + closingFuture.finishToValueAndCloser( + new NoOpValueAndCloserConsumer<>(), finishToValueAndCloserExecutor)); + } + + @Override + T getFinalValue(ClosingFuture closingFuture) throws ExecutionException { + return finishToValueAndCloser(closingFuture).get(); + } + + @Override + void assertFinallyFailsWithException(ClosingFuture closingFuture) { + assertThatFutureFailsWithException(closingFuture.statusFuture()); + ValueAndCloser valueAndCloser = finishToValueAndCloser(closingFuture); + try { + valueAndCloser.get(); + fail(); + } catch (ExecutionException expected) { + assertThat(expected).hasCauseThat().isSameInstanceAs(exception); + } + valueAndCloser.closeAsync(); + } + + @Override + void assertBecomesCanceled(ClosingFuture closingFuture) throws ExecutionException { + assertThatFutureBecomesCancelled(closingFuture.statusFuture()); + } + + @Override + void waitUntilClosed(ClosingFuture closingFuture) { + if (valueAndCloser != null) { + valueAndCloser.closeAsync(); + } + super.waitUntilClosed(closingFuture); + } + + @Override + void cancelFinalStepAndWait(ClosingFuture closingFuture) { + assertThat(closingFuture.cancel(false)).isTrue(); + ValueAndCloser unused = finishToValueAndCloser(closingFuture); + waitUntilClosed(closingFuture); + futureCancelled.countDown(); + } + + private ValueAndCloser finishToValueAndCloser(ClosingFuture closingFuture) { + CountDownLatch valueAndCloserSet = new CountDownLatch(1); + closingFuture.finishToValueAndCloser( + new ValueAndCloserConsumer() { + @Override + public void accept(ValueAndCloser valueAndCloser) { + ClosingFutureFinishToValueAndCloserTest.this.valueAndCloser = valueAndCloser; + valueAndCloserSet.countDown(); + } + }, + finishToValueAndCloserExecutor); + assertWithMessage("valueAndCloser was set") + .that(awaitUninterruptibly(valueAndCloserSet, 10, SECONDS)) + .isTrue(); + @SuppressWarnings("unchecked") + ValueAndCloser valueAndCloserWithType = (ValueAndCloser) valueAndCloser; + return valueAndCloserWithType; + } +} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/CycleDetectingLockFactoryTest.java b/android/guava-tests/test/com/google/common/util/concurrent/CycleDetectingLockFactoryTest.java index 18e69b10ee39..ba7d11ef8f67 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/CycleDetectingLockFactoryTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/CycleDetectingLockFactoryTest.java @@ -16,25 +16,27 @@ package com.google.common.util.concurrent; +import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.util.concurrent.CycleDetectingLockFactory.Policies; import com.google.common.util.concurrent.CycleDetectingLockFactory.Policy; import com.google.common.util.concurrent.CycleDetectingLockFactory.PotentialDeadlockException; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unittests for {@link CycleDetectingLockFactory}. * * @author Darick Tong */ +@NullUnmarked public class CycleDetectingLockFactoryTest extends TestCase { private ReentrantLock lockA; @@ -103,24 +105,15 @@ public void testDeadlock_twoLocks() { // The opposite order should fail (Policies.THROW). PotentialDeadlockException firstException = null; lockB.lock(); - try { - lockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "LockB -> LockA", "LockA -> LockB"); - firstException = expected; - } - + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> lockA.lock()); + checkMessage(expected, "LockB -> LockA", "LockA -> LockB"); + firstException = expected; // Second time should also fail, with a cached causal chain. - try { - lockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "LockB -> LockA", "LockA -> LockB"); - // The causal chain should be cached. - assertSame(firstException.getCause(), expected.getCause()); - } - + expected = assertThrows(PotentialDeadlockException.class, () -> lockA.lock()); + checkMessage(expected, "LockB -> LockA", "LockA -> LockB"); + // The causal chain should be cached. + assertSame(firstException.getCause(), expected.getCause()); // lockA should work after lockB is released. lockB.unlock(); lockA.lock(); @@ -140,12 +133,9 @@ public void testDeadlock_threeLocks() { lockB.unlock(); // lockC -> lockA should fail. - try { - lockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "LockC -> LockA", "LockB -> LockC", "LockA -> LockB"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> lockA.lock()); + checkMessage(expected, "LockC -> LockA", "LockB -> LockC", "LockA -> LockB"); } public void testReentrancy_noDeadlock() { @@ -164,29 +154,18 @@ public void testExplicitOrdering_noViolations() { public void testExplicitOrdering_violations() { lock3.lock(); - try { - lock2.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "MyOrder.THIRD -> MyOrder.SECOND"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> lock2.lock()); + checkMessage(expected, "MyOrder.THIRD -> MyOrder.SECOND"); - try { - lock1.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "MyOrder.THIRD -> MyOrder.FIRST"); - } + expected = assertThrows(PotentialDeadlockException.class, () -> lock1.lock()); + checkMessage(expected, "MyOrder.THIRD -> MyOrder.FIRST"); lock3.unlock(); lock2.lock(); - try { - lock1.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "MyOrder.SECOND -> MyOrder.FIRST"); - } + expected = assertThrows(PotentialDeadlockException.class, () -> lock1.lock()); + checkMessage(expected, "MyOrder.SECOND -> MyOrder.FIRST"); } public void testDifferentOrderings_noViolations() { @@ -199,26 +178,18 @@ public void testExplicitOrderings_generalCycleDetection() { lock01.lock(); // OtherOrder, ordinal() == 1 lock3.unlock(); - try { - lock3.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage( - expected, "OtherOrder.FIRST -> MyOrder.THIRD", "MyOrder.THIRD -> OtherOrder.FIRST"); - } - + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> lock3.lock()); + checkMessage( + expected, "OtherOrder.FIRST -> MyOrder.THIRD", "MyOrder.THIRD -> OtherOrder.FIRST"); lockA.lock(); lock01.unlock(); lockB.lock(); lockA.unlock(); - try { - lock01.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage( - expected, "LockB -> OtherOrder.FIRST", "LockA -> LockB", "OtherOrder.FIRST -> LockA"); - } + expected = assertThrows(PotentialDeadlockException.class, () -> lock01.lock()); + checkMessage( + expected, "LockB -> OtherOrder.FIRST", "LockA -> LockB", "OtherOrder.FIRST -> LockA"); } public void testExplicitOrdering_cycleWithUnorderedLock() { @@ -227,16 +198,13 @@ public void testExplicitOrdering_cycleWithUnorderedLock() { myLock.lock(); lock03.unlock(); - try { - lock01.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage( - expected, - "MyLock -> OtherOrder.FIRST", - "OtherOrder.THIRD -> MyLock", - "OtherOrder.FIRST -> OtherOrder.THIRD"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> lock01.lock()); + checkMessage( + expected, + "MyLock -> OtherOrder.FIRST", + "OtherOrder.THIRD -> MyLock", + "OtherOrder.FIRST -> OtherOrder.THIRD"); } public void testExplicitOrdering_reentrantAcquisition() { @@ -262,11 +230,7 @@ public void testExplicitOrdering_acquiringMultipleLocksWithSameRank() { Lock lockB = factory.newReentrantReadWriteLock(OtherOrder.FIRST).readLock(); lockA.lock(); - try { - lockB.lock(); - fail("Expected IllegalStateException"); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> lockB.lock()); lockA.unlock(); lockB.lock(); @@ -279,12 +243,9 @@ public void testReadLock_deadlock() { readLockA.unlock(); lockB.lock(); - try { - readLockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> readLockA.lock()); + checkMessage(expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB"); } public void testReadLock_transitive() { @@ -301,13 +262,10 @@ public void testReadLock_transitive() { // readLockC -> readLockA readLockC.lock(); - try { - readLockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage( - expected, "ReadWriteC -> ReadWriteA", "LockB -> ReadWriteC", "ReadWriteA -> LockB"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> readLockA.lock()); + checkMessage( + expected, "ReadWriteC -> ReadWriteA", "LockB -> ReadWriteC", "ReadWriteA -> LockB"); } public void testWriteLock_threeLockDeadLock() { @@ -323,16 +281,13 @@ public void testWriteLock_threeLockDeadLock() { writeLockB.unlock(); // writeLockC -> writeLockA should fail. - try { - writeLockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage( - expected, - "ReadWriteC -> ReadWriteA", - "ReadWriteB -> ReadWriteC", - "ReadWriteA -> ReadWriteB"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> writeLockA.lock()); + checkMessage( + expected, + "ReadWriteC -> ReadWriteA", + "ReadWriteB -> ReadWriteC", + "ReadWriteA -> ReadWriteB"); } public void testWriteToReadLockDowngrading() { @@ -344,12 +299,9 @@ public void testWriteToReadLockDowngrading() { readLockA.unlock(); // lockB -> writeLockA should fail - try { - writeLockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> writeLockA.lock()); + checkMessage(expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB"); } public void testReadWriteLockDeadlock() { @@ -360,12 +312,9 @@ public void testReadWriteLockDeadlock() { // lockB -> readLockA should fail. lockB.lock(); - try { - readLockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> readLockA.lock()); + checkMessage(expected, "LockB -> ReadWriteA", "ReadWriteA -> LockB"); } public void testReadWriteLockDeadlock_transitive() { @@ -382,12 +331,9 @@ public void testReadWriteLockDeadlock_transitive() { // lockC -> writeLockA should fail. lockC.lock(); - try { - writeLockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "LockC -> ReadWriteA", "LockB -> LockC", "ReadWriteA -> LockB"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> writeLockA.lock()); + checkMessage(expected, "LockC -> ReadWriteA", "LockB -> LockC", "ReadWriteA -> LockB"); } public void testReadWriteLockDeadlock_treatedEquivalently() { @@ -398,12 +344,9 @@ public void testReadWriteLockDeadlock_treatedEquivalently() { // readLockB -> writeLockA should fail. readLockB.lock(); - try { - writeLockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "ReadWriteB -> ReadWriteA", "ReadWriteA -> ReadWriteB"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> writeLockA.lock()); + checkMessage(expected, "ReadWriteB -> ReadWriteA", "ReadWriteA -> ReadWriteB"); } public void testDifferentLockFactories() { @@ -418,12 +361,9 @@ public void testDifferentLockFactories() { // lockD -> lockA should fail even though lockD is from a different factory. lockD.lock(); - try { - lockA.lock(); - fail("Expected PotentialDeadlockException"); - } catch (PotentialDeadlockException expected) { - checkMessage(expected, "LockD -> LockA", "LockA -> LockD"); - } + PotentialDeadlockException expected = + assertThrows(PotentialDeadlockException.class, () -> lockA.lock()); + checkMessage(expected, "LockD -> LockA", "LockA -> LockD"); } public void testDifferentLockFactories_policyExecution() { @@ -442,7 +382,6 @@ public void testDifferentLockFactories_policyExecution() { lockD.lock(); } - public void testReentrantLock_tryLock() throws Exception { LockingThread thread = new LockingThread(lockA); thread.start(); @@ -454,7 +393,6 @@ public void testReentrantLock_tryLock() throws Exception { assertTrue(lockA.tryLock()); } - public void testReentrantWriteLock_tryLock() throws Exception { LockingThread thread = new LockingThread(writeLockA); thread.start(); @@ -468,7 +406,6 @@ public void testReentrantWriteLock_tryLock() throws Exception { assertTrue(readLockA.tryLock()); } - public void testReentrantReadLock_tryLock() throws Exception { LockingThread thread = new LockingThread(readLockA); thread.start(); @@ -497,7 +434,7 @@ public void run() { lock.lock(); try { locked.countDown(); - finishLatch.await(1, TimeUnit.MINUTES); + finishLatch.await(1, MINUTES); } catch (InterruptedException e) { fail(e.toString()); } finally { @@ -506,7 +443,7 @@ public void run() { } void waitUntilHoldingLock() throws InterruptedException { - locked.await(1, TimeUnit.MINUTES); + locked.await(1, MINUTES); } void releaseLockAndFinish() throws InterruptedException { @@ -546,16 +483,6 @@ private enum OtherOrder { // "LockA -> LockB \b.*\b LockB -> LockC \b.*\b LockC -> LockA" private void checkMessage(IllegalStateException exception, String... expectedLockCycle) { String regex = Joiner.on("\\b.*\\b").join(expectedLockCycle); - assertContainsRegex(regex, exception.getMessage()); - } - - // TODO(cpovirk): consider adding support for regex to Truth - private static void assertContainsRegex(String expectedRegex, String actual) { - Pattern pattern = Pattern.compile(expectedRegex); - Matcher matcher = pattern.matcher(actual); - if (!matcher.find()) { - String actualDesc = (actual == null) ? "null" : ('<' + actual + '>'); - fail("expected to contain regex:<" + expectedRegex + "> but was:" + actualDesc); - } + assertThat(exception).hasMessageThat().containsMatch(regex); } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ExecutionListTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ExecutionListTest.java index 5bd3cf7f44da..fdab04e3483c 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ExecutionListTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ExecutionListTest.java @@ -17,14 +17,15 @@ package com.google.common.util.concurrent; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.concurrent.Executors.newCachedThreadPool; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.testing.NullPointerTester; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link ExecutionList}. @@ -32,13 +33,13 @@ * @author Nishant Thakkar * @author Sven Mawson */ +@NullUnmarked public class ExecutionListTest extends TestCase { private final ExecutionList list = new ExecutionList(); - public void testRunOnPopulatedList() throws Exception { - Executor exec = Executors.newCachedThreadPool(); + Executor exec = newCachedThreadPool(); CountDownLatch countDownLatch = new CountDownLatch(3); list.add(new MockRunnable(countDownLatch), exec); list.add(new MockRunnable(countDownLatch), exec); @@ -48,11 +49,11 @@ public void testRunOnPopulatedList() throws Exception { list.execute(); // Verify that all of the runnables execute in a reasonable amount of time. - assertTrue(countDownLatch.await(1L, TimeUnit.SECONDS)); + assertTrue(countDownLatch.await(1L, SECONDS)); } public void testExecute_idempotent() { - final AtomicInteger runCalled = new AtomicInteger(); + AtomicInteger runCalled = new AtomicInteger(); list.add( new Runnable() { @Override @@ -67,10 +68,9 @@ public void run() { assertEquals(1, runCalled.get()); } - public void testExecute_idempotentConcurrently() throws InterruptedException { - final CountDownLatch okayToRun = new CountDownLatch(1); - final AtomicInteger runCalled = new AtomicInteger(); + CountDownLatch okayToRun = new CountDownLatch(1); + AtomicInteger runCalled = new AtomicInteger(); list.add( new Runnable() { @Override @@ -103,21 +103,20 @@ public void run() { assertEquals(1, runCalled.get()); } - public void testAddAfterRun() throws Exception { // Run the previous test testRunOnPopulatedList(); // If it passed, then verify an Add will be executed without calling run CountDownLatch countDownLatch = new CountDownLatch(1); - list.add(new MockRunnable(countDownLatch), Executors.newCachedThreadPool()); - assertTrue(countDownLatch.await(1L, TimeUnit.SECONDS)); + list.add(new MockRunnable(countDownLatch), newCachedThreadPool()); + assertTrue(countDownLatch.await(1L, SECONDS)); } public void testOrdering() throws Exception { - final AtomicInteger integer = new AtomicInteger(); + AtomicInteger integer = new AtomicInteger(); for (int i = 0; i < 10; i++) { - final int expectedCount = i; + int expectedCount = i; list.add( new Runnable() { @Override @@ -125,14 +124,14 @@ public void run() { integer.compareAndSet(expectedCount, expectedCount + 1); } }, - MoreExecutors.directExecutor()); + directExecutor()); } list.execute(); assertEquals(10, integer.get()); } private class MockRunnable implements Runnable { - CountDownLatch countDownLatch; + final CountDownLatch countDownLatch; MockRunnable(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ExecutionSequencerTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ExecutionSequencerTest.java index 1c03f5ac4e57..f2503d42b808 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ExecutionSequencerTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ExecutionSequencerTest.java @@ -17,11 +17,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.Futures.allAsList; import static com.google.common.util.concurrent.Futures.getDone; +import static com.google.common.util.concurrent.Futures.immediateVoidFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.concurrent.Executors.newCachedThreadPool; +import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Function; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.testing.GcFinalization; import com.google.common.testing.TestLogHandler; import com.google.j2objc.annotations.J2ObjCIncompatible; @@ -32,24 +35,25 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** Tests for {@link ExecutionSequencer} */ +@NullUnmarked public class ExecutionSequencerTest extends TestCase { ExecutorService executor; private ExecutionSequencer serializer; - private SettableFuture firstFuture; + private SettableFuture<@Nullable Void> firstFuture; private TestCallable firstCallable; @Override public void setUp() throws Exception { - executor = Executors.newCachedThreadPool(); + executor = newCachedThreadPool(); serializer = ExecutionSequencer.create(); firstFuture = SettableFuture.create(); firstCallable = new TestCallable(firstFuture); @@ -63,7 +67,7 @@ public void tearDown() throws Exception { public void testCallableStartsAfterFirstFutureCompletes() { @SuppressWarnings({"unused", "nullness"}) Future possiblyIgnoredError = serializer.submitAsync(firstCallable, directExecutor()); - TestCallable secondCallable = new TestCallable(Futures.immediateFuture(null)); + TestCallable secondCallable = new TestCallable(immediateVoidFuture()); @SuppressWarnings({"unused", "nullness"}) Future possiblyIgnoredError1 = serializer.submitAsync(secondCallable, directExecutor()); assertThat(firstCallable.called).isTrue(); @@ -75,9 +79,10 @@ public void testCallableStartsAfterFirstFutureCompletes() { public void testCancellationDoesNotViolateSerialization() { @SuppressWarnings({"unused", "nullness"}) Future possiblyIgnoredError = serializer.submitAsync(firstCallable, directExecutor()); - TestCallable secondCallable = new TestCallable(Futures.immediateFuture(null)); - ListenableFuture secondFuture = serializer.submitAsync(secondCallable, directExecutor()); - TestCallable thirdCallable = new TestCallable(Futures.immediateFuture(null)); + TestCallable secondCallable = new TestCallable(immediateVoidFuture()); + ListenableFuture<@Nullable Void> secondFuture = + serializer.submitAsync(secondCallable, directExecutor()); + TestCallable thirdCallable = new TestCallable(immediateVoidFuture()); @SuppressWarnings({"unused", "nullness"}) Future possiblyIgnoredError1 = serializer.submitAsync(thirdCallable, directExecutor()); secondFuture.cancel(true); @@ -88,10 +93,9 @@ public void testCancellationDoesNotViolateSerialization() { assertThat(thirdCallable.called).isTrue(); } - public void testCancellationMultipleThreads() throws Exception { - final BlockingCallable blockingCallable = new BlockingCallable(); - ListenableFuture unused = serializer.submit(blockingCallable, executor); + BlockingCallable blockingCallable = new BlockingCallable(); + ListenableFuture<@Nullable Void> unused = serializer.submit(blockingCallable, executor); ListenableFuture future2 = serializer.submit( new Callable() { @@ -112,14 +116,13 @@ public Boolean call() { // Stop the first task. The second task should then run. blockingCallable.stop(); executor.shutdown(); - assertThat(executor.awaitTermination(10, TimeUnit.SECONDS)).isTrue(); + assertThat(executor.awaitTermination(10, SECONDS)).isTrue(); assertThat(getDone(future2)).isFalse(); } - public void testSecondTaskWaitsForFirstEvenIfCancelled() throws Exception { - final BlockingCallable blockingCallable = new BlockingCallable(); - ListenableFuture future1 = serializer.submit(blockingCallable, executor); + BlockingCallable blockingCallable = new BlockingCallable(); + ListenableFuture<@Nullable Void> future1 = serializer.submit(blockingCallable, executor); ListenableFuture future2 = serializer.submit( new Callable() { @@ -145,22 +148,23 @@ public Boolean call() { // Stop the first task. The second task should then run. blockingCallable.stop(); executor.shutdown(); - assertThat(executor.awaitTermination(10, TimeUnit.SECONDS)).isTrue(); + assertThat(executor.awaitTermination(10, SECONDS)).isTrue(); assertThat(getDone(future2)).isFalse(); } + @J2ktIncompatible @GwtIncompatible @J2ObjCIncompatible // gc @AndroidIncompatible public void testCancellationWithReferencedObject() throws Exception { Object toBeGCed = new Object(); WeakReference ref = new WeakReference<>(toBeGCed); - final SettableFuture settableFuture = SettableFuture.create(); + SettableFuture<@Nullable Void> settableFuture = SettableFuture.create(); ListenableFuture ignored = serializer.submitAsync( - new AsyncCallable() { + new AsyncCallable<@Nullable Void>() { @Override - public ListenableFuture call() { + public ListenableFuture<@Nullable Void> call() { return settableFuture; } }, @@ -170,7 +174,7 @@ public ListenableFuture call() { GcFinalization.awaitClear(ref); } - private static Callable toStringCallable(final Object object) { + private static Callable toStringCallable(Object object) { return new Callable() { @Override public String call() { @@ -184,7 +188,7 @@ public void testCancellationDuringReentrancy() throws Exception { Logger.getLogger(AbstractFuture.class.getName()).addHandler(logHandler); List> results = new ArrayList<>(); - final Runnable[] manualExecutorTask = new Runnable[1]; + Runnable[] manualExecutorTask = new Runnable[1]; Executor manualExecutor = new Executor() { @Override @@ -194,12 +198,12 @@ public void execute(Runnable task) { }; results.add(serializer.submit(Callables.returning(null), manualExecutor)); - final Future[] thingToCancel = new Future[1]; + Future[] thingToCancel = new Future[1]; results.add( serializer.submit( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() { + public @Nullable Void call() { thingToCancel[0].cancel(false); return null; } @@ -225,31 +229,31 @@ public Void call() { } public void testAvoidsStackOverflow_manySubmitted() throws Exception { - final SettableFuture settableFuture = SettableFuture.create(); - ArrayList> results = new ArrayList<>(50_001); + SettableFuture<@Nullable Void> settableFuture = SettableFuture.create(); + ArrayList> results = new ArrayList<>(50_001); results.add( serializer.submitAsync( - new AsyncCallable() { + new AsyncCallable<@Nullable Void>() { @Override - public ListenableFuture call() { + public ListenableFuture<@Nullable Void> call() { return settableFuture; } }, directExecutor())); for (int i = 0; i < 50_000; i++) { - results.add(serializer.submit(Callables.returning(null), directExecutor())); + results.add(serializer.submit(Callables.returning(null), directExecutor())); } settableFuture.set(null); getDone(allAsList(results)); } public void testAvoidsStackOverflow_manyCancelled() throws Exception { - final SettableFuture settableFuture = SettableFuture.create(); - ListenableFuture unused = + SettableFuture<@Nullable Void> settableFuture = SettableFuture.create(); + ListenableFuture<@Nullable Void> unused = serializer.submitAsync( - new AsyncCallable() { + new AsyncCallable<@Nullable Void>() { @Override - public ListenableFuture call() { + public ListenableFuture<@Nullable Void> call() { return settableFuture; } }, @@ -272,19 +276,19 @@ public Integer call() { } public void testAvoidsStackOverflow_alternatingCancelledAndSubmitted() throws Exception { - final SettableFuture settableFuture = SettableFuture.create(); - ListenableFuture unused = + SettableFuture<@Nullable Void> settableFuture = SettableFuture.create(); + ListenableFuture<@Nullable Void> unused = serializer.submitAsync( - new AsyncCallable() { + new AsyncCallable<@Nullable Void>() { @Override - public ListenableFuture call() { + public ListenableFuture<@Nullable Void> call() { return settableFuture; } }, directExecutor()); for (int i = 0; i < 25_000; i++) { serializer.submit(Callables.returning(null), directExecutor()).cancel(true); - unused = serializer.submit(Callables.returning(null), directExecutor()); + unused = serializer.submit(Callables.returning(null), directExecutor()); } ListenableFuture stackDepthCheck = serializer.submit( @@ -300,25 +304,6 @@ public Integer call() { .isLessThan(Thread.currentThread().getStackTrace().length + 100); } - private static Function add(final int delta) { - return new Function() { - @Override - public Integer apply(Integer input) { - return input + delta; - } - }; - } - - private static AsyncCallable asyncAdd( - final ListenableFuture future, final int delta, final Executor executor) { - return new AsyncCallable() { - @Override - public ListenableFuture call() throws Exception { - return Futures.transform(future, add(delta), executor); - } - }; - } - private static final class LongHolder { long count; } @@ -326,14 +311,14 @@ private static final class LongHolder { private static final int ITERATION_COUNT = 50_000; private static final int DIRECT_EXECUTIONS_PER_THREAD = 100; + @J2ktIncompatible @GwtIncompatible // threads - public void testAvoidsStackOverflow_multipleThreads() throws Exception { - final LongHolder holder = new LongHolder(); - final ArrayList> lengthChecks = new ArrayList<>(); - final List completeLengthChecks; - final int baseStackDepth; - ExecutorService service = Executors.newFixedThreadPool(5); + LongHolder holder = new LongHolder(); + ArrayList> lengthChecks = new ArrayList<>(); + List completeLengthChecks; + int baseStackDepth; + ExecutorService service = newFixedThreadPool(5); try { // Avoid counting frames from the executor itself, or the ExecutionSequencer baseStackDepth = @@ -347,12 +332,12 @@ public Integer call() { }, service) .get(); - final SettableFuture settableFuture = SettableFuture.create(); + SettableFuture<@Nullable Void> settableFuture = SettableFuture.create(); ListenableFuture unused = serializer.submitAsync( - new AsyncCallable() { + new AsyncCallable<@Nullable Void>() { @Override - public ListenableFuture call() { + public ListenableFuture<@Nullable Void> call() { return settableFuture; } }, @@ -362,9 +347,9 @@ public ListenableFuture call() { // after some number of iterations, switch threads unused = serializer.submit( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() { + public @Nullable Void call() { holder.count++; return null; } @@ -386,9 +371,9 @@ public Integer call() { // Otherwise, schedule a task on directExecutor unused = serializer.submit( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() { + public @Nullable Void call() { holder.count++; return null; } @@ -411,7 +396,7 @@ public Void call() { @SuppressWarnings("ObjectToString") // Intended behavior public void testToString() { Future unused = serializer.submitAsync(firstCallable, directExecutor()); - TestCallable secondCallable = new TestCallable(SettableFuture.create()); + TestCallable secondCallable = new TestCallable(SettableFuture.create()); Future second = serializer.submitAsync(secondCallable, directExecutor()); assertThat(secondCallable.called).isFalse(); assertThat(second.toString()).contains(secondCallable.toString()); @@ -419,14 +404,14 @@ public void testToString() { assertThat(second.toString()).contains(secondCallable.future.toString()); } - private static class BlockingCallable implements Callable { + private static class BlockingCallable implements Callable<@Nullable Void> { private final CountDownLatch startLatch = new CountDownLatch(1); private final CountDownLatch stopLatch = new CountDownLatch(1); private volatile boolean running = false; @Override - public Void call() throws InterruptedException { + public @Nullable Void call() throws InterruptedException { running = true; startLatch.countDown(); stopLatch.await(); @@ -434,30 +419,30 @@ public Void call() throws InterruptedException { return null; } - public void waitForStart() throws InterruptedException { + void waitForStart() throws InterruptedException { startLatch.await(); } - public void stop() { + void stop() { stopLatch.countDown(); } - public boolean isRunning() { + boolean isRunning() { return running; } } - private static final class TestCallable implements AsyncCallable { + private static final class TestCallable implements AsyncCallable<@Nullable Void> { - private final ListenableFuture future; + private final ListenableFuture<@Nullable Void> future; private boolean called = false; - private TestCallable(ListenableFuture future) { + private TestCallable(ListenableFuture<@Nullable Void> future) { this.future = future; } @Override - public ListenableFuture call() throws Exception { + public ListenableFuture<@Nullable Void> call() throws Exception { called = true; return future; } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FakeTimeLimiterTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FakeTimeLimiterTest.java index a0e0634695ed..6df360c409ae 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FakeTimeLimiterTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FakeTimeLimiterTest.java @@ -17,17 +17,20 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertThrows; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link FakeTimeLimiter}. * * @author Jens Nyman */ +@NullUnmarked public class FakeTimeLimiterTest extends TestCase { private static final int DELAY_MS = 50; @@ -43,66 +46,62 @@ protected void setUp() throws Exception { public void testCallWithTimeout_propagatesReturnValue() throws Exception { String result = - timeLimiter.callWithTimeout( - Callables.returning(RETURN_VALUE), DELAY_MS, TimeUnit.MILLISECONDS); + timeLimiter.callWithTimeout(Callables.returning(RETURN_VALUE), DELAY_MS, MILLISECONDS); assertThat(result).isEqualTo(RETURN_VALUE); } public void testCallWithTimeout_wrapsCheckedException() throws Exception { Exception exception = new SampleCheckedException(); - try { - timeLimiter.callWithTimeout(callableThrowing(exception), DELAY_MS, TimeUnit.MILLISECONDS); - fail("Expected ExecutionException"); - } catch (ExecutionException e) { - assertThat(e.getCause()).isEqualTo(exception); - } + ExecutionException e = + assertThrows( + ExecutionException.class, + () -> timeLimiter.callWithTimeout(callableThrowing(exception), DELAY_MS, MILLISECONDS)); + assertThat(e).hasCauseThat().isEqualTo(exception); } public void testCallWithTimeout_wrapsUncheckedException() throws Exception { Exception exception = new RuntimeException("test"); - try { - timeLimiter.callWithTimeout(callableThrowing(exception), DELAY_MS, TimeUnit.MILLISECONDS); - fail("Expected UncheckedExecutionException"); - } catch (UncheckedExecutionException e) { - assertThat(e.getCause()).isEqualTo(exception); - } + UncheckedExecutionException e = + assertThrows( + UncheckedExecutionException.class, + () -> timeLimiter.callWithTimeout(callableThrowing(exception), DELAY_MS, MILLISECONDS)); + assertThat(e).hasCauseThat().isEqualTo(exception); } public void testCallUninterruptiblyWithTimeout_propagatesReturnValue() throws Exception { String result = timeLimiter.callUninterruptiblyWithTimeout( - Callables.returning(RETURN_VALUE), DELAY_MS, TimeUnit.MILLISECONDS); + Callables.returning(RETURN_VALUE), DELAY_MS, MILLISECONDS); assertThat(result).isEqualTo(RETURN_VALUE); } public void testRunWithTimeout_returnsWithoutException() throws Exception { - timeLimiter.runWithTimeout(Runnables.doNothing(), DELAY_MS, TimeUnit.MILLISECONDS); + timeLimiter.runWithTimeout(Runnables.doNothing(), DELAY_MS, MILLISECONDS); } public void testRunWithTimeout_wrapsUncheckedException() throws Exception { RuntimeException exception = new RuntimeException("test"); - try { - timeLimiter.runWithTimeout(runnableThrowing(exception), DELAY_MS, TimeUnit.MILLISECONDS); - fail("Expected UncheckedExecutionException"); - } catch (UncheckedExecutionException e) { - assertThat(e.getCause()).isEqualTo(exception); - } + UncheckedExecutionException e = + assertThrows( + UncheckedExecutionException.class, + () -> timeLimiter.runWithTimeout(runnableThrowing(exception), DELAY_MS, MILLISECONDS)); + assertThat(e).hasCauseThat().isEqualTo(exception); } public void testRunUninterruptiblyWithTimeout_wrapsUncheckedException() throws Exception { RuntimeException exception = new RuntimeException("test"); - try { - timeLimiter.runUninterruptiblyWithTimeout( - runnableThrowing(exception), DELAY_MS, TimeUnit.MILLISECONDS); - fail("Expected UncheckedExecutionException"); - } catch (UncheckedExecutionException e) { - assertThat(e.getCause()).isEqualTo(exception); - } + UncheckedExecutionException e = + assertThrows( + UncheckedExecutionException.class, + () -> + timeLimiter.runUninterruptiblyWithTimeout( + runnableThrowing(exception), DELAY_MS, MILLISECONDS)); + assertThat(e).hasCauseThat().isEqualTo(exception); } - public static Callable callableThrowing(final Exception exception) { + public static Callable callableThrowing(Exception exception) { return new Callable() { @Override public T call() throws Exception { @@ -111,7 +110,7 @@ public T call() throws Exception { }; } - private static Runnable runnableThrowing(final RuntimeException e) { + private static Runnable runnableThrowing(RuntimeException e) { return new Runnable() { @Override public void run() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FluentFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FluentFutureTest.java index cc4751d0f217..90b037d87ffd 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FluentFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FluentFutureTest.java @@ -23,29 +23,34 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; /** * Tests for {@link FluentFuture}. The tests cover only the basics for the API. The actual logic is * tested in {@link FuturesTest}. */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class FluentFutureTest extends TestCase { + @SuppressWarnings({"deprecation", "InlineMeInliner"}) // test of a deprecated method public void testFromFluentFuture() { - FluentFuture f = FluentFuture.from(SettableFuture.create()); + FluentFuture f = FluentFuture.from(SettableFuture.create()); assertThat(FluentFuture.from(f)).isSameInstanceAs(f); } public void testFromFluentFuturePassingAsNonFluent() { - ListenableFuture f = FluentFuture.from(SettableFuture.create()); + ListenableFuture f = FluentFuture.from(SettableFuture.create()); assertThat(FluentFuture.from(f)).isSameInstanceAs(f); } @@ -59,7 +64,7 @@ public void testFromNonFluentFuture() throws Exception { public void testAddCallback() { FluentFuture f = FluentFuture.from(immediateFuture("a")); - final boolean[] called = new boolean[1]; + boolean[] called = new boolean[1]; f.addCallback( new FutureCallback() { @Override @@ -74,9 +79,12 @@ public void onFailure(Throwable t) {} assertThat(called[0]).isTrue(); } + // Avoid trouble with automatic mapping between JRE and Kotlin runtime classes. + static class CustomRuntimeException extends RuntimeException {} + public void testCatching() throws Exception { FluentFuture f = - FluentFuture.from(immediateFailedFuture(new RuntimeException())) + FluentFuture.from(immediateFailedFuture(new CustomRuntimeException())) .catching( Throwable.class, new Function>() { @@ -86,22 +94,22 @@ public Class apply(Throwable input) { } }, directExecutor()); - assertThat(f.get()).isEqualTo(RuntimeException.class); + assertThat(f.get()).isEqualTo(CustomRuntimeException.class); } public void testCatchingAsync() throws Exception { FluentFuture f = - FluentFuture.from(immediateFailedFuture(new RuntimeException())) + FluentFuture.from(immediateFailedFuture(new CustomRuntimeException())) .catchingAsync( Throwable.class, new AsyncFunction>() { @Override public ListenableFuture> apply(Throwable input) { - return Futures.>immediateFuture(input.getClass()); + return immediateFuture(input.getClass()); } }, directExecutor()); - assertThat(f.get()).isEqualTo(RuntimeException.class); + assertThat(f.get()).isEqualTo(CustomRuntimeException.class); } public void testTransform() throws Exception { @@ -132,19 +140,15 @@ public ListenableFuture apply(Integer input) { assertThat(f.get()).isEqualTo(2); } - + @J2ktIncompatible @GwtIncompatible // withTimeout public void testWithTimeout() throws Exception { ScheduledExecutorService executor = newScheduledThreadPool(1); try { FluentFuture f = FluentFuture.from(SettableFuture.create()).withTimeout(0, SECONDS, executor); - try { - f.get(); - fail(); - } catch (ExecutionException e) { - assertThat(e).hasCauseThat().isInstanceOf(TimeoutException.class); - } + ExecutionException e = assertThrows(ExecutionException.class, () -> f.get()); + assertThat(e).hasCauseThat().isInstanceOf(TimeoutException.class); } finally { executor.shutdown(); } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingBlockingDequeTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingBlockingDequeTest.java index 35cecee71e2a..002ad8581176 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingBlockingDequeTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingBlockingDequeTest.java @@ -17,12 +17,14 @@ package com.google.common.util.concurrent; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test for {@link ForwardingBlockingDeque} * * @author Emily Soldal */ +@NullUnmarked public class ForwardingBlockingDequeTest extends TestCase { public void testForwarding() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingBlockingQueueTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingBlockingQueueTest.java index eda852f6cedc..0c7c1b4c45b1 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingBlockingQueueTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingBlockingQueueTest.java @@ -17,8 +17,10 @@ package com.google.common.util.concurrent; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link ForwardingBlockingQueue} */ +@NullUnmarked public class ForwardingBlockingQueueTest extends TestCase { public void testForwarding() { ForwardingObjectTester.testForwardingObject(ForwardingBlockingQueue.class); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingExecutorServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingExecutorServiceTest.java index ccd3eb8000e9..d39465f575b2 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingExecutorServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingExecutorServiceTest.java @@ -16,11 +16,60 @@ package com.google.common.util.concurrent; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static com.google.common.truth.Truth.assertThat; +import static java.lang.Integer.parseInt; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.lang.reflect.Method; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link ForwardingExecutorService} */ +@NullUnmarked public class ForwardingExecutorServiceTest extends TestCase { public void testForwarding() { ForwardingObjectTester.testForwardingObject(ForwardingExecutorService.class); } + + public void testNoForwardingOfDefaultMethod() throws Exception { + ExecutorService delegate = + new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, SECONDS, new SynchronousQueue<>()) { + @Override + public void close() { + throw new AssertionError( + "ForwardingExecutorService should have used the default method" + + " ExecutorService.close() (which would forward to methods like shutdown() on" + + " the delegate) instead of forwarding to delegate.close()"); + } + }; + ExecutorService wrapper = + new ForwardingExecutorService() { + @Override + protected ExecutorService delegate() { + return delegate; + } + }; + Method closeMethod; + try { + closeMethod = wrapper.getClass().getMethod("close"); + } catch (NoSuchMethodException e) { + assertThat(isAndroid() || isBeforeJava19()).isTrue(); + return; // close() doesn't exist, so we can't test it. + } + closeMethod.invoke(wrapper); + assertThat(delegate.isTerminated()).isTrue(); + } + + private static boolean isAndroid() { + return System.getProperty("java.runtime.name", "").contains("Android"); + } + + private static boolean isBeforeJava19() { + return JAVA_SPECIFICATION_VERSION.value().equals("1.8") + || parseInt(JAVA_SPECIFICATION_VERSION.value()) < 19; + } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingFutureTest.java index f5d282a141f9..eaf8e964ff70 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingFutureTest.java @@ -17,8 +17,10 @@ package com.google.common.util.concurrent; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link ForwardingFuture} */ +@NullUnmarked public class ForwardingFutureTest extends TestCase { public void testForwarding() { ForwardingObjectTester.testForwardingObject(ForwardingFuture.class); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingListenableFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingListenableFutureTest.java index 827f50ea55d1..435d17d49d1d 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingListenableFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingListenableFutureTest.java @@ -17,12 +17,14 @@ package com.google.common.util.concurrent; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link ForwardingListenableFuture}. * * @author Ben Yu */ +@NullUnmarked public class ForwardingListenableFutureTest extends TestCase { public void testForwarding() { ForwardingObjectTester.testForwardingObject(ForwardingListenableFuture.class); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingListeningExecutorServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingListeningExecutorServiceTest.java index ecfa7ddb32bb..1eabe021238d 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingListeningExecutorServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingListeningExecutorServiceTest.java @@ -17,8 +17,10 @@ package com.google.common.util.concurrent; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link ForwardingListeningExecutorService} */ +@NullUnmarked public class ForwardingListeningExecutorServiceTest extends TestCase { public void testForwarding() { ForwardingObjectTester.testForwardingObject(ForwardingListeningExecutorService.class); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingObjectTester.java b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingObjectTester.java index 021d96de7436..96e268aaf40d 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingObjectTester.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingObjectTester.java @@ -25,13 +25,17 @@ import com.google.common.collect.Iterables; import com.google.common.testing.ForwardingWrapperTester; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import org.jspecify.annotations.NullUnmarked; /** - * Tester for typical subclass of {@link ForwardingObject} by using EasyMock partial mocks. + * Tester for typical subclass of {@link ForwardingObject} by using Mockito. * * @author Ben Yu */ +@NullUnmarked final class ForwardingObjectTester { private static final Method DELEGATE_METHOD; @@ -51,17 +55,19 @@ final class ForwardingObjectTester { * Ensures that all interface methods of {@code forwarderClass} are forwarded to the {@link * ForwardingObject#delegate}. {@code forwarderClass} is assumed to only implement one interface. */ - static void testForwardingObject(final Class forwarderClass) { + static void testForwardingObject(Class forwarderClass) { + List> interfaces = new ArrayList<>(Arrays.asList(forwarderClass.getInterfaces())); + // Desugaring may introduce AutoCloseable as an extra interface. + interfaces.remove(AutoCloseable.class); @SuppressWarnings("unchecked") // super interface type of T - Class interfaceType = - (Class) Iterables.getOnlyElement(Arrays.asList(forwarderClass.getInterfaces())); + Class interfaceType = (Class) Iterables.getOnlyElement(interfaces); new ForwardingWrapperTester() .testForwarding( interfaceType, new Function() { @Override public T apply(Object delegate) { - T mock = mock(forwarderClass, CALLS_REAL_METHODS.get()); + T mock = mock(forwarderClass, CALLS_REAL_METHODS); try { T stubber = doReturn(delegate).when(mock); DELEGATE_METHOD.invoke(stubber); @@ -72,4 +78,6 @@ public T apply(Object delegate) { } }); } + + private ForwardingObjectTester() {} } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingObjectTesterTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingObjectTesterTest.java index 51a0842af36d..d96ef1e2ddbe 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ForwardingObjectTesterTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ForwardingObjectTesterTest.java @@ -18,12 +18,14 @@ import com.google.common.collect.ForwardingObject; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link ForwardingObjectTester}. * * @author Ben Yu */ +@NullUnmarked public class ForwardingObjectTesterTest extends TestCase { public void testFailsToForward() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FutureCallbackTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FutureCallbackTest.java index 4febc5a8dcc5..dc5ce43d3263 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FutureCallbackTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FutureCallbackTest.java @@ -19,21 +19,23 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.Futures.addCallback; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; -import com.google.common.annotations.GwtIncompatible; +import com.google.common.util.concurrent.TestExceptions.SomeError; import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; -import org.mockito.Mockito; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Test for {@link FutureCallback}. * * @author Anthony Zana */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class FutureCallbackTest extends TestCase { public void testSameThreadSuccess() { SettableFuture f = SettableFuture.create(); @@ -64,6 +66,7 @@ public void testCancel() { SettableFuture f = SettableFuture.create(); FutureCallback callback = new FutureCallback() { + private final Object monitor = new Object(); private boolean called = false; @Override @@ -72,10 +75,12 @@ public void onSuccess(String result) { } @Override - public synchronized void onFailure(Throwable t) { - assertFalse(called); - assertThat(t).isInstanceOf(CancellationException.class); - called = true; + public void onFailure(Throwable t) { + synchronized (monitor) { + assertFalse(called); + assertThat(t).isInstanceOf(CancellationException.class); + called = true; + } } }; addCallback(f, callback, directExecutor()); @@ -89,56 +94,73 @@ public void testThrowErrorFromGet() { addCallback(f, callback, directExecutor()); } - public void testRuntimeExeceptionFromGet() { + public void testRuntimeExceptionFromGet() { RuntimeException e = new IllegalArgumentException("foo not found"); ListenableFuture f = UncheckedThrowingFuture.throwingRuntimeException(e); MockCallback callback = new MockCallback(e); addCallback(f, callback, directExecutor()); } - @GwtIncompatible // Mockito public void testOnSuccessThrowsRuntimeException() throws Exception { RuntimeException exception = new RuntimeException(); String result = "result"; SettableFuture future = SettableFuture.create(); - @SuppressWarnings("unchecked") // Safe for a mock - FutureCallback callback = Mockito.mock(FutureCallback.class); + int[] successCalls = new int[1]; + int[] failureCalls = new int[1]; + FutureCallback callback = + new FutureCallback() { + @Override + public void onSuccess(String result) { + successCalls[0]++; + throw exception; + } + + @Override + public void onFailure(Throwable t) { + failureCalls[0]++; + } + }; addCallback(future, callback, directExecutor()); - Mockito.doThrow(exception).when(callback).onSuccess(result); future.set(result); assertEquals(result, future.get()); - Mockito.verify(callback).onSuccess(result); - Mockito.verifyNoMoreInteractions(callback); + assertThat(successCalls[0]).isEqualTo(1); + assertThat(failureCalls[0]).isEqualTo(0); } - @GwtIncompatible // Mockito public void testOnSuccessThrowsError() throws Exception { - class TestError extends Error {} - TestError error = new TestError(); + SomeError error = new SomeError(); String result = "result"; SettableFuture future = SettableFuture.create(); - @SuppressWarnings("unchecked") // Safe for a mock - FutureCallback callback = Mockito.mock(FutureCallback.class); + int[] successCalls = new int[1]; + int[] failureCalls = new int[1]; + FutureCallback callback = + new FutureCallback() { + @Override + public void onSuccess(String result) { + successCalls[0]++; + throw error; + } + + @Override + public void onFailure(Throwable t) { + failureCalls[0]++; + } + }; addCallback(future, callback, directExecutor()); - Mockito.doThrow(error).when(callback).onSuccess(result); - try { - future.set(result); - fail("Should have thrown"); - } catch (TestError e) { - assertSame(error, e); - } + SomeError e = assertThrows(SomeError.class, () -> future.set(result)); + assertSame(error, e); assertEquals(result, future.get()); - Mockito.verify(callback).onSuccess(result); - Mockito.verifyNoMoreInteractions(callback); + assertThat(successCalls[0]).isEqualTo(1); + assertThat(failureCalls[0]).isEqualTo(0); } public void testWildcardFuture() { SettableFuture settable = SettableFuture.create(); ListenableFuture f = settable; - FutureCallback callback = - new FutureCallback() { + FutureCallback<@Nullable Object> callback = + new FutureCallback<@Nullable Object>() { @Override - public void onSuccess(Object result) {} + public void onSuccess(@Nullable Object result) {} @Override public void onFailure(Throwable t) {} @@ -157,30 +179,35 @@ public void execute(Runnable command) { } private final class MockCallback implements FutureCallback { - @NullableDecl private String value = null; - @NullableDecl private Throwable failure = null; + @Nullable private String value = null; + @Nullable private Throwable failure = null; private boolean wasCalled = false; + private final Object monitor = new Object(); MockCallback(String expectedValue) { this.value = expectedValue; } - public MockCallback(Throwable expectedFailure) { + MockCallback(Throwable expectedFailure) { this.failure = expectedFailure; } @Override - public synchronized void onSuccess(String result) { - assertFalse(wasCalled); - wasCalled = true; - assertEquals(value, result); + public void onSuccess(String result) { + synchronized (monitor) { + assertFalse(wasCalled); + wasCalled = true; + assertEquals(value, result); + } } @Override public synchronized void onFailure(Throwable t) { - assertFalse(wasCalled); - wasCalled = true; - assertEquals(failure, t); + synchronized (monitor) { + assertFalse(wasCalled); + wasCalled = true; + assertEquals(failure, t); + } } } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetCheckedInputs.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetCheckedInputs.java index 27916d8a1005..71708beca24c 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetCheckedInputs.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetCheckedInputs.java @@ -18,11 +18,14 @@ import com.google.common.annotations.GwtCompatible; import java.util.concurrent.Future; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Classes and futures used in {@link FuturesGetCheckedTest} and {@link FuturesGetUncheckedTest}. */ @GwtCompatible +@NullUnmarked final class FuturesGetCheckedInputs { static final Exception CHECKED_EXCEPTION = new Exception("mymessage"); static final Future FAILED_FUTURE_CHECKED_EXCEPTION = @@ -58,6 +61,47 @@ private ExceptionWithPrivateConstructor(String message, Throwable cause) { } } + public static final class ExceptionWithManyConstructorsButOnlyOneThrowable extends Exception { + private @Nullable Throwable antecedent; + + public ExceptionWithManyConstructorsButOnlyOneThrowable(String message, String a1) { + super(message); + } + + public ExceptionWithManyConstructorsButOnlyOneThrowable(String message, String a1, String a2) { + super(message); + } + + public ExceptionWithManyConstructorsButOnlyOneThrowable( + String message, String a1, String a2, String a3) { + super(message); + } + + public ExceptionWithManyConstructorsButOnlyOneThrowable(String message, Throwable antecedent) { + super(message); + this.antecedent = antecedent; + } + + public ExceptionWithManyConstructorsButOnlyOneThrowable( + String message, String a1, String a2, String a3, String a4) { + super(message); + } + + public ExceptionWithManyConstructorsButOnlyOneThrowable( + String message, String a1, String a2, String a3, String a4, String a5) { + super(message); + } + + public ExceptionWithManyConstructorsButOnlyOneThrowable( + String message, String a1, String a2, String a3, String a4, String a5, String a6) { + super(message); + } + + public Throwable getAntecedent() { + return antecedent; + } + } + @SuppressWarnings("unused") // we're testing that they're not used public static final class ExceptionWithSomePrivateConstructors extends Exception { private ExceptionWithSomePrivateConstructors(String a) {} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetCheckedTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetCheckedTest.java index 72b5a46564b6..7a9e818df755 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetCheckedTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetCheckedTest.java @@ -30,11 +30,13 @@ import static com.google.common.util.concurrent.FuturesGetCheckedInputs.RUNTIME_EXCEPTION_FUTURE; import static com.google.common.util.concurrent.FuturesGetCheckedInputs.UNCHECKED_EXCEPTION; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.testing.GcFinalization; import com.google.common.util.concurrent.FuturesGetCheckedInputs.ExceptionWithBadConstructor; import com.google.common.util.concurrent.FuturesGetCheckedInputs.ExceptionWithGoodAndBadConstructor; import com.google.common.util.concurrent.FuturesGetCheckedInputs.ExceptionWithManyConstructors; +import com.google.common.util.concurrent.FuturesGetCheckedInputs.ExceptionWithManyConstructorsButOnlyOneThrowable; import com.google.common.util.concurrent.FuturesGetCheckedInputs.ExceptionWithPrivateConstructor; import com.google.common.util.concurrent.FuturesGetCheckedInputs.ExceptionWithSomePrivateConstructors; import com.google.common.util.concurrent.FuturesGetCheckedInputs.ExceptionWithWrongTypesConstructor; @@ -45,11 +47,12 @@ import java.net.URLClassLoader; import java.util.concurrent.CancellationException; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link Futures#getChecked(Future, Class)}. */ +@NullUnmarked public class FuturesGetCheckedTest extends TestCase { // Boring untimed-get tests: @@ -74,60 +77,52 @@ public void testGetCheckedUntimed_interrupted() { public void testGetCheckedUntimed_cancelled() throws TwoArgConstructorException { SettableFuture future = SettableFuture.create(); future.cancel(true); - try { - getChecked(future, TwoArgConstructorException.class); - fail(); - } catch (CancellationException expected) { - } + assertThrows( + CancellationException.class, () -> getChecked(future, TwoArgConstructorException.class)); } - public void testGetCheckedUntimed_ExecutionExceptionChecked() { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, TwoArgConstructorException.class); - fail(); - } catch (TwoArgConstructorException expected) { - assertThat(expected).hasCauseThat().isEqualTo(CHECKED_EXCEPTION); - } + public void testGetCheckedUntimed_executionExceptionChecked() { + TwoArgConstructorException expected = + assertThrows( + TwoArgConstructorException.class, + () -> getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, TwoArgConstructorException.class)); + assertThat(expected).hasCauseThat().isEqualTo(CHECKED_EXCEPTION); } - public void testGetCheckedUntimed_ExecutionExceptionUnchecked() + public void testGetCheckedUntimed_executionExceptionUnchecked() throws TwoArgConstructorException { - try { - getChecked(FAILED_FUTURE_UNCHECKED_EXCEPTION, TwoArgConstructorException.class); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isEqualTo(UNCHECKED_EXCEPTION); - } + UncheckedExecutionException expected = + assertThrows( + UncheckedExecutionException.class, + () -> getChecked(FAILED_FUTURE_UNCHECKED_EXCEPTION, TwoArgConstructorException.class)); + assertThat(expected).hasCauseThat().isEqualTo(UNCHECKED_EXCEPTION); } - public void testGetCheckedUntimed_ExecutionExceptionError() throws TwoArgConstructorException { - try { - getChecked(FAILED_FUTURE_ERROR, TwoArgConstructorException.class); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isEqualTo(ERROR); - } + public void testGetCheckedUntimed_executionExceptionError() throws TwoArgConstructorException { + ExecutionError expected = + assertThrows( + ExecutionError.class, + () -> getChecked(FAILED_FUTURE_ERROR, TwoArgConstructorException.class)); + assertThat(expected).hasCauseThat().isEqualTo(ERROR); } - public void testGetCheckedUntimed_ExecutionExceptionOtherThrowable() { - try { - getChecked(FAILED_FUTURE_OTHER_THROWABLE, TwoArgConstructorException.class); - fail(); - } catch (TwoArgConstructorException expected) { - assertThat(expected).hasCauseThat().isEqualTo(OTHER_THROWABLE); - } + public void testGetCheckedUntimed_executionExceptionOtherThrowable() { + TwoArgConstructorException expected = + assertThrows( + TwoArgConstructorException.class, + () -> getChecked(FAILED_FUTURE_OTHER_THROWABLE, TwoArgConstructorException.class)); + assertThat(expected).hasCauseThat().isEqualTo(OTHER_THROWABLE); } - public void testGetCheckedUntimed_RuntimeException() throws TwoArgConstructorException { - try { - getChecked(RUNTIME_EXCEPTION_FUTURE, TwoArgConstructorException.class); - fail(); - } catch (RuntimeException expected) { - assertEquals(RUNTIME_EXCEPTION, expected); - } + public void testGetCheckedUntimed_runtimeException() throws TwoArgConstructorException { + RuntimeException expected = + assertThrows( + RuntimeException.class, + () -> getChecked(RUNTIME_EXCEPTION_FUTURE, TwoArgConstructorException.class)); + assertEquals(RUNTIME_EXCEPTION, expected); } - public void testGetCheckedUntimed_Error() throws TwoArgConstructorException { + public void testGetCheckedUntimed_error() throws TwoArgConstructorException { try { getChecked(ERROR_FUTURE, TwoArgConstructorException.class); } catch (Error expected) { @@ -139,29 +134,26 @@ public void testGetCheckedUntimed_Error() throws TwoArgConstructorException { public void testGetCheckedUntimed_badExceptionConstructor_failsEvenForSuccessfulInput() throws Exception { - try { - getChecked(immediateFuture("x"), ExceptionWithBadConstructor.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> getChecked(immediateFuture("x"), ExceptionWithBadConstructor.class)); } public void testGetCheckedUntimed_badExceptionConstructor_wrapsOriginalChecked() throws Exception { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithBadConstructor.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithBadConstructor.class)); } public void testGetCheckedUntimed_withGoodAndBadExceptionConstructor() throws Exception { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithGoodAndBadConstructor.class); - fail(); - } catch (ExceptionWithGoodAndBadConstructor expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(CHECKED_EXCEPTION); - } + ExceptionWithGoodAndBadConstructor expected = + assertThrows( + ExceptionWithGoodAndBadConstructor.class, + () -> + getChecked( + FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithGoodAndBadConstructor.class)); + assertThat(expected).hasCauseThat().isSameInstanceAs(CHECKED_EXCEPTION); } // Boring timed-get tests: @@ -188,59 +180,62 @@ public void testGetCheckedTimed_interrupted() { public void testGetCheckedTimed_cancelled() throws TwoArgConstructorException { SettableFuture future = SettableFuture.create(); future.cancel(true); - try { - getChecked(future, TwoArgConstructorException.class, 0, SECONDS); - fail(); - } catch (CancellationException expected) { - } - } - - public void testGetCheckedTimed_ExecutionExceptionChecked() { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, TwoArgConstructorException.class, 0, SECONDS); - fail(); - } catch (TwoArgConstructorException expected) { - assertThat(expected).hasCauseThat().isEqualTo(CHECKED_EXCEPTION); - } - } - - public void testGetCheckedTimed_ExecutionExceptionUnchecked() throws TwoArgConstructorException { - try { - getChecked(FAILED_FUTURE_UNCHECKED_EXCEPTION, TwoArgConstructorException.class, 0, SECONDS); - fail(); - } catch (UncheckedExecutionException expected) { - assertThat(expected).hasCauseThat().isEqualTo(UNCHECKED_EXCEPTION); - } - } - - public void testGetCheckedTimed_ExecutionExceptionError() throws TwoArgConstructorException { - try { - getChecked(FAILED_FUTURE_ERROR, TwoArgConstructorException.class, 0, SECONDS); - fail(); - } catch (ExecutionError expected) { - assertThat(expected).hasCauseThat().isEqualTo(ERROR); - } - } - - public void testGetCheckedTimed_ExecutionExceptionOtherThrowable() { - try { - getChecked(FAILED_FUTURE_OTHER_THROWABLE, TwoArgConstructorException.class, 0, SECONDS); - fail(); - } catch (TwoArgConstructorException expected) { - assertThat(expected).hasCauseThat().isEqualTo(OTHER_THROWABLE); - } - } - - public void testGetCheckedTimed_RuntimeException() throws TwoArgConstructorException { - try { - getChecked(RUNTIME_EXCEPTION_FUTURE, TwoArgConstructorException.class, 0, SECONDS); - fail(); - } catch (RuntimeException expected) { - assertEquals(RUNTIME_EXCEPTION, expected); - } - } - - public void testGetCheckedTimed_Error() throws TwoArgConstructorException { + assertThrows( + CancellationException.class, + () -> getChecked(future, TwoArgConstructorException.class, 0, SECONDS)); + } + + public void testGetCheckedTimed_executionExceptionChecked() { + TwoArgConstructorException expected = + assertThrows( + TwoArgConstructorException.class, + () -> + getChecked( + FAILED_FUTURE_CHECKED_EXCEPTION, TwoArgConstructorException.class, 0, SECONDS)); + assertThat(expected).hasCauseThat().isEqualTo(CHECKED_EXCEPTION); + } + + public void testGetCheckedTimed_executionExceptionUnchecked() throws TwoArgConstructorException { + UncheckedExecutionException expected = + assertThrows( + UncheckedExecutionException.class, + () -> + getChecked( + FAILED_FUTURE_UNCHECKED_EXCEPTION, + TwoArgConstructorException.class, + 0, + SECONDS)); + assertThat(expected).hasCauseThat().isEqualTo(UNCHECKED_EXCEPTION); + } + + public void testGetCheckedTimed_executionExceptionError() throws TwoArgConstructorException { + ExecutionError expected = + assertThrows( + ExecutionError.class, + () -> getChecked(FAILED_FUTURE_ERROR, TwoArgConstructorException.class, 0, SECONDS)); + assertThat(expected).hasCauseThat().isEqualTo(ERROR); + } + + public void testGetCheckedTimed_executionExceptionOtherThrowable() { + TwoArgConstructorException expected = + assertThrows( + TwoArgConstructorException.class, + () -> + getChecked( + FAILED_FUTURE_OTHER_THROWABLE, TwoArgConstructorException.class, 0, SECONDS)); + assertThat(expected).hasCauseThat().isEqualTo(OTHER_THROWABLE); + } + + public void testGetCheckedTimed_runtimeException() throws TwoArgConstructorException { + RuntimeException expected = + assertThrows( + RuntimeException.class, + () -> + getChecked(RUNTIME_EXCEPTION_FUTURE, TwoArgConstructorException.class, 0, SECONDS)); + assertEquals(RUNTIME_EXCEPTION, expected); + } + + public void testGetCheckedTimed_error() throws TwoArgConstructorException { try { getChecked(ERROR_FUTURE, TwoArgConstructorException.class, 0, SECONDS); } catch (Error expected) { @@ -250,110 +245,113 @@ public void testGetCheckedTimed_Error() throws TwoArgConstructorException { fail(); } - public void testGetCheckedTimed_TimeoutException() { + public void testGetCheckedTimed_timeoutException() { SettableFuture future = SettableFuture.create(); - try { - getChecked(future, TwoArgConstructorException.class, 0, SECONDS); - fail(); - } catch (TwoArgConstructorException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(TimeoutException.class); - } + TwoArgConstructorException expected = + assertThrows( + TwoArgConstructorException.class, + () -> getChecked(future, TwoArgConstructorException.class, 0, SECONDS)); + assertThat(expected).hasCauseThat().isInstanceOf(TimeoutException.class); } public void testGetCheckedTimed_badExceptionConstructor_failsEvenForSuccessfulInput() throws Exception { - try { - getChecked(immediateFuture("x"), ExceptionWithBadConstructor.class, 1, TimeUnit.SECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> getChecked(immediateFuture("x"), ExceptionWithBadConstructor.class, 1, SECONDS)); } public void testGetCheckedTimed_badExceptionConstructor_wrapsOriginalChecked() throws Exception { - try { - getChecked( - FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithBadConstructor.class, 1, TimeUnit.SECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + getChecked( + FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithBadConstructor.class, 1, SECONDS)); } public void testGetCheckedTimed_withGoodAndBadExceptionConstructor() { - try { - getChecked( - FAILED_FUTURE_CHECKED_EXCEPTION, - ExceptionWithGoodAndBadConstructor.class, - 1, - TimeUnit.SECONDS); - fail(); - } catch (ExceptionWithGoodAndBadConstructor expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(CHECKED_EXCEPTION); - } + ExceptionWithGoodAndBadConstructor expected = + assertThrows( + ExceptionWithGoodAndBadConstructor.class, + () -> + getChecked( + FAILED_FUTURE_CHECKED_EXCEPTION, + ExceptionWithGoodAndBadConstructor.class, + 1, + SECONDS)); + assertThat(expected).hasCauseThat().isSameInstanceAs(CHECKED_EXCEPTION); } // Edge case tests of the exception-construction code through untimed get(): @SuppressWarnings("FuturesGetCheckedIllegalExceptionType") public void testGetCheckedUntimed_exceptionClassIsRuntimeException() { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, TwoArgConstructorRuntimeException.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, TwoArgConstructorRuntimeException.class)); } public void testGetCheckedUntimed_exceptionClassSomePrivateConstructors() { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithSomePrivateConstructors.class); - fail(); - } catch (ExceptionWithSomePrivateConstructors expected) { - } + assertThrows( + ExceptionWithSomePrivateConstructors.class, + () -> + getChecked( + FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithSomePrivateConstructors.class)); } @SuppressWarnings("FuturesGetCheckedIllegalExceptionType") public void testGetCheckedUntimed_exceptionClassNoPublicConstructor() throws ExceptionWithPrivateConstructor { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithPrivateConstructor.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithPrivateConstructor.class)); } @SuppressWarnings("FuturesGetCheckedIllegalExceptionType") public void testGetCheckedUntimed_exceptionClassPublicConstructorWrongType() throws ExceptionWithWrongTypesConstructor { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithWrongTypesConstructor.class); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> + getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithWrongTypesConstructor.class)); } public void testGetCheckedUntimed_exceptionClassPrefersStringConstructor() { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithManyConstructors.class); - fail(); - } catch (ExceptionWithManyConstructors expected) { - assertTrue(expected.usedExpectedConstructor); - } + ExceptionWithManyConstructors expected = + assertThrows( + ExceptionWithManyConstructors.class, + () -> getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithManyConstructors.class)); + assertTrue(expected.usedExpectedConstructor); } public void testGetCheckedUntimed_exceptionClassUsedInitCause() { - try { - getChecked(FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithoutThrowableConstructor.class); - fail(); - } catch (ExceptionWithoutThrowableConstructor expected) { - assertThat(expected).hasMessageThat().contains("mymessage"); - assertThat(expected).hasCauseThat().isEqualTo(CHECKED_EXCEPTION); - } + ExceptionWithoutThrowableConstructor expected = + assertThrows( + ExceptionWithoutThrowableConstructor.class, + () -> + getChecked( + FAILED_FUTURE_CHECKED_EXCEPTION, ExceptionWithoutThrowableConstructor.class)); + assertThat(expected).hasMessageThat().contains("mymessage"); + assertThat(expected).hasCauseThat().isEqualTo(CHECKED_EXCEPTION); + } + + public void testPrefersConstructorWithThrowableParameter() { + ExceptionWithManyConstructorsButOnlyOneThrowable exception = + assertThrows( + ExceptionWithManyConstructorsButOnlyOneThrowable.class, + () -> + getChecked( + FAILED_FUTURE_CHECKED_EXCEPTION, + ExceptionWithManyConstructorsButOnlyOneThrowable.class)); + assertThat(exception).hasMessageThat().contains("mymessage"); + assertThat(exception.getAntecedent()).isEqualTo(CHECKED_EXCEPTION); } // Class unloading test: public static final class WillBeUnloadedException extends Exception {} - + @AndroidIncompatible // "Parent ClassLoader may not be null"; maybe avoidable if we try? public void testGetChecked_classUnloading() throws Exception { WeakReference classUsedByGetChecked = doTestClassUnloading(); GcFinalization.awaitClear(classUsedByGetChecked); @@ -381,5 +379,8 @@ private WeakReference doTestClassUnloading() throws Exception { * environment that forces Futures.getChecked to its fallback WeakSetValidator. One awful way of * doing so would be to derive a separate test library by using remove_from_jar to strip out * ClassValueValidator. + * + * Fortunately, we get pretty good coverage "by accident": We run all these tests against the + * *backport*, where ClassValueValidator is not present. */ } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetDoneTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetDoneTest.java index cf9cb9df36b3..9a8f7d65b652 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetDoneTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetDoneTest.java @@ -19,46 +19,39 @@ import static com.google.common.util.concurrent.Futures.immediateCancelledFuture; import static com.google.common.util.concurrent.Futures.immediateFailedFuture; import static com.google.common.util.concurrent.Futures.immediateFuture; +import static com.google.common.util.concurrent.Futures.immediateVoidFuture; +import static com.google.common.util.concurrent.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link Futures#getDone}. */ @GwtCompatible +@NullUnmarked public class FuturesGetDoneTest extends TestCase { public void testSuccessful() throws ExecutionException { assertThat(getDone(immediateFuture("a"))).isEqualTo("a"); } public void testSuccessfulNull() throws ExecutionException { - assertThat(getDone(immediateFuture((String) null))).isEqualTo(null); + assertThat(getDone(immediateVoidFuture())).isEqualTo(null); } public void testFailed() { Exception failureCause = new Exception(); - try { - getDone(immediateFailedFuture(failureCause)); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isEqualTo(failureCause); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(immediateFailedFuture(failureCause))); + assertThat(expected).hasCauseThat().isEqualTo(failureCause); } public void testCancelled() throws ExecutionException { - try { - getDone(immediateCancelledFuture()); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> getDone(immediateCancelledFuture())); } public void testPending() throws ExecutionException { - try { - getDone(SettableFuture.create()); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> getDone(SettableFuture.create())); } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetUncheckedTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetUncheckedTest.java index 93baaf651230..c98a9c37cae3 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetUncheckedTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesGetUncheckedTest.java @@ -14,6 +14,7 @@ package com.google.common.util.concurrent; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.Futures.getUnchecked; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.FuturesGetCheckedInputs.CHECKED_EXCEPTION; @@ -27,20 +28,25 @@ import static com.google.common.util.concurrent.FuturesGetCheckedInputs.RUNTIME_EXCEPTION; import static com.google.common.util.concurrent.FuturesGetCheckedInputs.RUNTIME_EXCEPTION_FUTURE; import static com.google.common.util.concurrent.FuturesGetCheckedInputs.UNCHECKED_EXCEPTION; +import static com.google.common.util.concurrent.ReflectionFreeAssertThrows.assertThrows; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.concurrent.CancellationException; import java.util.concurrent.Future; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link Futures#getUnchecked(Future)}. */ -@GwtCompatible(emulated = true) +@GwtCompatible +@NullUnmarked public class FuturesGetUncheckedTest extends TestCase { public void testGetUnchecked_success() { assertEquals("foo", getUnchecked(immediateFuture("foo"))); } + @J2ktIncompatible @GwtIncompatible // Thread.interrupt public void testGetUnchecked_interrupted() { Thread.currentThread().interrupt(); @@ -55,59 +61,44 @@ public void testGetUnchecked_interrupted() { public void testGetUnchecked_cancelled() { SettableFuture future = SettableFuture.create(); future.cancel(true); - try { - getUnchecked(future); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> getUnchecked(future)); } - public void testGetUnchecked_ExecutionExceptionChecked() { - try { - getUnchecked(FAILED_FUTURE_CHECKED_EXCEPTION); - fail(); - } catch (UncheckedExecutionException expected) { - assertEquals(CHECKED_EXCEPTION, expected.getCause()); - } + public void testGetUnchecked_executionExceptionChecked() { + UncheckedExecutionException expected = + assertThrows( + UncheckedExecutionException.class, () -> getUnchecked(FAILED_FUTURE_CHECKED_EXCEPTION)); + assertThat(expected).hasCauseThat().isEqualTo(CHECKED_EXCEPTION); } - public void testGetUnchecked_ExecutionExceptionUnchecked() { - try { - getUnchecked(FAILED_FUTURE_UNCHECKED_EXCEPTION); - fail(); - } catch (UncheckedExecutionException expected) { - assertEquals(UNCHECKED_EXCEPTION, expected.getCause()); - } + public void testGetUnchecked_executionExceptionUnchecked() { + UncheckedExecutionException expected = + assertThrows( + UncheckedExecutionException.class, + () -> getUnchecked(FAILED_FUTURE_UNCHECKED_EXCEPTION)); + assertThat(expected).hasCauseThat().isEqualTo(UNCHECKED_EXCEPTION); } - public void testGetUnchecked_ExecutionExceptionError() { - try { - getUnchecked(FAILED_FUTURE_ERROR); - fail(); - } catch (ExecutionError expected) { - assertEquals(ERROR, expected.getCause()); - } + public void testGetUnchecked_executionExceptionError() { + ExecutionError expected = + assertThrows(ExecutionError.class, () -> getUnchecked(FAILED_FUTURE_ERROR)); + assertThat(expected).hasCauseThat().isEqualTo(ERROR); } - public void testGetUnchecked_ExecutionExceptionOtherThrowable() { - try { - getUnchecked(FAILED_FUTURE_OTHER_THROWABLE); - fail(); - } catch (UncheckedExecutionException expected) { - assertEquals(OTHER_THROWABLE, expected.getCause()); - } + public void testGetUnchecked_executionExceptionOtherThrowable() { + UncheckedExecutionException expected = + assertThrows( + UncheckedExecutionException.class, () -> getUnchecked(FAILED_FUTURE_OTHER_THROWABLE)); + assertThat(expected).hasCauseThat().isEqualTo(OTHER_THROWABLE); } - public void testGetUnchecked_RuntimeException() { - try { - getUnchecked(RUNTIME_EXCEPTION_FUTURE); - fail(); - } catch (RuntimeException expected) { - assertEquals(RUNTIME_EXCEPTION, expected); - } + public void testGetUnchecked_runtimeException() { + RuntimeException expected = + assertThrows(RuntimeException.class, () -> getUnchecked(RUNTIME_EXCEPTION_FUTURE)); + assertEquals(RUNTIME_EXCEPTION, expected); } - public void testGetUnchecked_Error() { + public void testGetUnchecked_error() { try { getUnchecked(ERROR_FUTURE); } catch (Error expected) { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java index fa3b14a26830..9464866df0c7 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java @@ -44,6 +44,7 @@ import static com.google.common.util.concurrent.Futures.whenAllComplete; import static com.google.common.util.concurrent.Futures.whenAllSucceed; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.util.concurrent.TestPlatform.clearInterrupt; import static com.google.common.util.concurrent.TestPlatform.getDoneFromTimeoutOverload; import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; @@ -53,10 +54,12 @@ import static java.util.concurrent.Executors.newSingleThreadExecutor; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; @@ -66,10 +69,13 @@ import com.google.common.testing.ClassSanityTester; import com.google.common.testing.GcFinalization; import com.google.common.testing.TestLogHandler; +import com.google.common.util.concurrent.TestExceptions.SomeError; +import com.google.common.util.concurrent.TestExceptions.SomeUncheckedException; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; @@ -78,7 +84,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; @@ -89,14 +94,16 @@ import java.util.logging.Logger; import junit.framework.AssertionFailedError; import junit.framework.TestCase; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Unit tests for {@link Futures}. * * @author Nishant Thakkar */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class FuturesTest extends TestCase { private static final Logger aggregateFutureLogger = Logger.getLogger(AggregateFuture.class.getName()); @@ -140,7 +147,7 @@ public void testImmediateFuture() throws Exception { } public void testImmediateVoidFuture() throws Exception { - ListenableFuture voidFuture = immediateVoidFuture(); + ListenableFuture<@Nullable Void> voidFuture = immediateVoidFuture(); assertThat(getDone(voidFuture)).isNull(); assertThat(getDoneFromTimeoutOverload(voidFuture)).isNull(); @@ -152,19 +159,11 @@ public void testImmediateFailedFuture() throws Exception { ListenableFuture future = immediateFailedFuture(exception); assertThat(future.toString()).endsWith("[status=FAILURE, cause=[" + exception + "]]"); - try { - getDone(future); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(future)); + assertSame(exception, expected.getCause()); - try { - getDoneFromTimeoutOverload(future); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } + expected = assertThrows(ExecutionException.class, () -> getDoneFromTimeoutOverload(future)); + assertSame(exception, expected.getCause()); } public void testImmediateFailedFuture_cancellationException() throws Exception { @@ -173,19 +172,11 @@ public void testImmediateFailedFuture_cancellationException() throws Exception { assertFalse(future.isCancelled()); assertThat(future.toString()).endsWith("[status=FAILURE, cause=[" + exception + "]]"); - try { - getDone(future); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(future)); + assertSame(exception, expected.getCause()); - try { - getDoneFromTimeoutOverload(future); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } + expected = assertThrows(ExecutionException.class, () -> getDoneFromTimeoutOverload(future)); + assertSame(exception, expected.getCause()); } public void testImmediateCancelledFutureBasic() throws Exception { @@ -193,29 +184,25 @@ public void testImmediateCancelledFutureBasic() throws Exception { assertTrue(future.isCancelled()); } + @J2ktIncompatible @GwtIncompatible public void testImmediateCancelledFutureStack() throws Exception { ListenableFuture future = CallerClass1.makeImmediateCancelledFuture(); assertTrue(future.isCancelled()); - try { - CallerClass2.get(future); - fail(); - } catch (CancellationException expected) { - // There should be two CancellationException chained together. The outer one should have the - // stack trace of where the get() call was made, and the inner should have the stack trace of - // where the immediateCancelledFuture() call was made. - List stackTrace = ImmutableList.copyOf(expected.getStackTrace()); - assertFalse(Iterables.any(stackTrace, hasClassName(CallerClass1.class))); - assertTrue(Iterables.any(stackTrace, hasClassName(CallerClass2.class))); - - // See AbstractFutureCancellationCauseTest for how to set causes. - assertThat(expected.getCause()).isNull(); - } + CancellationException expected = + assertThrows(CancellationException.class, () -> CallerClass2.get(future)); + List stackTrace = ImmutableList.copyOf(expected.getStackTrace()); + assertFalse(Iterables.any(stackTrace, hasClassName(CallerClass1.class))); + assertTrue(Iterables.any(stackTrace, hasClassName(CallerClass2.class))); + + // See AbstractFutureCancellationCauseTest for how to set causes. + assertThat(expected).hasCauseThat().isNull(); } + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests - private static Predicate hasClassName(final Class clazz) { + private static Predicate hasClassName(Class clazz) { return new Predicate() { @Override public boolean apply(StackTraceElement element) { @@ -248,15 +235,17 @@ private static class Bar {} private static class BarChild extends Bar {} + @J2ktIncompatible // TODO(b/324550390): Enable public void testTransform_genericsNull() throws Exception { - ListenableFuture nullFuture = immediateFuture(null); + ListenableFuture nullFuture = immediateVoidFuture(); ListenableFuture transformedFuture = transform(nullFuture, constant(null), directExecutor()); - assertNull(getDone(transformedFuture)); + assertThat(getDone(transformedFuture)).isNull(); } + @J2ktIncompatible // TODO(b/324550390): Enable public void testTransform_genericsHierarchy() throws Exception { ListenableFuture future = immediateFuture(null); - final BarChild barChild = new BarChild(); + BarChild barChild = new BarChild(); Function function = new Function() { @Override @@ -268,43 +257,28 @@ public BarChild apply(Foo unused) { assertSame(barChild, bar); } - /* - * Android does not handle this stack overflow gracefully... though somehow some other - * stack-overflow tests work. It must depend on the exact place the error occurs. - */ - @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible // StackOverflowError - public void testTransform_StackOverflow() throws Exception { - { - /* - * Initialize all relevant classes before running the test, which may otherwise poison any - * classes it is trying to load during its stack overflow. - */ - SettableFuture root = SettableFuture.create(); - ListenableFuture unused = transform(root, identity(), directExecutor()); - root.set("foo"); - } - - SettableFuture root = SettableFuture.create(); - ListenableFuture output = root; - for (int i = 0; i < 10000; i++) { - output = transform(output, identity(), directExecutor()); - } - try { - root.set("foo"); - fail(); - } catch (StackOverflowError expected) { - } + public void testTransform_stackOverflow() throws Exception { + SettableFuture input = SettableFuture.create(); + ListenableFuture output = transform(input, identity(), directExecutor()); + output.addListener( + () -> { + throw new StackOverflowError(); + }, + directExecutor()); + assertThrows(StackOverflowError.class, () -> input.set("foo")); } - public void testTransform_ErrorAfterCancellation() throws Exception { + public void testTransform_errorAfterCancellation() throws Exception { class Transformer implements Function { + @SuppressWarnings("nullness:initialization.field.uninitialized") ListenableFuture output; @Override public Object apply(Object input) { output.cancel(false); - throw new MyError(); + throw new SomeError(); } } Transformer transformer = new Transformer(); @@ -317,14 +291,15 @@ public Object apply(Object input) { assertTrue(output.isCancelled()); } - public void testTransform_ExceptionAfterCancellation() throws Exception { + public void testTransform_exceptionAfterCancellation() throws Exception { class Transformer implements Function { + @SuppressWarnings("nullness:initialization.field.uninitialized") ListenableFuture output; @Override public Object apply(Object input) { output.cancel(false); - throw new MyRuntimeException(); + throw new SomeUncheckedException(); } } Transformer transformer = new Transformer(); @@ -339,27 +314,19 @@ public Object apply(Object input) { public void testTransform_getThrowsRuntimeException() throws Exception { ListenableFuture input = - UncheckedThrowingFuture.throwingRuntimeException(new MyRuntimeException()); + UncheckedThrowingFuture.throwingRuntimeException(new SomeUncheckedException()); ListenableFuture output = transform(input, identity(), directExecutor()); - try { - getDone(output); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyRuntimeException.class); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(output)); + assertThat(expected).hasCauseThat().isInstanceOf(SomeUncheckedException.class); } public void testTransform_getThrowsError() throws Exception { - ListenableFuture input = UncheckedThrowingFuture.throwingError(new MyError()); + ListenableFuture input = UncheckedThrowingFuture.throwingError(new SomeError()); ListenableFuture output = transform(input, identity(), directExecutor()); - try { - getDone(output); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyError.class); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(output)); + assertThat(expected).hasCauseThat().isInstanceOf(SomeError.class); } public void testTransform_listenerThrowsError() throws Exception { @@ -370,15 +337,11 @@ public void testTransform_listenerThrowsError() throws Exception { new Runnable() { @Override public void run() { - throw new MyError(); + throw new SomeError(); } }, directExecutor()); - try { - input.set("foo"); - fail(); - } catch (MyError expected) { - } + assertThrows(SomeError.class, () -> input.set("foo")); } public void testTransformAsync_cancelPropagatesToInput() throws Exception { @@ -387,7 +350,7 @@ public void testTransformAsync_cancelPropagatesToInput() throws Exception { new AsyncFunction() { @Override public ListenableFuture apply(Foo unused) { - throw new AssertionFailedError("Unexpeted call to apply."); + throw new AssertionFailedError("Unexpected call to apply."); } }; assertTrue(transformAsync(input, function, directExecutor()).cancel(false)); @@ -401,7 +364,7 @@ public void testTransformAsync_interruptPropagatesToInput() throws Exception { new AsyncFunction() { @Override public ListenableFuture apply(Foo unused) { - throw new AssertionFailedError("Unexpeted call to apply."); + throw new AssertionFailedError("Unexpected call to apply."); } }; assertTrue(transformAsync(input, function, directExecutor()).cancel(true)); @@ -409,13 +372,13 @@ public ListenableFuture apply(Foo unused) { assertTrue(input.wasInterrupted()); } + @J2ktIncompatible @GwtIncompatible // threads - public void testTransformAsync_interruptPropagatesToTransformingThread() throws Exception { SettableFuture input = SettableFuture.create(); - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch shouldCompleteFunction = new CountDownLatch(1); - final CountDownLatch gotException = new CountDownLatch(1); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch shouldCompleteFunction = new CountDownLatch(1); + CountDownLatch gotException = new CountDownLatch(1); AsyncFunction function = new AsyncFunction() { @Override @@ -431,27 +394,25 @@ public ListenableFuture apply(String s) throws Exception { } }; - ListenableFuture futureResult = - transformAsync(input, function, newSingleThreadExecutor()); + ExecutorService service = newSingleThreadExecutor(); + ListenableFuture futureResult = transformAsync(input, function, service); input.set("value"); inFunction.await(); futureResult.cancel(true); shouldCompleteFunction.countDown(); - try { - futureResult.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> futureResult.get()); // TODO(cpovirk): implement interruption, updating this test: // https://github.com/google/guava/issues/1989 assertEquals(1, gotException.getCount()); // gotException.await(); + service.shutdown(); + service.awaitTermination(30, SECONDS); } public void testTransformAsync_cancelPropagatesToAsyncOutput() throws Exception { ListenableFuture immediate = immediateFuture(new Foo()); - final SettableFuture secondary = SettableFuture.create(); + SettableFuture secondary = SettableFuture.create(); AsyncFunction function = new AsyncFunction() { @Override @@ -466,7 +427,7 @@ public ListenableFuture apply(Foo unused) { public void testTransformAsync_interruptPropagatesToAsyncOutput() throws Exception { ListenableFuture immediate = immediateFuture(new Foo()); - final SettableFuture secondary = SettableFuture.create(); + SettableFuture secondary = SettableFuture.create(); AsyncFunction function = new AsyncFunction() { @Override @@ -481,7 +442,7 @@ public ListenableFuture apply(Foo unused) { public void testTransformAsync_inputCancelButNotInterruptPropagatesToOutput() throws Exception { SettableFuture f1 = SettableFuture.create(); - final SettableFuture secondary = SettableFuture.create(); + SettableFuture secondary = SettableFuture.create(); AsyncFunction function = new AsyncFunction() { @Override @@ -499,43 +460,28 @@ public ListenableFuture apply(Foo unused) { assertFalse(((AbstractFuture) f2).wasInterrupted()); } - /* - * Android does not handle this stack overflow gracefully... though somehow some other - * stack-overflow tests work. It must depend on the exact place the error occurs. - */ - @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible // StackOverflowError - public void testTransformAsync_StackOverflow() throws Exception { - { - /* - * Initialize all relevant classes before running the test, which may otherwise poison any - * classes it is trying to load during its stack overflow. - */ - SettableFuture root = SettableFuture.create(); - ListenableFuture unused = transformAsync(root, asyncIdentity(), directExecutor()); - root.set("foo"); - } - - SettableFuture root = SettableFuture.create(); - ListenableFuture output = root; - for (int i = 0; i < 10000; i++) { - output = transformAsync(output, asyncIdentity(), directExecutor()); - } - try { - root.set("foo"); - fail(); - } catch (StackOverflowError expected) { - } + public void testTransformAsync_stackOverflow() throws Exception { + SettableFuture input = SettableFuture.create(); + ListenableFuture output = transformAsync(input, asyncIdentity(), directExecutor()); + output.addListener( + () -> { + throw new StackOverflowError(); + }, + directExecutor()); + assertThrows(StackOverflowError.class, () -> input.set("foo")); } - public void testTransformAsync_ErrorAfterCancellation() throws Exception { + public void testTransformAsync_errorAfterCancellation() throws Exception { class Transformer implements AsyncFunction { + @SuppressWarnings("nullness:initialization.field.uninitialized") ListenableFuture output; @Override public ListenableFuture apply(Object input) { output.cancel(false); - throw new MyError(); + throw new SomeError(); } } Transformer transformer = new Transformer(); @@ -548,14 +494,15 @@ public ListenableFuture apply(Object input) { assertTrue(output.isCancelled()); } - public void testTransformAsync_ExceptionAfterCancellation() throws Exception { + public void testTransformAsync_exceptionAfterCancellation() throws Exception { class Transformer implements AsyncFunction { + @SuppressWarnings("nullness:initialization.field.uninitialized") ListenableFuture output; @Override public ListenableFuture apply(Object input) { output.cancel(false); - throw new MyRuntimeException(); + throw new SomeUncheckedException(); } } Transformer transformer = new Transformer(); @@ -570,27 +517,19 @@ public ListenableFuture apply(Object input) { public void testTransformAsync_getThrowsRuntimeException() throws Exception { ListenableFuture input = - UncheckedThrowingFuture.throwingRuntimeException(new MyRuntimeException()); + UncheckedThrowingFuture.throwingRuntimeException(new SomeUncheckedException()); ListenableFuture output = transformAsync(input, asyncIdentity(), directExecutor()); - try { - getDone(output); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyRuntimeException.class); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(output)); + assertThat(expected).hasCauseThat().isInstanceOf(SomeUncheckedException.class); } public void testTransformAsync_getThrowsError() throws Exception { - ListenableFuture input = UncheckedThrowingFuture.throwingError(new MyError()); + ListenableFuture input = UncheckedThrowingFuture.throwingError(new SomeError()); ListenableFuture output = transformAsync(input, asyncIdentity(), directExecutor()); - try { - getDone(output); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyError.class); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(output)); + assertThat(expected).hasCauseThat().isInstanceOf(SomeError.class); } public void testTransformAsync_listenerThrowsError() throws Exception { @@ -601,15 +540,11 @@ public void testTransformAsync_listenerThrowsError() throws Exception { new Runnable() { @Override public void run() { - throw new MyError(); + throw new SomeError(); } }, directExecutor()); - try { - input.set("foo"); - fail(); - } catch (MyError expected) { - } + assertThrows(SomeError.class, () -> input.set("foo")); } public void testTransform_rejectionPropagatesToOutput() throws Exception { @@ -617,12 +552,9 @@ public void testTransform_rejectionPropagatesToOutput() throws Exception { Function identity = identity(); ListenableFuture transformed = transform(input, identity, REJECTING_EXECUTOR); input.set(new Foo()); - try { - getDone(transformed); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(RejectedExecutionException.class); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(transformed)); + assertThat(expected).hasCauseThat().isInstanceOf(RejectedExecutionException.class); } public void testTransformAsync_rejectionPropagatesToOutput() throws Exception { @@ -630,12 +562,9 @@ public void testTransformAsync_rejectionPropagatesToOutput() throws Exception { AsyncFunction asyncIdentity = asyncIdentity(); ListenableFuture transformed = transformAsync(input, asyncIdentity, REJECTING_EXECUTOR); input.set(new Foo()); - try { - getDone(transformed); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(RejectedExecutionException.class); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(transformed)); + assertThat(expected).hasCauseThat().isInstanceOf(RejectedExecutionException.class); } /** Tests that the function is invoked only once, even if it throws an exception. */ @@ -644,7 +573,7 @@ class Holder { int value = 2; } - final Holder holder = new Holder(); + Holder holder = new Holder(); // This function adds the holder's value to the input value. Function adder = @@ -687,14 +616,11 @@ public Integer apply(Integer from) { getDoneFromTimeoutOverload(transform(immediateFuture, adder, directExecutor())).intValue()); } - static class MyError extends Error {} - - static class MyRuntimeException extends RuntimeException {} - /** * Test that the function is invoked only once, even if it throws an exception. Also, test that * that function's result is wrapped in an ExecutionException. */ + @J2ktIncompatible @GwtIncompatible // reflection public void testTransformExceptionRemainsMemoized() throws Throwable { // We need to test with two input futures since ExecutionList.execute @@ -705,14 +631,14 @@ public void testTransformExceptionRemainsMemoized() throws Throwable { ListenableFuture exceptionComposedFuture = transform(exceptionInput, newOneTimeExceptionThrower(), directExecutor()); exceptionInput.set(0); - runGetIdempotencyTest(exceptionComposedFuture, MyRuntimeException.class); + runGetIdempotencyTest(exceptionComposedFuture, SomeUncheckedException.class); SettableFuture errorInput = SettableFuture.create(); ListenableFuture errorComposedFuture = transform(errorInput, newOneTimeErrorThrower(), directExecutor()); errorInput.set(0); - runGetIdempotencyTest(errorComposedFuture, MyError.class); + runGetIdempotencyTest(errorComposedFuture, SomeError.class); /* * Try again when the input's value is already filled in, since the flow is @@ -720,13 +646,14 @@ public void testTransformExceptionRemainsMemoized() throws Throwable { */ exceptionComposedFuture = transform(exceptionInput, newOneTimeExceptionThrower(), directExecutor()); - runGetIdempotencyTest(exceptionComposedFuture, MyRuntimeException.class); + runGetIdempotencyTest(exceptionComposedFuture, SomeUncheckedException.class); runGetIdempotencyTest( - transform(errorInput, newOneTimeErrorThrower(), directExecutor()), MyError.class); - runGetIdempotencyTest(errorComposedFuture, MyError.class); + transform(errorInput, newOneTimeErrorThrower(), directExecutor()), SomeError.class); + runGetIdempotencyTest(errorComposedFuture, SomeError.class); } + @J2ktIncompatible @GwtIncompatible // reflection private static void runGetIdempotencyTest( Future transformedFuture, Class expectedExceptionClass) @@ -743,6 +670,7 @@ private static void runGetIdempotencyTest( } } + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private static Function newOneTimeExceptionThrower() { return new Function() { @@ -753,11 +681,12 @@ public Integer apply(Integer from) { if (++calls > 1) { fail(); } - throw new MyRuntimeException(); + throw new SomeUncheckedException(); } }; } + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private static Function newOneTimeErrorThrower() { return new Function() { @@ -768,7 +697,7 @@ public Integer apply(Integer from) { if (++calls > 1) { fail(); } - throw new MyError(); + throw new SomeError(); } }; } @@ -790,7 +719,7 @@ public void execute(Runnable command) { } } - public void testTransform_Executor() throws Exception { + public void testTransform_executor() throws Exception { Object value = new Object(); ExecutorSpy spy = new ExecutorSpy(directExecutor()); @@ -802,11 +731,11 @@ public void testTransform_Executor() throws Exception { assertTrue(spy.wasExecuted); } + @J2ktIncompatible @GwtIncompatible // Threads - public void testTransformAsync_functionToString() throws Exception { - final CountDownLatch functionCalled = new CountDownLatch(1); - final CountDownLatch functionBlocking = new CountDownLatch(1); + CountDownLatch functionCalled = new CountDownLatch(1); + CountDownLatch functionBlocking = new CountDownLatch(1); AsyncFunction function = tagged( "Called my toString", @@ -819,7 +748,7 @@ public ListenableFuture apply(Object input) throws Exception { } }); - ExecutorService executor = Executors.newSingleThreadExecutor(); + ExecutorService executor = newSingleThreadExecutor(); try { ListenableFuture output = Futures.transformAsync(immediateFuture(null), function, executor); @@ -831,6 +760,7 @@ public ListenableFuture apply(Object input) throws Exception { } } + @J2ktIncompatible @GwtIncompatible // lazyTransform public void testLazyTransform() throws Exception { FunctionSpy spy = new FunctionSpy<>(constant("bar")); @@ -843,9 +773,10 @@ public void testLazyTransform() throws Exception { spy.verifyCallCount(2); } + @J2ktIncompatible @GwtIncompatible // lazyTransform public void testLazyTransform_exception() throws Exception { - final RuntimeException exception = new RuntimeException("deliberate"); + RuntimeException exception = new RuntimeException("deliberate"); Function function = new Function() { @Override @@ -854,25 +785,19 @@ public String apply(Integer input) { } }; Future transformed = lazyTransform(immediateFuture(1), function); - try { - getDone(transformed); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } - try { - getDoneFromTimeoutOverload(transformed); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(transformed)); + assertSame(exception, expected.getCause()); + expected = + assertThrows(ExecutionException.class, () -> getDoneFromTimeoutOverload(transformed)); + assertSame(exception, expected.getCause()); } private static class FunctionSpy implements Function { private int applyCount; private final Function delegate; - public FunctionSpy(Function delegate) { + FunctionSpy(Function delegate) { this.delegate = delegate; } @@ -891,7 +816,7 @@ private static Function unexpectedFunction() { return new Function() { @Override public V apply(X t) { - throw newAssertionError("Unexpected fallback", t); + throw new AssertionError("Unexpected fallback", t); } }; } @@ -900,7 +825,7 @@ private static class AsyncFunctionSpy implements AsyncFu private int count; private final AsyncFunction delegate; - public AsyncFunctionSpy(AsyncFunction delegate) { + AsyncFunctionSpy(AsyncFunction delegate) { this.delegate = delegate; } @@ -927,18 +852,11 @@ private static AsyncFunction unexpectedAsyncFunct return new AsyncFunction() { @Override public ListenableFuture apply(X t) { - throw newAssertionError("Unexpected fallback", t); + throw new AssertionError("Unexpected fallback", t); } }; } - /** Alternative to AssertionError(String, Throwable), which doesn't exist in GWT 2.6.1. */ - private static AssertionError newAssertionError(String message, Throwable cause) { - AssertionError e = new AssertionError(message); - e.initCause(cause); - return e; - } - // catchingAsync tests cloned from the old withFallback tests: public void testCatchingAsync_inputDoesNotRaiseException() throws Exception { @@ -950,7 +868,7 @@ public void testCatchingAsync_inputDoesNotRaiseException() throws Exception { } public void testCatchingAsync_inputRaisesException() throws Exception { - final RuntimeException raisedException = new RuntimeException(); + RuntimeException raisedException = new RuntimeException(); AsyncFunctionSpy fallback = spy( new AsyncFunction() { @@ -967,6 +885,7 @@ public ListenableFuture apply(Throwable t) throws Exception { fallback.verifyCallCount(1); } + @J2ktIncompatible @GwtIncompatible // non-Throwable exceptionType public void testCatchingAsync_inputCancelledWithoutFallback() throws Exception { AsyncFunction fallback = unexpectedAsyncFunction(); @@ -987,7 +906,7 @@ public void testCatchingAsync_fallbackGeneratesCheckedException() throws Excepti } public void testCatchingAsync_fallbackGeneratesError() throws Exception { - final Error error = new Error("deliberate"); + Error error = new Error("deliberate"); AsyncFunction fallback = new AsyncFunction() { @Override @@ -996,12 +915,12 @@ public ListenableFuture apply(Throwable t) throws Exception { } }; ListenableFuture failingFuture = immediateFailedFuture(new RuntimeException()); - try { - getDone(catchingAsync(failingFuture, Throwable.class, fallback, directExecutor())); - fail(); - } catch (ExecutionException expected) { - assertSame(error, expected.getCause()); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> + getDone(catchingAsync(failingFuture, Throwable.class, fallback, directExecutor()))); + assertSame(error, expected.getCause()); } public void testCatchingAsync_fallbackReturnsRuntimeException() throws Exception { @@ -1015,7 +934,7 @@ public void testCatchingAsync_fallbackReturnsCheckedException() throws Exception } private void runExpectedExceptionCatchingAsyncTest( - final Exception expectedException, final boolean wrapInFuture) throws Exception { + Exception expectedException, boolean wrapInFuture) throws Exception { AsyncFunctionSpy fallback = spy( new AsyncFunction() { @@ -1044,7 +963,7 @@ public ListenableFuture apply(Throwable t) throws Exception { public void testCatchingAsync_fallbackNotReady() throws Exception { ListenableFuture primary = immediateFailedFuture(new Exception()); - final SettableFuture secondary = SettableFuture.create(); + SettableFuture secondary = SettableFuture.create(); AsyncFunction fallback = new AsyncFunction() { @Override @@ -1078,12 +997,9 @@ public void testCatchingAsync_resultCancelledBeforeFallback() throws Exception { assertFalse(primary.wasInterrupted()); } - @GwtIncompatible // mocks - // TODO(cpovirk): eliminate use of mocks - @SuppressWarnings("unchecked") public void testCatchingAsync_resultCancelledAfterFallback() throws Exception { - final SettableFuture secondary = SettableFuture.create(); - final RuntimeException raisedException = new RuntimeException(); + SettableFuture secondary = SettableFuture.create(); + RuntimeException raisedException = new RuntimeException(); AsyncFunctionSpy fallback = spy( new AsyncFunction() { @@ -1104,6 +1020,7 @@ public ListenableFuture apply(Throwable t) throws Exception { fallback.verifyCallCount(1); } + @J2ktIncompatible // Nullability public void testCatchingAsync_nullInsteadOfFuture() throws Exception { ListenableFuture inputFuture = immediateFailedFuture(new Exception()); ListenableFuture chainedFuture = @@ -1118,26 +1035,23 @@ public ListenableFuture apply(Throwable t) { } }, directExecutor()); - try { - getDone(chainedFuture); - fail(); - } catch (ExecutionException expected) { - NullPointerException cause = (NullPointerException) expected.getCause(); - assertThat(cause) - .hasMessageThat() - .contains( - "AsyncFunction.apply returned null instead of a Future. " - + "Did you mean to return immediateFuture(null)?"); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(chainedFuture)); + NullPointerException cause = (NullPointerException) expected.getCause(); + assertThat(cause) + .hasMessageThat() + .contains( + "AsyncFunction.apply returned null instead of a Future. " + + "Did you mean to return immediateFuture(null)?"); } + @J2ktIncompatible @GwtIncompatible // threads - public void testCatchingAsync_interruptPropagatesToTransformingThread() throws Exception { SettableFuture input = SettableFuture.create(); - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch shouldCompleteFunction = new CountDownLatch(1); - final CountDownLatch gotException = new CountDownLatch(1); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch shouldCompleteFunction = new CountDownLatch(1); + CountDownLatch gotException = new CountDownLatch(1); AsyncFunction function = new AsyncFunction() { @Override @@ -1153,29 +1067,28 @@ public ListenableFuture apply(Throwable t) throws Exception { } }; + ExecutorService executor = newSingleThreadExecutor(); ListenableFuture futureResult = - catchingAsync(input, Exception.class, function, newSingleThreadExecutor()); + catchingAsync(input, Exception.class, function, executor); input.setException(new Exception()); inFunction.await(); futureResult.cancel(true); shouldCompleteFunction.countDown(); - try { - futureResult.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> futureResult.get()); // TODO(cpovirk): implement interruption, updating this test: // https://github.com/google/guava/issues/1989 assertEquals(1, gotException.getCount()); // gotException.await(); + executor.shutdown(); + executor.awaitTermination(30, SECONDS); } + @J2ktIncompatible @GwtIncompatible // Threads - public void testCatchingAsync_functionToString() throws Exception { - final CountDownLatch functionCalled = new CountDownLatch(1); - final CountDownLatch functionBlocking = new CountDownLatch(1); + CountDownLatch functionCalled = new CountDownLatch(1); + CountDownLatch functionBlocking = new CountDownLatch(1); AsyncFunction function = tagged( "Called my toString", @@ -1188,7 +1101,7 @@ public ListenableFuture apply(Object input) throws Exception { } }); - ExecutorService executor = Executors.newSingleThreadExecutor(); + ExecutorService executor = newSingleThreadExecutor(); try { ListenableFuture output = Futures.catchingAsync( @@ -1202,7 +1115,7 @@ public ListenableFuture apply(Object input) throws Exception { } public void testCatchingAsync_futureToString() throws Exception { - final SettableFuture toReturn = SettableFuture.create(); + SettableFuture toReturn = SettableFuture.create(); AsyncFunction function = tagged( "Called my toString", @@ -1233,7 +1146,7 @@ public void testCatching_inputDoesNotRaiseException() throws Exception { } public void testCatching_inputRaisesException() throws Exception { - final RuntimeException raisedException = new RuntimeException(); + RuntimeException raisedException = new RuntimeException(); FunctionSpy fallback = spy( new Function() { @@ -1250,6 +1163,7 @@ public Integer apply(Throwable t) { fallback.verifyCallCount(1); } + @J2ktIncompatible @GwtIncompatible // non-Throwable exceptionType public void testCatching_inputCancelledWithoutFallback() throws Exception { Function fallback = unexpectedFunction(); @@ -1270,7 +1184,7 @@ public void testCatching_fallbackGeneratesRuntimeException() throws Exception { */ public void testCatching_fallbackGeneratesError() throws Exception { - final Error error = new Error("deliberate"); + Error error = new Error("deliberate"); Function fallback = new Function() { @Override @@ -1279,12 +1193,11 @@ public Integer apply(Throwable t) { } }; ListenableFuture failingFuture = immediateFailedFuture(new RuntimeException()); - try { - getDone(catching(failingFuture, Throwable.class, fallback, directExecutor())); - fail(); - } catch (ExecutionException expected) { - assertSame(error, expected.getCause()); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> getDone(catching(failingFuture, Throwable.class, fallback, directExecutor()))); + assertSame(error, expected.getCause()); } /* @@ -1292,7 +1205,7 @@ public Integer apply(Throwable t) { * or testCatching_fallbackReturnsCheckedException(). */ - private void runExpectedExceptionCatchingTest(final RuntimeException expectedException) + private void runExpectedExceptionCatchingTest(RuntimeException expectedException) throws Exception { FunctionSpy fallback = spy( @@ -1344,7 +1257,7 @@ public void testCatching_resultCancelledBeforeFallback() throws Exception { // Some tests of the exceptionType parameter: - public void testCatching_Throwable() throws Exception { + public void testCatching_throwable() throws Exception { Function fallback = functionReturningOne(); ListenableFuture originalFuture = immediateFailedFuture(new IOException()); ListenableFuture faultTolerantFuture = @@ -1352,6 +1265,7 @@ public void testCatching_Throwable() throws Exception { assertEquals(1, (int) getDone(faultTolerantFuture)); } + @J2ktIncompatible @GwtIncompatible // non-Throwable exceptionType public void testCatching_customTypeMatch() throws Exception { Function fallback = functionReturningOne(); @@ -1361,53 +1275,41 @@ public void testCatching_customTypeMatch() throws Exception { assertEquals(1, (int) getDone(faultTolerantFuture)); } + @J2ktIncompatible @GwtIncompatible // non-Throwable exceptionType public void testCatching_customTypeNoMatch() throws Exception { Function fallback = functionReturningOne(); ListenableFuture originalFuture = immediateFailedFuture(new RuntimeException()); ListenableFuture faultTolerantFuture = catching(originalFuture, IOException.class, fallback, directExecutor()); - try { - getDone(faultTolerantFuture); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(RuntimeException.class); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(faultTolerantFuture)); + assertThat(expected).hasCauseThat().isInstanceOf(RuntimeException.class); } + @J2ktIncompatible @GwtIncompatible // StackOverflowError - public void testCatching_StackOverflow() throws Exception { - { - /* - * Initialize all relevant classes before running the test, which may otherwise poison any - * classes it is trying to load during its stack overflow. - */ - SettableFuture root = SettableFuture.create(); - ListenableFuture unused = - catching(root, MyException.class, identity(), directExecutor()); - root.setException(new MyException()); - } - - SettableFuture root = SettableFuture.create(); - ListenableFuture output = root; - for (int i = 0; i < 10000; i++) { - output = catching(output, MyException.class, identity(), directExecutor()); - } - try { - root.setException(new MyException()); - fail(); - } catch (StackOverflowError expected) { - } + public void testCatching_stackOverflow() throws Exception { + SettableFuture input = SettableFuture.create(); + ListenableFuture output = + catching(input, MyException.class, identity(), directExecutor()); + output.addListener( + () -> { + throw new StackOverflowError(); + }, + directExecutor()); + assertThrows(StackOverflowError.class, () -> input.setException(new MyException())); } - public void testCatching_ErrorAfterCancellation() throws Exception { + public void testCatching_errorAfterCancellation() throws Exception { class Fallback implements Function { + @SuppressWarnings("nullness:initialization.field.uninitialized") ListenableFuture output; @Override public Object apply(Throwable input) { output.cancel(false); - throw new MyError(); + throw new SomeError(); } } Fallback fallback = new Fallback(); @@ -1420,14 +1322,15 @@ public Object apply(Throwable input) { assertTrue(output.isCancelled()); } - public void testCatching_ExceptionAfterCancellation() throws Exception { + public void testCatching_exceptionAfterCancellation() throws Exception { class Fallback implements Function { + @SuppressWarnings("nullness:initialization.field.uninitialized") ListenableFuture output; @Override public Object apply(Throwable input) { output.cancel(false); - throw new MyRuntimeException(); + throw new SomeUncheckedException(); } } Fallback fallback = new Fallback(); @@ -1442,21 +1345,21 @@ public Object apply(Throwable input) { public void testCatching_getThrowsRuntimeException() throws Exception { ListenableFuture input = - UncheckedThrowingFuture.throwingRuntimeException(new MyRuntimeException()); + UncheckedThrowingFuture.throwingRuntimeException(new SomeUncheckedException()); - // We'd catch only MyRuntimeException.class here, but then the test won't compile under GWT. + // We'd catch only SomeUncheckedException.class here, but then the test won't compile under GWT. ListenableFuture output = catching(input, Throwable.class, identity(), directExecutor()); - assertThat(getDone(output)).isInstanceOf(MyRuntimeException.class); + assertThat(getDone(output)).isInstanceOf(SomeUncheckedException.class); } public void testCatching_getThrowsError() throws Exception { - ListenableFuture input = UncheckedThrowingFuture.throwingError(new MyError()); + ListenableFuture input = UncheckedThrowingFuture.throwingError(new SomeError()); - // We'd catch only MyError.class here, but then the test won't compile under GWT. + // We'd catch only SomeError.class here, but then the test won't compile under GWT. ListenableFuture output = catching(input, Throwable.class, identity(), directExecutor()); - assertThat(getDone(output)).isInstanceOf(MyError.class); + assertThat(getDone(output)).isInstanceOf(SomeError.class); } public void testCatching_listenerThrowsError() throws Exception { @@ -1468,18 +1371,14 @@ public void testCatching_listenerThrowsError() throws Exception { new Runnable() { @Override public void run() { - throw new MyError(); + throw new SomeError(); } }, directExecutor()); - try { - input.setException(new MyException()); - fail(); - } catch (MyError expected) { - } + assertThrows(SomeError.class, () -> input.setException(new MyException())); } - public void testCatchingAsync_Throwable() throws Exception { + public void testCatchingAsync_throwable() throws Exception { AsyncFunction fallback = asyncFunctionReturningOne(); ListenableFuture originalFuture = immediateFailedFuture(new IOException()); ListenableFuture faultTolerantFuture = @@ -1487,6 +1386,7 @@ public void testCatchingAsync_Throwable() throws Exception { assertEquals(1, (int) getDone(faultTolerantFuture)); } + @J2ktIncompatible @GwtIncompatible // non-Throwable exceptionType public void testCatchingAsync_customTypeMatch() throws Exception { AsyncFunction fallback = asyncFunctionReturningOne(); @@ -1496,53 +1396,41 @@ public void testCatchingAsync_customTypeMatch() throws Exception { assertEquals(1, (int) getDone(faultTolerantFuture)); } + @J2ktIncompatible @GwtIncompatible // non-Throwable exceptionType public void testCatchingAsync_customTypeNoMatch() throws Exception { AsyncFunction fallback = asyncFunctionReturningOne(); ListenableFuture originalFuture = immediateFailedFuture(new RuntimeException()); ListenableFuture faultTolerantFuture = catchingAsync(originalFuture, IOException.class, fallback, directExecutor()); - try { - getDone(faultTolerantFuture); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(RuntimeException.class); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(faultTolerantFuture)); + assertThat(expected).hasCauseThat().isInstanceOf(RuntimeException.class); } + @J2ktIncompatible @GwtIncompatible // StackOverflowError - public void testCatchingAsync_StackOverflow() throws Exception { - { - /* - * Initialize all relevant classes before running the test, which may otherwise poison any - * classes it is trying to load during its stack overflow. - */ - SettableFuture root = SettableFuture.create(); - ListenableFuture unused = - catchingAsync(root, MyException.class, asyncIdentity(), directExecutor()); - root.setException(new MyException()); - } - - SettableFuture root = SettableFuture.create(); - ListenableFuture output = root; - for (int i = 0; i < 10000; i++) { - output = catchingAsync(output, MyException.class, asyncIdentity(), directExecutor()); - } - try { - root.setException(new MyException()); - fail(); - } catch (StackOverflowError expected) { - } + public void testCatchingAsync_stackOverflow() throws Exception { + SettableFuture input = SettableFuture.create(); + ListenableFuture output = + catchingAsync(input, MyException.class, asyncIdentity(), directExecutor()); + output.addListener( + () -> { + throw new StackOverflowError(); + }, + directExecutor()); + assertThrows(StackOverflowError.class, () -> input.setException(new MyException())); } - public void testCatchingAsync_ErrorAfterCancellation() throws Exception { + public void testCatchingAsync_errorAfterCancellation() throws Exception { class Fallback implements AsyncFunction { + @SuppressWarnings("nullness:initialization.field.uninitialized") ListenableFuture output; @Override public ListenableFuture apply(Throwable input) { output.cancel(false); - throw new MyError(); + throw new SomeError(); } } Fallback fallback = new Fallback(); @@ -1556,14 +1444,15 @@ public ListenableFuture apply(Throwable input) { assertTrue(output.isCancelled()); } - public void testCatchingAsync_ExceptionAfterCancellation() throws Exception { + public void testCatchingAsync_exceptionAfterCancellation() throws Exception { class Fallback implements AsyncFunction { + @SuppressWarnings("nullness:initialization.field.uninitialized") ListenableFuture output; @Override public ListenableFuture apply(Throwable input) { output.cancel(false); - throw new MyRuntimeException(); + throw new SomeUncheckedException(); } } Fallback fallback = new Fallback(); @@ -1579,21 +1468,21 @@ public ListenableFuture apply(Throwable input) { public void testCatchingAsync_getThrowsRuntimeException() throws Exception { ListenableFuture input = - UncheckedThrowingFuture.throwingRuntimeException(new MyRuntimeException()); + UncheckedThrowingFuture.throwingRuntimeException(new SomeUncheckedException()); - // We'd catch only MyRuntimeException.class here, but then the test won't compile under GWT. + // We'd catch only SomeUncheckedException.class here, but then the test won't compile under GWT. ListenableFuture output = catchingAsync(input, Throwable.class, asyncIdentity(), directExecutor()); - assertThat(getDone(output)).isInstanceOf(MyRuntimeException.class); + assertThat(getDone(output)).isInstanceOf(SomeUncheckedException.class); } public void testCatchingAsync_getThrowsError() throws Exception { - ListenableFuture input = UncheckedThrowingFuture.throwingError(new MyError()); + ListenableFuture input = UncheckedThrowingFuture.throwingError(new SomeError()); - // We'd catch only MyError.class here, but then the test won't compile under GWT. + // We'd catch only SomeError.class here, but then the test won't compile under GWT. ListenableFuture output = catchingAsync(input, Throwable.class, asyncIdentity(), directExecutor()); - assertThat(getDone(output)).isInstanceOf(MyError.class); + assertThat(getDone(output)).isInstanceOf(SomeError.class); } public void testCatchingAsync_listenerThrowsError() throws Exception { @@ -1605,15 +1494,11 @@ public void testCatchingAsync_listenerThrowsError() throws Exception { new Runnable() { @Override public void run() { - throw new MyError(); + throw new SomeError(); } }, directExecutor()); - try { - input.setException(new MyException()); - fail(); - } catch (MyError expected) { - } + assertThrows(SomeError.class, () -> input.setException(new MyException())); } public void testCatching_rejectionPropagatesToOutput() throws Exception { @@ -1621,12 +1506,9 @@ public void testCatching_rejectionPropagatesToOutput() throws Exception { ListenableFuture transformed = catching(input, Throwable.class, constant("foo"), REJECTING_EXECUTOR); input.setException(new Exception()); - try { - getDone(transformed); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(RejectedExecutionException.class); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(transformed)); + assertThat(expected).hasCauseThat().isInstanceOf(RejectedExecutionException.class); } public void testCatchingAsync_rejectionPropagatesToOutput() throws Exception { @@ -1638,12 +1520,9 @@ public void testCatchingAsync_rejectionPropagatesToOutput() throws Exception { constantAsyncFunction(immediateFuture("foo")), REJECTING_EXECUTOR); input.setException(new Exception()); - try { - getDone(transformed); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(RejectedExecutionException.class); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(transformed)); + assertThat(expected).hasCauseThat().isInstanceOf(RejectedExecutionException.class); } private Function functionReturningOne() { @@ -1665,7 +1544,7 @@ public ListenableFuture apply(X t) { } private static AsyncFunction constantAsyncFunction( - final ListenableFuture output) { + @Nullable ListenableFuture output) { return new AsyncFunction() { @Override public ListenableFuture apply(I input) { @@ -1674,16 +1553,18 @@ public ListenableFuture apply(I input) { }; } - public void testTransformAsync_genericsWildcard_AsyncFunction() throws Exception { - ListenableFuture nullFuture = immediateFuture(null); + @J2ktIncompatible // Wildcard generics + public void testTransformAsync_genericsWildcard_asyncFunction() throws Exception { + ListenableFuture nullFuture = immediateVoidFuture(); ListenableFuture chainedFuture = transformAsync(nullFuture, constantAsyncFunction(nullFuture), directExecutor()); - assertNull(getDone(chainedFuture)); + assertThat(getDone(chainedFuture)).isNull(); } - public void testTransformAsync_genericsHierarchy_AsyncFunction() throws Exception { + @J2ktIncompatible // TODO(b/324550390): Enable + public void testTransformAsync_genericsHierarchy_asyncFunction() throws Exception { ListenableFuture future = immediateFuture(null); - final BarChild barChild = new BarChild(); + BarChild barChild = new BarChild(); AsyncFunction function = new AsyncFunction() { @Override @@ -1697,21 +1578,18 @@ public AbstractFuture apply(Foo unused) { assertSame(barChild, bar); } + @J2ktIncompatible @GwtIncompatible // get() timeout public void testTransformAsync_asyncFunction_timeout() throws InterruptedException, ExecutionException { AsyncFunction function = constantAsyncFunction(immediateFuture(1)); ListenableFuture future = - transformAsync(SettableFuture.create(), function, directExecutor()); - try { - future.get(1, MILLISECONDS); - fail(); - } catch (TimeoutException expected) { - } + transformAsync(SettableFuture.create(), function, directExecutor()); + assertThrows(TimeoutException.class, () -> future.get(1, MILLISECONDS)); } public void testTransformAsync_asyncFunction_error() throws InterruptedException { - final Error error = new Error("deliberate"); + Error error = new Error("deliberate"); AsyncFunction function = new AsyncFunction() { @Override @@ -1723,38 +1601,33 @@ public ListenableFuture apply(String input) { ListenableFuture outputFuture = transformAsync(inputFuture, function, directExecutor()); inputFuture.set("value"); - try { - getDone(outputFuture); - fail(); - } catch (ExecutionException expected) { - assertSame(error, expected.getCause()); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(outputFuture)); + assertSame(error, expected.getCause()); } + @J2ktIncompatible // Nullability public void testTransformAsync_asyncFunction_nullInsteadOfFuture() throws Exception { ListenableFuture inputFuture = immediateFuture("a"); ListenableFuture chainedFuture = transformAsync(inputFuture, constantAsyncFunction(null), directExecutor()); - try { - getDone(chainedFuture); - fail(); - } catch (ExecutionException expected) { - NullPointerException cause = (NullPointerException) expected.getCause(); - assertThat(cause) - .hasMessageThat() - .contains( - "AsyncFunction.apply returned null instead of a Future. " - + "Did you mean to return immediateFuture(null)?"); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(chainedFuture)); + NullPointerException cause = (NullPointerException) expected.getCause(); + assertThat(cause) + .hasMessageThat() + .contains( + "AsyncFunction.apply returned null instead of a Future. " + + "Did you mean to return immediateFuture(null)?"); } + @J2ktIncompatible @GwtIncompatible // threads - public void testTransformAsync_asyncFunction_cancelledWhileApplyingFunction() throws InterruptedException, ExecutionException { - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch functionDone = new CountDownLatch(1); - final SettableFuture resultFuture = SettableFuture.create(); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch functionDone = new CountDownLatch(1); + SettableFuture resultFuture = SettableFuture.create(); AsyncFunction function = new AsyncFunction() { @Override @@ -1765,29 +1638,23 @@ public ListenableFuture apply(String input) throws Exception { } }; SettableFuture inputFuture = SettableFuture.create(); - ListenableFuture future = - transformAsync(inputFuture, function, newSingleThreadExecutor()); + ExecutorService service = newSingleThreadExecutor(); + ListenableFuture future = transformAsync(inputFuture, function, service); inputFuture.set("value"); inFunction.await(); future.cancel(false); functionDone.countDown(); - try { - future.get(); - fail(); - } catch (CancellationException expected) { - } - try { - resultFuture.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> future.get()); + assertThrows(CancellationException.class, () -> resultFuture.get()); + service.shutdown(); + service.awaitTermination(30, SECONDS); } + @J2ktIncompatible @GwtIncompatible // threads - public void testTransformAsync_asyncFunction_cancelledBeforeApplyingFunction() throws InterruptedException { - final AtomicBoolean functionCalled = new AtomicBoolean(); + AtomicBoolean functionCalled = new AtomicBoolean(); AsyncFunction function = new AsyncFunction() { @Override @@ -1801,7 +1668,7 @@ public ListenableFuture apply(String input) throws Exception { ListenableFuture future = transformAsync(inputFuture, function, executor); // Pause the executor. - final CountDownLatch beforeFunction = new CountDownLatch(1); + CountDownLatch beforeFunction = new CountDownLatch(1); executor.execute( new Runnable() { @Override @@ -1823,7 +1690,7 @@ public void run() { } public void testSubmitAsync_asyncCallable_error() throws InterruptedException { - final Error error = new Error("deliberate"); + Error error = new Error("deliberate"); AsyncCallable callable = new AsyncCallable() { @Override @@ -1834,36 +1701,31 @@ public ListenableFuture call() { SettableFuture inputFuture = SettableFuture.create(); ListenableFuture outputFuture = submitAsync(callable, directExecutor()); inputFuture.set("value"); - try { - getDone(outputFuture); - fail(); - } catch (ExecutionException expected) { - assertSame(error, expected.getCause()); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(outputFuture)); + assertSame(error, expected.getCause()); } + @J2ktIncompatible // TODO(b/324550390): Enable public void testSubmitAsync_asyncCallable_nullInsteadOfFuture() throws Exception { ListenableFuture chainedFuture = submitAsync(constantAsyncCallable(null), directExecutor()); - try { - getDone(chainedFuture); - fail(); - } catch (ExecutionException expected) { - NullPointerException cause = (NullPointerException) expected.getCause(); - assertThat(cause) - .hasMessageThat() - .contains( - "AsyncCallable.call returned null instead of a Future. " - + "Did you mean to return immediateFuture(null)?"); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(chainedFuture)); + NullPointerException cause = (NullPointerException) expected.getCause(); + assertThat(cause) + .hasMessageThat() + .contains( + "AsyncCallable.call returned null instead of a Future. " + + "Did you mean to return immediateFuture(null)?"); } + @J2ktIncompatible @GwtIncompatible // threads - public void testSubmitAsync_asyncCallable_cancelledWhileApplyingFunction() throws InterruptedException, ExecutionException { - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch callableDone = new CountDownLatch(1); - final SettableFuture resultFuture = SettableFuture.create(); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch callableDone = new CountDownLatch(1); + SettableFuture resultFuture = SettableFuture.create(); AsyncCallable callable = new AsyncCallable() { @Override @@ -1874,28 +1736,23 @@ public ListenableFuture call() throws InterruptedException { } }; SettableFuture inputFuture = SettableFuture.create(); - ListenableFuture future = submitAsync(callable, newSingleThreadExecutor()); + ExecutorService service = newSingleThreadExecutor(); + ListenableFuture future = submitAsync(callable, service); inputFuture.set("value"); inFunction.await(); future.cancel(false); callableDone.countDown(); - try { - future.get(); - fail(); - } catch (CancellationException expected) { - } - try { - resultFuture.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> future.get()); + assertThrows(CancellationException.class, () -> resultFuture.get()); + service.shutdown(); + service.awaitTermination(30, SECONDS); } + @J2ktIncompatible @GwtIncompatible // threads - public void testSubmitAsync_asyncCallable_cancelledBeforeApplyingFunction() throws InterruptedException { - final AtomicBoolean callableCalled = new AtomicBoolean(); + AtomicBoolean callableCalled = new AtomicBoolean(); AsyncCallable callable = new AsyncCallable() { @Override @@ -1906,7 +1763,7 @@ public ListenableFuture call() { }; ExecutorService executor = newSingleThreadExecutor(); // Pause the executor. - final CountDownLatch beforeFunction = new CountDownLatch(1); + CountDownLatch beforeFunction = new CountDownLatch(1); executor.execute( new Runnable() { @Override @@ -1925,8 +1782,8 @@ public void run() { assertFalse(callableCalled.get()); } + @J2ktIncompatible @GwtIncompatible // threads - public void testSubmitAsync_asyncCallable_returnsInterruptedFuture() throws InterruptedException { assertThat(Thread.interrupted()).isFalse(); SettableFuture cancelledFuture = SettableFuture.create(); @@ -1952,7 +1809,7 @@ public Integer call() { } public void testSubmit_callable_throwsException() { - final Exception exception = new Exception("Exception for testing"); + Exception exception = new Exception("Exception for testing"); Callable callable = new Callable() { @Override @@ -1961,17 +1818,13 @@ public Integer call() throws Exception { } }; ListenableFuture future = submit(callable, directExecutor()); - try { - getDone(future); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(exception); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(future)); + assertThat(expected).hasCauseThat().isSameInstanceAs(exception); } public void testSubmit_runnable_completesAfterRun() throws Exception { - final List pendingRunnables = newArrayList(); - final List executedRunnables = newArrayList(); + List pendingRunnables = new ArrayList<>(); + List executedRunnables = new ArrayList<>(); Runnable runnable = new Runnable() { @Override @@ -1986,7 +1839,7 @@ public void execute(Runnable runnable) { pendingRunnables.add(runnable); } }; - ListenableFuture future = submit(runnable, executor); + ListenableFuture<@Nullable Void> future = submit(runnable, executor); assertThat(future.isDone()).isFalse(); assertThat(executedRunnables).isEmpty(); assertThat(pendingRunnables).hasSize(1); @@ -1997,7 +1850,7 @@ public void execute(Runnable runnable) { } public void testSubmit_runnable_throwsException() throws Exception { - final RuntimeException exception = new RuntimeException("Exception for testing"); + RuntimeException exception = new RuntimeException("Exception for testing"); Runnable runnable = new Runnable() { @Override @@ -2005,19 +1858,15 @@ public void run() { throw exception; } }; - ListenableFuture future = submit(runnable, directExecutor()); - try { - getDone(future); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isSameInstanceAs(exception); - } + ListenableFuture<@Nullable Void> future = submit(runnable, directExecutor()); + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(future)); + assertThat(expected).hasCauseThat().isSameInstanceAs(exception); } + @J2ktIncompatible @GwtIncompatible // threads - public void testScheduleAsync_asyncCallable_error() throws InterruptedException { - final Error error = new Error("deliberate"); + Error error = new Error("deliberate"); AsyncCallable callable = new AsyncCallable() { @Override @@ -2028,43 +1877,36 @@ public ListenableFuture call() { SettableFuture inputFuture = SettableFuture.create(); ListenableFuture outputFuture = submitAsync(callable, directExecutor()); inputFuture.set("value"); - try { - getDone(outputFuture); - fail(); - } catch (ExecutionException expected) { - assertSame(error, expected.getCause()); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(outputFuture)); + assertSame(error, expected.getCause()); } + @J2ktIncompatible @GwtIncompatible // threads - public void testScheduleAsync_asyncCallable_nullInsteadOfFuture() throws Exception { + ExecutorService service = newSingleThreadScheduledExecutor(); ListenableFuture chainedFuture = scheduleAsync( - constantAsyncCallable(null), - 1, - TimeUnit.NANOSECONDS, - newSingleThreadScheduledExecutor()); - try { - chainedFuture.get(); - fail(); - } catch (ExecutionException expected) { - NullPointerException cause = (NullPointerException) expected.getCause(); - assertThat(cause) - .hasMessageThat() - .contains( - "AsyncCallable.call returned null instead of a Future. " - + "Did you mean to return immediateFuture(null)?"); - } - } - + constantAsyncCallable(null), 1, NANOSECONDS, newSingleThreadScheduledExecutor()); + ExecutionException expected = assertThrows(ExecutionException.class, () -> chainedFuture.get()); + NullPointerException cause = (NullPointerException) expected.getCause(); + assertThat(cause) + .hasMessageThat() + .contains( + "AsyncCallable.call returned null instead of a Future. " + + "Did you mean to return immediateFuture(null)?"); + service.shutdown(); + service.awaitTermination(30, SECONDS); + } + + @J2ktIncompatible @GwtIncompatible // threads - public void testScheduleAsync_asyncCallable_cancelledWhileApplyingFunction() throws InterruptedException, ExecutionException { - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch callableDone = new CountDownLatch(1); - final SettableFuture resultFuture = SettableFuture.create(); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch callableDone = new CountDownLatch(1); + SettableFuture resultFuture = SettableFuture.create(); AsyncCallable callable = new AsyncCallable() { @Override @@ -2074,28 +1916,22 @@ public ListenableFuture call() throws InterruptedException { return resultFuture; } }; - ListenableFuture future = - scheduleAsync(callable, 1, TimeUnit.NANOSECONDS, newSingleThreadScheduledExecutor()); + ScheduledExecutorService service = newSingleThreadScheduledExecutor(); + ListenableFuture future = scheduleAsync(callable, 1, NANOSECONDS, service); inFunction.await(); future.cancel(false); callableDone.countDown(); - try { - future.get(); - fail(); - } catch (CancellationException expected) { - } - try { - resultFuture.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> future.get()); + assertThrows(CancellationException.class, () -> resultFuture.get()); + service.shutdown(); + service.awaitTermination(30, SECONDS); } + @J2ktIncompatible @GwtIncompatible // threads - public void testScheduleAsync_asyncCallable_cancelledBeforeCallingFunction() throws InterruptedException { - final AtomicBoolean callableCalled = new AtomicBoolean(); + AtomicBoolean callableCalled = new AtomicBoolean(); AsyncCallable callable = new AsyncCallable() { @Override @@ -2106,7 +1942,7 @@ public ListenableFuture call() { }; ScheduledExecutorService executor = newSingleThreadScheduledExecutor(); // Pause the executor. - final CountDownLatch beforeFunction = new CountDownLatch(1); + CountDownLatch beforeFunction = new CountDownLatch(1); executor.execute( new Runnable() { @Override @@ -2114,7 +1950,7 @@ public void run() { awaitUninterruptibly(beforeFunction); } }); - ListenableFuture future = scheduleAsync(callable, 1, TimeUnit.NANOSECONDS, executor); + ListenableFuture future = scheduleAsync(callable, 1, NANOSECONDS, executor); future.cancel(false); // Unpause the executor. @@ -2125,7 +1961,8 @@ public void run() { assertFalse(callableCalled.get()); } - private static AsyncCallable constantAsyncCallable(final ListenableFuture returnValue) { + private static AsyncCallable constantAsyncCallable( + @Nullable ListenableFuture returnValue) { return new AsyncCallable() { @Override public ListenableFuture call() { @@ -2148,12 +1985,12 @@ public void run() { called.set(true); } - public void expectCall() { + void expectCall() { assertFalse("expectCall is already true", expectCall); expectCall = true; } - public boolean wasCalled() { + boolean wasCalled() { return called.get(); } } @@ -2163,7 +2000,6 @@ public void testAllAsList() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); SettableFuture future3 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = allAsList(future1, future2, future3); // Attach a listener @@ -2197,7 +2033,6 @@ public void testAllAsList_emptyList() throws Exception { public void testAllAsList_emptyArray() throws Exception { SingleCallListener listener = new SingleCallListener(); listener.expectCall(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = allAsList(); compound.addListener(listener, directExecutor()); assertThat(getDone(compound)).isEmpty(); @@ -2208,7 +2043,6 @@ public void testAllAsList_failure() throws Exception { SingleCallListener listener = new SingleCallListener(); SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = allAsList(future1, future2); compound.addListener(listener, directExecutor()); @@ -2219,12 +2053,8 @@ public void testAllAsList_failure() throws Exception { assertTrue(listener.wasCalled()); assertFalse(future2.isDone()); - try { - getDone(compound); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(compound)); + assertSame(exception, expected.getCause()); } public void testAllAsList_singleFailure() throws Exception { @@ -2232,12 +2062,8 @@ public void testAllAsList_singleFailure() throws Exception { ListenableFuture future = immediateFailedFuture(exception); ListenableFuture> compound = allAsList(ImmutableList.of(future)); - try { - getDone(compound); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(compound)); + assertSame(exception, expected.getCause()); } public void testAllAsList_immediateFailure() throws Exception { @@ -2246,12 +2072,8 @@ public void testAllAsList_immediateFailure() throws Exception { ListenableFuture future2 = immediateFuture("results"); ListenableFuture> compound = allAsList(ImmutableList.of(future1, future2)); - try { - getDone(compound); - fail(); - } catch (ExecutionException expected) { - assertSame(exception, expected.getCause()); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(compound)); + assertSame(exception, expected.getCause()); } public void testAllAsList_error() throws Exception { @@ -2261,19 +2083,14 @@ public void testAllAsList_error() throws Exception { ListenableFuture> compound = allAsList(ImmutableList.of(future1, future2)); future1.setException(error); - try { - getDone(compound); - fail(); - } catch (ExecutionException expected) { - assertSame(error, expected.getCause()); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(compound)); + assertSame(error, expected.getCause()); } public void testAllAsList_cancelled() throws Exception { SingleCallListener listener = new SingleCallListener(); SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = allAsList(future1, future2); compound.addListener(listener, directExecutor()); @@ -2283,17 +2100,12 @@ public void testAllAsList_cancelled() throws Exception { assertTrue(listener.wasCalled()); assertFalse(future2.isDone()); - try { - getDone(compound); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> getDone(compound)); } public void testAllAsList_resultCancelled() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = allAsList(future1, future2); future2.set(DATA2); @@ -2337,7 +2149,6 @@ public void testAllAsList_resultCancelled_withSecondaryListFuture() throws Excep public void testAllAsList_resultInterrupted() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = allAsList(future1, future2); future2.set(DATA2); @@ -2365,7 +2176,6 @@ public void testAllAsList_doneFutures() throws Exception { future2.set(DATA2); future3.set(DATA3); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = allAsList(future1, future2, future3); // Attach a listener @@ -2380,50 +2190,45 @@ public void testAllAsList_doneFutures() throws Exception { } /** A single non-error failure is not logged because it is reported via the output future. */ - @SuppressWarnings("unchecked") public void testAllAsList_logging_exception() throws Exception { - try { - getDone(allAsList(immediateFailedFuture(new MyException()))); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyException.class); - assertEquals( - "Nothing should be logged", 0, aggregateFutureLogHandler.getStoredLogRecords().size()); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> getDone(allAsList(immediateFailedFuture(new MyException())))); + assertThat(expected).hasCauseThat().isInstanceOf(MyException.class); + assertEquals( + "Nothing should be logged", 0, aggregateFutureLogHandler.getStoredLogRecords().size()); } /** Ensure that errors are always logged. */ - @SuppressWarnings("unchecked") public void testAllAsList_logging_error() throws Exception { - try { - getDone(allAsList(immediateFailedFuture(new MyError()))); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyError.class); - List logged = aggregateFutureLogHandler.getStoredLogRecords(); - assertThat(logged).hasSize(1); // errors are always logged - assertThat(logged.get(0).getThrown()).isInstanceOf(MyError.class); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> getDone(allAsList(immediateFailedFuture(new SomeError())))); + assertThat(expected).hasCauseThat().isInstanceOf(SomeError.class); + List logged = aggregateFutureLogHandler.getStoredLogRecords(); + assertThat(logged).hasSize(1); // errors are always logged + assertThat(logged.get(0).getThrown()).isInstanceOf(SomeError.class); } /** All as list will log extra exceptions that have already occurred. */ - @SuppressWarnings("unchecked") public void testAllAsList_logging_multipleExceptions_alreadyDone() throws Exception { - try { - getDone( - allAsList( - immediateFailedFuture(new MyException()), immediateFailedFuture(new MyException()))); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyException.class); - List logged = aggregateFutureLogHandler.getStoredLogRecords(); - assertThat(logged).hasSize(1); // the second failure is logged - assertThat(logged.get(0).getThrown()).isInstanceOf(MyException.class); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> + getDone( + allAsList( + immediateFailedFuture(new MyException()), + immediateFailedFuture(new MyException())))); + assertThat(expected).hasCauseThat().isInstanceOf(MyException.class); + List logged = aggregateFutureLogHandler.getStoredLogRecords(); + assertThat(logged).hasSize(1); // the second failure is logged + assertThat(logged.get(0).getThrown()).isInstanceOf(MyException.class); } /** All as list will log extra exceptions that occur later. */ - @SuppressWarnings("unchecked") public void testAllAsList_logging_multipleExceptions_doneLater() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); @@ -2434,35 +2239,33 @@ public void testAllAsList_logging_multipleExceptions_doneLater() throws Exceptio future2.setException(new MyException()); future3.setException(new MyException()); - try { - getDone(all); - fail(); - } catch (ExecutionException expected) { - List logged = aggregateFutureLogHandler.getStoredLogRecords(); - assertThat(logged).hasSize(2); // failures after the first are logged - assertThat(logged.get(0).getThrown()).isInstanceOf(MyException.class); - assertThat(logged.get(1).getThrown()).isInstanceOf(MyException.class); - } + assertThrows(ExecutionException.class, () -> getDone(all)); + List logged = aggregateFutureLogHandler.getStoredLogRecords(); + assertThat(logged).hasSize(2); // failures after the first are logged + assertThat(logged.get(0).getThrown()).isInstanceOf(MyException.class); + assertThat(logged.get(1).getThrown()).isInstanceOf(MyException.class); } /** The same exception happening on multiple futures should not be logged. */ - @SuppressWarnings("unchecked") public void testAllAsList_logging_same_exception() throws Exception { - try { - MyException sameInstance = new MyException(); - getDone(allAsList(immediateFailedFuture(sameInstance), immediateFailedFuture(sameInstance))); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyException.class); - assertEquals( - "Nothing should be logged", 0, aggregateFutureLogHandler.getStoredLogRecords().size()); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> { + MyException sameInstance = new MyException(); + getDone( + allAsList( + immediateFailedFuture(sameInstance), immediateFailedFuture(sameInstance))); + }); + assertThat(expected).hasCauseThat().isInstanceOf(MyException.class); + assertEquals( + "Nothing should be logged", 0, aggregateFutureLogHandler.getStoredLogRecords().size()); } public void testAllAsList_logging_seenExceptionUpdateRace() throws Exception { - final MyException sameInstance = new MyException(); + MyException sameInstance = new MyException(); SettableFuture firstFuture = SettableFuture.create(); - final SettableFuture secondFuture = SettableFuture.create(); + SettableFuture secondFuture = SettableFuture.create(); ListenableFuture> bulkFuture = allAsList(firstFuture, secondFuture); bulkFuture.addListener( @@ -2480,19 +2283,15 @@ public void run() { directExecutor()); firstFuture.setException(sameInstance); - try { - getDone(bulkFuture); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyException.class); - assertThat(aggregateFutureLogHandler.getStoredLogRecords()).isEmpty(); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(bulkFuture)); + assertThat(expected).hasCauseThat().isInstanceOf(MyException.class); + assertThat(aggregateFutureLogHandler.getStoredLogRecords()).isEmpty(); } public void testAllAsList_logging_seenExceptionUpdateCancelRace() throws Exception { - final MyException subsequentFailure = new MyException(); + MyException subsequentFailure = new MyException(); SettableFuture firstFuture = SettableFuture.create(); - final SettableFuture secondFuture = SettableFuture.create(); + SettableFuture secondFuture = SettableFuture.create(); ListenableFuture> bulkFuture = allAsList(firstFuture, secondFuture); bulkFuture.addListener( @@ -2510,46 +2309,43 @@ public void run() { directExecutor()); firstFuture.cancel(false); - try { - getDone(bulkFuture); - fail(); - } catch (CancellationException expected) { - assertThat(getOnlyElement(aggregateFutureLogHandler.getStoredLogRecords()).getThrown()) - .isSameInstanceAs(subsequentFailure); - } + assertThrows(CancellationException.class, () -> getDone(bulkFuture)); + assertThat(getOnlyElement(aggregateFutureLogHandler.getStoredLogRecords()).getThrown()) + .isSameInstanceAs(subsequentFailure); } /** * Different exceptions happening on multiple futures with the same cause should not be logged. */ - @SuppressWarnings("unchecked") public void testAllAsList_logging_same_cause() throws Exception { - try { - MyException exception1 = new MyException(); - MyException exception2 = new MyException(); - MyException exception3 = new MyException(); - - MyException sameInstance = new MyException(); - exception1.initCause(sameInstance); - exception2.initCause(sameInstance); - exception3.initCause(exception2); - getDone(allAsList(immediateFailedFuture(exception1), immediateFailedFuture(exception3))); - fail(); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(MyException.class); - assertEquals( - "Nothing should be logged", 0, aggregateFutureLogHandler.getStoredLogRecords().size()); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> { + MyException exception1 = new MyException(); + MyException exception2 = new MyException(); + MyException exception3 = new MyException(); + + MyException sameInstance = new MyException(); + exception1.initCause(sameInstance); + exception2.initCause(sameInstance); + exception3.initCause(exception2); + getDone( + allAsList(immediateFailedFuture(exception1), immediateFailedFuture(exception3))); + }); + assertThat(expected).hasCauseThat().isInstanceOf(MyException.class); + assertEquals( + "Nothing should be logged", 0, aggregateFutureLogHandler.getStoredLogRecords().size()); } private static String createCombinedResult(Integer i, Boolean b) { return "-" + i + "-" + b; } + @J2ktIncompatible @GwtIncompatible // threads - public void testWhenAllComplete_noLeakInterruption() throws Exception { - final SettableFuture stringFuture = SettableFuture.create(); + SettableFuture stringFuture = SettableFuture.create(); AsyncCallable combiner = new AsyncCallable() { @Override @@ -2565,6 +2361,7 @@ public ListenableFuture call() throws Exception { assertThat(Thread.interrupted()).isFalse(); } + @J2ktIncompatible // Wildcard generics public void testWhenAllComplete_wildcard() throws Exception { ListenableFuture futureA = immediateFuture("a"); ListenableFuture futureB = immediateFuture("b"); @@ -2590,15 +2387,15 @@ public String call() throws Exception { unused = whenAllComplete(asList(futures)).call(combiner, directExecutor()); } + @J2ktIncompatible @GwtIncompatible // threads - public void testWhenAllComplete_asyncResult() throws Exception { SettableFuture futureInteger = SettableFuture.create(); SettableFuture futureBoolean = SettableFuture.create(); - final ExecutorService executor = newSingleThreadExecutor(); - final CountDownLatch callableBlocking = new CountDownLatch(1); - final SettableFuture resultOfCombiner = SettableFuture.create(); + ExecutorService executor = newSingleThreadExecutor(); + CountDownLatch callableBlocking = new CountDownLatch(1); + SettableFuture resultOfCombiner = SettableFuture.create(); AsyncCallable combiner = tagged( "Called my toString", @@ -2653,10 +2450,10 @@ public ListenableFuture call() throws Exception { } public void testWhenAllComplete_asyncError() throws Exception { - final Exception thrown = new RuntimeException("test"); + Exception thrown = new RuntimeException("test"); - final SettableFuture futureInteger = SettableFuture.create(); - final SettableFuture futureBoolean = SettableFuture.create(); + SettableFuture futureInteger = SettableFuture.create(); + SettableFuture futureBoolean = SettableFuture.create(); AsyncCallable combiner = new AsyncCallable() { @Override @@ -2674,22 +2471,19 @@ public ListenableFuture call() throws Exception { Boolean booleanPartial = true; futureBoolean.set(booleanPartial); - try { - getDone(futureResult); - fail(); - } catch (ExecutionException expected) { - assertSame(thrown, expected.getCause()); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(futureResult)); + assertSame(thrown, expected.getCause()); } + @J2ktIncompatible @GwtIncompatible // threads - public void testWhenAllComplete_cancelledNotInterrupted() throws Exception { SettableFuture stringFuture = SettableFuture.create(); SettableFuture booleanFuture = SettableFuture.create(); - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch shouldCompleteFunction = new CountDownLatch(1); - final SettableFuture resultFuture = SettableFuture.create(); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch shouldCompleteFunction = new CountDownLatch(1); + SettableFuture resultFuture = SettableFuture.create(); AsyncCallable combiner = new AsyncCallable() { @Override @@ -2700,34 +2494,29 @@ public ListenableFuture call() throws Exception { } }; + ExecutorService service = newSingleThreadExecutor(); ListenableFuture futureResult = - whenAllComplete(stringFuture, booleanFuture).callAsync(combiner, newSingleThreadExecutor()); + whenAllComplete(stringFuture, booleanFuture).callAsync(combiner, service); stringFuture.set("value"); booleanFuture.set(true); inFunction.await(); futureResult.cancel(false); shouldCompleteFunction.countDown(); - try { - futureResult.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> futureResult.get()); - try { - resultFuture.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> resultFuture.get()); + service.shutdown(); + service.awaitTermination(30, SECONDS); } + @J2ktIncompatible @GwtIncompatible // threads - public void testWhenAllComplete_interrupted() throws Exception { SettableFuture stringFuture = SettableFuture.create(); SettableFuture booleanFuture = SettableFuture.create(); - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch gotException = new CountDownLatch(1); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch gotException = new CountDownLatch(1); AsyncCallable combiner = new AsyncCallable() { @Override @@ -2743,25 +2532,24 @@ public ListenableFuture call() throws Exception { } }; + ExecutorService service = newSingleThreadExecutor(); ListenableFuture futureResult = - whenAllComplete(stringFuture, booleanFuture).callAsync(combiner, newSingleThreadExecutor()); + whenAllComplete(stringFuture, booleanFuture).callAsync(combiner, service); stringFuture.set("value"); booleanFuture.set(true); inFunction.await(); futureResult.cancel(true); - try { - futureResult.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> futureResult.get()); gotException.await(); + service.shutdown(); + service.awaitTermination(30, SECONDS); } public void testWhenAllComplete_runnableResult() throws Exception { - final SettableFuture futureInteger = SettableFuture.create(); - final SettableFuture futureBoolean = SettableFuture.create(); - final String[] result = new String[1]; + SettableFuture futureInteger = SettableFuture.create(); + SettableFuture futureBoolean = SettableFuture.create(); + String[] result = new String[1]; Runnable combiner = new Runnable() { @Override @@ -2785,10 +2573,10 @@ public void run() { } public void testWhenAllComplete_runnableError() throws Exception { - final RuntimeException thrown = new RuntimeException("test"); + RuntimeException thrown = new RuntimeException("test"); - final SettableFuture futureInteger = SettableFuture.create(); - final SettableFuture futureBoolean = SettableFuture.create(); + SettableFuture futureInteger = SettableFuture.create(); + SettableFuture futureBoolean = SettableFuture.create(); Runnable combiner = new Runnable() { @Override @@ -2806,23 +2594,20 @@ public void run() { Boolean booleanPartial = true; futureBoolean.set(booleanPartial); - try { - getDone(futureResult); - fail(); - } catch (ExecutionException expected) { - assertSame(thrown, expected.getCause()); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(futureResult)); + assertSame(thrown, expected.getCause()); } + @J2ktIncompatible @GwtIncompatible // threads - public void testWhenAllCompleteRunnable_resultCanceledWithoutInterrupt_doesNotInterruptRunnable() throws Exception { SettableFuture stringFuture = SettableFuture.create(); SettableFuture booleanFuture = SettableFuture.create(); - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch shouldCompleteFunction = new CountDownLatch(1); - final CountDownLatch combinerCompletedWithoutInterrupt = new CountDownLatch(1); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch shouldCompleteFunction = new CountDownLatch(1); + CountDownLatch combinerCompletedWithoutInterrupt = new CountDownLatch(1); Runnable combiner = new Runnable() { @Override @@ -2839,30 +2624,29 @@ public void run() { } }; + ExecutorService service = newSingleThreadExecutor(); ListenableFuture futureResult = - whenAllComplete(stringFuture, booleanFuture).run(combiner, newSingleThreadExecutor()); + whenAllComplete(stringFuture, booleanFuture).run(combiner, service); stringFuture.set("value"); booleanFuture.set(true); inFunction.await(); futureResult.cancel(false); shouldCompleteFunction.countDown(); - try { - futureResult.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> futureResult.get()); combinerCompletedWithoutInterrupt.await(); + service.shutdown(); + service.awaitTermination(30, SECONDS); } + @J2ktIncompatible @GwtIncompatible // threads - - public void testWhenAllCompleteRunnable_resultCanceledWithInterrupt_InterruptsRunnable() + public void testWhenAllCompleteRunnable_resultCanceledWithInterrupt_interruptsRunnable() throws Exception { SettableFuture stringFuture = SettableFuture.create(); SettableFuture booleanFuture = SettableFuture.create(); - final CountDownLatch inFunction = new CountDownLatch(1); - final CountDownLatch gotException = new CountDownLatch(1); + CountDownLatch inFunction = new CountDownLatch(1); + CountDownLatch gotException = new CountDownLatch(1); Runnable combiner = new Runnable() { @Override @@ -2878,26 +2662,25 @@ public void run() { } }; + ExecutorService service = newSingleThreadExecutor(); ListenableFuture futureResult = - whenAllComplete(stringFuture, booleanFuture).run(combiner, newSingleThreadExecutor()); + whenAllComplete(stringFuture, booleanFuture).run(combiner, service); stringFuture.set("value"); booleanFuture.set(true); inFunction.await(); futureResult.cancel(true); - try { - futureResult.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> futureResult.get()); gotException.await(); + service.shutdown(); + service.awaitTermination(30, SECONDS); } public void testWhenAllSucceed() throws Exception { class PartialResultException extends Exception {} - final SettableFuture futureInteger = SettableFuture.create(); - final SettableFuture futureBoolean = SettableFuture.create(); + SettableFuture futureInteger = SettableFuture.create(); + SettableFuture futureBoolean = SettableFuture.create(); AsyncCallable combiner = new AsyncCallable() { @Override @@ -2912,15 +2695,13 @@ public ListenableFuture call() throws Exception { futureInteger.setException(partialResultException); Boolean booleanPartial = true; futureBoolean.set(booleanPartial); - try { - getDone(futureResult); - fail(); - } catch (ExecutionException expected) { - assertSame(partialResultException, expected.getCause()); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> getDone(futureResult)); + assertSame(partialResultException, expected.getCause()); } @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible public void testWhenAllSucceed_releasesInputFuturesUponSubmission() throws Exception { SettableFuture future1 = SettableFuture.create(); @@ -2956,6 +2737,7 @@ public Long call() { } @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible public void testWhenAllComplete_releasesInputFuturesUponCancellation() throws Exception { SettableFuture future = SettableFuture.create(); @@ -2979,6 +2761,7 @@ public Long call() { } @AndroidIncompatible + @J2ktIncompatible @GwtIncompatible public void testWhenAllSucceed_releasesCallable() throws Exception { AsyncCallable combiner = @@ -3008,6 +2791,7 @@ public ListenableFuture call() { * finisher}, a task that will complete the future in some fashion when it is called, allowing for * testing both before and after the completion of the future. */ + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private static final class TestFuture { @@ -3030,6 +2814,7 @@ private static final class TestFuture { *

    Each test requires a new {@link TestFutureBatch} because we need new delayed futures each * time, as the old delayed futures were completed as part of the old test. */ + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private static final class TestFutureBatch { @@ -3163,7 +2948,7 @@ String smartToString(ImmutableSet> inputs) { void smartAssertTrue( ImmutableSet> inputs, Exception cause, boolean expression) { if (!expression) { - throw failureWithCause(cause, smartToString(inputs)); + throw new AssertionError(smartToString(inputs), cause); } } @@ -3215,6 +3000,7 @@ void assertHasImmediateCancel( * {@link Futures#allAsList(Iterable)} or {@link Futures#successfulAsList(Iterable)}, hidden * behind a common interface for testing. */ + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private interface Merger { @@ -3246,8 +3032,9 @@ public ListenableFuture> merged( * forever in the case of failure. */ @CanIgnoreReturnValue + @J2ktIncompatible @GwtIncompatible // threads - static V pseudoTimedGetUninterruptibly(final Future input, long timeout, TimeUnit unit) + static V pseudoTimedGetUninterruptibly(Future input, long timeout, TimeUnit unit) throws ExecutionException, TimeoutException { ExecutorService executor = newSingleThreadExecutor(); Future waiter = @@ -3264,7 +3051,7 @@ public V call() throws Exception { } catch (ExecutionException e) { propagateIfInstanceOf(e.getCause(), ExecutionException.class); propagateIfInstanceOf(e.getCause(), CancellationException.class); - throw failureWithCause(e, "Unexpected exception"); + throw new AssertionError("Unexpected exception", e); } finally { executor.shutdownNow(); // TODO(cpovirk): assertTrue(awaitTerminationUninterruptibly(executor, 10, SECONDS)); @@ -3277,6 +3064,7 @@ public V call() throws Exception { * before future completion, and untimed after future completion) return or throw the proper * values. */ + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private static void runExtensiveMergerTest(Merger merger) throws InterruptedException { int inputCount = new TestFutureBatch().allFutures.size(); @@ -3356,6 +3144,7 @@ private static void runExtensiveMergerTest(Merger merger) throws InterruptedExce * that is expected to succeed; the fact that the numbers match is only a coincidence.) See the * comment below for how to restore the fast but hang-y version. */ + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private static List conditionalPseudoTimedGetUninterruptibly( TestFutureBatch inputs, @@ -3370,18 +3159,18 @@ private static List conditionalPseudoTimedGetUninterruptibly( * a bug!), switch the second branch to call untimed future.get() instead of * pseudoTimedGet. */ - return (inputs.hasDelayed(iFuture, jFuture)) + return inputs.hasDelayed(iFuture, jFuture) ? pseudoTimedGetUninterruptibly(future, timeout, unit) : pseudoTimedGetUninterruptibly(future, 2500, MILLISECONDS); } - + @J2ktIncompatible @GwtIncompatible // threads public void testAllAsList_extensive() throws InterruptedException { runExtensiveMergerTest(Merger.allMerger); } - + @J2ktIncompatible @GwtIncompatible // threads public void testSuccessfulAsList_extensive() throws InterruptedException { runExtensiveMergerTest(Merger.successMerger); @@ -3392,7 +3181,6 @@ public void testSuccessfulAsList() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); SettableFuture future3 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(future1, future2, future3); // Attach a listener @@ -3426,7 +3214,6 @@ public void testSuccessfulAsList_emptyList() throws Exception { public void testSuccessfulAsList_emptyArray() throws Exception { SingleCallListener listener = new SingleCallListener(); listener.expectCall(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(); compound.addListener(listener, directExecutor()); assertThat(getDone(compound)).isEmpty(); @@ -3437,7 +3224,6 @@ public void testSuccessfulAsList_partialFailure() throws Exception { SingleCallListener listener = new SingleCallListener(); SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(future1, future2); compound.addListener(listener, directExecutor()); @@ -3456,7 +3242,6 @@ public void testSuccessfulAsList_totalFailure() throws Exception { SingleCallListener listener = new SingleCallListener(); SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(future1, future2); compound.addListener(listener, directExecutor()); @@ -3475,7 +3260,6 @@ public void testSuccessfulAsList_cancelled() throws Exception { SingleCallListener listener = new SingleCallListener(); SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(future1, future2); compound.addListener(listener, directExecutor()); @@ -3493,7 +3277,6 @@ public void testSuccessfulAsList_cancelled() throws Exception { public void testSuccessfulAsList_resultCancelled() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(future1, future2); future2.set(DATA2); @@ -3509,7 +3292,7 @@ public void testSuccessfulAsList_resultCancelledRacingInputDone() throws Excepti Logger exceptionLogger = Logger.getLogger(AbstractFuture.class.getName()); exceptionLogger.addHandler(listenerLoggerHandler); try { - doTestSuccessfulAsList_resultCancelledRacingInputDone(); + doTestSuccessfulAsListResultCancelledRacingInputDone(); assertWithMessage("Nothing should be logged") .that(listenerLoggerHandler.getStoredLogRecords()) @@ -3519,7 +3302,7 @@ public void testSuccessfulAsList_resultCancelledRacingInputDone() throws Excepti } } - private static void doTestSuccessfulAsList_resultCancelledRacingInputDone() throws Exception { + private static void doTestSuccessfulAsListResultCancelledRacingInputDone() throws Exception { // Simple (combined.cancel -> input.cancel -> setOneValue): successfulAsList(ImmutableList.of(SettableFuture.create())).cancel(true); @@ -3528,9 +3311,8 @@ private static void doTestSuccessfulAsList_resultCancelledRacingInputDone() thro * to show that this isn't just about problems with the input future we just * cancelled: */ - final SettableFuture future1 = SettableFuture.create(); - final SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified + SettableFuture future1 = SettableFuture.create(); + SettableFuture future2 = SettableFuture.create(); ListenableFuture> compound = successfulAsList(future1, future2); future1.addListener( @@ -3565,7 +3347,6 @@ public void run() { public void testSuccessfulAsList_resultInterrupted() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(future1, future2); future2.set(DATA2); @@ -3581,7 +3362,6 @@ public void testSuccessfulAsList_mixed() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); SettableFuture future3 = SettableFuture.create(); - @SuppressWarnings("unchecked") // array is never modified ListenableFuture> compound = successfulAsList(future1, future2, future3); compound.addListener(listener, directExecutor()); @@ -3600,7 +3380,7 @@ public void testSuccessfulAsList_mixed() throws Exception { } /** Non-Error exceptions are never logged. */ - @SuppressWarnings("unchecked") + @J2ktIncompatible // TODO(b/324550390): Enable public void testSuccessfulAsList_logging_exception() throws Exception { assertEquals( newArrayList((Object) null), @@ -3623,14 +3403,14 @@ public void testSuccessfulAsList_logging_exception() throws Exception { } /** Ensure that errors are always logged. */ - @SuppressWarnings("unchecked") + @J2ktIncompatible // TODO(b/324550390): Enable public void testSuccessfulAsList_logging_error() throws Exception { assertEquals( newArrayList((Object) null), - getDone(successfulAsList(immediateFailedFuture(new MyError())))); + getDone(successfulAsList(immediateFailedFuture(new SomeError())))); List logged = aggregateFutureLogHandler.getStoredLogRecords(); assertThat(logged).hasSize(1); // errors are always logged - assertThat(logged.get(0).getThrown()).isInstanceOf(MyError.class); + assertThat(logged.get(0).getThrown()).isInstanceOf(SomeError.class); } public void testSuccessfulAsList_failureLoggedEvenAfterOutputCancelled() throws Exception { @@ -3669,12 +3449,8 @@ public void testNonCancellationPropagating_failure() throws Exception { assertFalse(wrapper.isDone()); input.setException(failure); - try { - getDone(wrapper); - fail(); - } catch (ExecutionException expected) { - assertSame(failure, expected.getCause()); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(wrapper)); + assertSame(failure, expected.getCause()); } public void testNonCancellationPropagating_delegateCancelled() throws Exception { @@ -3697,14 +3473,16 @@ public void testNonCancellationPropagating_doesNotPropagate() throws Exception { assertFalse(input.isDone()); } + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private static class TestException extends Exception { - TestException(@NullableDecl Throwable cause) { + TestException(@Nullable Throwable cause) { super(cause); } } + @J2ktIncompatible @GwtIncompatible // used only in GwtIncompatible tests private interface MapperFunction extends Function {} @@ -3752,12 +3530,8 @@ public void testCompletionOrderExceptionThrown() throws Exception { if (expectedResult != 2) { assertEquals((Long) expectedResult, getDone(future)); } else { - try { - getDone(future); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("2L"); - } + ExecutionException expected = assertThrows(ExecutionException.class, () -> getDone(future)); + assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("2L"); } expectedResult++; } @@ -3784,11 +3558,7 @@ public void testCompletionOrderFutureCancelled() throws Exception { if (expectedResult != 4) { assertEquals((Long) expectedResult, getDone(future)); } else { - try { - getDone(future); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> getDone(future)); } expectedResult++; } @@ -3839,7 +3609,7 @@ public void testCancellingADelegatePropagates() throws Exception { public void testCancellingAllDelegatesIsNotQuadratic() throws Exception { ImmutableList.Builder> builder = ImmutableList.builder(); for (int i = 0; i < 500_000; i++) { - builder.add(SettableFuture.create()); + builder.add(SettableFuture.create()); } ImmutableList> inputs = builder.build(); ImmutableList> delegates = inCompletionOrder(inputs); @@ -3854,6 +3624,7 @@ public void testCancellingAllDelegatesIsNotQuadratic() throws Exception { } @AndroidIncompatible // reference is never cleared under some versions of the emulator + @J2ktIncompatible @GwtIncompatible public void testInputGCedIfUnreferenced() throws Exception { SettableFuture future1 = SettableFuture.create(); @@ -3878,6 +3649,7 @@ public void testInputGCedIfUnreferenced() throws Exception { } // Mostly an example of how it would look like to use a list of mixed types + @J2ktIncompatible // Wildcard generics public void testCompletionOrderMixedBagOTypes() throws Exception { SettableFuture future1 = SettableFuture.create(); SettableFuture future2 = SettableFuture.create(); @@ -3896,6 +3668,7 @@ public void testCompletionOrderMixedBagOTypes() throws Exception { } } + @J2ktIncompatible @GwtIncompatible // ClassSanityTester public void testFutures_nullChecks() throws Exception { new ClassSanityTester() @@ -3904,12 +3677,6 @@ public void testFutures_nullChecks() throws Exception { .testNulls(); } - static AssertionFailedError failureWithCause(Throwable cause, String message) { - AssertionFailedError failure = new AssertionFailedError(message); - failure.initCause(cause); - return failure; - } - // This test covers a bug where an Error thrown from a callback could cause the TimeoutFuture to // never complete when timing out. Notably, nothing would get logged since the Error would get // stuck in the ScheduledFuture inside of TimeoutFuture and nothing ever calls get on it. @@ -3938,8 +3705,7 @@ public ListenableFuture apply(V input) { }; } - private static AsyncFunction tagged( - final String toString, final AsyncFunction function) { + private static AsyncFunction tagged(String toString, AsyncFunction function) { return new AsyncFunction() { @Override public ListenableFuture apply(I input) throws Exception { @@ -3953,8 +3719,7 @@ public String toString() { }; } - private static AsyncCallable tagged( - final String toString, final AsyncCallable callable) { + private static AsyncCallable tagged(String toString, AsyncCallable callable) { return new AsyncCallable() { @Override public ListenableFuture call() throws Exception { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTransformAsyncTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTransformAsyncTest.java index d0fcee2384be..9c58e89c1e6d 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTransformAsyncTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTransformAsyncTest.java @@ -17,20 +17,24 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.Futures.transformAsync; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; +import static org.junit.Assert.assertThrows; import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)}. * * @author Nishant Thakkar */ +@NullUnmarked public class FuturesTransformAsyncTest extends AbstractChainedListenableFutureTest { protected static final int SLOW_OUTPUT_VALID_INPUT_DATA = 2; protected static final int SLOW_FUNC_VALID_INPUT_DATA = 3; @@ -82,23 +86,13 @@ public void testFutureGetThrowsFunctionException() throws Exception { public void testFutureGetThrowsCancellationIfInputCancelled() throws Exception { inputFuture.cancel(true); // argument is ignored - try { - resultFuture.get(); - fail("Result future must throw CancellationException" + " if input future is cancelled."); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> resultFuture.get()); } public void testFutureGetThrowsCancellationIfOutputCancelled() throws Exception { inputFuture.set(SLOW_OUTPUT_VALID_INPUT_DATA); outputFuture.cancel(true); // argument is ignored - try { - resultFuture.get(); - fail( - "Result future must throw CancellationException" - + " if function output future is cancelled."); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> resultFuture.get()); } public void testAsyncToString() throws Exception { @@ -111,11 +105,7 @@ public void testFutureCancelBeforeInputCompletion() throws Exception { assertTrue(resultFuture.isCancelled()); assertTrue(inputFuture.isCancelled()); assertFalse(outputFuture.isCancelled()); - try { - resultFuture.get(); - fail("Result future is cancelled and should have thrown a" + " CancellationException"); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> resultFuture.get()); } public void testFutureCancellableBeforeOutputCompletion() throws Exception { @@ -124,14 +114,9 @@ public void testFutureCancellableBeforeOutputCompletion() throws Exception { assertTrue(resultFuture.isCancelled()); assertFalse(inputFuture.isCancelled()); assertTrue(outputFuture.isCancelled()); - try { - resultFuture.get(); - fail("Result future is cancelled and should have thrown a" + " CancellationException"); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> resultFuture.get()); } - public void testFutureCancellableBeforeFunctionCompletion() throws Exception { // Set the result in a separate thread since this test runs the function // (which will block) in the same thread. @@ -147,20 +132,10 @@ public void run() { assertTrue(resultFuture.isCancelled()); assertFalse(inputFuture.isCancelled()); assertFalse(outputFuture.isCancelled()); - try { - resultFuture.get(); - fail("Result future is cancelled and should have thrown a" + " CancellationException"); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> resultFuture.get()); funcCompletionLatch.countDown(); // allow the function to complete - try { - outputFuture.get(); - fail( - "The function output future is cancelled and should have thrown a" - + " CancellationException"); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> outputFuture.get()); } public void testFutureCancelAfterCompletion() throws Exception { @@ -173,14 +148,10 @@ public void testFutureCancelAfterCompletion() throws Exception { } public void testFutureGetThrowsRuntimeException() throws Exception { - BadFuture badInput = new BadFuture(Futures.immediateFuture(20)); + BadFuture badInput = new BadFuture(immediateFuture(20)); ListenableFuture chain = buildChainingFuture(badInput); - try { - chain.get(); - fail("Future.get must throw an exception when the input future fails."); - } catch (ExecutionException e) { - assertSame(RuntimeException.class, e.getCause().getClass()); - } + ExecutionException e = assertThrows(ExecutionException.class, () -> chain.get()); + assertSame(RuntimeException.class, e.getCause().getClass()); } /** Proxy to throw a {@link RuntimeException} out of the {@link #get()} method. */ diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTransformTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTransformTest.java index 9f211dd8b8b2..301bdb6a6fc0 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTransformTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTransformTest.java @@ -21,12 +21,14 @@ import com.google.common.base.Function; import java.lang.reflect.UndeclaredThrowableException; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Futures#transform(ListenableFuture, Function, Executor)}. * * @author Nishant Thakkar */ +@NullUnmarked public class FuturesTransformTest extends AbstractChainedListenableFutureTest { private static final String RESULT_DATA = "SUCCESS"; private static final UndeclaredThrowableException WRAPPED_EXCEPTION = diff --git a/android/guava-tests/test/com/google/common/util/concurrent/GeneratedMonitorTest.java b/android/guava-tests/test/com/google/common/util/concurrent/GeneratedMonitorTest.java index 872197be8b91..596908c9c0f5 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/GeneratedMonitorTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/GeneratedMonitorTest.java @@ -16,22 +16,27 @@ package com.google.common.util.concurrent; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; import com.google.common.base.CaseFormat; import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Generated tests for {@link Monitor}. @@ -43,7 +48,7 @@ * * @author Justin T. Sampson */ - +@NullUnmarked public class GeneratedMonitorTest extends TestCase { public static TestSuite suite() { @@ -58,7 +63,7 @@ public static TestSuite suite() { } } - assertEquals(548, suite.testCount()); + assertEquals(980, suite.testCount()); return suite; } @@ -186,14 +191,26 @@ private static boolean isGuarded(Method method) { return parameterTypes.length >= 1 && parameterTypes[0] == Monitor.Guard.class; } - /** Determines whether the given method takes a time and unit as its last two parameters. */ + /** Determines whether the given method is time-based. */ private static boolean isTimed(Method method) { + return isLongTimeUnitBased(method) || isDurationBased(method); + } + + /** Determines whether the given method takes a time and unit as its last two parameters. */ + private static boolean isLongTimeUnitBased(Method method) { Class[] parameterTypes = method.getParameterTypes(); return parameterTypes.length >= 2 && parameterTypes[parameterTypes.length - 2] == long.class && parameterTypes[parameterTypes.length - 1] == TimeUnit.class; } + /** Determines whether the given method takes a Duration as its last parameter. */ + private static boolean isDurationBased(Method method) { + Class[] parameterTypes = method.getParameterTypes(); + return parameterTypes.length >= 1 + && parameterTypes[parameterTypes.length - 1] == Duration.class; + } + /** Determines whether the given method returns a boolean value. */ private static boolean isBoolean(Method method) { return method.getReturnType() == boolean.class; @@ -215,7 +232,7 @@ public int compare(Method m1, Method m2) { if (nameComparison != 0) { return nameComparison; } else { - return Ints.compare(m1.getParameterTypes().length, m2.getParameterTypes().length); + return Integer.compare(m1.getParameterTypes().length, m2.getParameterTypes().length); } } }); @@ -233,11 +250,21 @@ private static void validateMethod(Method method) { assertFalse(desc, isTimed(method)); break; case 1: - assertTrue(desc, isGuarded(method)); - assertFalse(desc, isTimed(method)); + if (isDurationBased(method)) { + assertFalse(desc, isGuarded(method)); + } else { + assertTrue(desc, isGuarded(method)); + } + // we can't make an assumption about isTimed() because now we have single-parameter methods + // that accept a java.time.Duration + assertFalse(desc, isLongTimeUnitBased(method)); break; case 2: - assertFalse(desc, isGuarded(method)); + if (isDurationBased(method)) { + assertTrue(desc, isGuarded(method)); + } else { + assertFalse(desc, isGuarded(method)); + } assertTrue(desc, isTimed(method)); break; case 3: @@ -398,7 +425,7 @@ private static void addTests( suite.addTest(new GeneratedMonitorTest(method, scenario, fair, timeout, expectedOutcome)); } } else { - Timeout implicitTimeout = (isTryEnter(method) ? Timeout.ZERO : Timeout.MAX); + Timeout implicitTimeout = isTryEnter(method) ? Timeout.ZERO : Timeout.MAX; if (timeoutsToUse.timeouts.contains(implicitTimeout)) { suite.addTest(new GeneratedMonitorTest(method, scenario, fair, null, expectedOutcome)); } @@ -436,7 +463,11 @@ public void setSatisfied(boolean satisfied) { private final CountDownLatch callCompletedLatch; private GeneratedMonitorTest( - Method method, Scenario scenario, boolean fair, Timeout timeout, Outcome expectedOutcome) { + Method method, + Scenario scenario, + boolean fair, + @Nullable Timeout timeout, + Outcome expectedOutcome) { super(nameFor(method, scenario, fair, timeout, expectedOutcome)); this.method = method; this.scenario = scenario; @@ -463,14 +494,14 @@ private static String nameFor( @Override protected void runTest() throws Throwable { - final Runnable runChosenTest = + Runnable runChosenTest = new Runnable() { @Override public void run() { runChosenTest(); } }; - final FutureTask task = new FutureTask<>(runChosenTest, null); + FutureTask<@Nullable Void> task = new FutureTask<>(runChosenTest, null); startThread( new Runnable() { @Override @@ -488,7 +519,7 @@ public void run() { if (hung) { assertEquals(expectedOutcome, Outcome.HANG); } else { - assertNull(task.get(UNEXPECTED_HANG_DELAY_MILLIS, TimeUnit.MILLISECONDS)); + assertThat(task.get(UNEXPECTED_HANG_DELAY_MILLIS, TimeUnit.MILLISECONDS)).isNull(); } } @@ -619,21 +650,22 @@ private void doWaitScenarioSetUp() { } private Outcome doCall() { - boolean guarded = isGuarded(method); - boolean timed = isTimed(method); - Object[] arguments = new Object[(guarded ? 1 : 0) + (timed ? 2 : 0)]; - if (guarded) { - arguments[0] = guard; + List arguments = new ArrayList<>(); + if (isGuarded(method)) { + arguments.add(guard); + } + if (isLongTimeUnitBased(method)) { + arguments.add(timeout.millis); + arguments.add(TimeUnit.MILLISECONDS); } - if (timed) { - arguments[arguments.length - 2] = timeout.millis; - arguments[arguments.length - 1] = TimeUnit.MILLISECONDS; + if (isDurationBased(method)) { + arguments.add(Duration.ofMillis(timeout.millis)); } try { Object result; doingCallLatch.countDown(); try { - result = method.invoke(monitor, arguments); + result = method.invoke(monitor, arguments.toArray()); } finally { callCompletedLatch.countDown(); } @@ -649,10 +681,10 @@ private Outcome doCall() { if (actualException instanceof InterruptedException) { return Outcome.INTERRUPT; } else { - throw newAssertionError("unexpected exception", targetException); + throw new AssertionError("unexpected exception", targetException); } } catch (IllegalAccessException e) { - throw newAssertionError("unexpected exception", e); + throw new AssertionError("unexpected exception", e); } } @@ -666,7 +698,7 @@ private void enterSatisfyGuardAndLeaveInCurrentThread() { } private void enterSatisfyGuardAndLeaveInAnotherThread() { - final CountDownLatch startedLatch = new CountDownLatch(1); + CountDownLatch startedLatch = new CountDownLatch(1); startThread( new Runnable() { @Override @@ -679,7 +711,7 @@ public void run() { } private void enterAndRemainOccupyingInAnotherThread() { - final CountDownLatch enteredLatch = new CountDownLatch(1); + CountDownLatch enteredLatch = new CountDownLatch(1); startThread( new Runnable() { @Override @@ -710,16 +742,23 @@ static Thread startThread(Runnable runnable) { * with a guard that doesn't match the monitor produces an IllegalMonitorStateException. */ private static TestCase generateGuardWithWrongMonitorTestCase( - final Method method, final boolean fair1, final boolean fair2) { - final boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms. + Method method, boolean fair1, boolean fair2) { + boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms. return new TestCase(method.getName() + (timed ? "(0ms)" : "()") + "/WrongMonitor->IMSE") { @Override protected void runTest() throws Throwable { Monitor monitor1 = new Monitor(fair1); Monitor monitor2 = new Monitor(fair2); FlagGuard guard = new FlagGuard(monitor2); - Object[] arguments = - (timed ? new Object[] {guard, 0L, TimeUnit.MILLISECONDS} : new Object[] {guard}); + List arguments = new ArrayList<>(); + arguments.add(guard); + if (isDurationBased(method)) { + arguments.add(Duration.ZERO); + } + if (isLongTimeUnitBased(method)) { + arguments.add(0L); + arguments.add(TimeUnit.MILLISECONDS); + } boolean occupyMonitor = isWaitFor(method); if (occupyMonitor) { // If we don't already occupy the monitor, we'll get an IMSE regardless of the guard (see @@ -727,7 +766,7 @@ protected void runTest() throws Throwable { monitor1.enter(); } try { - method.invoke(monitor1, arguments); + method.invoke(monitor1, arguments.toArray()); fail("expected IllegalMonitorStateException"); } catch (InvocationTargetException e) { assertEquals(IllegalMonitorStateException.class, e.getTargetException().getClass()); @@ -744,9 +783,8 @@ protected void runTest() throws Throwable { * Generates a test case verifying that calling any waitForXxx method when not occupying the * monitor produces an IllegalMonitorStateException. */ - private static TestCase generateWaitForWhenNotOccupyingTestCase( - final Method method, final boolean fair) { - final boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms. + private static TestCase generateWaitForWhenNotOccupyingTestCase(Method method, boolean fair) { + boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms. String testName = method.getName() + (fair ? "(fair)" : "(nonfair)") @@ -757,10 +795,17 @@ private static TestCase generateWaitForWhenNotOccupyingTestCase( protected void runTest() throws Throwable { Monitor monitor = new Monitor(fair); FlagGuard guard = new FlagGuard(monitor); - Object[] arguments = - (timed ? new Object[] {guard, 0L, TimeUnit.MILLISECONDS} : new Object[] {guard}); + List arguments = new ArrayList<>(); + arguments.add(guard); + if (isDurationBased(method)) { + arguments.add(Duration.ZERO); + } + if (isLongTimeUnitBased(method)) { + arguments.add(0L); + arguments.add(TimeUnit.MILLISECONDS); + } try { - method.invoke(monitor, arguments); + method.invoke(monitor, arguments.toArray()); fail("expected IllegalMonitorStateException"); } catch (InvocationTargetException e) { assertEquals(IllegalMonitorStateException.class, e.getTargetException().getClass()); @@ -768,11 +813,4 @@ protected void runTest() throws Throwable { } }; } - - /** Alternative to AssertionError(String, Throwable), which doesn't exist in Java 1.6 */ - private static AssertionError newAssertionError(String message, Throwable cause) { - AssertionError e = new AssertionError(message); - e.initCause(cause); - return e; - } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/InterruptibleMonitorTest.java b/android/guava-tests/test/com/google/common/util/concurrent/InterruptibleMonitorTest.java index 4d7a45f9c46e..44126f5fed51 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/InterruptibleMonitorTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/InterruptibleMonitorTest.java @@ -16,13 +16,14 @@ package com.google.common.util.concurrent; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Monitor}'s interruptible methods. * * @author Justin T. Sampson */ - +@NullUnmarked public class InterruptibleMonitorTest extends MonitorTestCase { public InterruptibleMonitorTest() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/InterruptibleTaskTest.java b/android/guava-tests/test/com/google/common/util/concurrent/InterruptibleTaskTest.java index b946501096df..0210e3ad333a 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/InterruptibleTaskTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/InterruptibleTaskTest.java @@ -16,31 +16,35 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; -import java.lang.reflect.Method; +import com.google.common.util.concurrent.InterruptibleTask.Blocker; import java.nio.channels.spi.AbstractInterruptibleChannel; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.AbstractOwnableSynchronizer; +import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.LockSupport; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; - +@NullUnmarked public final class InterruptibleTaskTest extends TestCase { // Regression test for a deadlock where a task could be stuck busy waiting for the task to // transition to DONE public void testInterruptThrows() throws Exception { - final CountDownLatch isInterruptibleRegistered = new CountDownLatch(1); - InterruptibleTask task = - new InterruptibleTask() { + CountDownLatch isInterruptibleRegistered = new CountDownLatch(1); + SettableFuture taskResult = SettableFuture.create(); + InterruptibleTask task = + new InterruptibleTask() { @Override - Void runInterruptibly() throws Exception { + String runInterruptibly() throws Exception { BrokenChannel bc = new BrokenChannel(); bc.doBegin(); isInterruptibleRegistered.countDown(); new CountDownLatch(1).await(); // the interrupt will wake us up - return null; + return "impossible!"; } @Override @@ -54,22 +58,31 @@ String toPendingString() { } @Override - void afterRanInterruptibly(Void result, Throwable error) {} + void afterRanInterruptiblySuccess(String result) { + taskResult.set(result); + } + + @Override + void afterRanInterruptiblyFailure(Throwable error) { + taskResult.setException(error); + } }; Thread runner = new Thread(task); runner.start(); isInterruptibleRegistered.await(); - try { - task.interruptTask(); - fail(); - } catch (RuntimeException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("I bet you didn't think Thread.interrupt could throw"); - } - // We need to wait for the runner to exit. It used to be that the runner would get stuck in the - // busy loop when interrupt threw. - runner.join(TimeUnit.SECONDS.toMillis(10)); + RuntimeException expected = assertThrows(RuntimeException.class, () -> task.interruptTask()); + assertThat(expected) + .hasMessageThat() + .isEqualTo("I bet you didn't think Thread.interrupt could throw"); + /* + * We need to wait for the runner to exit. It used to be that the runner would get stuck in the + * busy loop when interrupt threw. + * + * While we're at it, we confirm that the interrupt happened as expected. + */ + ExecutionException fromRunInterruptibly = + assertThrows(ExecutionException.class, () -> taskResult.get(10, SECONDS)); + assertThat(fromRunInterruptibly).hasCauseThat().isInstanceOf(InterruptedException.class); } static final class BrokenChannel extends AbstractInterruptibleChannel { @@ -88,13 +101,21 @@ void doBegin() { * protect ourselves from that we want to make sure that tasks don't spin too much waiting for the * interrupting thread to complete the protocol. */ + /* + * This test hangs (or maybe is just *very* slow) under Android. + * + * TODO(b/218700094): Ideally, get this to pass under Android. Failing that, convince ourselves + * that the test isn't exposing a real problem with InterruptibleTask, one that could matter in + * prod. + */ + @AndroidIncompatible public void testInterruptIsSlow() throws Exception { - final CountDownLatch isInterruptibleRegistered = new CountDownLatch(1); - final SlowChannel slowChannel = new SlowChannel(); - final InterruptibleTask task = - new InterruptibleTask() { + CountDownLatch isInterruptibleRegistered = new CountDownLatch(1); + SlowChannel slowChannel = new SlowChannel(); + InterruptibleTask<@Nullable Void> task = + new InterruptibleTask<@Nullable Void>() { @Override - Void runInterruptibly() throws Exception { + @Nullable Void runInterruptibly() throws Exception { slowChannel.doBegin(); isInterruptibleRegistered.countDown(); try { @@ -117,7 +138,10 @@ String toPendingString() { } @Override - void afterRanInterruptibly(Void result, Throwable error) {} + void afterRanInterruptiblySuccess(@Nullable Void result) {} + + @Override + void afterRanInterruptiblyFailure(Throwable error) {} }; Thread runner = new Thread(task, "runner"); runner.start(); @@ -139,19 +163,15 @@ public void run() { // waiting for the slow interrupting thread to complete Thread.interrupt awaitBlockedOnInstanceOf(runner, InterruptibleTask.Blocker.class); - Object blocker = LockSupport.getBlocker(runner); - assertThat(blocker).isInstanceOf(AbstractOwnableSynchronizer.class); - Method getExclusiveOwnerThread = - AbstractOwnableSynchronizer.class.getDeclaredMethod("getExclusiveOwnerThread"); - getExclusiveOwnerThread.setAccessible(true); - Thread owner = (Thread) getExclusiveOwnerThread.invoke(blocker); + Blocker blocker = (Blocker) LockSupport.getBlocker(runner); + Thread owner = blocker.getOwner(); assertThat(owner).isSameInstanceAs(interrupter); slowChannel.exitClose.countDown(); // release the interrupter // We need to wait for the runner to exit. To make sure that the interrupting thread wakes it // back up. - runner.join(TimeUnit.SECONDS.toMillis(10)); + runner.join(SECONDS.toMillis(10)); } // waits for the given thread to be blocked on the given object diff --git a/android/guava-tests/test/com/google/common/util/concurrent/InterruptionUtil.java b/android/guava-tests/test/com/google/common/util/concurrent/InterruptionUtil.java index 919b0c8cec62..a38b1a60cd1b 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/InterruptionUtil.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/InterruptionUtil.java @@ -25,6 +25,7 @@ import com.google.common.testing.TearDownAccepter; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import org.jspecify.annotations.NullUnmarked; /** * Utilities for performing thread interruption in tests @@ -32,6 +33,7 @@ * @author Kevin Bourrillion * @author Chris Povirk */ +@NullUnmarked final class InterruptionUtil { private static final Logger logger = Logger.getLogger(InterruptionUtil.class.getName()); @@ -67,9 +69,9 @@ void stopInterrupting() { } /** Interrupts the current thread after sleeping for the specified delay. */ - static void requestInterruptIn(final long time, final TimeUnit unit) { + static void requestInterruptIn(long time, TimeUnit unit) { checkNotNull(unit); - final Thread interruptee = Thread.currentThread(); + Thread interruptee = Thread.currentThread(); new Thread( new Runnable() { @Override @@ -87,9 +89,9 @@ public void run() { static void repeatedlyInterruptTestThread( long interruptPeriodMillis, TearDownAccepter tearDownAccepter) { - final Interruptenator interruptingTask = + Interruptenator interruptingTask = new Interruptenator(Thread.currentThread(), interruptPeriodMillis); - final Thread interruptingThread = new Thread(interruptingTask); + Thread interruptingThread = new Thread(interruptingTask); interruptingThread.start(); tearDownAccepter.addTearDown( new TearDown() { @@ -135,4 +137,6 @@ private static void joinUninterruptibly(Thread thread, long timeout, TimeUnit un } } } + + private InterruptionUtil() {} } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/JSR166TestCase.java b/android/guava-tests/test/com/google/common/util/concurrent/JSR166TestCase.java index 0822ae14a8df..511955282959 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/JSR166TestCase.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/JSR166TestCase.java @@ -15,11 +15,13 @@ package com.google.common.util.concurrent; +import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FilePermission; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.CodeSource; @@ -29,7 +31,6 @@ import java.security.Policy; import java.security.ProtectionDomain; import java.security.SecurityPermission; -import java.util.Arrays; import java.util.Date; import java.util.NoSuchElementException; import java.util.PropertyPermission; @@ -47,6 +48,7 @@ import java.util.concurrent.atomic.AtomicReference; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Base class for JSR166 Junit TCK tests. Defines some constants, utility methods and classes, as @@ -98,9 +100,15 @@ * tests. * */ +@SuppressWarnings({ + // We call threadUnexpectedException, which does the right thing for errors. + "AssertionFailureIgnored", + // We're following the upstream naming to reduce diffs. + "IdentifierName", + "ConstantCaseForConstants", +}) +@NullUnmarked abstract class JSR166TestCase extends TestCase { - private static final boolean useSecurityManager = Boolean.getBoolean("jsr166.useSecurityManager"); - protected static final boolean expensiveTests = Boolean.getBoolean("jsr166.expensiveTests"); /** @@ -115,6 +123,7 @@ abstract class JSR166TestCase extends TestCase { */ private static final long profileThreshold = Long.getLong("jsr166.profileThreshold", 100); + @Override protected void runTest() throws Throwable { if (profileTests) runTestProfiled(); else super.runTest(); @@ -280,6 +289,7 @@ public void threadRecordFailure(Throwable t) { threadFailure.compareAndSet(null, t); } + @Override public void setUp() { setDelays(); } @@ -292,6 +302,7 @@ public void setUp() { * *

    Triggers test case failure if interrupt status is set in the main thread. */ + @Override public void tearDown() throws Exception { Throwable t = threadFailure.getAndSet(null); if (t != null) { @@ -353,7 +364,7 @@ public void threadAssertFalse(boolean b) { */ public void threadAssertNull(Object x) { try { - assertNull(x); + assertThat(x).isNull(); } catch (AssertionFailedError t) { threadRecordFailure(t); throw t; @@ -431,6 +442,7 @@ public void threadUnexpectedException(Throwable t) { * Delays, via Thread.sleep, for the given millisecond delay, but if the sleep is shorter than * specified, may re-sleep or yield until time elapses. */ + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait? static void delay(long millis) throws InterruptedException { long startTime = System.nanoTime(); long ns = millis * 1000 * 1000; @@ -445,7 +457,7 @@ static void delay(long millis) throws InterruptedException { } /** Waits out termination of a thread pool or fails doing so. */ - void joinPool(ExecutorService exec) { + void joinPool(ExecutorService exec) throws InterruptedException { try { exec.shutdown(); assertTrue( @@ -453,8 +465,6 @@ void joinPool(ExecutorService exec) { exec.awaitTermination(2 * LONG_DELAY_MS, MILLISECONDS)); } catch (SecurityException ok) { // Allowed in case test doesn't have privs - } catch (InterruptedException ie) { - fail("Unexpected InterruptedException"); } } @@ -462,68 +472,52 @@ void joinPool(ExecutorService exec) { * Checks that thread does not terminate within the default millisecond delay of {@code * timeoutMillis()}. */ - void assertThreadStaysAlive(Thread thread) { + void assertThreadStaysAlive(Thread thread) throws InterruptedException { assertThreadStaysAlive(thread, timeoutMillis()); } /** Checks that thread does not terminate within the given millisecond delay. */ - void assertThreadStaysAlive(Thread thread, long millis) { - try { - // No need to optimize the failing case via Thread.join. - delay(millis); - assertTrue(thread.isAlive()); - } catch (InterruptedException ie) { - fail("Unexpected InterruptedException"); - } + void assertThreadStaysAlive(Thread thread, long millis) throws InterruptedException { + // No need to optimize the failing case via Thread.join. + delay(millis); + assertTrue(thread.isAlive()); } /** * Checks that the threads do not terminate within the default millisecond delay of {@code * timeoutMillis()}. */ - void assertThreadsStayAlive(Thread... threads) { + void assertThreadsStayAlive(Thread... threads) throws InterruptedException { assertThreadsStayAlive(timeoutMillis(), threads); } /** Checks that the threads do not terminate within the given millisecond delay. */ - void assertThreadsStayAlive(long millis, Thread... threads) { - try { - // No need to optimize the failing case via Thread.join. - delay(millis); - for (Thread thread : threads) assertTrue(thread.isAlive()); - } catch (InterruptedException ie) { - fail("Unexpected InterruptedException"); + void assertThreadsStayAlive(long millis, Thread... threads) throws InterruptedException { + // No need to optimize the failing case via Thread.join. + delay(millis); + for (Thread thread : threads) { + assertTrue(thread.isAlive()); } } /** Checks that future.get times out, with the default timeout of {@code timeoutMillis()}. */ - void assertFutureTimesOut(Future future) { + void assertFutureTimesOut(Future future) { assertFutureTimesOut(future, timeoutMillis()); } /** Checks that future.get times out, with the given millisecond timeout. */ - void assertFutureTimesOut(Future future, long timeoutMillis) { + void assertFutureTimesOut(Future future, long timeoutMillis) { long startTime = System.nanoTime(); try { future.get(timeoutMillis, MILLISECONDS); - shouldThrow(); + fail("Should throw exception"); } catch (TimeoutException success) { } catch (Exception e) { threadUnexpectedException(e); } finally { future.cancel(true); } - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); - } - - /** Fails with message "should throw exception". */ - public void shouldThrow() { - fail("Should throw exception"); - } - - /** Fails with message "should throw " + exceptionName. */ - public void shouldThrow(String exceptionName) { - fail("Should throw " + exceptionName); + assertThat(millisElapsedSince(startTime)).isAtLeast(timeoutMillis); } /** The number of elements to place in collections, arrays, etc. */ @@ -531,23 +525,23 @@ public void shouldThrow(String exceptionName) { // Some convenient Integer constants - public static final Integer zero = new Integer(0); - public static final Integer one = new Integer(1); - public static final Integer two = new Integer(2); - public static final Integer three = new Integer(3); - public static final Integer four = new Integer(4); - public static final Integer five = new Integer(5); - public static final Integer six = new Integer(6); - public static final Integer seven = new Integer(7); - public static final Integer eight = new Integer(8); - public static final Integer nine = new Integer(9); - public static final Integer m1 = new Integer(-1); - public static final Integer m2 = new Integer(-2); - public static final Integer m3 = new Integer(-3); - public static final Integer m4 = new Integer(-4); - public static final Integer m5 = new Integer(-5); - public static final Integer m6 = new Integer(-6); - public static final Integer m10 = new Integer(-10); + public static final Integer zero = 0; + public static final Integer one = 1; + public static final Integer two = 2; + public static final Integer three = 3; + public static final Integer four = 4; + public static final Integer five = 5; + public static final Integer six = 6; + public static final Integer seven = 7; + public static final Integer eight = 8; + public static final Integer nine = 9; + public static final Integer m1 = -1; + public static final Integer m2 = -2; + public static final Integer m3 = -3; + public static final Integer m4 = -4; + public static final Integer m5 = -5; + public static final Integer m6 = -6; + public static final Integer m10 = -10; /** * Runs Runnable r with a security policy that permits precisely the specified permissions. If @@ -587,7 +581,7 @@ public void runWithoutPermissions(Runnable r) { } /** A security policy where new permissions can be dynamically added or all cleared. */ - public static class AdjustablePolicy extends java.security.Policy { + public static class AdjustablePolicy extends Policy { Permissions perms = new Permissions(); AdjustablePolicy(Permission... permissions) { @@ -602,18 +596,22 @@ void clearPermissions() { perms = new Permissions(); } + @Override public PermissionCollection getPermissions(CodeSource cs) { return perms; } + @Override public PermissionCollection getPermissions(ProtectionDomain pd) { return perms; } + @Override public boolean implies(ProtectionDomain pd, Permission p) { return perms.implies(p); } + @Override public void refresh() {} } @@ -632,7 +630,7 @@ public static Policy permissivePolicy() { // Permissions needed by the junit test harness new RuntimePermission("accessDeclaredMembers"), new PropertyPermission("*", "read"), - new java.io.FilePermission("<>", "read")); + new FilePermission("<>", "read")); } /** Sleeps until the given time has elapsed. Throws AssertionFailedError if interrupted. */ @@ -650,6 +648,7 @@ void sleep(long millis) { * Spin-waits up to the specified number of milliseconds for the given thread to enter a wait * state: BLOCKED, WAITING, or TIMED_WAITING. */ + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. void waitForThreadToEnterWaitState(Thread thread, long timeoutMillis) { long startTime = System.nanoTime(); for (; ; ) { @@ -675,7 +674,7 @@ void waitForThreadToEnterWaitState(Thread thread) { /** * Returns the number of milliseconds since time given by startNanoTime, which must have been - * previously returned from a call to {@link System.nanoTime()}. + * previously returned from a call to {@link System#nanoTime()}. */ long millisElapsedSince(long startNanoTime) { return NANOSECONDS.toMillis(System.nanoTime() - startNanoTime); @@ -721,6 +720,7 @@ void awaitTermination(Thread t) { public abstract class CheckedRunnable implements Runnable { protected abstract void realRun() throws Throwable; + @Override public final void run() { try { realRun(); @@ -739,6 +739,7 @@ RunnableShouldThrow(Class exceptionClass) { this.exceptionClass = exceptionClass; } + @Override public final void run() { try { realRun(); @@ -758,6 +759,7 @@ ThreadShouldThrow(Class exceptionClass) { this.exceptionClass = exceptionClass; } + @Override public final void run() { try { realRun(); @@ -771,6 +773,7 @@ public final void run() { public abstract class CheckedInterruptedRunnable implements Runnable { protected abstract void realRun() throws Throwable; + @Override public final void run() { try { realRun(); @@ -786,6 +789,7 @@ public final void run() { public abstract class CheckedCallable implements Callable { protected abstract T realCall() throws Throwable; + @Override public final T call() { try { return realCall(); @@ -799,6 +803,7 @@ public final T call() { public abstract class CheckedInterruptedCallable implements Callable { protected abstract T realCall() throws Throwable; + @Override public final T call() { try { T result = realCall(); @@ -814,10 +819,12 @@ public final T call() { } public static class NoOpRunnable implements Runnable { + @Override public void run() {} } - public static class NoOpCallable implements Callable { + public static class NoOpCallable implements Callable { + @Override public Object call() { return Boolean.TRUE; } @@ -826,13 +833,15 @@ public Object call() { public static final String TEST_STRING = "a test string"; public static class StringTask implements Callable { + @Override public String call() { return TEST_STRING; } } - public Callable latchAwaitingStringTask(final CountDownLatch latch) { + public Callable latchAwaitingStringTask(CountDownLatch latch) { return new CheckedCallable() { + @Override protected String realCall() { try { latch.await(); @@ -843,8 +852,9 @@ protected String realCall() { }; } - public Runnable awaiter(final CountDownLatch latch) { + public Runnable awaiter(CountDownLatch latch) { return new CheckedRunnable() { + @Override public void realRun() throws InterruptedException { await(latch); } @@ -887,36 +897,42 @@ public void await(Semaphore semaphore) { // } public static class NPETask implements Callable { + @Override public String call() { throw new NullPointerException(); } } public static class CallableOne implements Callable { + @Override public Integer call() { return one; } } public class ShortRunnable extends CheckedRunnable { + @Override protected void realRun() throws Throwable { delay(SHORT_DELAY_MS); } } public class ShortInterruptedRunnable extends CheckedInterruptedRunnable { + @Override protected void realRun() throws InterruptedException { delay(SHORT_DELAY_MS); } } public class SmallRunnable extends CheckedRunnable { + @Override protected void realRun() throws Throwable { delay(SMALL_DELAY_MS); } } public class SmallPossiblyInterruptedRunnable extends CheckedRunnable { + @Override protected void realRun() { try { delay(SMALL_DELAY_MS); @@ -925,7 +941,8 @@ protected void realRun() { } } - public class SmallCallable extends CheckedCallable { + public class SmallCallable extends CheckedCallable { + @Override protected Object realCall() throws InterruptedException { delay(SMALL_DELAY_MS); return Boolean.TRUE; @@ -933,19 +950,22 @@ protected Object realCall() throws InterruptedException { } public class MediumRunnable extends CheckedRunnable { + @Override protected void realRun() throws Throwable { delay(MEDIUM_DELAY_MS); } } public class MediumInterruptedRunnable extends CheckedInterruptedRunnable { + @Override protected void realRun() throws InterruptedException { delay(MEDIUM_DELAY_MS); } } - public Runnable possiblyInterruptedRunnable(final long timeoutMillis) { + public Runnable possiblyInterruptedRunnable(long timeoutMillis) { return new CheckedRunnable() { + @Override protected void realRun() { try { delay(timeoutMillis); @@ -956,6 +976,7 @@ protected void realRun() { } public class MediumPossiblyInterruptedRunnable extends CheckedRunnable { + @Override protected void realRun() { try { delay(MEDIUM_DELAY_MS); @@ -965,6 +986,7 @@ protected void realRun() { } public class LongPossiblyInterruptedRunnable extends CheckedRunnable { + @Override protected void realRun() { try { delay(LONG_DELAY_MS); @@ -975,6 +997,7 @@ protected void realRun() { /** For use as ThreadFactory in constructors */ public static class SimpleThreadFactory implements ThreadFactory { + @Override public Thread newThread(Runnable r) { return new Thread(r); } @@ -984,14 +1007,16 @@ public interface TrackedRunnable extends Runnable { boolean isDone(); } - public static TrackedRunnable trackedRunnable(final long timeoutMillis) { + public static TrackedRunnable trackedRunnable(long timeoutMillis) { return new TrackedRunnable() { private volatile boolean done = false; + @Override public boolean isDone() { return done; } + @Override public void run() { try { delay(timeoutMillis); @@ -1005,6 +1030,7 @@ public void run() { public static class TrackedShortRunnable implements Runnable { public volatile boolean done = false; + @Override public void run() { try { delay(SHORT_DELAY_MS); @@ -1017,6 +1043,7 @@ public void run() { public static class TrackedSmallRunnable implements Runnable { public volatile boolean done = false; + @Override public void run() { try { delay(SMALL_DELAY_MS); @@ -1029,6 +1056,7 @@ public void run() { public static class TrackedMediumRunnable implements Runnable { public volatile boolean done = false; + @Override public void run() { try { delay(MEDIUM_DELAY_MS); @@ -1041,6 +1069,7 @@ public void run() { public static class TrackedLongRunnable implements Runnable { public volatile boolean done = false; + @Override public void run() { try { delay(LONG_DELAY_MS); @@ -1053,14 +1082,16 @@ public void run() { public static class TrackedNoOpRunnable implements Runnable { public volatile boolean done = false; + @Override public void run() { done = true; } } - public static class TrackedCallable implements Callable { + public static class TrackedCallable implements Callable { public volatile boolean done = false; + @Override public Object call() { try { delay(SMALL_DELAY_MS); @@ -1104,6 +1135,7 @@ public Object call() { /** For use as RejectedExecutionHandler in constructors */ public static class NoOpREHandler implements RejectedExecutionHandler { + @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {} } @@ -1116,6 +1148,7 @@ public CheckedBarrier(int parties) { super(parties); } + @Override public int await() { try { return super.await(2 * LONG_DELAY_MS, MILLISECONDS); @@ -1129,29 +1162,29 @@ public int await() { } } - void checkEmpty(BlockingQueue q) { + void checkEmpty(BlockingQueue q) { try { assertTrue(q.isEmpty()); assertEquals(0, q.size()); - assertNull(q.peek()); - assertNull(q.poll()); - assertNull(q.poll(0, MILLISECONDS)); + assertThat(q.peek()).isNull(); + assertThat(q.poll()).isNull(); + assertThat(q.poll(0, MILLISECONDS)).isNull(); assertEquals("[]", q.toString()); - assertTrue(Arrays.equals(q.toArray(), new Object[0])); + assertThat(q.toArray()).isEmpty(); assertFalse(q.iterator().hasNext()); try { q.element(); - shouldThrow(); + fail("Should throw exception"); } catch (NoSuchElementException success) { } try { q.iterator().next(); - shouldThrow(); + fail("Should throw exception"); } catch (NoSuchElementException success) { } try { q.remove(); - shouldThrow(); + fail("Should throw exception"); } catch (NoSuchElementException success) { } } catch (InterruptedException ie) { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/JdkFutureAdaptersTest.java b/android/guava-tests/test/com/google/common/util/concurrent/JdkFutureAdaptersTest.java index 3bb819a349fd..4772a249979a 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/JdkFutureAdaptersTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/JdkFutureAdaptersTest.java @@ -17,6 +17,7 @@ package com.google.common.util.concurrent; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.JdkFutureAdapters.listenInPoolThread; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; @@ -33,6 +34,7 @@ import java.util.concurrent.TimeUnit; import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link JdkFutureAdapters}. @@ -40,6 +42,7 @@ * @author Sven Mawson * @author Kurt Alfred Kluever */ +@NullUnmarked public class JdkFutureAdaptersTest extends TestCase { private static final String DATA1 = "data"; @@ -60,16 +63,16 @@ public void run() { calledCountDown.countDown(); } - public void expectCall() { + void expectCall() { assertFalse("expectCall is already true", expectCall); expectCall = true; } - public boolean wasCalled() { + boolean wasCalled() { return calledCountDown.getCount() == 0; } - public void waitForCall() throws InterruptedException { + void waitForCall() throws InterruptedException { assertTrue("expectCall is false", expectCall); calledCountDown.await(); } @@ -100,7 +103,6 @@ public void testListenInPoolThreadIgnoresExecutorWhenDelegateIsDone() throws Exc assertTrue(listenableFuture.isDone()); } - public void testListenInPoolThreadUsesGivenExecutor() throws Exception { ExecutorService executorService = newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).build()); @@ -125,15 +127,14 @@ public void testListenInPoolThreadUsesGivenExecutor() throws Exception { assertTrue(listenableFuture.isDone()); } - public void testListenInPoolThreadCustomExecutorInterrupted() throws Exception { - final CountDownLatch submitSuccessful = new CountDownLatch(1); + CountDownLatch submitSuccessful = new CountDownLatch(1); ExecutorService executorService = new ThreadPoolExecutor( 0, Integer.MAX_VALUE, 60L, - TimeUnit.SECONDS, + SECONDS, new SynchronousQueue(), new ThreadFactoryBuilder().setDaemon(true).build()) { @Override @@ -235,19 +236,18 @@ public synchronized void run() { } } - - @SuppressWarnings("IsInstanceIncompatibleType") // intentional. public void testListenInPoolThreadRunsListenerAfterRuntimeException() throws Exception { RuntimeExceptionThrowingFuture input = new RuntimeExceptionThrowingFuture<>(); /* - * The compiler recognizes that "input instanceof ListenableFuture" is - * impossible. We want the test, though, in case that changes in the future, - * so we use isInstance instead. + * RuntimeExceptionThrowingFuture is provably not a ListenableFuture at compile time, so this + * code may someday upset Error Prone. We want the test, though, in case that changes in the + * future, so we will suppress any such future Error Prone reports. */ - assertFalse( - "Can't test the main listenInPoolThread path " - + "if the input is already a ListenableFuture", - ListenableFuture.class.isInstance(input)); + assertWithMessage( + "Can't test the main listenInPoolThread path " + + "if the input is already a ListenableFuture") + .that(input) + .isNotInstanceOf(ListenableFuture.class); ListenableFuture listenable = listenInPoolThread(input); /* * This will occur before the waiting get() in the diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTaskTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTaskTest.java index 8969a7574d04..55c20f3d4831 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTaskTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTaskTest.java @@ -17,20 +17,23 @@ package com.google.common.util.concurrent; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.concurrent.Executors.newCachedThreadPool; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test case for {@link ListenableFutureTask}. * * @author Sven Mawson */ +@NullUnmarked public class ListenableFutureTaskTest extends TestCase { private ExecutorService exec; @@ -59,7 +62,7 @@ public Integer call() throws Exception { protected void setUp() throws Exception { super.setUp(); - exec = Executors.newCachedThreadPool(); + exec = newCachedThreadPool(); task.addListener( new Runnable() { @@ -80,7 +83,6 @@ protected void tearDown() throws Exception { super.tearDown(); } - public void testListenerDoesNotRunUntilTaskCompletes() throws Exception { // Test default state of not started. @@ -101,12 +103,11 @@ public void testListenerDoesNotRunUntilTaskCompletes() throws Exception { // listener to be called by blocking on the listener latch. taskLatch.countDown(); assertEquals(25, task.get().intValue()); - assertTrue(listenerLatch.await(5, TimeUnit.SECONDS)); + assertTrue(listenerLatch.await(5, SECONDS)); assertTrue(task.isDone()); assertFalse(task.isCancelled()); } - public void testListenerCalledOnException() throws Exception { throwException = true; @@ -115,14 +116,10 @@ public void testListenerCalledOnException() throws Exception { runLatch.await(); taskLatch.countDown(); - try { - task.get(5, TimeUnit.SECONDS); - fail("Should have propagated the failure."); - } catch (ExecutionException e) { - assertEquals(IllegalStateException.class, e.getCause().getClass()); - } + ExecutionException e = assertThrows(ExecutionException.class, () -> task.get(5, SECONDS)); + assertEquals(IllegalStateException.class, e.getCause().getClass()); - assertTrue(listenerLatch.await(5, TimeUnit.SECONDS)); + assertTrue(listenerLatch.await(5, SECONDS)); assertTrue(task.isDone()); assertFalse(task.isCancelled()); } @@ -134,7 +131,7 @@ public void testListenerCalledOnCancelFromNotRunning() throws Exception { assertEquals(1, runLatch.getCount()); // Wait for the listeners to be called, don't rely on the same-thread exec. - listenerLatch.await(5, TimeUnit.SECONDS); + listenerLatch.await(5, SECONDS); assertTrue(task.isDone()); assertTrue(task.isCancelled()); @@ -142,7 +139,6 @@ public void testListenerCalledOnCancelFromNotRunning() throws Exception { assertEquals(1, runLatch.getCount()); } - public void testListenerCalledOnCancelFromRunning() throws Exception { exec.execute(task); runLatch.await(); @@ -154,7 +150,7 @@ public void testListenerCalledOnCancelFromRunning() throws Exception { assertEquals(1, taskLatch.getCount()); // Wait for the listeners to be called. - listenerLatch.await(5, TimeUnit.SECONDS); + listenerLatch.await(5, SECONDS); assertTrue(task.isDone()); assertTrue(task.isCancelled()); assertEquals(1, taskLatch.getCount()); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTest.java new file mode 100644 index 000000000000..e9efc76df802 --- /dev/null +++ b/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.truth.Truth.assertWithMessage; + +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; + +/** Test for {@link ListenableFuture}. */ +@NullUnmarked +public class ListenableFutureTest extends TestCase { + public void testNoNewApis() throws Exception { + assertWithMessage( + "Do not add new methods to ListenableFuture. Its API needs to continue to match the" + + " version we released in a separate artifact com.google.guava:listenablefuture.") + .that(ListenableFuture.class.getDeclaredMethods()) + .asList() + .containsExactly( + ListenableFuture.class.getMethod("addListener", Runnable.class, Executor.class)); + assertWithMessage( + "Do not add new supertypes to ListenableFuture. Its API needs to continue to match the" + + " version we released in a separate artifact com.google.guava:listenablefuture.") + .that(ListenableFuture.class.getInterfaces()) + .asList() + .containsExactly(Future.class); + } +} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTester.java b/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTester.java index 2dcccdb1894f..21640771e047 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTester.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ListenableFutureTester.java @@ -18,24 +18,27 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.Executors.newCachedThreadPool; +import static java.util.concurrent.TimeUnit.SECONDS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import static org.junit.Assert.assertThrows; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * Used to test listenable future implementations. * * @author Sven Mawson */ +@NullUnmarked public class ListenableFutureTester { private final ExecutorService exec; @@ -43,7 +46,7 @@ public class ListenableFutureTester { private final CountDownLatch latch; public ListenableFutureTester(ListenableFuture future) { - this.exec = Executors.newCachedThreadPool(); + this.exec = newCachedThreadPool(); this.future = checkNotNull(future); this.latch = new CountDownLatch(1); } @@ -67,12 +70,12 @@ public void tearDown() { exec.shutdown(); } - public void testCompletedFuture(@NullableDecl Object expectedValue) + public void testCompletedFuture(@Nullable Object expectedValue) throws InterruptedException, ExecutionException { assertTrue(future.isDone()); assertFalse(future.isCancelled()); - assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(latch.await(5, SECONDS)); assertTrue(future.isDone()); assertFalse(future.isCancelled()); @@ -83,22 +86,18 @@ public void testCancelledFuture() throws InterruptedException, ExecutionExceptio assertTrue(future.isDone()); assertTrue(future.isCancelled()); - assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(latch.await(5, SECONDS)); assertTrue(future.isDone()); assertTrue(future.isCancelled()); - try { - future.get(); - fail("Future should throw CancellationException on cancel."); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> future.get()); } - public void testFailedFuture(@NullableDecl String message) throws InterruptedException { + public void testFailedFuture(@Nullable String message) throws InterruptedException { assertTrue(future.isDone()); assertFalse(future.isCancelled()); - assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(latch.await(5, SECONDS)); assertTrue(future.isDone()); assertFalse(future.isCancelled()); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ListenerCallQueueTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ListenerCallQueueTest.java index 5fd9b950855b..ded5e3119c24 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ListenerCallQueueTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ListenerCallQueueTest.java @@ -17,6 +17,7 @@ package com.google.common.util.concurrent; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.concurrent.Executors.newFixedThreadPool; import com.google.common.collect.ConcurrentHashMultiset; import com.google.common.collect.ImmutableMap; @@ -27,12 +28,13 @@ import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Tests for {@link ListenerCallQueue}. */ +@NullUnmarked public class ListenerCallQueueTest extends TestCase { private static final ListenerCallQueue.Event THROWING_EVENT = @@ -129,15 +131,14 @@ public void testEnqueueAndDispatch_withLabeledExceptions() { logHandler.getStoredLogRecords().get(0).getMessage()); } - public void testEnqueueAndDispatch_multithreaded() throws InterruptedException { Object listener = new Object(); - ExecutorService service = Executors.newFixedThreadPool(4); + ExecutorService service = newFixedThreadPool(4); ListenerCallQueue queue = new ListenerCallQueue<>(); try { queue.addListener(listener, service); - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); Multiset counters = ConcurrentHashMultiset.create(); queue.enqueue(incrementingEvent(counters, listener, 1)); queue.enqueue(incrementingEvent(counters, listener, 2)); @@ -153,16 +154,15 @@ public void testEnqueueAndDispatch_multithreaded() throws InterruptedException { } } - public void testEnqueueAndDispatch_multithreaded_withThrowingRunnable() throws InterruptedException { Object listener = new Object(); - ExecutorService service = Executors.newFixedThreadPool(4); + ExecutorService service = newFixedThreadPool(4); ListenerCallQueue queue = new ListenerCallQueue<>(); try { queue.addListener(listener, service); - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); Multiset counters = ConcurrentHashMultiset.create(); queue.enqueue(incrementingEvent(counters, listener, 1)); queue.enqueue(THROWING_EVENT); @@ -188,7 +188,7 @@ private ListenerCallQueue.Event incrementingEvent( } private ListenerCallQueue.Event incrementingEvent( - final Multiset counters, final Multiset expected) { + Multiset counters, Multiset expected) { return new ListenerCallQueue.Event() { @Override public void call(Object listener) { @@ -219,7 +219,7 @@ private static ImmutableMultiset multiset(Map counts) { return builder.build(); } - private ListenerCallQueue.Event countDownEvent(final CountDownLatch latch) { + private ListenerCallQueue.Event countDownEvent(CountDownLatch latch) { return new ListenerCallQueue.Event() { @Override public void call(Object listener) { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/MonitorTestCase.java b/android/guava-tests/test/com/google/common/util/concurrent/MonitorTestCase.java index 6d620ffc24cd..ba64f4724c27 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/MonitorTestCase.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/MonitorTestCase.java @@ -20,13 +20,14 @@ import com.google.common.testing.TearDownStack; import java.util.Random; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Monitor}, either interruptible or uninterruptible. * * @author Justin T. Sampson */ - +@NullUnmarked public abstract class MonitorTestCase extends TestCase { public class TestGuard extends Monitor.Guard { @@ -49,7 +50,7 @@ public void setSatisfied(boolean satisfied) { private final boolean interruptible; private Monitor monitor; - private final TearDownStack tearDownStack = new TearDownStack(true); + private final TearDownStack tearDownStack = new TearDownStack(); private TestThread thread1; private TestThread thread2; diff --git a/android/guava-tests/test/com/google/common/util/concurrent/MoreExecutorsTest.java b/android/guava-tests/test/com/google/common/util/concurrent/MoreExecutorsTest.java index fe4e7f540038..4e0d191f067c 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/MoreExecutorsTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/MoreExecutorsTest.java @@ -30,14 +30,19 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.MoreExecutors.invokeAnyImpl; import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; import static com.google.common.util.concurrent.MoreExecutors.renamingDecorator; import static com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination; +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -46,7 +51,6 @@ import com.google.common.base.Suppliers; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import com.google.common.testing.ClassSanityTester; import com.google.common.util.concurrent.MoreExecutors.Application; import java.lang.Thread.State; @@ -69,9 +73,10 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.mockito.InOrder; import org.mockito.Mockito; @@ -80,6 +85,7 @@ * * @author Kyle Littlefield (klittle) */ +@NullUnmarked public class MoreExecutorsTest extends JSR166TestCase { private static final Runnable EMPTY_RUNNABLE = @@ -88,18 +94,17 @@ public class MoreExecutorsTest extends JSR166TestCase { public void run() {} }; - public void testDirectExecutorServiceServiceInThreadExecution() throws Exception { - final ListeningExecutorService executor = newDirectExecutorService(); - final ThreadLocal threadLocalCount = + ListeningExecutorService executor = newDirectExecutorService(); + ThreadLocal threadLocalCount = new ThreadLocal() { @Override protected Integer initialValue() { return 0; } }; - final AtomicReference throwableFromOtherThread = new AtomicReference<>(null); - final Runnable incrementTask = + AtomicReference throwableFromOtherThread = new AtomicReference<>(null); + Runnable incrementTask = new Runnable() { @Override public void run() { @@ -131,15 +136,16 @@ public void run() { otherThread.join(1000); assertEquals(Thread.State.TERMINATED, otherThread.getState()); Throwable throwable = throwableFromOtherThread.get(); - assertNull( - "Throwable from other thread: " - + (throwable == null ? null : Throwables.getStackTraceAsString(throwable)), - throwableFromOtherThread.get()); + assertWithMessage( + "Throwable from other thread: " + + (throwable == null ? null : Throwables.getStackTraceAsString(throwable))) + .that(throwableFromOtherThread.get()) + .isNull(); } public void testDirectExecutorServiceInvokeAll() throws Exception { - final ExecutorService executor = newDirectExecutorService(); - final ThreadLocal threadLocalCount = + ExecutorService executor = newDirectExecutorService(); + ThreadLocal threadLocalCount = new ThreadLocal() { @Override protected Integer initialValue() { @@ -147,7 +153,7 @@ protected Integer initialValue() { } }; - final Callable incrementTask = + Callable incrementTask = new Callable() { @Override public Integer call() { @@ -168,12 +174,11 @@ public Integer call() { assertEquals(10, threadLocalCount.get().intValue()); } - public void testDirectExecutorServiceServiceTermination() throws Exception { - final ExecutorService executor = newDirectExecutorService(); - final CyclicBarrier barrier = new CyclicBarrier(2); - final AtomicReference throwableFromOtherThread = new AtomicReference<>(null); - final Runnable doNothingRunnable = + ExecutorService executor = newDirectExecutorService(); + CyclicBarrier barrier = new CyclicBarrier(2); + AtomicReference throwableFromOtherThread = new AtomicReference<>(null); + Runnable doNothingRunnable = new Runnable() { @Override public void run() {} @@ -187,19 +192,19 @@ public void run() { try { Future future = executor.submit( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { // WAIT #1 - barrier.await(1, TimeUnit.SECONDS); + barrier.await(1, SECONDS); // WAIT #2 - barrier.await(1, TimeUnit.SECONDS); + barrier.await(1, SECONDS); assertTrue(executor.isShutdown()); assertFalse(executor.isTerminated()); // WAIT #3 - barrier.await(1, TimeUnit.SECONDS); + barrier.await(1, SECONDS); return null; } }); @@ -215,59 +220,49 @@ public Void call() throws Exception { otherThread.start(); // WAIT #1 - barrier.await(1, TimeUnit.SECONDS); + barrier.await(1, SECONDS); assertFalse(executor.isShutdown()); assertFalse(executor.isTerminated()); executor.shutdown(); assertTrue(executor.isShutdown()); - try { - executor.submit(doNothingRunnable); - fail("Should have encountered RejectedExecutionException"); - } catch (RejectedExecutionException ex) { - // good to go - } + assertThrows(RejectedExecutionException.class, () -> executor.submit(doNothingRunnable)); assertFalse(executor.isTerminated()); // WAIT #2 - barrier.await(1, TimeUnit.SECONDS); - assertFalse(executor.awaitTermination(20, TimeUnit.MILLISECONDS)); + barrier.await(1, SECONDS); + assertFalse(executor.awaitTermination(20, MILLISECONDS)); // WAIT #3 - barrier.await(1, TimeUnit.SECONDS); - assertTrue(executor.awaitTermination(1, TimeUnit.SECONDS)); - assertTrue(executor.awaitTermination(0, TimeUnit.SECONDS)); + barrier.await(1, SECONDS); + assertTrue(executor.awaitTermination(1, SECONDS)); + assertTrue(executor.awaitTermination(0, SECONDS)); assertTrue(executor.isShutdown()); - try { - executor.submit(doNothingRunnable); - fail("Should have encountered RejectedExecutionException"); - } catch (RejectedExecutionException ex) { - // good to go - } + assertThrows(RejectedExecutionException.class, () -> executor.submit(doNothingRunnable)); assertTrue(executor.isTerminated()); otherThread.join(1000); assertEquals(Thread.State.TERMINATED, otherThread.getState()); Throwable throwable = throwableFromOtherThread.get(); - assertNull( - "Throwable from other thread: " - + (throwable == null ? null : Throwables.getStackTraceAsString(throwable)), - throwableFromOtherThread.get()); + assertWithMessage( + "Throwable from other thread: " + + (throwable == null ? null : Throwables.getStackTraceAsString(throwable))) + .that(throwableFromOtherThread.get()) + .isNull(); } /** * Test for a bug where threads weren't getting signaled when shutdown was called, only when tasks * completed. */ - public void testDirectExecutorService_awaitTermination_missedSignal() { - final ExecutorService service = MoreExecutors.newDirectExecutorService(); + ExecutorService service = newDirectExecutorService(); Thread waiter = new Thread() { @Override public void run() { try { - service.awaitTermination(1, TimeUnit.DAYS); + service.awaitTermination(1, DAYS); } catch (InterruptedException e) { return; } @@ -276,7 +271,7 @@ public void run() { waiter.start(); awaitTimedWaiting(waiter); service.shutdown(); - Uninterruptibles.joinUninterruptibly(waiter, 10, TimeUnit.SECONDS); + Uninterruptibles.joinUninterruptibly(waiter, 10, SECONDS); if (waiter.isAlive()) { waiter.interrupt(); fail("awaitTermination failed to trigger after shutdown()"); @@ -284,6 +279,7 @@ public void run() { } /** Wait for the given thread to reach the {@link State#TIMED_WAITING} thread state. */ + @SuppressWarnings("ThreadPriorityCheck") // TODO: b/175898629 - Consider onSpinWait. void awaitTimedWaiting(Thread thread) { while (true) { switch (thread.getState()) { @@ -296,7 +292,6 @@ void awaitTimedWaiting(Thread thread) { case TIMED_WAITING: return; case TERMINATED: - default: throw new AssertionError(); } } @@ -311,11 +306,7 @@ public void testDirectExecutorService_shutdownNow() { public void testExecuteAfterShutdown() { ExecutorService executor = newDirectExecutorService(); executor.shutdown(); - try { - executor.execute(EMPTY_RUNNABLE); - fail(); - } catch (RejectedExecutionException expected) { - } + assertThrows(RejectedExecutionException.class, () -> executor.execute(EMPTY_RUNNABLE)); } public void testListeningExecutorServiceInvokeAllJavadocCodeCompiles() throws Exception { @@ -342,6 +333,7 @@ public void testListeningDecorator() throws Exception { */ } + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testListeningDecorator_noWrapExecuteTask() { ExecutorService delegate = mock(ExecutorService.class); ListeningExecutorService service = listeningDecorator(delegate); @@ -354,9 +346,8 @@ public void run() {} verify(delegate).execute(task); } - public void testListeningDecorator_scheduleSuccess() throws Exception { - final CountDownLatch completed = new CountDownLatch(1); + CountDownLatch completed = new CountDownLatch(1); ScheduledThreadPoolExecutor delegate = new ScheduledThreadPoolExecutor(1) { @Override @@ -365,8 +356,7 @@ protected void afterExecute(Runnable r, Throwable t) { } }; ListeningScheduledExecutorService service = listeningDecorator(delegate); - ListenableFuture future = - service.schedule(Callables.returning(42), 1, TimeUnit.MILLISECONDS); + ListenableFuture future = service.schedule(Callables.returning(42), 1, MILLISECONDS); /* * Wait not just until the Future's value is set (as in future.get()) but @@ -380,18 +370,15 @@ protected void afterExecute(Runnable r, Throwable t) { assertEquals(0, delegate.getQueue().size()); } - public void testListeningDecorator_scheduleFailure() throws Exception { ScheduledThreadPoolExecutor delegate = new ScheduledThreadPoolExecutor(1); ListeningScheduledExecutorService service = listeningDecorator(delegate); RuntimeException ex = new RuntimeException(); - ListenableFuture future = - service.schedule(new ThrowingRunnable(0, ex), 1, TimeUnit.MILLISECONDS); + ListenableFuture future = service.schedule(new ThrowingRunnable(0, ex), 1, MILLISECONDS); assertExecutionException(future, ex); assertEquals(0, delegate.getQueue().size()); } - public void testListeningDecorator_schedulePeriodic() throws Exception { ScheduledThreadPoolExecutor delegate = new ScheduledThreadPoolExecutor(1); ListeningScheduledExecutorService service = listeningDecorator(delegate); @@ -400,19 +387,18 @@ public void testListeningDecorator_schedulePeriodic() throws Exception { ListenableFuture future; ThrowingRunnable runnable = new ThrowingRunnable(5, ex); - future = service.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.MILLISECONDS); + future = service.scheduleAtFixedRate(runnable, 1, 1, MILLISECONDS); assertExecutionException(future, ex); assertEquals(5, runnable.count); assertEquals(0, delegate.getQueue().size()); runnable = new ThrowingRunnable(5, ex); - future = service.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.MILLISECONDS); + future = service.scheduleWithFixedDelay(runnable, 1, 1, MILLISECONDS); assertExecutionException(future, ex); assertEquals(5, runnable.count); assertEquals(0, delegate.getQueue().size()); } - public void testListeningDecorator_cancelled() throws Exception { ScheduledThreadPoolExecutor delegate = new ScheduledThreadPoolExecutor(1); BlockingQueue delegateQueue = delegate.getQueue(); @@ -426,7 +412,7 @@ public void testListeningDecorator_cancelled() throws Exception { public void run() {} }; - future = service.schedule(runnable, 5, TimeUnit.MINUTES); + future = service.schedule(runnable, 5, MINUTES); future.cancel(true); assertTrue(future.isCancelled()); delegateFuture = (ScheduledFuture) delegateQueue.element(); @@ -434,7 +420,7 @@ public void run() {} delegateQueue.clear(); - future = service.scheduleAtFixedRate(runnable, 5, 5, TimeUnit.MINUTES); + future = service.scheduleAtFixedRate(runnable, 5, 5, MINUTES); future.cancel(true); assertTrue(future.isCancelled()); delegateFuture = (ScheduledFuture) delegateQueue.element(); @@ -442,7 +428,7 @@ public void run() {} delegateQueue.clear(); - future = service.scheduleWithFixedDelay(runnable, 5, 5, TimeUnit.MINUTES); + future = service.scheduleWithFixedDelay(runnable, 5, 5, MINUTES); future.cancel(true); assertTrue(future.isCancelled()); delegateFuture = (ScheduledFuture) delegateQueue.element(); @@ -481,7 +467,7 @@ private static void assertExecutionException(Future future, Exception expecte public void testInvokeAnyImpl_nullTasks() throws Exception { ListeningExecutorService e = newDirectExecutorService(); try { - invokeAnyImpl(e, null, false, 0, TimeUnit.NANOSECONDS); + invokeAnyImpl(e, null, false, 0, NANOSECONDS); fail(); } catch (NullPointerException success) { } finally { @@ -493,7 +479,7 @@ public void testInvokeAnyImpl_nullTasks() throws Exception { public void testInvokeAnyImpl_emptyTasks() throws Exception { ListeningExecutorService e = newDirectExecutorService(); try { - invokeAnyImpl(e, new ArrayList>(), false, 0, TimeUnit.NANOSECONDS); + invokeAnyImpl(e, new ArrayList>(), false, 0, NANOSECONDS); fail(); } catch (IllegalArgumentException success) { } finally { @@ -514,7 +500,7 @@ public Integer call() { }); l.add(null); try { - invokeAnyImpl(e, l, false, 0, TimeUnit.NANOSECONDS); + invokeAnyImpl(e, l, false, 0, NANOSECONDS); fail(); } catch (NullPointerException success) { } finally { @@ -528,7 +514,7 @@ public void testInvokeAnyImpl_noTaskCompletes() throws Exception { List> l = new ArrayList<>(); l.add(new NPETask()); try { - invokeAnyImpl(e, l, false, 0, TimeUnit.NANOSECONDS); + invokeAnyImpl(e, l, false, 0, NANOSECONDS); fail(); } catch (ExecutionException success) { assertThat(success).hasCauseThat().isInstanceOf(NullPointerException.class); @@ -544,7 +530,7 @@ public void testInvokeAnyImpl() throws Exception { List> l = new ArrayList<>(); l.add(new StringTask()); l.add(new StringTask()); - String result = invokeAnyImpl(e, l, false, 0, TimeUnit.NANOSECONDS); + String result = invokeAnyImpl(e, l, false, 0, NANOSECONDS); assertSame(TEST_STRING, result); } finally { joinPool(e); @@ -566,38 +552,37 @@ public void run() { } } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testAddDelayedShutdownHook_success() throws InterruptedException { TestApplication application = new TestApplication(); ExecutorService service = mock(ExecutorService.class); - application.addDelayedShutdownHook(service, 2, TimeUnit.SECONDS); + application.addDelayedShutdownHook(service, 2, SECONDS); verify(service, Mockito.never()).shutdown(); application.shutdown(); InOrder shutdownFirst = Mockito.inOrder(service); shutdownFirst.verify(service).shutdown(); - shutdownFirst.verify(service).awaitTermination(2, TimeUnit.SECONDS); + shutdownFirst.verify(service).awaitTermination(2, SECONDS); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testAddDelayedShutdownHook_interrupted() throws InterruptedException { TestApplication application = new TestApplication(); ExecutorService service = mock(ExecutorService.class); - application.addDelayedShutdownHook(service, 2, TimeUnit.SECONDS); - when(service.awaitTermination(2, TimeUnit.SECONDS)).thenThrow(new InterruptedException()); + application.addDelayedShutdownHook(service, 2, SECONDS); + when(service.awaitTermination(2, SECONDS)).thenThrow(new InterruptedException()); application.shutdown(); verify(service).shutdown(); } - public void testGetExitingExecutorService_executorSetToUseDaemonThreads() { TestApplication application = new TestApplication(); ThreadPoolExecutor executor = - new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS, new ArrayBlockingQueue(1)); - assertNotNull(application.getExitingExecutorService(executor)); + new ThreadPoolExecutor(1, 2, 3, SECONDS, new ArrayBlockingQueue(1)); + assertThat(application.getExitingExecutorService(executor)).isNotNull(); assertTrue(executor.getThreadFactory().newThread(EMPTY_RUNNABLE).isDaemon()); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testGetExitingExecutorService_executorDelegatesToOriginal() { TestApplication application = new TestApplication(); ThreadPoolExecutor executor = mock(ThreadPoolExecutor.class); @@ -607,7 +592,7 @@ public void testGetExitingExecutorService_executorDelegatesToOriginal() { verify(executor).execute(EMPTY_RUNNABLE); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testGetExitingExecutorService_shutdownHookRegistered() throws InterruptedException { TestApplication application = new TestApplication(); ThreadPoolExecutor executor = mock(ThreadPoolExecutor.class); @@ -618,15 +603,14 @@ public void testGetExitingExecutorService_shutdownHookRegistered() throws Interr verify(executor).shutdown(); } - public void testGetExitingScheduledExecutorService_executorSetToUseDaemonThreads() { TestApplication application = new TestApplication(); ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); - assertNotNull(application.getExitingScheduledExecutorService(executor)); + assertThat(application.getExitingScheduledExecutorService(executor)).isNotNull(); assertTrue(executor.getThreadFactory().newThread(EMPTY_RUNNABLE).isDaemon()); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testGetExitingScheduledExecutorService_executorDelegatesToOriginal() { TestApplication application = new TestApplication(); ScheduledThreadPoolExecutor executor = mock(ScheduledThreadPoolExecutor.class); @@ -636,7 +620,7 @@ public void testGetExitingScheduledExecutorService_executorDelegatesToOriginal() verify(executor).execute(EMPTY_RUNNABLE); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testGetScheduledExitingExecutorService_shutdownHookRegistered() throws InterruptedException { TestApplication application = new TestApplication(); @@ -650,7 +634,7 @@ public void testGetScheduledExitingExecutorService_shutdownHookRegistered() public void testPlatformThreadFactory_default() { ThreadFactory factory = MoreExecutors.platformThreadFactory(); - assertNotNull(factory); + assertThat(factory).isNotNull(); // Executors#defaultThreadFactory() may return a new instance each time. assertEquals(factory.getClass(), Executors.defaultThreadFactory().getClass()); } @@ -669,7 +653,6 @@ public void run() { assertEquals(oldName, Thread.currentThread().getName()); } - public void testExecutors_nullCheck() throws Exception { new ClassSanityTester() .setDefault(RateLimiter.class, RateLimiter.create(1.0)) @@ -679,7 +662,7 @@ public void testExecutors_nullCheck() throws Exception { } private static class TestApplication extends Application { - private final List hooks = Lists.newArrayList(); + private final List hooks = new ArrayList<>(); @Override synchronized void addShutdownHook(Thread hook) { @@ -699,14 +682,13 @@ synchronized void shutdown() throws InterruptedException { /* Half of a 1-second timeout in nanoseconds */ private static final long HALF_SECOND_NANOS = NANOSECONDS.convert(1L, SECONDS) / 2; - public void testShutdownAndAwaitTermination_immediateShutdown() throws Exception { ExecutorService service = Executors.newSingleThreadExecutor(); assertTrue(shutdownAndAwaitTermination(service, 1L, SECONDS)); assertTrue(service.isTerminated()); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testShutdownAndAwaitTermination_immediateShutdownInternal() throws Exception { ExecutorService service = mock(ExecutorService.class); when(service.awaitTermination(HALF_SECOND_NANOS, NANOSECONDS)).thenReturn(true); @@ -716,7 +698,7 @@ public void testShutdownAndAwaitTermination_immediateShutdownInternal() throws E verify(service).awaitTermination(HALF_SECOND_NANOS, NANOSECONDS); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testShutdownAndAwaitTermination_forcedShutDownInternal() throws Exception { ExecutorService service = mock(ExecutorService.class); when(service.awaitTermination(HALF_SECOND_NANOS, NANOSECONDS)) @@ -729,7 +711,7 @@ public void testShutdownAndAwaitTermination_forcedShutDownInternal() throws Exce verify(service).shutdownNow(); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testShutdownAndAwaitTermination_nonTerminationInternal() throws Exception { ExecutorService service = mock(ExecutorService.class); when(service.awaitTermination(HALF_SECOND_NANOS, NANOSECONDS)) @@ -741,15 +723,15 @@ public void testShutdownAndAwaitTermination_nonTerminationInternal() throws Exce verify(service).shutdownNow(); } - + @AndroidIncompatible // Mocking ExecutorService is forbidden there. TODO(b/218700094): Don't mock. public void testShutdownAndAwaitTermination_interruptedInternal() throws Exception { - final ExecutorService service = mock(ExecutorService.class); + ExecutorService service = mock(ExecutorService.class); when(service.awaitTermination(HALF_SECOND_NANOS, NANOSECONDS)) .thenThrow(new InterruptedException()); - final AtomicBoolean terminated = new AtomicBoolean(); + AtomicBoolean terminated = new AtomicBoolean(); // we need to keep this in a flag because t.isInterrupted() returns false after t.join() - final AtomicBoolean interrupted = new AtomicBoolean(); + AtomicBoolean interrupted = new AtomicBoolean(); // we need to use another thread because it will be interrupted and thus using // the current one, owned by JUnit, would make the test fail Thread thread = diff --git a/android/guava-tests/test/com/google/common/util/concurrent/RateLimiterTest.java b/android/guava-tests/test/com/google/common/util/concurrent/RateLimiterTest.java index 40713545bf13..93a2f8e93933 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/RateLimiterTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/RateLimiterTest.java @@ -16,26 +16,29 @@ package com.google.common.util.concurrent; +import static com.google.common.truth.Truth.assertThat; +import static java.lang.Math.max; import static java.lang.reflect.Modifier.isStatic; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableClassToInstanceMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import com.google.common.testing.NullPointerTester; import com.google.common.testing.NullPointerTester.Visibility; import com.google.common.util.concurrent.RateLimiter.SleepingStopwatch; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Random; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; -import org.easymock.EasyMock; +import org.jspecify.annotations.NullUnmarked; import org.mockito.Mockito; /** @@ -43,6 +46,7 @@ * * @author Dimitris Andreou */ +@NullUnmarked public class RateLimiterTest extends TestCase { private static final double EPSILON = 1e-8; @@ -72,54 +76,23 @@ public void testDoubleMinValueCanAcquireExactlyOnce() { public void testSimpleRateUpdate() { RateLimiter limiter = RateLimiter.create(5.0, 5, SECONDS); - assertEquals(5.0, limiter.getRate()); + assertThat(limiter.getRate()).isEqualTo(5.0); limiter.setRate(10.0); - assertEquals(10.0, limiter.getRate()); + assertThat(limiter.getRate()).isEqualTo(10.0); - try { - limiter.setRate(0.0); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - limiter.setRate(-10.0); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> limiter.setRate(0.0)); + assertThrows(IllegalArgumentException.class, () -> limiter.setRate(-10.0)); + assertThrows(IllegalArgumentException.class, () -> limiter.setRate(Double.NaN)); } public void testAcquireParameterValidation() { RateLimiter limiter = RateLimiter.create(999); - try { - limiter.acquire(0); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - limiter.acquire(-1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - limiter.tryAcquire(0); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - limiter.tryAcquire(-1); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - limiter.tryAcquire(0, 1, SECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - limiter.tryAcquire(-1, 1, SECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> limiter.acquire(0)); + assertThrows(IllegalArgumentException.class, () -> limiter.acquire(-1)); + assertThrows(IllegalArgumentException.class, () -> limiter.tryAcquire(0)); + assertThrows(IllegalArgumentException.class, () -> limiter.tryAcquire(-1)); + assertThrows(IllegalArgumentException.class, () -> limiter.tryAcquire(0, 1, SECONDS)); + assertThrows(IllegalArgumentException.class, () -> limiter.tryAcquire(-1, 1, SECONDS)); } public void testSimpleWithWait() { @@ -133,20 +106,22 @@ public void testSimpleWithWait() { public void testSimpleAcquireReturnValues() { RateLimiter limiter = RateLimiter.create(5.0, stopwatch); - assertEquals(0.0, limiter.acquire(), EPSILON); // R0.00 + assertThat(limiter.acquire()).isWithin(EPSILON).of(0.0); // R0.00 stopwatch.sleepMillis(200); // U0.20, we are ready for the next request... - assertEquals(0.0, limiter.acquire(), EPSILON); // R0.00, ...which is granted immediately - assertEquals(0.2, limiter.acquire(), EPSILON); // R0.20 + assertThat(limiter.acquire()) + .isWithin(EPSILON) + .of(0.0); // R0.00, ...which is granted immediately + assertThat(limiter.acquire()).isWithin(EPSILON).of(0.2); // R0.20 assertEvents("R0.00", "U0.20", "R0.00", "R0.20"); } public void testSimpleAcquireEarliestAvailableIsInPast() { RateLimiter limiter = RateLimiter.create(5.0, stopwatch); - assertEquals(0.0, limiter.acquire(), EPSILON); + assertThat(limiter.acquire()).isWithin(EPSILON).of(0.0); stopwatch.sleepMillis(400); - assertEquals(0.0, limiter.acquire(), EPSILON); - assertEquals(0.0, limiter.acquire(), EPSILON); - assertEquals(0.2, limiter.acquire(), EPSILON); + assertThat(limiter.acquire()).isWithin(EPSILON).of(0.0); + assertThat(limiter.acquire()).isWithin(EPSILON).of(0.0); + assertThat(limiter.acquire()).isWithin(EPSILON).of(0.2); } public void testOneSecondBurst() { @@ -170,17 +145,9 @@ public void testCreateWarmupParameterValidation() { unused = RateLimiter.create(1.0, 1, NANOSECONDS); unused = RateLimiter.create(1.0, 0, NANOSECONDS); - try { - RateLimiter.create(0.0, 1, NANOSECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> RateLimiter.create(0.0, 1, NANOSECONDS)); - try { - RateLimiter.create(1.0, -1, NANOSECONDS); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> RateLimiter.create(1.0, -1, NANOSECONDS)); } @AndroidIncompatible // difference in String.format rounding? @@ -373,7 +340,7 @@ public void testSimpleWeights() { assertEvents("R0.00", "R1.00", "R1.00", "R2.00", "R4.00", "R8.00"); } - public void testInfinity_Bursty() { + public void testInfinity_bursty() { RateLimiter limiter = RateLimiter.create(Double.POSITIVE_INFINITY, stopwatch); limiter.acquire(Integer.MAX_VALUE / 4); limiter.acquire(Integer.MAX_VALUE / 2); @@ -399,8 +366,8 @@ public void testInfinity_Bursty() { assertEvents("R0.50", "R0.00", "R0.00"); // we repay the last request (.5sec), then back to +oo } - /** https://code.google.com/p/guava-libraries/issues/detail?id=1791 */ - public void testInfinity_BustyTimeElapsed() { + /** https://github.com/google/guava/issues/1791 */ + public void testInfinity_bustyTimeElapsed() { RateLimiter limiter = RateLimiter.create(Double.POSITIVE_INFINITY, stopwatch); stopwatch.instant += 1000000; limiter.setRate(2.0); @@ -414,7 +381,7 @@ public void testInfinity_BustyTimeElapsed() { "R0.50"); } - public void testInfinity_WarmUp() { + public void testInfinity_warmUp() { RateLimiter limiter = RateLimiter.create(Double.POSITIVE_INFINITY, 10, SECONDS, 3.0, stopwatch); limiter.acquire(Integer.MAX_VALUE / 4); limiter.acquire(Integer.MAX_VALUE / 2); @@ -434,7 +401,7 @@ public void testInfinity_WarmUp() { assertEvents("R1.00", "R0.00", "R0.00"); } - public void testInfinity_WarmUpTimeElapsed() { + public void testInfinity_warmUpTimeElapsed() { RateLimiter limiter = RateLimiter.create(Double.POSITIVE_INFINITY, 10, SECONDS, 3.0, stopwatch); stopwatch.instant += 1000000; limiter.setRate(1.0); @@ -457,10 +424,10 @@ public void testWeNeverGetABurstMoreThanOneSec() { limiter.setRate(rate); long burst = measureTotalTimeMillis(limiter, oneSecWorthOfWork, new Random()); // we allow one second worth of work to go in a burst (i.e. take less than a second) - assertTrue(burst <= 1000); + assertThat(burst).isAtMost(1000); long afterBurst = measureTotalTimeMillis(limiter, oneSecWorthOfWork, new Random()); // but work beyond that must take at least one second - assertTrue(afterBurst >= 1000); + assertThat(afterBurst).isAtLeast(1000); } } @@ -511,7 +478,7 @@ public void testVerySmallDoubleValues() throws Exception { private long measureTotalTimeMillis(RateLimiter rateLimiter, int permits, Random random) { long startTime = stopwatch.instant; while (permits > 0) { - int nextPermitsToAcquire = Math.max(1, random.nextInt(permits)); + int nextPermitsToAcquire = max(1, random.nextInt(permits)); permits -= nextPermitsToAcquire; rateLimiter.acquire(nextPermitsToAcquire); } @@ -529,7 +496,7 @@ private void assertEvents(String... events) { */ static class FakeStopwatch extends SleepingStopwatch { long instant = 0L; - final List events = Lists.newArrayList(); + final List events = new ArrayList<>(); @Override public long readMicros() { @@ -542,7 +509,7 @@ void sleepMillis(int millis) { void sleepMicros(String caption, long micros) { instant += MICROSECONDS.toNanos(micros); - events.add(caption + String.format(Locale.ROOT, "%3.2f", (micros / 1000000.0))); + events.add(caption + String.format(Locale.ROOT, "%3.2f", micros / 1000000.0)); } @Override @@ -564,24 +531,9 @@ public String toString() { } } - /* - * Note: Mockito appears to lose its ability to Mock doGetRate as of Android 21. If we start - * testing with that version or newer, we'll need to suppress this test (or see if Mockito can be - * changed to support this). - */ + @AndroidIncompatible // Mockito loses its ability to mock doGetRate as of Android 21 public void testMockingMockito() throws Exception { RateLimiter mock = Mockito.mock(RateLimiter.class); - doTestMocking(mock); - } - - @AndroidIncompatible // EasyMock Class Extension doesn't appear to work on Android. - public void testMockingEasyMock() throws Exception { - RateLimiter mock = EasyMock.createNiceMock(RateLimiter.class); - EasyMock.replay(mock); - doTestMocking(mock); - } - - private static void doTestMocking(RateLimiter mock) throws Exception { for (Method method : RateLimiter.class.getMethods()) { if (!isStatic(method.getModifiers()) && !NOT_WORKING_ON_MOCKS.contains(method.getName()) diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ReflectionFreeAssertThrows.java b/android/guava-tests/test/com/google/common/util/concurrent/ReflectionFreeAssertThrows.java new file mode 100644 index 000000000000..e41890baac30 --- /dev/null +++ b/android/guava-tests/test/com/google/common/util/concurrent/ReflectionFreeAssertThrows.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.TestExceptions.SomeCheckedException; +import com.google.common.util.concurrent.TestExceptions.SomeError; +import com.google.common.util.concurrent.TestExceptions.SomeOtherCheckedException; +import com.google.common.util.concurrent.TestExceptions.SomeUncheckedException; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import junit.framework.AssertionFailedError; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** Replacements for JUnit's {@code assertThrows} that work under GWT/J2CL. */ +@GwtCompatible +@NullMarked +final class ReflectionFreeAssertThrows { + interface ThrowingRunnable { + void run() throws Throwable; + } + + interface ThrowingSupplier { + @Nullable Object get() throws Throwable; + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingSupplier supplier) { + return doAssertThrows(expectedThrowable, supplier, /* userPassedSupplier= */ true); + } + + @CanIgnoreReturnValue + static T assertThrows( + Class expectedThrowable, ThrowingRunnable runnable) { + return doAssertThrows( + expectedThrowable, + () -> { + runnable.run(); + return null; + }, + /* userPassedSupplier= */ false); + } + + private static T doAssertThrows( + Class expectedThrowable, ThrowingSupplier supplier, boolean userPassedSupplier) { + checkNotNull(expectedThrowable); + checkNotNull(supplier); + Predicate predicate = INSTANCE_OF.get(expectedThrowable); + if (predicate == null) { + throw new IllegalArgumentException( + expectedThrowable + + " is not yet supported by ReflectionFreeAssertThrows. Add an entry for it in the" + + " map in that class."); + } + Object result; + try { + result = supplier.get(); + } catch (Throwable t) { + if (predicate.apply(t)) { + // We are careful to set up INSTANCE_OF to match each Predicate to its target Class. + @SuppressWarnings("unchecked") + T caught = (T) t; + return caught; + } + throw new AssertionError( + "expected to throw " + expectedThrowable.getSimpleName() + " but threw " + t, t); + } + if (userPassedSupplier) { + throw new AssertionError( + "expected to throw " + + expectedThrowable.getSimpleName() + + " but returned result: " + + result); + } else { + throw new AssertionError("expected to throw " + expectedThrowable.getSimpleName()); + } + } + + private enum PlatformSpecificExceptionBatch { + PLATFORM { + @GwtIncompatible + @J2ktIncompatible + @Override + // returns the types available in "normal" environments + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of( + InvocationTargetException.class, + e -> e instanceof InvocationTargetException, + StackOverflowError.class, + e -> e instanceof StackOverflowError); + } + }; + + // used under GWT, etc., since the override of this method does not exist there + ImmutableMap, Predicate> exceptions() { + return ImmutableMap.of(); + } + } + + private static final ImmutableMap, Predicate> INSTANCE_OF = + ImmutableMap., Predicate>builder() + .put(ArithmeticException.class, e -> e instanceof ArithmeticException) + .put( + ArrayIndexOutOfBoundsException.class, + e -> e instanceof ArrayIndexOutOfBoundsException) + .put(ArrayStoreException.class, e -> e instanceof ArrayStoreException) + .put(AssertionFailedError.class, e -> e instanceof AssertionFailedError) + .put(CancellationException.class, e -> e instanceof CancellationException) + .put(ClassCastException.class, e -> e instanceof ClassCastException) + .put( + ConcurrentModificationException.class, + e -> e instanceof ConcurrentModificationException) + .put(ExecutionError.class, e -> e instanceof ExecutionError) + .put(ExecutionException.class, e -> e instanceof ExecutionException) + .put(IllegalArgumentException.class, e -> e instanceof IllegalArgumentException) + .put(IllegalStateException.class, e -> e instanceof IllegalStateException) + .put(IndexOutOfBoundsException.class, e -> e instanceof IndexOutOfBoundsException) + .put(NoSuchElementException.class, e -> e instanceof NoSuchElementException) + .put(NullPointerException.class, e -> e instanceof NullPointerException) + .put(NumberFormatException.class, e -> e instanceof NumberFormatException) + .put(RuntimeException.class, e -> e instanceof RuntimeException) + .put(SomeCheckedException.class, e -> e instanceof SomeCheckedException) + .put(SomeError.class, e -> e instanceof SomeError) + .put(SomeOtherCheckedException.class, e -> e instanceof SomeOtherCheckedException) + .put(SomeUncheckedException.class, e -> e instanceof SomeUncheckedException) + .put(TimeoutException.class, e -> e instanceof TimeoutException) + .put(UncheckedExecutionException.class, e -> e instanceof UncheckedExecutionException) + .put(UnsupportedCharsetException.class, e -> e instanceof UnsupportedCharsetException) + .put(UnsupportedOperationException.class, e -> e instanceof UnsupportedOperationException) + .put(VerifyException.class, e -> e instanceof VerifyException) + .putAll(PlatformSpecificExceptionBatch.PLATFORM.exceptions()) + .buildOrThrow(); + + private ReflectionFreeAssertThrows() {} +} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/RunnablesTest.java b/android/guava-tests/test/com/google/common/util/concurrent/RunnablesTest.java index c4bd1c7c4ec6..6203761caf15 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/RunnablesTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/RunnablesTest.java @@ -18,6 +18,7 @@ import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit tests for {@link Runnables}. @@ -25,6 +26,7 @@ * @author Olivier Pernet */ @GwtCompatible +@NullUnmarked public class RunnablesTest extends TestCase { public void testDoNothingRunnableIsSingleton() { assertSame(Runnables.doNothing(), Runnables.doNothing()); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/SequentialExecutorTest.java b/android/guava-tests/test/com/google/common/util/concurrent/SequentialExecutorTest.java index 20209e8b80dc..f55ffb486037 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/SequentialExecutorTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/SequentialExecutorTest.java @@ -19,10 +19,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.newSequentialExecutor; import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; +import static java.util.concurrent.Executors.newCachedThreadPool; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.collect.Queues; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.CountDownLatch; @@ -30,23 +34,23 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests {@link SequentialExecutor}. * * @author JJ Furman */ +@NullUnmarked public class SequentialExecutorTest extends TestCase { private static class FakeExecutor implements Executor { - Queue tasks = Queues.newArrayDeque(); + final Queue tasks = new ArrayDeque<>(); @Override public void execute(Runnable command) { @@ -79,15 +83,11 @@ public void setUp() { } public void testConstructingWithNullExecutor_fails() { - try { - new SequentialExecutor(null); - fail("Should have failed with NullPointerException."); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> new SequentialExecutor(null)); } public void testBasics() { - final AtomicInteger totalCalls = new AtomicInteger(); + AtomicInteger totalCalls = new AtomicInteger(); Runnable intCounter = new Runnable() { @Override @@ -122,7 +122,7 @@ public void run() { } public void testOrdering() { - final List callOrder = Lists.newArrayList(); + List callOrder = new ArrayList<>(); class FakeOp implements Runnable { final int op; @@ -147,7 +147,7 @@ public void run() { public void testRuntimeException_doesNotStopExecution() { - final AtomicInteger numCalls = new AtomicInteger(); + AtomicInteger numCalls = new AtomicInteger(); Runnable runMe = new Runnable() { @@ -221,7 +221,7 @@ public void run() { public void testInterrupt_doesNotStopExecution() { - final AtomicInteger numCalls = new AtomicInteger(); + AtomicInteger numCalls = new AtomicInteger(); Runnable runMe = new Runnable() { @@ -243,9 +243,9 @@ public void run() { } public void testDelegateRejection() { - final AtomicInteger numCalls = new AtomicInteger(); - final AtomicBoolean reject = new AtomicBoolean(true); - final SequentialExecutor executor = + AtomicInteger numCalls = new AtomicInteger(); + AtomicBoolean reject = new AtomicBoolean(true); + SequentialExecutor executor = new SequentialExecutor( new Executor() { @Override @@ -263,25 +263,28 @@ public void run() { numCalls.incrementAndGet(); } }; - try { - executor.execute(task); - fail(); - } catch (RejectedExecutionException expected) { - } + assertThrows(RejectedExecutionException.class, () -> executor.execute(task)); assertEquals(0, numCalls.get()); reject.set(false); executor.execute(task); assertEquals(1, numCalls.get()); } - + /* + * Under Android, MyError propagates up and fails the test? + * + * TODO(b/218700094): Does this matter to prod users, or is it just a feature of our testing + * environment? If the latter, maybe write a custom Executor that avoids failing the test when it + * sees an Error? + */ + @AndroidIncompatible public void testTaskThrowsError() throws Exception { class MyError extends Error {} - final CyclicBarrier barrier = new CyclicBarrier(2); + CyclicBarrier barrier = new CyclicBarrier(2); // we need to make sure the error gets thrown on a different thread. - ExecutorService service = Executors.newSingleThreadExecutor(); + ExecutorService service = newSingleThreadExecutor(); try { - final SequentialExecutor executor = new SequentialExecutor(service); + SequentialExecutor executor = new SequentialExecutor(service); Runnable errorTask = new Runnable() { @Override @@ -303,21 +306,20 @@ public void run() { executor.execute(errorTask); service.execute(barrierTask); // submit directly to the service // the barrier task runs after the error task so we know that the error has been observed by - // SequentialExecutor by the time the barrier is satified - barrier.await(1, TimeUnit.SECONDS); + // SequentialExecutor by the time the barrier is satisfied + barrier.await(1, SECONDS); executor.execute(barrierTask); // timeout means the second task wasn't even tried - barrier.await(1, TimeUnit.SECONDS); + barrier.await(1, SECONDS); } finally { service.shutdown(); } } - public void testRejectedExecutionThrownWithMultipleCalls() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - final SettableFuture future = SettableFuture.create(); - final Executor delegate = + CountDownLatch latch = new CountDownLatch(1); + SettableFuture future = SettableFuture.create(); + Executor delegate = new Executor() { @Override public void execute(Runnable task) { @@ -327,8 +329,8 @@ public void execute(Runnable task) { throw new RejectedExecutionException(); } }; - final SequentialExecutor executor = new SequentialExecutor(delegate); - final ExecutorService blocked = Executors.newCachedThreadPool(); + SequentialExecutor executor = new SequentialExecutor(delegate); + ExecutorService blocked = newCachedThreadPool(); Future first = blocked.submit( new Runnable() { @@ -337,24 +339,17 @@ public void run() { executor.execute(Runnables.doNothing()); } }); - future.get(10, TimeUnit.SECONDS); - try { - executor.execute(Runnables.doNothing()); - fail(); - } catch (RejectedExecutionException expected) { - } + future.get(10, SECONDS); + assertThrows(RejectedExecutionException.class, () -> executor.execute(Runnables.doNothing())); latch.countDown(); - try { - first.get(10, TimeUnit.SECONDS); - fail(); - } catch (ExecutionException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(RejectedExecutionException.class); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> first.get(10, SECONDS)); + assertThat(expected).hasCauseThat().isInstanceOf(RejectedExecutionException.class); } public void testToString() { - final Runnable[] currentTask = new Runnable[1]; - final Executor delegate = + Runnable[] currentTask = new Runnable[1]; + Executor delegate = new Executor() { @Override public void execute(Runnable task) { @@ -372,7 +367,7 @@ public String toString() { Executor sequential2 = newSequentialExecutor(delegate); assertThat(sequential1.toString()).contains("theDelegate"); assertThat(sequential1.toString()).isNotEqualTo(sequential2.toString()); - final String[] whileRunningToString = new String[1]; + String[] whileRunningToString = new String[1]; sequential1.execute( new Runnable() { @Override diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ServiceManagerTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ServiceManagerTest.java index 20ed3dea5461..1e75f1f9f53f 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ServiceManagerTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ServiceManagerTest.java @@ -16,19 +16,25 @@ package com.google.common.util.concurrent; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static com.google.common.base.StandardSystemProperty.OS_NAME; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.Arrays.asList; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.testing.NullPointerTester; import com.google.common.testing.TestLogHandler; import com.google.common.util.concurrent.Service.State; import com.google.common.util.concurrent.ServiceManager.Listener; +import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -42,6 +48,7 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link ServiceManager}. @@ -49,6 +56,7 @@ * @author Luke Sandberg * @author Chris Nokleberg */ +@NullUnmarked public class ServiceManagerTest extends TestCase { private static class NoOpService extends AbstractService { @@ -68,9 +76,9 @@ protected void doStop() { * of time. */ private static class NoOpDelayedService extends NoOpService { - private long delay; + private final long delay; - public NoOpDelayedService(long delay) { + NoOpDelayedService(long delay) { this.delay = delay; } @@ -79,7 +87,7 @@ protected void doStart() { new Thread() { @Override public void run() { - Uninterruptibles.sleepUninterruptibly(delay, TimeUnit.MILLISECONDS); + Uninterruptibles.sleepUninterruptibly(delay, MILLISECONDS); notifyStarted(); } }.start(); @@ -90,7 +98,7 @@ protected void doStop() { new Thread() { @Override public void run() { - Uninterruptibles.sleepUninterruptibly(delay, TimeUnit.MILLISECONDS); + Uninterruptibles.sleepUninterruptibly(delay, MILLISECONDS); notifyStopped(); } }.start(); @@ -119,8 +127,11 @@ protected void doStop() { } } - public void testServiceStartupTimes() { + if (isWindows() && isJava8()) { + // Flaky there: https://github.com/google/guava/pull/6731#issuecomment-1736298607 + return; + } Service a = new NoOpDelayedService(150); Service b = new NoOpDelayedService(353); ServiceManager serviceManager = new ServiceManager(asList(a, b)); @@ -131,19 +142,33 @@ public void testServiceStartupTimes() { assertThat(startupTimes.get(b)).isAtLeast(353); } + public void testServiceStartupDurations() { + if (isWindows() && isJava8()) { + // Flaky there: https://github.com/google/guava/pull/6731#issuecomment-1736298607 + return; + } + Service a = new NoOpDelayedService(150); + Service b = new NoOpDelayedService(353); + ServiceManager serviceManager = new ServiceManager(asList(a, b)); + serviceManager.startAsync().awaitHealthy(); + ImmutableMap startupTimes = serviceManager.startupDurations(); + assertThat(startupTimes).hasSize(2); + assertThat(startupTimes.get(a)).isAtLeast(Duration.ofMillis(150)); + assertThat(startupTimes.get(b)).isAtLeast(Duration.ofMillis(353)); + } public void testServiceStartupTimes_selfStartingServices() { // This tests to ensure that: // 1. service times are accurate when the service is started by the manager // 2. service times are recorded when the service is not started by the manager (but they may // not be accurate). - final Service b = + Service b = new NoOpDelayedService(353) { @Override protected void doStart() { super.doStart(); // This will delay service listener execution at least 150 milliseconds - Uninterruptibles.sleepUninterruptibly(150, TimeUnit.MILLISECONDS); + Uninterruptibles.sleepUninterruptibly(150, MILLISECONDS); } }; Service a = @@ -166,7 +191,6 @@ protected void doStart() { assertThat(startupTimes.get(b)).isNotNull(); } - public void testServiceStartStop() { Service a = new NoOpService(); Service b = new NoOpService(); @@ -188,7 +212,6 @@ public void testServiceStartStop() { assertTrue(listener.failedServices.isEmpty()); } - public void testFailStart() throws Exception { Service a = new NoOpService(); Service b = new FailStartService(); @@ -199,11 +222,7 @@ public void testFailStart() throws Exception { RecordingListener listener = new RecordingListener(); manager.addListener(listener, directExecutor()); assertState(manager, Service.State.NEW, a, b, c, d, e); - try { - manager.startAsync().awaitHealthy(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> manager.startAsync().awaitHealthy()); assertFalse(listener.healthyCalled); assertState(manager, Service.State.RUNNING, a, c, e); assertEquals(ImmutableSet.of(b, d), listener.failedServices); @@ -216,7 +235,6 @@ public void testFailStart() throws Exception { assertTrue(listener.stoppedCalled); } - public void testFailRun() throws Exception { Service a = new NoOpService(); Service b = new FailRunService(); @@ -224,11 +242,7 @@ public void testFailRun() throws Exception { RecordingListener listener = new RecordingListener(); manager.addListener(listener, directExecutor()); assertState(manager, Service.State.NEW, a, b); - try { - manager.startAsync().awaitHealthy(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> manager.startAsync().awaitHealthy()); assertTrue(listener.healthyCalled); assertEquals(ImmutableSet.of(b), listener.failedServices); @@ -239,7 +253,6 @@ public void testFailRun() throws Exception { assertTrue(listener.stoppedCalled); } - public void testFailStop() throws Exception { Service a = new NoOpService(); Service b = new FailStopService(); @@ -268,24 +281,15 @@ public void testToString() throws Exception { assertThat(toString).contains("FailStartService"); } - public void testTimeouts() throws Exception { Service a = new NoOpDelayedService(50); ServiceManager manager = new ServiceManager(asList(a)); manager.startAsync(); - try { - manager.awaitHealthy(1, TimeUnit.MILLISECONDS); - fail(); - } catch (TimeoutException expected) { - } + assertThrows(TimeoutException.class, () -> manager.awaitHealthy(1, MILLISECONDS)); manager.awaitHealthy(5, SECONDS); // no exception thrown manager.stopAsync(); - try { - manager.awaitStopped(1, TimeUnit.MILLISECONDS); - fail(); - } catch (TimeoutException expected) { - } + assertThrows(TimeoutException.class, () -> manager.awaitStopped(1, MILLISECONDS)); manager.awaitStopped(5, SECONDS); // no exception thrown } @@ -298,11 +302,7 @@ public void testSingleFailedServiceCallsStopped() { ServiceManager manager = new ServiceManager(asList(a)); RecordingListener listener = new RecordingListener(); manager.addListener(listener, directExecutor()); - try { - manager.startAsync().awaitHealthy(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> manager.startAsync().awaitHealthy()); assertTrue(listener.stoppedCalled); } @@ -315,11 +315,7 @@ public void testFailStart_singleServiceCallsHealthy() { ServiceManager manager = new ServiceManager(asList(a)); RecordingListener listener = new RecordingListener(); manager.addListener(listener, directExecutor()); - try { - manager.startAsync().awaitHealthy(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> manager.startAsync().awaitHealthy()); assertFalse(listener.healthyCalled); } @@ -332,7 +328,7 @@ public void testFailStart_singleServiceCallsHealthy() { public void testFailStart_stopOthers() throws TimeoutException { Service a = new FailStartService(); Service b = new NoOpService(); - final ServiceManager manager = new ServiceManager(asList(a, b)); + ServiceManager manager = new ServiceManager(asList(a, b)); manager.addListener( new Listener() { @Override @@ -342,7 +338,7 @@ public void failure(Service service) { }, directExecutor()); manager.startAsync(); - manager.awaitStopped(10, TimeUnit.MILLISECONDS); + manager.awaitStopped(10, MILLISECONDS); } public void testDoCancelStart() throws TimeoutException { @@ -365,10 +361,10 @@ protected void doStop() { } }; - final ServiceManager manager = new ServiceManager(asList(a)); + ServiceManager manager = new ServiceManager(asList(a)); manager.startAsync(); manager.stopAsync(); - manager.awaitStopped(10, TimeUnit.MILLISECONDS); + manager.awaitStopped(10, MILLISECONDS); assertThat(manager.servicesByState().keySet()).containsExactly(Service.State.TERMINATED); } @@ -386,9 +382,9 @@ protected void doStop() { notifyStopped(); } }; - final ServiceManager manager = new ServiceManager(asList(a)); + ServiceManager manager = new ServiceManager(asList(a)); manager.startAsync(); - manager.awaitStopped(10, TimeUnit.MILLISECONDS); + manager.awaitStopped(10, MILLISECONDS); assertThat(manager.servicesByState().keySet()).containsExactly(Service.State.FAILED); } @@ -412,7 +408,7 @@ public void testEmptyServiceManager() { logger.setLevel(Level.FINEST); TestLogHandler logHandler = new TestLogHandler(); logger.addHandler(logHandler); - ServiceManager manager = new ServiceManager(Arrays.asList()); + ServiceManager manager = new ServiceManager(Arrays.asList()); RecordingListener listener = new RecordingListener(); manager.addListener(listener, directExecutor()); manager.startAsync().awaitHealthy(); @@ -441,15 +437,44 @@ public String format(LogRecord record) { } } + public void testStartupFailureOutput() { + Logger logger = Logger.getLogger(ServiceManager.class.getName()); + logger.setLevel(Level.SEVERE); + TestLogHandler logHandler = new TestLogHandler(); + logger.addHandler(logHandler); + ServiceManager manager = + new ServiceManager(Arrays.asList(new FailRunService(), new FailStartService())); + // Due to the implementation of the two services we know that both are now failed. So the + // following awaitHealthy call is just to get the exception. + manager.startAsync(); + assertThat(manager.servicesByState().get(State.FAILED)).hasSize(2); + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> manager.awaitHealthy()); + assertThat(e) + .hasMessageThat() + .contains("Expected to be healthy after starting. The following services are not running:"); + + Throwable[] suppressed = e.getSuppressed(); + assertThat(suppressed).hasLength(2); + assertThat(suppressed[0]).hasCauseThat().isInstanceOf(IllegalStateException.class); + assertThat(suppressed[0]).hasCauseThat().hasMessageThat().isEqualTo("run failure"); + + assertThat(suppressed[1]).hasCauseThat().isInstanceOf(IllegalStateException.class); + assertThat(suppressed[1]).hasCauseThat().hasMessageThat().isEqualTo("start failure"); + LogRecord record = Iterables.getOnlyElement(logHandler.getStoredLogRecords()); + // We log failures that occur after startup + assertThat(record.getMessage()) + .contains("Service FailRunService [FAILED] has failed in the RUNNING state"); + } + /** * Tests that a ServiceManager can be fully shut down if one of its failure listeners is slow or * even permanently blocked. */ - public void testListenerDeadlock() throws InterruptedException { - final CountDownLatch failEnter = new CountDownLatch(1); - final CountDownLatch failLeave = new CountDownLatch(1); - final CountDownLatch afterStarted = new CountDownLatch(1); + CountDownLatch failEnter = new CountDownLatch(1); + CountDownLatch failLeave = new CountDownLatch(1); + CountDownLatch afterStarted = new CountDownLatch(1); Service failRunService = new AbstractService() { @Override @@ -472,8 +497,7 @@ protected void doStop() { notifyStopped(); } }; - final ServiceManager manager = - new ServiceManager(Arrays.asList(failRunService, new NoOpService())); + ServiceManager manager = new ServiceManager(Arrays.asList(failRunService, new NoOpService())); manager.addListener( new ServiceManager.Listener() { @Override @@ -500,7 +524,7 @@ public void run() { } }; stoppingThread.start(); - // this should be super fast since the only non stopped service is a NoOpService + // this should be super fast since the only non-stopped service is a NoOpService stoppingThread.join(1000); assertFalse("stopAsync has deadlocked!.", stoppingThread.isAlive()); failLeave.countDown(); // release the background thread @@ -519,11 +543,7 @@ public void testPartiallyConstructedManager() { logger.addHandler(logHandler); NoOpService service = new NoOpService(); service.startAsync(); - try { - new ServiceManager(Arrays.asList(service)); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> new ServiceManager(Arrays.asList(service))); service.stopAsync(); // Nothing was logged! assertEquals(0, logHandler.getStoredLogRecords().size()); @@ -532,7 +552,7 @@ public void testPartiallyConstructedManager() { public void testPartiallyConstructedManager_transitionAfterAddListenerBeforeStateIsReady() { // The implementation of this test is pretty sensitive to the implementation :( but we want to // ensure that if weird things happen during construction then we get exceptions. - final NoOpService service1 = new NoOpService(); + NoOpService service1 = new NoOpService(); // This service will start service1 when addListener is called. This simulates service1 being // started asynchronously. Service service2 = @@ -544,6 +564,7 @@ public final void addListener(Listener listener, Executor executor) { service1.startAsync(); delegate.addListener(listener, executor); } + // Delegates from here on down @Override public final Service startAsync() { @@ -590,12 +611,11 @@ public final Throwable failureCause() { return delegate.failureCause(); } }; - try { - new ServiceManager(Arrays.asList(service1, service2)); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()).contains("started transitioning asynchronously"); - } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> new ServiceManager(Arrays.asList(service1, service2))); + assertThat(expected).hasMessageThat().contains("started transitioning asynchronously"); } /** @@ -606,21 +626,20 @@ public final Throwable failureCause() { * *

    Before the bug was fixed this test would fail at least 30% of the time. */ - public void testTransitionRace() throws TimeoutException { for (int k = 0; k < 1000; k++) { - List services = Lists.newArrayList(); + List services = new ArrayList<>(); for (int i = 0; i < 5; i++) { services.add(new SnappyShutdownService(i)); } ServiceManager manager = new ServiceManager(services); manager.startAsync().awaitHealthy(); - manager.stopAsync().awaitStopped(10, TimeUnit.SECONDS); + manager.stopAsync().awaitStopped(10, SECONDS); } } /** - * This service will shutdown very quickly after stopAsync is called and uses a background thread + * This service will shut down very quickly after stopAsync is called and uses a background thread * so that we know that the stopping() listeners will execute on a different thread than the * terminated() listeners. */ @@ -649,7 +668,7 @@ protected String serviceName() { } public void testNulls() { - ServiceManager manager = new ServiceManager(Arrays.asList()); + ServiceManager manager = new ServiceManager(Arrays.asList()); new NullPointerTester() .setDefault(ServiceManager.Listener.class, new RecordingListener()) .testAllPublicInstanceMethods(manager); @@ -675,4 +694,12 @@ public void failure(Service service) { failedServices.add(service); } } + + private static boolean isWindows() { + return OS_NAME.value().startsWith("Windows"); + } + + private static boolean isJava8() { + return JAVA_SPECIFICATION_VERSION.value().equals("1.8"); + } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ServiceTest.java index 98b8033d901b..53e299c70dc3 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ServiceTest.java @@ -25,8 +25,10 @@ import java.util.Locale; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** Unit tests for {@link Service} */ +@NullUnmarked public class ServiceTest extends TestCase { /** Assert on the comparison ordering of the State enum since we guarantee it. */ diff --git a/android/guava-tests/test/com/google/common/util/concurrent/SettableFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/SettableFutureTest.java index 9b8b88f27167..3c321ac0d892 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/SettableFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/SettableFutureTest.java @@ -17,18 +17,21 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertThrows; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test cases for {@link SettableFuture}. * * @author Sven Mawson */ +@NullUnmarked public class SettableFutureTest extends TestCase { private SettableFuture future; @@ -44,38 +47,26 @@ protected void setUp() throws Exception { } public void testDefaultState() throws Exception { - try { - future.get(5, TimeUnit.MILLISECONDS); - fail(); - } catch (TimeoutException expected) { - } + assertThrows(TimeoutException.class, () -> future.get(5, MILLISECONDS)); } - public void testSetValue() throws Exception { assertTrue(future.set("value")); tester.testCompletedFuture("value"); } - public void testSetFailure() throws Exception { assertTrue(future.setException(new Exception("failure"))); tester.testFailedFuture("failure"); } - public void testSetFailureNull() throws Exception { - try { - future.setException(null); - fail(); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> future.setException(null)); assertFalse(future.isDone()); assertTrue(future.setException(new Exception("failure"))); tester.testFailedFuture("failure"); } - public void testCancel() throws Exception { assertTrue(future.cancel(true)); tester.testCancelledFuture(); @@ -94,7 +85,7 @@ public void testSetValue_simpleThreaded() throws Exception { // Later attempts to set the future should return false. assertFalse(future.set(23)); assertFalse(future.setException(new Exception("bar"))); - assertFalse(future.setFuture(SettableFuture.create())); + assertFalse(future.setFuture(SettableFuture.create())); // Check that the future has been set properly. assertTrue(future.isDone()); assertFalse(future.isCancelled()); @@ -112,12 +103,8 @@ public void testSetException() throws Exception { // Check that the future has been set properly. assertTrue(future.isDone()); assertFalse(future.isCancelled()); - try { - future.get(); - fail("Expected ExecutionException"); - } catch (ExecutionException ee) { - assertThat(ee).hasCauseThat().isSameInstanceAs(e); - } + ExecutionException ee = assertThrows(ExecutionException.class, () -> future.get()); + assertThat(ee).hasCauseThat().isSameInstanceAs(e); } public void testSetFuture() throws Exception { @@ -127,16 +114,11 @@ public void testSetFuture() throws Exception { // Later attempts to set the future should return false. assertFalse(future.set("x")); assertFalse(future.setException(new Exception("bar"))); - assertFalse(future.setFuture(SettableFuture.create())); + assertFalse(future.setFuture(SettableFuture.create())); // Check that the future has been set properly. assertFalse(future.isDone()); assertFalse(future.isCancelled()); - try { - future.get(0, TimeUnit.MILLISECONDS); - fail("Expected TimeoutException"); - } catch (TimeoutException expected) { - /* expected */ - } + assertThrows(TimeoutException.class, () -> future.get(0, MILLISECONDS)); nested.set("foo"); assertTrue(future.isDone()); assertFalse(future.isCancelled()); @@ -154,16 +136,11 @@ public void testSetFuture_genericsHierarchy() throws Exception { // Later attempts to set the future should return false. assertFalse(future.set(new Foo())); assertFalse(future.setException(new Exception("bar"))); - assertFalse(future.setFuture(SettableFuture.create())); + assertFalse(future.setFuture(SettableFuture.create())); // Check that the future has been set properly. assertFalse(future.isDone()); assertFalse(future.isCancelled()); - try { - future.get(0, TimeUnit.MILLISECONDS); - fail("Expected TimeoutException"); - } catch (TimeoutException expected) { - /* expected */ - } + assertThrows(TimeoutException.class, () -> future.get(0, MILLISECONDS)); FooChild value = new FooChild(); nested.set(value); assertTrue(future.isDone()); @@ -177,12 +154,7 @@ public void testCancel_innerCancelsAsync() throws Exception { async.setFuture(inner); inner.cancel(true); assertTrue(async.isCancelled()); - try { - async.get(); - fail("Expected CancellationException"); - } catch (CancellationException expected) { - /* expected */ - } + assertThrows(CancellationException.class, () -> async.get()); } public void testCancel_resultCancelsInner_interrupted() throws Exception { @@ -192,12 +164,7 @@ public void testCancel_resultCancelsInner_interrupted() throws Exception { async.cancel(true); assertTrue(inner.isCancelled()); assertTrue(inner.wasInterrupted()); - try { - inner.get(); - fail("Expected CancellationException"); - } catch (CancellationException expected) { - /* expected */ - } + assertThrows(CancellationException.class, () -> inner.get()); } public void testCancel_resultCancelsInner() throws Exception { @@ -207,12 +174,7 @@ public void testCancel_resultCancelsInner() throws Exception { async.cancel(false); assertTrue(inner.isCancelled()); assertFalse(inner.wasInterrupted()); - try { - inner.get(); - fail("Expected CancellationException"); - } catch (CancellationException expected) { - /* expected */ - } + assertThrows(CancellationException.class, () -> inner.get()); } public void testCancel_beforeSet() throws Exception { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/SimpleTimeLimiterTest.java b/android/guava-tests/test/com/google/common/util/concurrent/SimpleTimeLimiterTest.java index 04b824f823f9..0ca53629d9d9 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/SimpleTimeLimiterTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/SimpleTimeLimiterTest.java @@ -17,16 +17,19 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.base.Stopwatch; import com.google.common.collect.Range; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Unit test for {@link SimpleTimeLimiter}. @@ -34,7 +37,7 @@ * @author kevinb * @author Jens Nyman */ - +@NullUnmarked public class SimpleTimeLimiterTest extends TestCase { private static final long DELAY_MS = 50; @@ -84,7 +87,7 @@ public void run() { private TimeLimiter service; - private static final ExecutorService executor = Executors.newFixedThreadPool(1); + private static final ExecutorService executor = newFixedThreadPool(1); @Override protected void setUp() throws Exception { @@ -109,11 +112,7 @@ public void testNewProxy_goodMethodWithNotEnoughTime() throws Exception { Sample proxy = service.newProxy(target, Sample.class, NOT_ENOUGH_MS, MILLISECONDS); Stopwatch stopwatch = Stopwatch.createStarted(); - try { - proxy.sleepThenReturnInput("x"); - fail("no exception thrown"); - } catch (UncheckedTimeoutException expected) { - } + assertThrows(UncheckedTimeoutException.class, () -> proxy.sleepThenReturnInput("x")); assertThat(stopwatch.elapsed(MILLISECONDS)).isIn(Range.closed(NOT_ENOUGH_MS, DELAY_MS * 2)); // Is it still computing away anyway? @@ -127,11 +126,7 @@ public void testNewProxy_badMethodWithEnoughTime() throws Exception { Sample proxy = service.newProxy(target, Sample.class, ENOUGH_MS, MILLISECONDS); Stopwatch stopwatch = Stopwatch.createStarted(); - try { - proxy.sleepThenThrowException(); - fail("no exception thrown"); - } catch (SampleException expected) { - } + assertThrows(SampleException.class, () -> proxy.sleepThenThrowException()); assertThat(stopwatch.elapsed(MILLISECONDS)).isIn(Range.closed(DELAY_MS, ENOUGH_MS)); } @@ -141,11 +136,7 @@ public void testNewProxy_badMethodWithNotEnoughTime() throws Exception { Sample proxy = service.newProxy(target, Sample.class, NOT_ENOUGH_MS, MILLISECONDS); Stopwatch stopwatch = Stopwatch.createStarted(); - try { - proxy.sleepThenThrowException(); - fail("no exception thrown"); - } catch (UncheckedTimeoutException expected) { - } + assertThrows(UncheckedTimeoutException.class, () -> proxy.sleepThenThrowException()); assertThat(stopwatch.elapsed(MILLISECONDS)).isIn(Range.closed(NOT_ENOUGH_MS, DELAY_MS * 2)); } @@ -160,20 +151,17 @@ public void testCallWithTimeout_goodCallableWithEnoughTime() throws Exception { } public void testCallWithTimeout_goodCallableWithNotEnoughTime() throws Exception { - try { - service.callWithTimeout(GOOD_CALLABLE, NOT_ENOUGH_MS, MILLISECONDS); - fail("no exception thrown"); - } catch (TimeoutException expected) { - } + assertThrows( + TimeoutException.class, + () -> service.callWithTimeout(GOOD_CALLABLE, NOT_ENOUGH_MS, MILLISECONDS)); } public void testCallWithTimeout_badCallableWithEnoughTime() throws Exception { - try { - service.callWithTimeout(BAD_CALLABLE, ENOUGH_MS, MILLISECONDS); - fail("no exception thrown"); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(SampleException.class); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> service.callWithTimeout(BAD_CALLABLE, ENOUGH_MS, MILLISECONDS)); + assertThat(expected).hasCauseThat().isInstanceOf(SampleException.class); } public void testCallUninterruptiblyWithTimeout_goodCallableWithEnoughTime() throws Exception { @@ -186,20 +174,17 @@ public void testCallUninterruptiblyWithTimeout_goodCallableWithEnoughTime() thro } public void testCallUninterruptiblyWithTimeout_goodCallableWithNotEnoughTime() throws Exception { - try { - service.callUninterruptiblyWithTimeout(GOOD_CALLABLE, NOT_ENOUGH_MS, MILLISECONDS); - fail("no exception thrown"); - } catch (TimeoutException expected) { - } + assertThrows( + TimeoutException.class, + () -> service.callUninterruptiblyWithTimeout(GOOD_CALLABLE, NOT_ENOUGH_MS, MILLISECONDS)); } public void testCallUninterruptiblyWithTimeout_badCallableWithEnoughTime() throws Exception { - try { - service.callUninterruptiblyWithTimeout(BAD_CALLABLE, ENOUGH_MS, MILLISECONDS); - fail("no exception thrown"); - } catch (ExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(SampleException.class); - } + ExecutionException expected = + assertThrows( + ExecutionException.class, + () -> service.callUninterruptiblyWithTimeout(BAD_CALLABLE, ENOUGH_MS, MILLISECONDS)); + assertThat(expected).hasCauseThat().isInstanceOf(SampleException.class); } public void testRunWithTimeout_goodRunnableWithEnoughTime() throws Exception { @@ -211,20 +196,17 @@ public void testRunWithTimeout_goodRunnableWithEnoughTime() throws Exception { } public void testRunWithTimeout_goodRunnableWithNotEnoughTime() throws Exception { - try { - service.runWithTimeout(GOOD_RUNNABLE, NOT_ENOUGH_MS, MILLISECONDS); - fail("no exception thrown"); - } catch (TimeoutException expected) { - } + assertThrows( + TimeoutException.class, + () -> service.runWithTimeout(GOOD_RUNNABLE, NOT_ENOUGH_MS, MILLISECONDS)); } public void testRunWithTimeout_badRunnableWithEnoughTime() throws Exception { - try { - service.runWithTimeout(BAD_RUNNABLE, ENOUGH_MS, MILLISECONDS); - fail("no exception thrown"); - } catch (UncheckedExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(SampleRuntimeException.class); - } + UncheckedExecutionException expected = + assertThrows( + UncheckedExecutionException.class, + () -> service.runWithTimeout(BAD_RUNNABLE, ENOUGH_MS, MILLISECONDS)); + assertThat(expected).hasCauseThat().isInstanceOf(SampleRuntimeException.class); } public void testRunUninterruptiblyWithTimeout_goodRunnableWithEnoughTime() throws Exception { @@ -236,20 +218,17 @@ public void testRunUninterruptiblyWithTimeout_goodRunnableWithEnoughTime() throw } public void testRunUninterruptiblyWithTimeout_goodRunnableWithNotEnoughTime() throws Exception { - try { - service.runUninterruptiblyWithTimeout(GOOD_RUNNABLE, NOT_ENOUGH_MS, MILLISECONDS); - fail("no exception thrown"); - } catch (TimeoutException expected) { - } + assertThrows( + TimeoutException.class, + () -> service.runUninterruptiblyWithTimeout(GOOD_RUNNABLE, NOT_ENOUGH_MS, MILLISECONDS)); } public void testRunUninterruptiblyWithTimeout_badRunnableWithEnoughTime() throws Exception { - try { - service.runUninterruptiblyWithTimeout(BAD_RUNNABLE, ENOUGH_MS, MILLISECONDS); - fail("no exception thrown"); - } catch (UncheckedExecutionException expected) { - assertThat(expected.getCause()).isInstanceOf(SampleRuntimeException.class); - } + UncheckedExecutionException expected = + assertThrows( + UncheckedExecutionException.class, + () -> service.runUninterruptiblyWithTimeout(BAD_RUNNABLE, ENOUGH_MS, MILLISECONDS)); + assertThat(expected).hasCauseThat().isInstanceOf(SampleRuntimeException.class); } private interface Sample { @@ -272,6 +251,7 @@ private static class SampleImpl implements Sample { this.delayMillis = delayMillis; } + @CanIgnoreReturnValue @Override public String sleepThenReturnInput(String input) { try { @@ -279,7 +259,7 @@ public String sleepThenReturnInput(String input) { finished = true; return input; } catch (InterruptedException e) { - return null; + throw new AssertionError(); } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/StripedTest.java b/android/guava-tests/test/com/google/common/util/concurrent/StripedTest.java index fa9d87f752f2..e1e5068081e9 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/StripedTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/StripedTest.java @@ -17,17 +17,18 @@ package com.google.common.util.concurrent; import static com.google.common.collect.Iterables.concat; +import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Functions; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.common.testing.GcFinalization; import com.google.common.testing.NullPointerTester; import java.lang.ref.WeakReference; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,12 +38,14 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for Striped. * * @author Dimitris Andreou */ +@NullUnmarked public class StripedTest extends TestCase { private static List> strongImplementations() { return ImmutableList.of( @@ -50,22 +53,8 @@ private static List> strongImplementations() { Striped.readWriteLock(256), Striped.lock(100), Striped.lock(256), - Striped.custom( - 100, - new Supplier() { - @Override - public Lock get() { - return new ReentrantLock(true); - } - }), - Striped.custom( - 256, - new Supplier() { - @Override - public Lock get() { - return new ReentrantLock(true); - } - }), + Striped.custom(100, FAIR_LOCK_SUPPLER), + Striped.custom(256, FAIR_LOCK_SUPPLER), Striped.semaphore(100, 1), Striped.semaphore(256, 1)); } @@ -86,6 +75,14 @@ public Lock get() { } }; + private static final Supplier FAIR_LOCK_SUPPLER = + new Supplier() { + @Override + public Lock get() { + return new ReentrantLock(true); + } + }; + private static final Supplier SEMAPHORE_SUPPLER = new Supplier() { @Override @@ -108,6 +105,8 @@ private static List> weakImplementations() { .add(new Striped.SmallLazyStriped(64, SEMAPHORE_SUPPLER)) .add(new Striped.LargeLazyStriped(50, SEMAPHORE_SUPPLER)) .add(new Striped.LargeLazyStriped(64, SEMAPHORE_SUPPLER)) + .add(Striped.lazyWeakCustom(50, FAIR_LOCK_SUPPLER)) + .add(Striped.lazyWeakCustom(64, FAIR_LOCK_SUPPLER)) .build(); } @@ -123,13 +122,13 @@ public void testNull() throws Exception { public void testSizes() { // not bothering testing all variations, since we know they share implementations - assertTrue(Striped.lock(100).size() >= 100); + assertThat(Striped.lock(100).size()).isAtLeast(100); assertTrue(Striped.lock(256).size() == 256); - assertTrue(Striped.lazyWeakLock(100).size() >= 100); + assertThat(Striped.lazyWeakLock(100).size()).isAtLeast(100); assertTrue(Striped.lazyWeakLock(256).size() == 256); } - + @AndroidIncompatible // Presumably GC doesn't trigger, despite our efforts. public void testWeakImplementations() { for (Striped striped : weakImplementations()) { WeakReference weakRef = new WeakReference<>(striped.get(new Object())); @@ -137,7 +136,7 @@ public void testWeakImplementations() { } } - + @AndroidIncompatible // Presumably GC doesn't trigger, despite our efforts. public void testWeakReadWrite() { Striped striped = Striped.lazyWeakReadWriteLock(1000); Object key = new Object(); @@ -150,13 +149,13 @@ public void testWeakReadWrite() { readLock.unlock(); } - + @AndroidIncompatible // Presumably GC doesn't trigger, despite our efforts. public void testStrongImplementations() { for (Striped striped : strongImplementations()) { WeakReference weakRef = new WeakReference<>(striped.get(new Object())); WeakReference garbage = new WeakReference<>(new Object()); GcFinalization.awaitClear(garbage); - assertNotNull(weakRef.get()); + assertThat(weakRef.get()).isNotNull(); } } @@ -170,7 +169,7 @@ public void testMaximalWeakStripedLock() { public void testBulkGetReturnsSorted() { for (Striped striped : allImplementations()) { - Map indexByLock = Maps.newHashMap(); + Map indexByLock = new HashMap<>(); for (int i = 0; i < striped.size(); i++) { indexByLock.put(striped.getAt(i), i); } @@ -204,7 +203,7 @@ private static void assertBasicInvariants(Striped striped) { // this gets the stripes with #getAt(index) for (int i = 0; i < striped.size(); i++) { Object object = striped.getAt(i); - assertNotNull(object); + assertThat(object).isNotNull(); assertSame(object, striped.getAt(i)); // idempotent observed.add(object); } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/SupplementalMonitorTest.java b/android/guava-tests/test/com/google/common/util/concurrent/SupplementalMonitorTest.java index 8a52ffeed591..ec7655e923a1 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/SupplementalMonitorTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/SupplementalMonitorTest.java @@ -16,14 +16,17 @@ package com.google.common.util.concurrent; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.GeneratedMonitorTest.startThread; import static com.google.common.util.concurrent.Uninterruptibles.joinUninterruptibly; +import static org.junit.Assert.assertThrows; import com.google.common.util.concurrent.GeneratedMonitorTest.FlagGuard; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Supplemental tests for {@link Monitor}. @@ -33,46 +36,30 @@ * * @author Justin T. Sampson */ - +@NullUnmarked public class SupplementalMonitorTest extends TestCase { public void testLeaveWithoutEnterThrowsIMSE() { Monitor monitor = new Monitor(); - try { - monitor.leave(); - fail("expected IllegalMonitorStateException"); - } catch (IllegalMonitorStateException expected) { - } + assertThrows(IllegalMonitorStateException.class, () -> monitor.leave()); } public void testGetWaitQueueLengthWithWrongMonitorThrowsIMSE() { Monitor monitor1 = new Monitor(); Monitor monitor2 = new Monitor(); FlagGuard guard = new FlagGuard(monitor2); - try { - monitor1.getWaitQueueLength(guard); - fail("expected IllegalMonitorStateException"); - } catch (IllegalMonitorStateException expected) { - } + assertThrows(IllegalMonitorStateException.class, () -> monitor1.getWaitQueueLength(guard)); } public void testHasWaitersWithWrongMonitorThrowsIMSE() { Monitor monitor1 = new Monitor(); Monitor monitor2 = new Monitor(); FlagGuard guard = new FlagGuard(monitor2); - try { - monitor1.hasWaiters(guard); - fail("expected IllegalMonitorStateException"); - } catch (IllegalMonitorStateException expected) { - } + assertThrows(IllegalMonitorStateException.class, () -> monitor1.hasWaiters(guard)); } public void testNullMonitorInGuardConstructorThrowsNPE() { - try { - new FlagGuard(null); - fail("expected NullPointerException"); - } catch (NullPointerException expected) { - } + assertThrows(NullPointerException.class, () -> new FlagGuard(null)); } public void testIsFair() { @@ -115,14 +102,14 @@ private static void verifyOccupiedMethodsInCurrentThread( } private static void verifyOccupiedMethodsInAnotherThread( - final Monitor monitor, + Monitor monitor, boolean expectedIsOccupied, boolean expectedIsOccupiedByCurrentThread, int expectedOccupiedDepth) { - final AtomicBoolean actualIsOccupied = new AtomicBoolean(); - final AtomicBoolean actualIsOccupiedByCurrentThread = new AtomicBoolean(); - final AtomicInteger actualOccupiedDepth = new AtomicInteger(); - final AtomicReference thrown = new AtomicReference<>(); + AtomicBoolean actualIsOccupied = new AtomicBoolean(); + AtomicBoolean actualIsOccupiedByCurrentThread = new AtomicBoolean(); + AtomicInteger actualOccupiedDepth = new AtomicInteger(); + AtomicReference thrown = new AtomicReference<>(); joinUninterruptibly( startThread( new Runnable() { @@ -137,7 +124,7 @@ public void run() { } } })); - assertNull(thrown.get()); + assertThat(thrown.get()).isNull(); assertEquals(expectedIsOccupied, actualIsOccupied.get()); assertEquals(expectedIsOccupiedByCurrentThread, actualIsOccupiedByCurrentThread.get()); assertEquals(expectedOccupiedDepth, actualOccupiedDepth.get()); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/TestExceptions.java b/android/guava-tests/test/com/google/common/util/concurrent/TestExceptions.java new file mode 100644 index 000000000000..3a0496818fbe --- /dev/null +++ b/android/guava-tests/test/com/google/common/util/concurrent/TestExceptions.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.NullUnmarked; + +/** Exception classes for use in tests. */ +@GwtCompatible +@NullUnmarked +final class TestExceptions { + static class SomeError extends Error {} + + static class SomeCheckedException extends Exception {} + + static class SomeOtherCheckedException extends Exception {} + + static class YetAnotherCheckedException extends Exception {} + + static class SomeUncheckedException extends RuntimeException {} + + static class SomeChainingException extends RuntimeException { + public SomeChainingException(Throwable cause) { + super(cause); + } + } + + private TestExceptions() {} +} diff --git a/android/guava-tests/test/com/google/common/util/concurrent/TestPlatform.java b/android/guava-tests/test/com/google/common/util/concurrent/TestPlatform.java index 5c87fe552a05..b7c749ad766e 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/TestPlatform.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/TestPlatform.java @@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static com.google.common.util.concurrent.FuturesTest.failureWithCause; import static com.google.common.util.concurrent.FuturesTest.pseudoTimedGetUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -30,10 +29,10 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; -import junit.framework.AssertionFailedError; +import org.jspecify.annotations.Nullable; /** Methods factored out so that they can be emulated differently in GWT. */ -@GwtCompatible(emulated = true) +@GwtCompatible final class TestPlatform { static void verifyGetOnPendingFuture(Future future) { checkNotNull(future); @@ -42,7 +41,7 @@ static void verifyGetOnPendingFuture(Future future) { fail(); } catch (TimeoutException expected) { } catch (ExecutionException e) { - throw failureWithCause(e, ""); + throw new AssertionError(e); } } @@ -52,7 +51,7 @@ static void verifyTimedGetOnPendingFuture(Future future) { fail(); } catch (TimeoutException expected) { } catch (ExecutionException e) { - throw failureWithCause(e, ""); + throw new AssertionError(e); } } @@ -68,14 +67,13 @@ static void clearInterrupt() { * Retrieves the result of a {@code Future} known to be done but uses the {@code get(long, * TimeUnit)} overload in order to test that method. */ - static V getDoneFromTimeoutOverload(Future future) throws ExecutionException { + static V getDoneFromTimeoutOverload(Future future) + throws ExecutionException { checkState(future.isDone(), "Future was expected to be done: %s", future); try { return getUninterruptibly(future, 0, SECONDS); } catch (TimeoutException e) { - AssertionFailedError error = new AssertionFailedError(e.getMessage()); - error.initCause(e); - throw error; + throw new AssertionError(e); } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/TestThread.java b/android/guava-tests/test/com/google/common/util/concurrent/TestThread.java index 1c3c88818e4b..c9ea54eaf010 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/TestThread.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/TestThread.java @@ -17,19 +17,19 @@ package com.google.common.util.concurrent; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertSame; import com.google.common.testing.TearDown; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.AssertionFailedError; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * A helper for concurrency testing. One or more {@code TestThread} instances are instantiated in a @@ -48,6 +48,7 @@ * @param the type of the lock-like object to be used * @author Justin T. Sampson */ +@NullUnmarked public final class TestThread extends Thread implements TearDown { private static final long DUE_DILIGENCE_MILLIS = 100; @@ -58,7 +59,7 @@ public final class TestThread extends Thread implements TearDown { private final SynchronousQueue requestQueue = new SynchronousQueue<>(); private final SynchronousQueue responseQueue = new SynchronousQueue<>(); - private Throwable uncaughtThrowable = null; + private @Nullable Throwable uncaughtThrowable = null; public TestThread(L lockLikeObject, String threadName) { super(threadName); @@ -66,20 +67,29 @@ public TestThread(L lockLikeObject, String threadName) { start(); } - // Thread.stop() is okay because all threads started by a test are dying at the end of the test, - // so there is no object state put at risk by stopping the threads abruptly. In some cases a test - // may put a thread into an uninterruptible operation intentionally, so there is no other way to - // clean up these threads. - @SuppressWarnings("deprecation") + /* + * TODO: b/318391980 - Once we test only under Java 20 and higher, avoid calling Thread.stop. As + * of Java 20, it always throws an exception, and as of Java 26, the method does not even exist. + * For now, we continue using it to clean up under older JDKs. + * + * Our usages should at least be *relatively* safe: Typically, threads started by a test are dying + * at the end of the test, so there is no object state put at risk by stopping the threads + * abruptly. In other cases, a test may put a thread into an uninterruptible operation + * intentionally, so there is no other way to clean up these threads. (The better solution, + * though, would be to run the tests that use TestThread in separate VMs so that their threads + * don't hang around during other tests.) + */ @Override public void tearDown() throws Exception { - stop(); - join(); + try { + Thread.class.getMethod("stop").invoke(this); + join(); + } catch (ReflectiveOperationException e) { + // stop() threw or did not exist. Don't join() the thread, which might hang forever. + } if (uncaughtThrowable != null) { - throw (AssertionFailedError) - new AssertionFailedError("Uncaught throwable in " + getName()) - .initCause(uncaughtThrowable); + throw new AssertionError("Uncaught throwable in " + getName(), uncaughtThrowable); } } @@ -143,7 +153,7 @@ public void callAndAssertBlocks(String methodName, Object... arguments) throws E sendRequest(methodName, arguments); Thread.sleep(DUE_DILIGENCE_MILLIS); assertEquals(true, invokeMethod("hasQueuedThread", this)); - assertNull(responseQueue.poll()); + assertThat(responseQueue.poll()).isNull(); } /** @@ -161,14 +171,14 @@ public void callAndAssertWaits(String methodName, Object conditionLikeObject) th sendRequest(methodName, conditionLikeObject); Thread.sleep(DUE_DILIGENCE_MILLIS); assertEquals(true, invokeMethod("hasWaiters", conditionLikeObject)); - assertNull(responseQueue.poll()); + assertThat(responseQueue.poll()).isNull(); } /** * Asserts that a prior call that had caused this thread to block or wait has since returned * normally. */ - public void assertPriorCallReturns(@NullableDecl String methodName) throws Exception { + public void assertPriorCallReturns(@Nullable String methodName) throws Exception { assertEquals(null, getResponse(methodName).getResult()); } @@ -176,7 +186,7 @@ public void assertPriorCallReturns(@NullableDecl String methodName) throws Excep * Asserts that a prior call that had caused this thread to block or wait has since returned the * expected boolean value. */ - public void assertPriorCallReturns(boolean expected, @NullableDecl String methodName) + public void assertPriorCallReturns(boolean expected, @Nullable String methodName) throws Exception { assertEquals(expected, getResponse(methodName).getResult()); } @@ -188,8 +198,7 @@ public void assertPriorCallReturns(boolean expected, @NullableDecl String method * of time */ private void sendRequest(String methodName, Object... arguments) throws Exception { - if (!requestQueue.offer( - new Request(methodName, arguments), TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { + if (!requestQueue.offer(new Request(methodName, arguments), TIMEOUT_MILLIS, MILLISECONDS)) { throw new TimeoutException(); } } @@ -203,7 +212,7 @@ private void sendRequest(String methodName, Object... arguments) throws Exceptio * this thread has called most recently */ private Response getResponse(String methodName) throws Exception { - Response response = responseQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + Response response = responseQueue.poll(TIMEOUT_MILLIS, MILLISECONDS); if (response == null) { throw new TimeoutException(); } @@ -275,7 +284,7 @@ private static class Response { final Object result; final Throwable throwable; - Response(String methodName, Object result, Throwable throwable) { + Response(String methodName, @Nullable Object result, @Nullable Throwable throwable) { this.methodName = methodName; this.result = result; this.throwable = throwable; @@ -283,13 +292,13 @@ private static class Response { Object getResult() { if (throwable != null) { - throw (AssertionFailedError) new AssertionFailedError().initCause(throwable); + throw new AssertionError(throwable); } return result; } Throwable getThrowable() { - assertNotNull(throwable); + assertThat(throwable).isNotNull(); return throwable; } } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/ThreadFactoryBuilderTest.java b/android/guava-tests/test/com/google/common/util/concurrent/ThreadFactoryBuilderTest.java index 7684b963376a..907159130e4d 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/ThreadFactoryBuilderTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/ThreadFactoryBuilderTest.java @@ -17,13 +17,15 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.Executors.defaultThreadFactory; +import static org.junit.Assert.assertThrows; import com.google.common.testing.NullPointerTester; import java.lang.Thread.UncaughtExceptionHandler; import java.util.Locale; -import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for ThreadFactoryBuilder. @@ -31,6 +33,7 @@ * @author Kurt Alfred Kluever * @author Martin Buchholz */ +@NullUnmarked public class ThreadFactoryBuilderTest extends TestCase { private final Runnable monitoredRunnable = new Runnable() { @@ -56,13 +59,12 @@ public void setUp() { builder = new ThreadFactoryBuilder(); } - public void testThreadFactoryBuilder_defaults() throws InterruptedException { ThreadFactory threadFactory = builder.build(); Thread thread = threadFactory.newThread(monitoredRunnable); checkThreadPoolName(thread, 1); - Thread defaultThread = Executors.defaultThreadFactory().newThread(monitoredRunnable); + Thread defaultThread = defaultThreadFactory().newThread(monitoredRunnable); assertEquals(defaultThread.isDaemon(), thread.isDaemon()); assertEquals(defaultThread.getPriority(), thread.getPriority()); assertSame(defaultThread.getThreadGroup(), thread.getThreadGroup()); @@ -93,7 +95,6 @@ private static void checkThreadPoolName(Thread thread, int threadId) { assertThat(thread.getName()).matches("^pool-\\d+-thread-" + threadId + "$"); } - public void testNameFormatWithPercentS_custom() { String format = "super-duper-thread-%s"; ThreadFactory factory = builder.setNameFormat(format).build(); @@ -102,7 +103,6 @@ public void testNameFormatWithPercentS_custom() { } } - public void testNameFormatWithPercentD_custom() { String format = "super-duper-thread-%d"; ThreadFactory factory = builder.setNameFormat(format).build(); @@ -111,21 +111,18 @@ public void testNameFormatWithPercentD_custom() { } } - public void testDaemon_false() { ThreadFactory factory = builder.setDaemon(false).build(); Thread thread = factory.newThread(monitoredRunnable); assertFalse(thread.isDaemon()); } - public void testDaemon_true() { ThreadFactory factory = builder.setDaemon(true).build(); Thread thread = factory.newThread(monitoredRunnable); assertTrue(thread.isDaemon()); } - public void testPriority_custom() { for (int i = Thread.MIN_PRIORITY; i <= Thread.MAX_PRIORITY; i++) { ThreadFactory factory = builder.setPriority(i).build(); @@ -135,22 +132,15 @@ public void testPriority_custom() { } public void testPriority_tooLow() { - try { - builder.setPriority(Thread.MIN_PRIORITY - 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> builder.setPriority(Thread.MIN_PRIORITY - 1)); } public void testPriority_tooHigh() { - try { - builder.setPriority(Thread.MAX_PRIORITY + 1); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, () -> builder.setPriority(Thread.MAX_PRIORITY + 1)); } - public void testUncaughtExceptionHandler_custom() { assertEquals( UNCAUGHT_EXCEPTION_HANDLER, @@ -161,7 +151,6 @@ public void testUncaughtExceptionHandler_custom() { .getUncaughtExceptionHandler()); } - public void testBuildMutateBuild() { ThreadFactory factory1 = builder.setPriority(1).build(); assertEquals(1, factory1.newThread(monitoredRunnable).getPriority()); @@ -177,7 +166,6 @@ public void testBuildTwice() { unused = builder.build(); // this is *also* allowed } - public void testBuildMutate() { ThreadFactory factory1 = builder.setPriority(1).build(); assertEquals(1, factory1.newThread(monitoredRunnable).getPriority()); @@ -186,11 +174,10 @@ public void testBuildMutate() { assertEquals(1, factory1.newThread(monitoredRunnable).getPriority()); } - public void testThreadFactory() throws InterruptedException { - final String THREAD_NAME = "ludicrous speed"; - final int THREAD_PRIORITY = 1; - final boolean THREAD_DAEMON = false; + String THREAD_NAME = "ludicrous speed"; + int THREAD_PRIORITY = 1; + boolean THREAD_DAEMON = false; ThreadFactory backingThreadFactory = new ThreadFactory() { @Override diff --git a/android/guava-tests/test/com/google/common/util/concurrent/TrustedInputFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/TrustedInputFutureTest.java index 1f2eccad932d..bcd6e95adcaa 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/TrustedInputFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/TrustedInputFutureTest.java @@ -16,15 +16,16 @@ package com.google.common.util.concurrent; - import com.google.common.annotations.GwtCompatible; import com.google.common.util.concurrent.AbstractFuture.TrustedFuture; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link AbstractFuture} that use a {@link TrustedFuture} for {@link * AbstractFuture#setFuture} calls. */ @GwtCompatible +@NullUnmarked public class TrustedInputFutureTest extends AbstractAbstractFutureTest { @Override AbstractFuture newDelegate() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/TrustedListenableFutureTaskTest.java b/android/guava-tests/test/com/google/common/util/concurrent/TrustedListenableFutureTaskTest.java index 157afa79d8a6..f09c6bd2fd67 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/TrustedListenableFutureTaskTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/TrustedListenableFutureTaskTest.java @@ -19,23 +19,28 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.Callables.returning; import static com.google.common.util.concurrent.Futures.getDone; +import static com.google.common.util.concurrent.ReflectionFreeAssertThrows.assertThrows; import static com.google.common.util.concurrent.TestPlatform.verifyThreadWasNotInterrupted; +import static java.util.concurrent.Executors.newFixedThreadPool; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import junit.framework.TestCase; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** Test case for {@link TrustedListenableFutureTask}. */ -@GwtCompatible(emulated = true) +@NullMarked +@GwtCompatible public class TrustedListenableFutureTaskTest extends TestCase { public void testSuccessful() throws Exception { @@ -54,16 +59,12 @@ public void testCancelled() throws Exception { assertTrue(task.isDone()); assertTrue(task.isCancelled()); assertFalse(task.wasInterrupted()); - try { - getDone(task); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> getDone(task)); verifyThreadWasNotInterrupted(); } public void testFailed() throws Exception { - final Exception e = new Exception(); + Exception e = new Exception(); TrustedListenableFutureTask task = TrustedListenableFutureTask.create( new Callable() { @@ -75,21 +76,18 @@ public Integer call() throws Exception { task.run(); assertTrue(task.isDone()); assertFalse(task.isCancelled()); - try { - getDone(task); - fail(); - } catch (ExecutionException executionException) { - assertThat(executionException).hasCauseThat().isEqualTo(e); - } + ExecutionException executionException = + assertThrows(ExecutionException.class, () -> getDone(task)); + assertThat(executionException).hasCauseThat().isEqualTo(e); } + @J2ktIncompatible @GwtIncompatible // blocking wait - public void testCancel_interrupted() throws Exception { - final AtomicBoolean interruptedExceptionThrown = new AtomicBoolean(); - final CountDownLatch enterLatch = new CountDownLatch(1); - final CountDownLatch exitLatch = new CountDownLatch(1); - final TrustedListenableFutureTask task = + AtomicBoolean interruptedExceptionThrown = new AtomicBoolean(); + CountDownLatch enterLatch = new CountDownLatch(1); + CountDownLatch exitLatch = new CountDownLatch(1); + TrustedListenableFutureTask task = TrustedListenableFutureTask.create( new Callable() { @Override @@ -125,23 +123,19 @@ public void run() { assertTrue(task.isDone()); assertTrue(task.isCancelled()); assertTrue(task.wasInterrupted()); - try { - task.get(); - fail(); - } catch (CancellationException expected) { - } + assertThrows(CancellationException.class, () -> task.get()); exitLatch.await(); assertTrue(interruptedExceptionThrown.get()); } + @J2ktIncompatible @GwtIncompatible // blocking wait - public void testRunIdempotency() throws Exception { - final int numThreads = 10; - final ExecutorService executor = Executors.newFixedThreadPool(numThreads); + int numThreads = 10; + ExecutorService executor = newFixedThreadPool(numThreads); for (int i = 0; i < 1000; i++) { - final AtomicInteger counter = new AtomicInteger(); - final TrustedListenableFutureTask task = + AtomicInteger counter = new AtomicInteger(); + TrustedListenableFutureTask task = TrustedListenableFutureTask.create( new Callable() { @Override @@ -149,7 +143,7 @@ public Integer call() { return counter.incrementAndGet(); } }); - final CyclicBarrier barrier = new CyclicBarrier(numThreads + 1); + CyclicBarrier barrier = new CyclicBarrier(numThreads + 1); Runnable wrapper = new Runnable() { @Override @@ -170,16 +164,16 @@ public void run() { executor.shutdown(); } + @J2ktIncompatible @GwtIncompatible // blocking wait - public void testToString() throws Exception { - final CountDownLatch enterLatch = new CountDownLatch(1); - final CountDownLatch exitLatch = new CountDownLatch(1); - final TrustedListenableFutureTask task = + CountDownLatch enterLatch = new CountDownLatch(1); + CountDownLatch exitLatch = new CountDownLatch(1); + TrustedListenableFutureTask<@Nullable Void> task = TrustedListenableFutureTask.create( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { enterLatch.countDown(); new CountDownLatch(1).await(); // wait forever return null; @@ -208,7 +202,8 @@ public void run() { exitLatch.await(); } - @GwtIncompatible // used only in GwtIncomaptible tests + @J2ktIncompatible + @GwtIncompatible // used only in GwtIncompatible tests private void awaitUnchecked(CyclicBarrier barrier) { try { barrier.await(); diff --git a/android/guava-tests/test/com/google/common/util/concurrent/UncaughtExceptionHandlersTest.java b/android/guava-tests/test/com/google/common/util/concurrent/UncaughtExceptionHandlersTest.java index eb8455b18323..92e629e1d4b0 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/UncaughtExceptionHandlersTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/UncaughtExceptionHandlersTest.java @@ -20,17 +20,21 @@ import static org.mockito.Mockito.verify; import com.google.common.util.concurrent.UncaughtExceptionHandlers.Exiter; +import com.google.common.util.concurrent.UncaughtExceptionHandlers.RuntimeWrapper; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; -/** @author Gregory Kick */ - +/** + * @author Gregory Kick + */ +@NullUnmarked public class UncaughtExceptionHandlersTest extends TestCase { - private Runtime runtimeMock; + private RuntimeWrapper runtimeMock; @Override protected void setUp() { - runtimeMock = mock(Runtime.class); + runtimeMock = mock(RuntimeWrapper.class); } public void testExiter() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/UncheckedThrowingFuture.java b/android/guava-tests/test/com/google/common/util/concurrent/UncheckedThrowingFuture.java index 405772279512..52975562a499 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/UncheckedThrowingFuture.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/UncheckedThrowingFuture.java @@ -23,6 +23,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.NullUnmarked; /** * A {@link Future} implementation which always throws directly from calls to {@code get()} (i.e. @@ -34,6 +35,7 @@ * @author Anthony Zana */ @GwtCompatible +@NullUnmarked final class UncheckedThrowingFuture extends AbstractFuture { public static ListenableFuture throwingError(Error error) { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptibleFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptibleFutureTest.java index 0d24266074b0..b2acfe821b3c 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptibleFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptibleFutureTest.java @@ -18,20 +18,22 @@ import static com.google.common.util.concurrent.InterruptionUtil.repeatedlyInterruptTestThread; import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; import com.google.common.testing.TearDown; import com.google.common.testing.TearDownStack; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; // TODO(cpovirk): Should this be merged into UninterruptiblesTest? /** @@ -40,6 +42,7 @@ * @author Kevin Bourrillion * @author Chris Povirk */ +@NullUnmarked public class UninterruptibleFutureTest extends TestCase { private SleepingRunnable sleeper; private Future delayedFuture; @@ -48,7 +51,7 @@ public class UninterruptibleFutureTest extends TestCase { @Override protected void setUp() { - final ExecutorService executor = Executors.newSingleThreadExecutor(); + ExecutorService executor = newSingleThreadExecutor(); tearDownStack.addTearDown( new TearDown() { @Override @@ -77,7 +80,6 @@ protected void tearDown() { * This first test doesn't test anything in Uninterruptibles, just demonstrates some normal * behavior of futures so that you can contrast the next test with it. */ - public void testRegularFutureInterrupted() throws ExecutionException { /* @@ -93,11 +95,11 @@ public void testRegularFutureInterrupted() throws ExecutionException { * 7. We expect get() to return this result. * 8. We expect the test thread's interrupt state to be false. */ - InterruptionUtil.requestInterruptIn(200, TimeUnit.MILLISECONDS); + InterruptionUtil.requestInterruptIn(200, MILLISECONDS); assertFalse(Thread.interrupted()); try { - delayedFuture.get(20000, TimeUnit.MILLISECONDS); + delayedFuture.get(20000, MILLISECONDS); fail("expected to be interrupted"); } catch (InterruptedException expected) { } catch (TimeoutException e) { @@ -116,17 +118,13 @@ public void testRegularFutureInterrupted() throws ExecutionException { assertTrue(sleeper.completed); } - public void testMakeUninterruptible_timeoutPreservedThroughInterruption() throws ExecutionException { repeatedlyInterruptTestThread(100, tearDownStack); - try { - getUninterruptibly(delayedFuture, 500, TimeUnit.MILLISECONDS); - fail("expected to time out"); - } catch (TimeoutException expected) { - } + assertThrows( + TimeoutException.class, () -> getUninterruptibly(delayedFuture, 500, MILLISECONDS)); assertTrue(Thread.interrupted()); // clears the interrupt state, too assertFalse(sleeper.completed); @@ -140,7 +138,7 @@ private static class SleepingRunnable implements Runnable { final int millis; volatile boolean completed; - public SleepingRunnable(int millis) { + SleepingRunnable(int millis) { this.millis = millis; } @@ -155,32 +153,26 @@ public void run() { } } - public void testMakeUninterruptible_untimed_uninterrupted() throws Exception { runUntimedInterruptsTest(0); } - public void testMakeUninterruptible_untimed_interrupted() throws Exception { runUntimedInterruptsTest(1); } - public void testMakeUninterruptible_untimed_multiplyInterrupted() throws Exception { runUntimedInterruptsTest(38); } - public void testMakeUninterruptible_timed_uninterrupted() throws Exception { runTimedInterruptsTest(0); } - public void testMakeUninterruptible_timed_interrupted() throws Exception { runTimedInterruptsTest(1); } - public void testMakeUninterruptible_timed_multiplyInterrupted() throws Exception { runTimedInterruptsTest(38); } @@ -218,7 +210,6 @@ private static void runNInterruptsTest( /** * Confirms that the test code triggers {@link InterruptedException} in a standard {@link Future}. */ - public void testMakeUninterruptible_plainFutureSanityCheck() throws Exception { SettableFuture future = SettableFuture.create(); FutureTask wasInterrupted = untimedInterruptReporter(future, true); @@ -226,16 +217,11 @@ public void testMakeUninterruptible_plainFutureSanityCheck() throws Exception { Thread waitingThread = new Thread(wasInterrupted); waitingThread.start(); waitingThread.interrupt(); - try { - wasInterrupted.get(); - fail(); - } catch (ExecutionException expected) { - assertTrue( - expected.getCause().toString(), expected.getCause() instanceof InterruptedException); - } + ExecutionException expected = + assertThrows(ExecutionException.class, () -> wasInterrupted.get()); + assertTrue(expected.getCause().toString(), expected.getCause() instanceof InterruptedException); } - public void testMakeUninterruptible_timedGetZeroTimeoutAttempted() throws TimeoutException, ExecutionException { SettableFuture future = SettableFuture.create(); @@ -248,7 +234,6 @@ public void testMakeUninterruptible_timedGetZeroTimeoutAttempted() assertEquals(RESULT, getUninterruptibly(future, 0, SECONDS)); } - public void testMakeUninterruptible_timedGetNegativeTimeoutAttempted() throws TimeoutException, ExecutionException { SettableFuture future = SettableFuture.create(); @@ -262,7 +247,7 @@ public void testMakeUninterruptible_timedGetNegativeTimeoutAttempted() } private static FutureTask untimedInterruptReporter( - final Future future, final boolean allowInterruption) { + Future future, boolean allowInterruption) { return new FutureTask<>( new Callable() { @Override @@ -279,7 +264,7 @@ public Boolean call() throws Exception { }); } - private static FutureTask timedInterruptReporter(final Future future) { + private static FutureTask timedInterruptReporter(Future future) { return new FutureTask<>( new Callable() { @Override diff --git a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptibleMonitorTest.java b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptibleMonitorTest.java index 59bf80878849..29c881126056 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptibleMonitorTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptibleMonitorTest.java @@ -16,13 +16,14 @@ package com.google.common.util.concurrent; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Monitor}'s uninterruptible methods. * * @author Justin T. Sampson */ - +@NullUnmarked public class UninterruptibleMonitorTest extends MonitorTestCase { public UninterruptibleMonitorTest() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java index e58cf6a60ef1..42b999eca013 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java @@ -16,6 +16,7 @@ package com.google.common.util.concurrent; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.InterruptionUtil.repeatedlyInterruptTestThread; import static com.google.common.util.concurrent.Uninterruptibles.awaitTerminationUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; @@ -25,6 +26,7 @@ import static com.google.common.util.concurrent.Uninterruptibles.tryAcquireUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.tryLockUninterruptibly; import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -34,12 +36,12 @@ import com.google.common.testing.TearDown; import com.google.common.testing.TearDownStack; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.time.Duration; import java.util.Date; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Semaphore; @@ -48,13 +50,14 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link Uninterruptibles}. * * @author Anthony Zana */ - +@NullUnmarked public class UninterruptiblesTest extends TestCase { private static final String EXPECTED_TAKE = "expectedTake"; @@ -75,6 +78,19 @@ protected void setUp() { + "Some test probably didn't clear the interrupt state"); } + /* + * b/456222735: Initialize Truth up front. Android Marshmallow appears to sometimes clear the + * interrupt bit when requesting class initialization, breaking our tests that check that the + * interrupt bit is set when appropriate. + * + * Merely calling assert_(), while apparently enough to clear the interrupt in my experiments, + * is not enough to make the test reliably pass. (Presumably it leaves more classes to be + * initialized later, at which point they cause more clearing of the interrupt?) It's not + * obvious that the following assertion is necessarily enough, either, but in practice, it seems + * to work, at least with the current set of Truth classes that this test uses. + */ + assertThat(1L).isGreaterThan(0); + tearDownStack.addTearDown( new TearDown() { @Override @@ -469,6 +485,26 @@ public void testTryAcquireTimeoutMultiInterruptExpiredMultiPermit() { } // executor.awaitTermination Testcases + public void testTryAwaitTerminationUninterruptiblyDuration_success() { + ExecutorService executor = newFixedThreadPool(1); + requestInterruptIn(500); + executor.execute(new SleepTask(1000)); + executor.shutdown(); + assertTrue(awaitTerminationUninterruptibly(executor, Duration.ofMillis(LONG_DELAY_MS))); + assertTrue(executor.isTerminated()); + assertInterrupted(); + } + + public void testTryAwaitTerminationUninterruptiblyDuration_failure() { + ExecutorService executor = newFixedThreadPool(1); + requestInterruptIn(500); + executor.execute(new SleepTask(10000)); + executor.shutdown(); + assertFalse(awaitTerminationUninterruptibly(executor, Duration.ofSeconds(1))); + assertFalse(executor.isTerminated()); + assertInterrupted(); + } + public void testTryAwaitTerminationUninterruptiblyLongTimeUnit_success() { ExecutorService executor = newFixedThreadPool(1); requestInterruptIn(500); @@ -670,7 +706,7 @@ private void scheduleRelease(long countdownInMillis) { private abstract static class DelayedActionRunnable implements Runnable { private final long tMinus; - protected DelayedActionRunnable(long tMinus) { + DelayedActionRunnable(long tMinus) { this.tMinus = tMinus; } @@ -684,13 +720,13 @@ public final void run() { doAction(); } - protected abstract void doAction(); + abstract void doAction(); } private static class CountDown extends DelayedActionRunnable { private final CountDownLatch latch; - public CountDown(CountDownLatch latch, long tMinus) { + CountDown(CountDownLatch latch, long tMinus) { super(tMinus); this.latch = latch; } @@ -704,7 +740,7 @@ protected void doAction() { private static class EnableWrites extends DelayedActionRunnable { private final BlockingQueue queue; - public EnableWrites(BlockingQueue queue, long tMinus) { + EnableWrites(BlockingQueue queue, long tMinus) { super(tMinus); assertFalse(queue.isEmpty()); assertFalse(queue.offer("shouldBeRejected")); @@ -713,14 +749,14 @@ public EnableWrites(BlockingQueue queue, long tMinus) { @Override protected void doAction() { - assertNotNull(queue.remove()); + assertThat(queue.remove()).isNotNull(); } } private static class EnableReads extends DelayedActionRunnable { private final BlockingQueue queue; - public EnableReads(BlockingQueue queue, long tMinus) { + EnableReads(BlockingQueue queue, long tMinus) { super(tMinus); assertTrue(queue.isEmpty()); this.queue = queue; @@ -761,12 +797,12 @@ void joinSuccessfully(long timeoutMillis) { void joinUnsuccessfully(long timeoutMillis) { Uninterruptibles.joinUninterruptibly(thread, timeoutMillis, MILLISECONDS); completed.assertCompletionNotExpected(timeoutMillis); - assertFalse(Thread.State.TERMINATED.equals(thread.getState())); + assertThat(thread.getState()).isNotEqualTo(Thread.State.TERMINATED); } } private static class JoinTarget extends DelayedActionRunnable { - public JoinTarget(long tMinus) { + JoinTarget(long tMinus) { super(tMinus); } @@ -777,7 +813,7 @@ protected void doAction() {} private static class Release extends DelayedActionRunnable { private final Semaphore semaphore; - public Release(Semaphore semaphore, long tMinus) { + Release(Semaphore semaphore, long tMinus) { super(tMinus); this.semaphore = semaphore; } @@ -805,7 +841,7 @@ private static void sleepSuccessfully(long sleepMillis) { private static void assertTimeNotPassed(Stopwatch stopwatch, long timelimitMillis) { long elapsedMillis = stopwatch.elapsed(MILLISECONDS); - assertTrue(elapsedMillis < timelimitMillis); + assertThat(elapsedMillis).isLessThan(timelimitMillis); } /** @@ -833,8 +869,8 @@ private static void requestInterruptIn(long millis) { } @CanIgnoreReturnValue - private static Thread acquireFor(final Lock lock, final long duration, final TimeUnit unit) { - final CountDownLatch latch = new CountDownLatch(1); + private static Thread acquireFor(Lock lock, long duration, TimeUnit unit) { + CountDownLatch latch = new CountDownLatch(1); Thread thread = new Thread() { @Override @@ -866,9 +902,9 @@ private TestCondition(Lock lock, Condition condition) { } static TestCondition createAndSignalAfter(long delay, TimeUnit unit) { - final TestCondition testCondition = create(); + TestCondition testCondition = create(); - ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(1); + ScheduledExecutorService scheduledPool = newScheduledThreadPool(1); // If signal() fails somehow, we should see a failed test, even without looking at the Future. Future unused = scheduledPool.schedule( diff --git a/android/guava-tests/test/com/google/common/util/concurrent/UntrustedInputFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/UntrustedInputFutureTest.java index a6241d2e7120..44ee313c7656 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/UntrustedInputFutureTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/UntrustedInputFutureTest.java @@ -16,15 +16,16 @@ package com.google.common.util.concurrent; - import com.google.common.annotations.GwtCompatible; import com.google.common.util.concurrent.AbstractFuture.TrustedFuture; +import org.jspecify.annotations.NullUnmarked; /** * Tests for {@link AbstractFuture} that use a non-{@link TrustedFuture} for {@link * AbstractFuture#setFuture} calls. */ @GwtCompatible +@NullUnmarked public class UntrustedInputFutureTest extends AbstractAbstractFutureTest { @Override AbstractFuture newDelegate() { diff --git a/android/guava-tests/test/com/google/common/util/concurrent/WrappingExecutorServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/WrappingExecutorServiceTest.java index fdb2c54e6792..38e0184cbd58 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/WrappingExecutorServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/WrappingExecutorServiceTest.java @@ -19,12 +19,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; import static com.google.common.util.concurrent.Runnables.doNothing; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; @@ -34,19 +36,22 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test for {@link WrappingExecutorService} * * @author Chris Nokleberg */ +@NullUnmarked public class WrappingExecutorServiceTest extends TestCase { private static final String RESULT_VALUE = "ran"; + // Uninteresting delegations public void testDelegations() throws InterruptedException { MockExecutor mock = new MockExecutor(); TestExecutor testExecutor = new TestExecutor(mock); - assertFalse(testExecutor.awaitTermination(10, TimeUnit.MILLISECONDS)); + assertFalse(testExecutor.awaitTermination(10, MILLISECONDS)); mock.assertLastMethodCalled("awaitTermination"); assertFalse(testExecutor.isTerminated()); mock.assertLastMethodCalled("isTerminated"); @@ -102,7 +107,7 @@ public void testInvokeAll() throws InterruptedException, ExecutionException { } { MockExecutor mock = new MockExecutor(); - TimeUnit unit = TimeUnit.SECONDS; + TimeUnit unit = SECONDS; long timeout = 5; TestExecutor testExecutor = new TestExecutor(mock); List> futures = testExecutor.invokeAll(tasks, timeout, unit); @@ -122,7 +127,7 @@ public void testInvokeAny() throws InterruptedException, ExecutionException, Tim } { MockExecutor mock = new MockExecutor(); - TimeUnit unit = TimeUnit.SECONDS; + TimeUnit unit = SECONDS; long timeout = 5; TestExecutor testExecutor = new TestExecutor(mock); String s = testExecutor.invokeAny(tasks, timeout, unit); @@ -139,7 +144,7 @@ private static void checkResults(List> futures) } private static List> createTasks(int n) { - List> callables = Lists.newArrayList(); + List> callables = new ArrayList<>(); for (int i = 0; i < n; i++) { callables.add(Callables.returning(RESULT_VALUE + i)); } @@ -149,7 +154,7 @@ private static List> createTasks(int n) { private static final class WrappedCallable implements Callable { private final Callable delegate; - public WrappedCallable(Callable delegate) { + WrappedCallable(Callable delegate) { this.delegate = delegate; } @@ -162,7 +167,7 @@ public T call() throws Exception { private static final class WrappedRunnable implements Runnable { private final Runnable delegate; - public WrappedRunnable(Runnable delegate) { + WrappedRunnable(Runnable delegate) { this.delegate = delegate; } @@ -173,7 +178,7 @@ public void run() { } private static final class TestExecutor extends WrappingExecutorService { - public TestExecutor(MockExecutor mock) { + TestExecutor(MockExecutor mock) { super(mock); } @@ -188,17 +193,17 @@ protected Runnable wrapTask(Runnable command) { } } - // TODO: If this test can ever depend on EasyMock or the like, use it instead. + // TODO: If this test can ever depend on Mockito or the like, use it instead. private static final class MockExecutor implements ExecutorService { private String lastMethodCalled = ""; private long lastTimeoutInMillis = -1; - private ExecutorService inline = newDirectExecutorService(); + private final ExecutorService inline = newDirectExecutorService(); - public void assertLastMethodCalled(String method) { + void assertLastMethodCalled(String method) { assertEquals(method, lastMethodCalled); } - public void assertMethodWithTimeout(String method, long timeout, TimeUnit unit) { + void assertMethodWithTimeout(String method, long timeout, TimeUnit unit) { assertLastMethodCalled(method + "Timeout"); assertEquals(unit.toMillis(timeout), lastTimeoutInMillis); } diff --git a/android/guava-tests/test/com/google/common/util/concurrent/WrappingScheduledExecutorServiceTest.java b/android/guava-tests/test/com/google/common/util/concurrent/WrappingScheduledExecutorServiceTest.java index 8d0183e3247c..a9d8fa0aab42 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/WrappingScheduledExecutorServiceTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/WrappingScheduledExecutorServiceTest.java @@ -17,24 +17,28 @@ package com.google.common.util.concurrent; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.Executors.callable; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Test for {@link WrappingScheduledExecutorService} * * @author Luke Sandberg */ +@NullUnmarked public class WrappingScheduledExecutorServiceTest extends TestCase { private static final Runnable DO_NOTHING = new Runnable() { @@ -46,14 +50,11 @@ public void testSchedule() { MockExecutor mock = new MockExecutor(); TestExecutor testExecutor = new TestExecutor(mock); - @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored - Future possiblyIgnoredError = testExecutor.schedule(DO_NOTHING, 10, TimeUnit.MINUTES); - mock.assertLastMethodCalled("scheduleRunnable", 10, TimeUnit.MINUTES); + Future unused1 = testExecutor.schedule(DO_NOTHING, 10, MINUTES); + mock.assertLastMethodCalled("scheduleRunnable", 10, MINUTES); - @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored - Future possiblyIgnoredError1 = - testExecutor.schedule(Executors.callable(DO_NOTHING), 5, TimeUnit.SECONDS); - mock.assertLastMethodCalled("scheduleCallable", 5, TimeUnit.SECONDS); + Future unused2 = testExecutor.schedule(callable(DO_NOTHING), 5, SECONDS); + mock.assertLastMethodCalled("scheduleCallable", 5, SECONDS); } public void testSchedule_repeating() { @@ -61,19 +62,18 @@ public void testSchedule_repeating() { TestExecutor testExecutor = new TestExecutor(mock); @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored Future possiblyIgnoredError = - testExecutor.scheduleWithFixedDelay(DO_NOTHING, 100, 10, TimeUnit.MINUTES); - mock.assertLastMethodCalled("scheduleWithFixedDelay", 100, 10, TimeUnit.MINUTES); + testExecutor.scheduleWithFixedDelay(DO_NOTHING, 100, 10, MINUTES); + mock.assertLastMethodCalled("scheduleWithFixedDelay", 100, 10, MINUTES); @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored - Future possiblyIgnoredError1 = - testExecutor.scheduleAtFixedRate(DO_NOTHING, 3, 7, TimeUnit.SECONDS); - mock.assertLastMethodCalled("scheduleAtFixedRate", 3, 7, TimeUnit.SECONDS); + Future possiblyIgnoredError1 = testExecutor.scheduleAtFixedRate(DO_NOTHING, 3, 7, SECONDS); + mock.assertLastMethodCalled("scheduleAtFixedRate", 3, 7, SECONDS); } private static final class WrappedCallable implements Callable { private final Callable delegate; - public WrappedCallable(Callable delegate) { + WrappedCallable(Callable delegate) { this.delegate = delegate; } @@ -86,7 +86,7 @@ public T call() throws Exception { private static final class WrappedRunnable implements Runnable { private final Runnable delegate; - public WrappedRunnable(Runnable delegate) { + WrappedRunnable(Runnable delegate) { this.delegate = delegate; } @@ -97,7 +97,7 @@ public void run() { } private static final class TestExecutor extends WrappingScheduledExecutorService { - public TestExecutor(MockExecutor mock) { + TestExecutor(MockExecutor mock) { super(mock); } diff --git a/android/guava-tests/test/com/google/common/xml/XmlEscapersTest.java b/android/guava-tests/test/com/google/common/xml/XmlEscapersTest.java index 00b5cf16b60b..d491115b19e1 100644 --- a/android/guava-tests/test/com/google/common/xml/XmlEscapersTest.java +++ b/android/guava-tests/test/com/google/common/xml/XmlEscapersTest.java @@ -22,6 +22,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.escape.CharEscaper; import junit.framework.TestCase; +import org.jspecify.annotations.NullUnmarked; /** * Tests for the {@link XmlEscapers} class. @@ -30,6 +31,7 @@ * @author David Beaumont */ @GwtCompatible +@NullUnmarked public class XmlEscapersTest extends TestCase { public void testXmlContentEscaper() throws Exception { diff --git a/android/guava/javadoc-link/checker-framework/package-list b/android/guava/javadoc-link/checker-framework/package-list deleted file mode 100644 index ce4e9fb098e0..000000000000 --- a/android/guava/javadoc-link/checker-framework/package-list +++ /dev/null @@ -1,101 +0,0 @@ -android.annotation -android.support.annotation -com.sun.istack.internal -edu.umd.cs.findbugs.annotations -javax.annotation -javax.annotation.concurrent -javax.annotation.meta -javax.validation.constraints -lombok -net.jcip.annotations -org.checkerframework.checker.compilermsgs -org.checkerframework.checker.compilermsgs.qual -org.checkerframework.checker.fenum -org.checkerframework.checker.fenum.qual -org.checkerframework.checker.formatter -org.checkerframework.checker.formatter.qual -org.checkerframework.checker.guieffect -org.checkerframework.checker.guieffect.qual -org.checkerframework.checker.i18n -org.checkerframework.checker.i18n.qual -org.checkerframework.checker.i18nformatter -org.checkerframework.checker.i18nformatter.qual -org.checkerframework.checker.i18nformatter.unittests -org.checkerframework.checker.index -org.checkerframework.checker.index.lowerbound -org.checkerframework.checker.index.qual -org.checkerframework.checker.index.samelen -org.checkerframework.checker.index.searchindex -org.checkerframework.checker.index.substringindex -org.checkerframework.checker.index.upperbound -org.checkerframework.checker.initialization -org.checkerframework.checker.initialization.qual -org.checkerframework.checker.interning -org.checkerframework.checker.interning.qual -org.checkerframework.checker.linear -org.checkerframework.checker.linear.qual -org.checkerframework.checker.lock -org.checkerframework.checker.lock.qual -org.checkerframework.checker.nullness -org.checkerframework.checker.nullness.compatqual -org.checkerframework.checker.nullness.qual -org.checkerframework.checker.propkey -org.checkerframework.checker.propkey.qual -org.checkerframework.checker.regex -org.checkerframework.checker.regex.qual -org.checkerframework.checker.signature -org.checkerframework.checker.signature.qual -org.checkerframework.checker.signedness -org.checkerframework.checker.signedness.qual -org.checkerframework.checker.tainting -org.checkerframework.checker.tainting.qual -org.checkerframework.checker.units -org.checkerframework.checker.units.qual -org.checkerframework.common.aliasing -org.checkerframework.common.aliasing.qual -org.checkerframework.common.basetype -org.checkerframework.common.reflection -org.checkerframework.common.reflection.qual -org.checkerframework.common.subtyping -org.checkerframework.common.util -org.checkerframework.common.util.count -org.checkerframework.common.util.debug -org.checkerframework.common.util.report -org.checkerframework.common.util.report.qual -org.checkerframework.common.value -org.checkerframework.common.value.qual -org.checkerframework.common.value.util -org.checkerframework.common.wholeprograminference -org.checkerframework.dataflow.analysis -org.checkerframework.dataflow.cfg -org.checkerframework.dataflow.cfg.block -org.checkerframework.dataflow.cfg.node -org.checkerframework.dataflow.cfg.playground -org.checkerframework.dataflow.constantpropagation -org.checkerframework.dataflow.qual -org.checkerframework.dataflow.util -org.checkerframework.framework.flow -org.checkerframework.framework.qual -org.checkerframework.framework.source -org.checkerframework.framework.test -org.checkerframework.framework.test.diagnostics -org.checkerframework.framework.type -org.checkerframework.framework.type.treeannotator -org.checkerframework.framework.type.typeannotator -org.checkerframework.framework.type.visitor -org.checkerframework.framework.util -org.checkerframework.framework.util.defaults -org.checkerframework.framework.util.dependenttypes -org.checkerframework.framework.util.element -org.checkerframework.framework.util.typeinference -org.checkerframework.framework.util.typeinference.constraint -org.checkerframework.framework.util.typeinference.solver -org.checkerframework.javacutil -org.checkerframework.javacutil.dist -org.checkerframework.javacutil.trees -org.eclipse.jdt.annotation -org.eclipse.jgit.annotations -org.jetbrains.annotations -org.jmlspecs.annotation -org.netbeans.api.annotations.common -org.springframework.lang diff --git a/android/guava/javadoc-link/jsr305/package-list b/android/guava/javadoc-link/jsr305/package-list deleted file mode 100644 index cc08202c352c..000000000000 --- a/android/guava/javadoc-link/jsr305/package-list +++ /dev/null @@ -1,3 +0,0 @@ -javax.annotation -javax.annotation.concurrent -javax.annotation.meta diff --git a/android/guava/pom.xml b/android/guava/pom.xml index bdf07e0a246d..81bfa8604207 100644 --- a/android/guava/pom.xml +++ b/android/guava/pom.xml @@ -1,11 +1,12 @@ + 4.0.0 com.google.guava guava-parent - HEAD-android-SNAPSHOT + 999.0.0-HEAD-android-SNAPSHOT guava bundle @@ -15,28 +16,21 @@ utility classes, Google's collections, I/O classes, and much more. + https://github.com/google/guava com.google.guava failureaccess - 1.0.1 + ${failureaccess.version} com.google.guava listenablefuture - 9999.0-empty-to-avoid-conflict-with-guava + ${listenablefuture.version} - com.google.code.findbugs - jsr305 - - - org.checkerframework - checker-qual - - - org.checkerframework - checker-compat-qual + org.jspecify + jspecify com.google.errorprone @@ -46,11 +40,26 @@ com.google.j2objc j2objc-annotations - - + + + .. + + LICENSE + proguard/* + + META-INF + + + + org.mvnsearch + toolchains-maven-plugin + + + maven-toolchains-plugin + maven-jar-plugin @@ -65,7 +74,7 @@ true org.apache.felix maven-bundle-plugin - 2.5.0 + ${maven-bundle-plugin.version} bundle-manifest @@ -77,14 +86,17 @@ + + <_fixupmessages>^Classes found in the wrong directory: .* !com.google.common.base.internal, !com.google.common.util.concurrent.internal, + !META-INF.*, com.google.common.* com.google.common.util.concurrent.internal, - javax.annotation;resolution:=optional, + org.jspecify.annotations;resolution:=optional, javax.crypto.*;resolution:=optional, sun.misc.*;resolution:=optional @@ -94,51 +106,61 @@ maven-compiler-plugin - - - maven-source-plugin - - - - maven-dependency-plugin - unpack-jdk-sources - generate-sources - unpack-dependencies + default-compile + + + -XDignore.symbol.file + + + + + compile-java9 + compile + + compile + - srczip - ${project.build.directory}/jdk-sources - false - - **/module-info.java,**/java/io/FileDescriptor.java + 9 + + ${project.basedir}/src + + + module-info.java + + + + + -sourcepath + ${project.basedir}/src + --add-reads=com.google.common=ALL-UNNAMED + + -XDcompilePolicy=simple + + true + + maven-source-plugin + org.codehaus.mojo animal-sniffer-maven-plugin - - - - java.util.Objects - - maven-javadoc-plugin - - - - ${project.build.sourceDirectory}:${project.build.directory}/jdk-sources - - com.google.common.base.internal,com.google.common.base.internal.*,com.google.thirdparty.publicsuffix,com.google.thirdparty.publicsuffix.*,com.oracle.*,com.sun.*,java.*,javax.*,jdk,jdk.*,org.*,sun.* + com.azul.tooling.in,com.google.common.base.internal,com.google.common.base.internal.*,com.google.thirdparty.publicsuffix,com.google.thirdparty.publicsuffix.*,com.oracle.*,com.sun.*,java.*,javax.*,jdk,jdk.*,org.*,sun.* @@ -169,94 +191,70 @@ - + false - - - https://static.javadoc.io/com.google.code.findbugs/jsr305/3.0.1/ - ${project.basedir}/javadoc-link/jsr305 - - https://static.javadoc.io/com.google.j2objc/j2objc-annotations/1.1/ + https://javadoc.io/doc/com.google.j2objc/j2objc-annotations/latest/ ${project.basedir}/javadoc-link/j2objc-annotations - - - https://docs.oracle.com/javase/9/docs/api/ - https://docs.oracle.com/javase/9/docs/api/ - - - https://checkerframework.org/api/ - ${project.basedir}/javadoc-link/checker-framework + https://errorprone.info/api/latest/ + ${project.basedir}/javadoc-link/error_prone_annotations - https://errorprone.info/api/latest/ + https://docs.oracle.com/en/java/javase/25/docs/api/ + https://jspecify.dev/docs/api/ + ../../overview.html + + + maven-resources-plugin - attach-docs + gradle-module-metadata + compile + + copy-resources + + + target/publish + + + ../../guava + + module.json + + true + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + - generate-javadoc-site-report - site - javadoc + attach-gradle-module-metadata + + attach-artifact + + + + + target/publish/module.json + module + + + - - - srczip-parent - - - ${java.home}/../src.zip - - - - - jdk - srczip - 999 - system - ${java.home}/../src.zip - true - - - - - srczip-lib - - - ${java.home}/lib/src.zip - - - - - jdk - srczip - 999 - system - ${java.home}/lib/src.zip - true - - - - - - maven-javadoc-plugin - - - ${project.build.sourceDirectory}:${project.build.directory}/jdk-sources/java.base - - - - - - diff --git a/android/guava/src/com/google/common/annotations/GwtCompatible.java b/android/guava/src/com/google/common/annotations/GwtCompatible.java index 4bf6efbb7e97..8da966380df1 100644 --- a/android/guava/src/com/google/common/annotations/GwtCompatible.java +++ b/android/guava/src/com/google/common/annotations/GwtCompatible.java @@ -21,36 +21,8 @@ import java.lang.annotation.Target; /** - * The presence of this annotation on a type indicates that the type may be used with the Google Web Toolkit (GWT). When applied to a method, - * the return type of the method is GWT compatible. It's useful to indicate that an instance created - * by factory methods has a GWT serializable type. In the following example, - * - *
    - * {@literal @}GwtCompatible
    - * class Lists {
    - *   ...
    - *   {@literal @}GwtCompatible(serializable = true)
    - *   {@literal static  List} newArrayList(E... elements) {
    - *     ...
    - *   }
    - * }
    - * 
    - * - *

    The return value of {@code Lists.newArrayList(E[])} has GWT serializable type. It is also - * useful in specifying contracts of interface methods. In the following example, - * - *

    - * {@literal @}GwtCompatible
    - * interface ListFactory {
    - *   ...
    - *   {@literal @}GwtCompatible(serializable = true)
    - *   {@literal  List} newArrayList(E... elements);
    - * }
    - * 
    - * - *

    The {@code newArrayList(E[])} method of all implementations of {@code ListFactory} is expected - * to return a value with a GWT serializable type. + * The presence of this annotation on a type indicates that the type may be used with GWT or J2CL. * *

    Note that a {@code GwtCompatible} type may have some {@link GwtIncompatible} methods. * @@ -64,11 +36,11 @@ public @interface GwtCompatible { /** - * When {@code true}, the annotated type or the type of the method return value is GWT - * serializable. + * Obsolete; formerly used to indicate when a value was GWT serializable back before Guava dropped + * support for GWT serialization. * * @see + * "https://www.gwtproject.org/doc/latest/DevGuideServerCommunication#DevGuideSerializableTypes"> * Documentation about GWT serialization */ boolean serializable() default false; @@ -78,7 +50,7 @@ * super-source) is different from the implementation used by the JVM. * * @see + * "https://www.gwtproject.org/doc/latest/DevGuideOrganizingProjects.html#DevGuideModules"> * Documentation about GWT emulated source */ boolean emulated() default false; diff --git a/android/guava/src/com/google/common/annotations/J2ktIncompatible.java b/android/guava/src/com/google/common/annotations/J2ktIncompatible.java new file mode 100644 index 000000000000..59511632e2af --- /dev/null +++ b/android/guava/src/com/google/common/annotations/J2ktIncompatible.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on an API indicates that the method may not be used with + * J2kt. + * + * @since 32.0.0 + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) +@GwtCompatible +public @interface J2ktIncompatible {} diff --git a/android/guava/src/com/google/common/annotations/VisibleForTesting.java b/android/guava/src/com/google/common/annotations/VisibleForTesting.java index e767afcdd3e2..24b4db5bde02 100644 --- a/android/guava/src/com/google/common/annotations/VisibleForTesting.java +++ b/android/guava/src/com/google/common/annotations/VisibleForTesting.java @@ -22,8 +22,8 @@ * bad design, and it does not prevent anyone from using the declaration---and experience has shown * that they will. If the method breaks the encapsulation of its class, then its internal * representation will be hard to change. Instead, use RestrictedApiChecker, which - * enforces fine-grained visibility policies. + * href="http://errorprone.info/bugpattern/RestrictedApi">RestrictedApiChecker, which enforces + * fine-grained visibility policies. * * @author Johannes Henkel */ diff --git a/android/guava/src/com/google/common/annotations/package-info.java b/android/guava/src/com/google/common/annotations/package-info.java index 9ad041ffeb60..3cff985b7f75 100644 --- a/android/guava/src/com/google/common/annotations/package-info.java +++ b/android/guava/src/com/google/common/annotations/package-info.java @@ -13,7 +13,7 @@ */ /** - * Common annotation types. This package is a part of the open-source Guava library. + * Annotation types. This package is a part of the open-source Guava library. */ package com.google.common.annotations; diff --git a/android/guava/src/com/google/common/base/Absent.java b/android/guava/src/com/google/common/base/Absent.java index 86aec0516d26..a57fb93fb0c7 100644 --- a/android/guava/src/com/google/common/base/Absent.java +++ b/android/guava/src/com/google/common/base/Absent.java @@ -17,9 +17,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.Collections; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** Implementation of an {@link Optional} not containing a reference. */ @GwtCompatible @@ -61,8 +63,7 @@ public T or(Supplier supplier) { } @Override - @NullableDecl - public T orNull() { + public @Nullable T orNull() { return null; } @@ -78,8 +79,8 @@ public Optional transform(Function function) { } @Override - public boolean equals(@NullableDecl Object object) { - return object == this; + public boolean equals(@Nullable Object obj) { + return this == obj; } @Override @@ -96,5 +97,5 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/base/AbstractIterator.java b/android/guava/src/com/google/common/base/AbstractIterator.java index f6f521e904a5..f46e12ecbe0b 100644 --- a/android/guava/src/com/google/common/base/AbstractIterator.java +++ b/android/guava/src/com/google/common/base/AbstractIterator.java @@ -14,20 +14,21 @@ package com.google.common.base; +import static com.google.common.base.NullnessCasts.uncheckedCastNullableTToT; import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Iterator; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Note this class is a copy of {@link com.google.common.collect.AbstractIterator} (for dependency * reasons). */ @GwtCompatible -abstract class AbstractIterator implements Iterator { +abstract class AbstractIterator implements Iterator { private State state = State.NOT_READY; protected AbstractIterator() {} @@ -39,13 +40,12 @@ private enum State { FAILED, } - @NullableDecl private T next; + private @Nullable T next; - protected abstract T computeNext(); + protected abstract @Nullable T computeNext(); @CanIgnoreReturnValue - @NullableDecl - protected final T endOfData() { + protected final @Nullable T endOfData() { state = State.DONE; return null; } @@ -74,12 +74,14 @@ private boolean tryToComputeNext() { } @Override + @ParametricNullness public final T next() { if (!hasNext()) { throw new NoSuchElementException(); } state = State.NOT_READY; - T result = next; + // Safe because hasNext() ensures that tryToComputeNext() has put a T into `next`. + T result = uncheckedCastNullableTToT(next); next = null; return result; } diff --git a/android/guava/src/com/google/common/base/Ascii.java b/android/guava/src/com/google/common/base/Ascii.java index dc404ea295f8..4d9b66d2f426 100644 --- a/android/guava/src/com/google/common/base/Ascii.java +++ b/android/guava/src/com/google/common/base/Ascii.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import java.nio.charset.StandardCharsets; /** * Static methods pertaining to ASCII characters (those in the range of values {@code 0x00} through @@ -27,7 +28,7 @@ * *

      * - *
    • {@link Charsets#US_ASCII} specifies the {@code Charset} of ASCII characters. + *
    • {@link StandardCharsets#US_ASCII} specifies the {@code Charset} of ASCII characters. *
    • {@link CharMatcher#ascii} matches ASCII characters and provides text processing methods * which operate only on the ASCII characters of a string. *
    @@ -37,7 +38,6 @@ * @since 7.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Ascii { private Ascii() {} @@ -440,7 +440,7 @@ public static String toLowerCase(CharSequence chars) { } /** - * If the argument is an {@linkplain #isUpperCase(char) uppercase ASCII character} returns the + * If the argument is an {@linkplain #isUpperCase(char) uppercase ASCII character}, returns the * lowercase equivalent. Otherwise returns the argument. */ public static char toLowerCase(char c) { @@ -488,7 +488,7 @@ public static String toUpperCase(CharSequence chars) { } /** - * If the argument is a {@linkplain #isLowerCase(char) lowercase ASCII character} returns the + * If the argument is a {@linkplain #isLowerCase(char) lowercase ASCII character}, returns the * uppercase equivalent. Otherwise returns the argument. */ public static char toUpperCase(char c) { @@ -523,10 +523,10 @@ public static boolean isUpperCase(char c) { * *

    Examples: * - *

    {@code
    +   * {@snippet :
        * Ascii.truncate("foobar", 7, "..."); // returns "foobar"
        * Ascii.truncate("foobar", 5, "..."); // returns "fo..."
    -   * }
    + * } * *

    Note: This method may work with certain non-ASCII text but is not safe for use * with arbitrary Unicode text. It is mostly intended for use with text that is known to be safe diff --git a/android/guava/src/com/google/common/base/CaseFormat.java b/android/guava/src/com/google/common/base/CaseFormat.java index 7b393ebd7e0f..d5041f3e8b72 100644 --- a/android/guava/src/com/google/common/base/CaseFormat.java +++ b/android/guava/src/com/google/common/base/CaseFormat.java @@ -18,8 +18,10 @@ import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Utility class for converting between various ASCII case formats. Behavior is undefined for @@ -29,9 +31,11 @@ * @since 1.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public enum CaseFormat { - /** Hyphenated variable naming convention, e.g., "lower-hyphen". */ + /** + * Hyphenated variable naming convention, e.g., "lower-hyphen". This format is also colloquially + * known as "kebab case". + */ LOWER_HYPHEN(CharMatcher.is('-'), "-") { @Override String normalizeWord(String word) { @@ -151,7 +155,8 @@ String convert(CaseFormat format, String s) { } /** - * Returns a {@code Converter} that converts strings from this format to {@code targetFormat}. + * Returns a serializable {@code Converter} that converts strings from this format to {@code + * targetFormat}. * * @since 16.0 */ @@ -181,9 +186,9 @@ protected String doBackward(String s) { } @Override - public boolean equals(@CheckForNull Object object) { - if (object instanceof StringConverter) { - StringConverter that = (StringConverter) object; + public boolean equals(@Nullable Object obj) { + if (obj instanceof StringConverter) { + StringConverter that = (StringConverter) obj; return sourceFormat.equals(that.sourceFormat) && targetFormat.equals(that.targetFormat); } return false; @@ -199,7 +204,7 @@ public String toString() { return sourceFormat + ".converterTo(" + targetFormat + ")"; } - private static final long serialVersionUID = 0L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0L; } abstract String normalizeWord(String word); diff --git a/android/guava/src/com/google/common/base/CharMatcher.java b/android/guava/src/com/google/common/base/CharMatcher.java index 7941883ec876..a22b8f245b12 100644 --- a/android/guava/src/com/google/common/base/CharMatcher.java +++ b/android/guava/src/com/google/common/base/CharMatcher.java @@ -21,6 +21,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.InlineMeValidationDisabled; import java.util.Arrays; import java.util.BitSet; @@ -60,8 +62,7 @@ * @author Kevin Bourrillion * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public abstract class CharMatcher implements Predicate { /* * N777777777NO @@ -133,7 +134,8 @@ public static CharMatcher none() { * illustrated here. * This is not the same definition used by other Java APIs. (See a comparison of several definitions of "whitespace".) + * href="https://docs.google.com/spreadsheets/d/1kq4ECwPjHX9B8QUCTPclgsDCXYaj7T-FlT4tB5q3ahk/edit">comparison + * of several definitions of "whitespace".) * *

    All Unicode White_Space characters are on the BMP and thus supported by this API. * @@ -292,7 +294,7 @@ public static CharMatcher singleWidth() { // Static factories /** Returns a {@code char} matcher that matches only one specified BMP character. */ - public static CharMatcher is(final char match) { + public static CharMatcher is(char match) { return new Is(match); } @@ -301,7 +303,7 @@ public static CharMatcher is(final char match) { * *

    To negate another {@code CharMatcher}, use {@link #negate()}. */ - public static CharMatcher isNot(final char match) { + public static CharMatcher isNot(char match) { return new IsNot(match); } @@ -309,7 +311,7 @@ public static CharMatcher isNot(final char match) { * Returns a {@code char} matcher that matches any BMP character present in the given character * sequence. Returns a bogus matcher if the sequence contains supplementary characters. */ - public static CharMatcher anyOf(final CharSequence sequence) { + public static CharMatcher anyOf(CharSequence sequence) { switch (sequence.length()) { case 0: return none(); @@ -339,7 +341,7 @@ public static CharMatcher noneOf(CharSequence sequence) { * * @throws IllegalArgumentException if {@code endInclusive < startInclusive} */ - public static CharMatcher inRange(final char startInclusive, final char endInclusive) { + public static CharMatcher inRange(char startInclusive, char endInclusive) { return new InRange(startInclusive, endInclusive); } @@ -347,7 +349,7 @@ public static CharMatcher inRange(final char startInclusive, final char endInclu * Returns a matcher with identical behavior to the given {@link Character}-based predicate, but * which operates on primitive {@code char} instances instead. */ - public static CharMatcher forPredicate(final Predicate predicate) { + public static CharMatcher forPredicate(Predicate predicate) { return predicate instanceof CharMatcher ? (CharMatcher) predicate : new ForPredicate(predicate); } @@ -367,7 +369,8 @@ protected CharMatcher() {} // Non-static factories /** Returns a matcher that matches any character not matched by this matcher. */ - // @Override under Java 8 but not under Java 7 + // This is not an override in java7, where Guava's Predicate does not extend the JDK's Predicate. + @SuppressWarnings("MissingOverride") public CharMatcher negate() { return new Negated(this); } @@ -388,12 +391,12 @@ public CharMatcher or(CharMatcher other) { /** * Returns a {@code char} matcher functionally equivalent to this one, but which may be faster to - * query than the original; your mileage may vary. Precomputation takes time and is likely to be - * worthwhile only if the precomputed matcher is queried many thousands of times. + * query than the original; your mileage may vary. Precomputation takes time and requires more + * memory, so it is only likely to be worthwhile if the precomputed matcher is queried very often. * *

    This method has no effect (returns {@code this}) when called in GWT: it's unclear whether a - * precomputed matcher is faster, but it certainly consumes more memory, which doesn't seem like a - * worthwhile tradeoff in a browser. + * precomputed matcher is faster, but it certainly would consume more memory (which doesn't seem + * like a worthwhile tradeoff in a browser). */ public CharMatcher precomputed() { return Platform.precomputeCharMatcher(this); @@ -413,7 +416,7 @@ public CharMatcher precomputed() { */ @GwtIncompatible // SmallCharMatcher CharMatcher precomputedInternal() { - final BitSet table = new BitSet(); + BitSet table = new BitSet(); setBits(table); int totalCharacters = table.cardinality(); if (totalCharacters * 2 <= DISTINCT_CHARS) { @@ -423,7 +426,7 @@ CharMatcher precomputedInternal() { table.flip(Character.MIN_VALUE, Character.MAX_VALUE + 1); int negatedCharacters = DISTINCT_CHARS - totalCharacters; String suffix = ".negate()"; - final String description = toString(); + String description = toString(); String negatedDescription = description.endsWith(suffix) ? description.substring(0, description.length() - suffix.length()) @@ -606,9 +609,9 @@ public int countIn(CharSequence sequence) { * Returns a string containing all non-matching characters of a character sequence, in order. For * example: * - *

    {@code
    +   * {@snippet :
        * CharMatcher.is('a').removeFrom("bazaar")
    -   * }
    + * } * * ... returns {@code "bzr"}. */ @@ -645,9 +648,9 @@ public String removeFrom(CharSequence sequence) { * Returns a string containing all matching BMP characters of a character sequence, in order. For * example: * - *
    {@code
    +   * {@snippet :
        * CharMatcher.is('a').retainFrom("bazaar")
    -   * }
    + * } * * ... returns {@code "aaa"}. */ @@ -659,9 +662,9 @@ public String retainFrom(CharSequence sequence) { * Returns a string copy of the input character sequence, with each matching BMP character * replaced by a given replacement character. For example: * - *
    {@code
    +   * {@snippet :
        * CharMatcher.is('a').replaceFrom("radar", 'o')
    -   * }
    + * } * * ... returns {@code "rodor"}. * @@ -694,9 +697,9 @@ public String replaceFrom(CharSequence sequence, char replacement) { * Returns a string copy of the input character sequence, with each matching BMP character * replaced by a given replacement sequence. For example: * - *
    {@code
    +   * {@snippet :
        * CharMatcher.is('a').replaceFrom("yaha", "oo")
    -   * }
    + * } * * ... returns {@code "yoohoo"}. * @@ -742,17 +745,17 @@ public String replaceFrom(CharSequence sequence, CharSequence replacement) { * Returns a substring of the input character sequence that omits all matching BMP characters from * the beginning and from the end of the string. For example: * - *
    {@code
    +   * {@snippet :
        * CharMatcher.anyOf("ab").trimFrom("abacatbab")
    -   * }
    + * } * * ... returns {@code "cat"}. * *

    Note that: * - *

    {@code
    +   * {@snippet :
        * CharMatcher.inRange('\0', ' ').trimFrom(str)
    -   * }
    + * } * * ... is equivalent to {@link String#trim()}. */ @@ -779,9 +782,9 @@ public String trimFrom(CharSequence sequence) { * Returns a substring of the input character sequence that omits all matching BMP characters from * the beginning of the string. For example: * - *
    {@code
    +   * {@snippet :
        * CharMatcher.anyOf("ab").trimLeadingFrom("abacatbab")
    -   * }
    + * } * * ... returns {@code "catbab"}. */ @@ -799,9 +802,9 @@ public String trimLeadingFrom(CharSequence sequence) { * Returns a substring of the input character sequence that omits all matching BMP characters from * the end of the string. For example: * - *
    {@code
    +   * {@snippet :
        * CharMatcher.anyOf("ab").trimTrailingFrom("abacatbab")
    -   * }
    + * } * * ... returns {@code "abacat"}. */ @@ -819,9 +822,9 @@ public String trimTrailingFrom(CharSequence sequence) { * Returns a string copy of the input character sequence, with each group of consecutive matching * BMP characters replaced by a single replacement character. For example: * - *
    {@code
    +   * {@snippet :
        * CharMatcher.anyOf("eko").collapseFrom("bookkeeper", '-')
    -   * }
    + * } * * ... returns {@code "b-p-r"}. * @@ -904,8 +907,13 @@ private String finishCollapseFrom( * @deprecated Provided only to satisfy the {@link Predicate} interface; use {@link #matches} * instead. */ + @InlineMe(replacement = "this.matches(character)") @Deprecated @Override + // We can't compatibly make this `final` now. + @InlineMeValidationDisabled( + "While apply() is not final, the inlining is still safe because all known overrides of" + + " apply() call matches().") public boolean apply(Character character) { return matches(character); } @@ -965,7 +973,7 @@ public final String toString() { } /** Negation of a {@link FastMatcher}. */ - static class NegatedFastMatcher extends Negated { + private static class NegatedFastMatcher extends Negated { NegatedFastMatcher(CharMatcher original) { super(original); @@ -1008,7 +1016,7 @@ void setBits(BitSet bitSet) { /** Implementation of {@link #any()}. */ private static final class Any extends NamedFastMatcher { - static final Any INSTANCE = new Any(); + static final CharMatcher INSTANCE = new Any(); private Any() { super("CharMatcher.any()"); @@ -1105,7 +1113,7 @@ public CharMatcher negate() { /** Implementation of {@link #none()}. */ private static final class None extends NamedFastMatcher { - static final None INSTANCE = new None(); + static final CharMatcher INSTANCE = new None(); private None() { super("CharMatcher.none()"); @@ -1221,7 +1229,7 @@ static final class Whitespace extends NamedFastMatcher { static final int MULTIPLIER = 1682554634; static final int SHIFT = Integer.numberOfLeadingZeros(TABLE.length() - 1); - static final Whitespace INSTANCE = new Whitespace(); + static final CharMatcher INSTANCE = new Whitespace(); Whitespace() { super("CharMatcher.whitespace()"); @@ -1278,7 +1286,7 @@ public String toString() { /** Implementation of {@link #ascii()}. */ private static final class Ascii extends NamedFastMatcher { - static final Ascii INSTANCE = new Ascii(); + static final CharMatcher INSTANCE = new Ascii(); Ascii() { super("CharMatcher.ascii()"); @@ -1352,7 +1360,7 @@ private static char[] nines() { return nines; } - static final Digit INSTANCE = new Digit(); + static final CharMatcher INSTANCE = new Digit(); private Digit() { super("CharMatcher.digit()", zeroes(), nines()); @@ -1362,7 +1370,7 @@ private Digit() { /** Implementation of {@link #javaDigit()}. */ private static final class JavaDigit extends CharMatcher { - static final JavaDigit INSTANCE = new JavaDigit(); + static final CharMatcher INSTANCE = new JavaDigit(); @Override public boolean matches(char c) { @@ -1378,7 +1386,7 @@ public String toString() { /** Implementation of {@link #javaLetter()}. */ private static final class JavaLetter extends CharMatcher { - static final JavaLetter INSTANCE = new JavaLetter(); + static final CharMatcher INSTANCE = new JavaLetter(); @Override public boolean matches(char c) { @@ -1394,7 +1402,7 @@ public String toString() { /** Implementation of {@link #javaLetterOrDigit()}. */ private static final class JavaLetterOrDigit extends CharMatcher { - static final JavaLetterOrDigit INSTANCE = new JavaLetterOrDigit(); + static final CharMatcher INSTANCE = new JavaLetterOrDigit(); @Override public boolean matches(char c) { @@ -1410,7 +1418,7 @@ public String toString() { /** Implementation of {@link #javaUpperCase()}. */ private static final class JavaUpperCase extends CharMatcher { - static final JavaUpperCase INSTANCE = new JavaUpperCase(); + static final CharMatcher INSTANCE = new JavaUpperCase(); @Override public boolean matches(char c) { @@ -1426,7 +1434,7 @@ public String toString() { /** Implementation of {@link #javaLowerCase()}. */ private static final class JavaLowerCase extends CharMatcher { - static final JavaLowerCase INSTANCE = new JavaLowerCase(); + static final CharMatcher INSTANCE = new JavaLowerCase(); @Override public boolean matches(char c) { @@ -1442,7 +1450,7 @@ public String toString() { /** Implementation of {@link #javaIsoControl()}. */ private static final class JavaIsoControl extends NamedFastMatcher { - static final JavaIsoControl INSTANCE = new JavaIsoControl(); + static final CharMatcher INSTANCE = new JavaIsoControl(); private JavaIsoControl() { super("CharMatcher.javaIsoControl()"); @@ -1461,13 +1469,13 @@ private static final class Invisible extends RangesMatcher { // [[[:Zs:][:Zl:][:Zp:][:Cc:][:Cf:][:Cs:][:Co:]]&[\u0000-\uFFFF]] // with the "Abbreviate" option, and get the ranges from there. private static final String RANGE_STARTS = - "\u0000\u007f\u00ad\u0600\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000\u2028\u205f\u2066" + "\u0000\u007f\u00ad\u0600\u061c\u06dd\u070f\u0890\u08e2\u1680\u180e\u2000\u2028\u205f\u2066" + "\u3000\ud800\ufeff\ufff9"; private static final String RANGE_ENDS = // inclusive ends - "\u0020\u00a0\u00ad\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u200f\u202f\u2064\u206f" + "\u0020\u00a0\u00ad\u0605\u061c\u06dd\u070f\u0891\u08e2\u1680\u180e\u200f\u202f\u2064\u206f" + "\u3000\uf8ff\ufeff\ufffb"; - static final Invisible INSTANCE = new Invisible(); + static final CharMatcher INSTANCE = new Invisible(); private Invisible() { super("CharMatcher.invisible()", RANGE_STARTS.toCharArray(), RANGE_ENDS.toCharArray()); @@ -1477,7 +1485,7 @@ private Invisible() { /** Implementation of {@link #singleWidth()}. */ private static final class SingleWidth extends RangesMatcher { - static final SingleWidth INSTANCE = new SingleWidth(); + static final CharMatcher INSTANCE = new SingleWidth(); private SingleWidth() { super( @@ -1567,7 +1575,7 @@ void setBits(BitSet table) { @Override public String toString() { - return "CharMatcher.and(" + first + ", " + second + ")"; + return first + ".and(" + second + ")"; } } @@ -1596,7 +1604,7 @@ public boolean matches(char c) { @Override public String toString() { - return "CharMatcher.or(" + first + ", " + second + ")"; + return first + ".or(" + second + ")"; } } @@ -1728,7 +1736,7 @@ private static final class AnyOf extends CharMatcher { private final char[] chars; - public AnyOf(CharSequence chars) { + AnyOf(CharSequence chars) { this.chars = chars.toString().toCharArray(); Arrays.sort(this.chars); } @@ -1804,12 +1812,6 @@ public boolean matches(char c) { return predicate.apply(c); } - @SuppressWarnings("deprecation") // intentional; deprecation is for callers primarily - @Override - public boolean apply(Character character) { - return predicate.apply(checkNotNull(character)); - } - @Override public String toString() { return "CharMatcher.forPredicate(" + predicate + ")"; diff --git a/android/guava/src/com/google/common/base/Charsets.java b/android/guava/src/com/google/common/base/Charsets.java index 7aebea826c78..16e4831976bf 100644 --- a/android/guava/src/com/google/common/base/Charsets.java +++ b/android/guava/src/com/google/common/base/Charsets.java @@ -16,7 +16,9 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Contains constant definitions for the six standard {@link Charset} instances, which are @@ -30,74 +32,55 @@ * @author Mike Bostock * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Charsets { - private Charsets() {} /** * US-ASCII: seven-bit ASCII, the Basic Latin block of the Unicode character set (ISO646-US). * - *

    Note for Java 7 and later: this constant should be treated as deprecated; use {@link - * java.nio.charset.StandardCharsets#US_ASCII} instead. - * + * @deprecated Use {@link StandardCharsets#US_ASCII} instead. */ - @GwtIncompatible // Charset not supported by GWT - public static final Charset US_ASCII = Charset.forName("US-ASCII"); + @Deprecated @J2ktIncompatible @GwtIncompatible // Charset not supported by GWT + public static final Charset US_ASCII = StandardCharsets.US_ASCII; /** * ISO-8859-1: ISO Latin Alphabet Number 1 (ISO-LATIN-1). * - *

    Note for Java 7 and later: this constant should be treated as deprecated; use {@link - * java.nio.charset.StandardCharsets#ISO_8859_1} instead. - * + * @deprecated Use {@link StandardCharsets#ISO_8859_1} instead. */ - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + @Deprecated public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; /** * UTF-8: eight-bit UCS Transformation Format. * - *

    Note for Java 7 and later: this constant should be treated as deprecated; use {@link - * java.nio.charset.StandardCharsets#UTF_8} instead. - * + * @deprecated Use {@link StandardCharsets#UTF_8} instead. */ - public static final Charset UTF_8 = Charset.forName("UTF-8"); + @Deprecated public static final Charset UTF_8 = StandardCharsets.UTF_8; /** * UTF-16BE: sixteen-bit UCS Transformation Format, big-endian byte order. * - *

    Note for Java 7 and later: this constant should be treated as deprecated; use {@link - * java.nio.charset.StandardCharsets#UTF_16BE} instead. - * + * @deprecated Use {@link StandardCharsets#UTF_16BE} instead. */ - @GwtIncompatible // Charset not supported by GWT - public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + @Deprecated @J2ktIncompatible @GwtIncompatible // Charset not supported by GWT + public static final Charset UTF_16BE = StandardCharsets.UTF_16BE; /** * UTF-16LE: sixteen-bit UCS Transformation Format, little-endian byte order. * - *

    Note for Java 7 and later: this constant should be treated as deprecated; use {@link - * java.nio.charset.StandardCharsets#UTF_16LE} instead. - * + * @deprecated Use {@link StandardCharsets#UTF_16LE} instead. */ - @GwtIncompatible // Charset not supported by GWT - public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + @Deprecated @J2ktIncompatible @GwtIncompatible // Charset not supported by GWT + public static final Charset UTF_16LE = StandardCharsets.UTF_16LE; /** * UTF-16: sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order * mark. * - *

    Note for Java 7 and later: this constant should be treated as deprecated; use {@link - * java.nio.charset.StandardCharsets#UTF_16} instead. - * + * @deprecated Use {@link StandardCharsets#UTF_16} instead. */ - @GwtIncompatible // Charset not supported by GWT - public static final Charset UTF_16 = Charset.forName("UTF-16"); + @Deprecated @J2ktIncompatible @GwtIncompatible // Charset not supported by GWT + public static final Charset UTF_16 = StandardCharsets.UTF_16; - /* - * Please do not add new Charset references to this class, unless those character encodings are - * part of the set required to be supported by all Java platform implementations! Any Charsets - * initialized here may cause unexpected delays when this class is loaded. See the Charset - * Javadocs for the list of built-in character encodings. - */ + private Charsets() {} } diff --git a/android/guava/src/com/google/common/base/CommonMatcher.java b/android/guava/src/com/google/common/base/CommonMatcher.java index d63b46b5d48f..6d14c6bc2630 100644 --- a/android/guava/src/com/google/common/base/CommonMatcher.java +++ b/android/guava/src/com/google/common/base/CommonMatcher.java @@ -22,7 +22,6 @@ * javadoc for details. */ @GwtCompatible -@ElementTypesAreNonnullByDefault abstract class CommonMatcher { public abstract boolean matches(); diff --git a/android/guava/src/com/google/common/base/CommonPattern.java b/android/guava/src/com/google/common/base/CommonPattern.java index c425d52609d6..6be5b01408aa 100644 --- a/android/guava/src/com/google/common/base/CommonPattern.java +++ b/android/guava/src/com/google/common/base/CommonPattern.java @@ -22,7 +22,6 @@ * javadoc for details. */ @GwtCompatible -@ElementTypesAreNonnullByDefault abstract class CommonPattern { public abstract CommonMatcher matcher(CharSequence t); diff --git a/android/guava/src/com/google/common/base/Converter.java b/android/guava/src/com/google/common/base/Converter.java index 208a0324ed45..bb6928e9c510 100644 --- a/android/guava/src/com/google/common/base/Converter.java +++ b/android/guava/src/com/google/common/base/Converter.java @@ -18,13 +18,16 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; -import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.ForOverride; +import com.google.errorprone.annotations.InlineMe; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedWith; import java.io.Serializable; import java.util.Iterator; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * A function from {@code A} to {@code B} with an associated reverse function from {@code B} @@ -68,9 +71,8 @@ * com.google.common.collect.Maps#asConverter Maps.asConverter}. For example, use this to * create a "fake" converter for a unit test. It is unnecessary (and confusing) to mock * the {@code Converter} type using a mocking framework. + *

  • Pass two lambda expressions or method references to the {@link #from from} factory method. *
  • Extend this class and implement its {@link #doForward} and {@link #doBackward} methods. - *
  • Java 8 users: you may prefer to pass two lambda expressions or method references to - * the {@link #from from} factory method. * * *

    Using a converter: @@ -89,24 +91,27 @@ * *

    Example

    * - *
    - *   return new Converter<Integer, String>() {
    - *     protected String doForward(Integer i) {
    - *       return Integer.toHexString(i);
    - *     }
    - *
    - *     protected Integer doBackward(String s) {
    - *       return parseUnsignedInt(s, 16);
    - *     }
    - *   };
    - * - *

    An alternative using Java 8: - * - *

    {@code
    + * {@snippet :
      * return Converter.from(
      *     Integer::toHexString,
      *     s -> parseUnsignedInt(s, 16));
    - * }
    + * } + * + *

    An alternative using a subclass: + * + * {@snippet : + * return new Converter() { + * @Override + * protected String doForward(Integer i) { + * return Integer.toHexString(i); + * } + * + * @Override + * protected Integer doBackward(String s) { + * return parseUnsignedInt(s, 16); + * } + * } + * } * * @author Mike Ward * @author Kurt Alfred Kluever @@ -114,7 +119,6 @@ * @since 16.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault /* * 1. The type parameter is rather than so that we can use T in the * doForward and doBackward methods to indicate that the parameter cannot be null. (We also take @@ -143,7 +147,7 @@ public abstract class Converter implements Function { private final boolean handleNullAutomatically; // We lazily cache the reverse view to avoid allocating on every call to reverse(). - @LazyInit @RetainedWith @CheckForNull private transient Converter reverse; + @LazyInit @RetainedWith private transient @Nullable Converter reverse; /** Constructor for use by subclasses. */ protected Converter() { @@ -189,14 +193,11 @@ protected Converter() { * * @return the converted value; is null if and only if {@code a} is null */ - @CanIgnoreReturnValue - @CheckForNull - public final B convert(@CheckForNull A a) { + public final @Nullable B convert(@Nullable A a) { return correctedDoForward(a); } - @CheckForNull - B correctedDoForward(@CheckForNull A a) { + @Nullable B correctedDoForward(@Nullable A a) { if (handleNullAutomatically) { // TODO(kevinb): we shouldn't be checking for a null result at runtime. Assert? return a == null ? null : checkNotNull(doForward(a)); @@ -205,8 +206,7 @@ B correctedDoForward(@CheckForNull A a) { } } - @CheckForNull - A correctedDoBackward(@CheckForNull B b) { + @Nullable A correctedDoBackward(@Nullable B b) { if (handleNullAutomatically) { // TODO(kevinb): we shouldn't be checking for a null result at runtime. Assert? return b == null ? null : checkNotNull(doBackward(b)); @@ -241,13 +241,11 @@ A correctedDoBackward(@CheckForNull B b) { * LegacyConverter does violate the assumptions we make elsewhere. */ - @CheckForNull - private B unsafeDoForward(@CheckForNull A a) { + private @Nullable B unsafeDoForward(@Nullable A a) { return doForward(uncheckedCastNullableTToT(a)); } - @CheckForNull - private A unsafeDoBackward(@CheckForNull B b) { + private @Nullable A unsafeDoBackward(@Nullable B b) { return doBackward(uncheckedCastNullableTToT(b)); } @@ -259,7 +257,6 @@ private A unsafeDoBackward(@CheckForNull B b) { * a successful {@code remove()} call, {@code fromIterable} no longer contains the corresponding * element. */ - @CanIgnoreReturnValue /* * Just as Converter could implement `Function<@Nullable A, @Nullable B>` instead of `Function`, convertAll could accept and return iterables with nullable element types. In both cases, @@ -270,12 +267,10 @@ private A unsafeDoBackward(@CheckForNull B b) { * both use cases by using @PolyNull. (By contrast, we can't use @PolyNull for our superinterface * (`implements Function<@PolyNull A, @PolyNull B>`), at least as far as I know.) */ - public Iterable convertAll(final Iterable fromIterable) { + public Iterable convertAll(Iterable fromIterable) { checkNotNull(fromIterable, "fromIterable"); - return new Iterable() { - @Override - public Iterator iterator() { - return new Iterator() { + return () -> + new Iterator() { private final Iterator fromIterator = fromIterable.iterator(); @Override @@ -284,8 +279,6 @@ public boolean hasNext() { } @Override - @SuppressWarnings("nullness") // See code comments on convertAll and Converter.apply. - @CheckForNull public B next() { return convert(fromIterator.next()); } @@ -295,8 +288,6 @@ public void remove() { fromIterator.remove(); } }; - } - }; } /** @@ -307,7 +298,7 @@ public void remove() { * *

    Note: you should not override this method. It is non-final for legacy reasons. */ - @CanIgnoreReturnValue + @CheckReturnValue public Converter reverse() { Converter result = reverse; return (result == null) ? reverse = new ReverseConverter<>(this) : result; @@ -339,14 +330,12 @@ protected B doBackward(A a) { } @Override - @CheckForNull - A correctedDoForward(@CheckForNull B b) { + @Nullable A correctedDoForward(@Nullable B b) { return original.correctedDoBackward(b); } @Override - @CheckForNull - B correctedDoBackward(@CheckForNull A a) { + @Nullable B correctedDoBackward(@Nullable A a) { return original.correctedDoForward(a); } @@ -356,7 +345,7 @@ public Converter reverse() { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof ReverseConverter) { ReverseConverter that = (ReverseConverter) object; return this.original.equals(that.original); @@ -374,7 +363,7 @@ public String toString() { return original + ".reverse()"; } - private static final long serialVersionUID = 0L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0L; } /** @@ -421,19 +410,17 @@ protected A doBackward(C c) { } @Override - @CheckForNull - C correctedDoForward(@CheckForNull A a) { + @Nullable C correctedDoForward(@Nullable A a) { return second.correctedDoForward(first.correctedDoForward(a)); } @Override - @CheckForNull - A correctedDoBackward(@CheckForNull C c) { + @Nullable A correctedDoBackward(@Nullable C c) { return first.correctedDoBackward(second.correctedDoBackward(c)); } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof ConverterComposition) { ConverterComposition that = (ConverterComposition) object; return this.first.equals(that.first) && this.second.equals(that.second); @@ -451,7 +438,7 @@ public String toString() { return first + ".andThen(" + second + ")"; } - private static final long serialVersionUID = 0L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0L; } /** @@ -459,56 +446,42 @@ public String toString() { */ @Deprecated @Override - @CanIgnoreReturnValue - /* - * Even though we implement `Function` instead of `Function<@Nullable A, @Nullable B>` (as - * discussed in a code comment at the top of the class), we declare our override of Function.apply - * to accept and return null. This requires a suppression, but it's safe: - * - * - Callers who use Converter as a Function will neither pass null nor have it returned to - * them. (Or, if they're not using nullness checking, they might be able to pass null and thus - * have null returned to them. But our signature isn't making their existing nullness type error - * any worse.) - * - In the relatively unlikely event that anyone calls Converter.apply directly, that caller is - * allowed to pass null but is also forced to deal with a potentially null return. - * - Perhaps more important than actual *callers* of this method are various tools that look at - * bytecode. Notably, NullPointerTester expects a method to throw NPE when passed null unless it - * is annotated in a way that identifies its parameter type as potentially including null. (And - * this method does not throw NPE -- nor do we want to enact a dangerous change to make it begin - * doing so.) We can even imagine tools that rewrite bytecode to insert null checks before and - * after calling methods with allegedly non-nullable parameters[*]. If we didn't annotate the - * parameter and return type here, then anyone who used such a tool (and managed to pass null to - * this method, presumably because that user doesn't run a normal nullness checker) could see - * NullPointerException. - * - * [*] Granted, such tools could conceivably be smart enough to recognize that the apply() method - * on a a Function should never allow null inputs and never produce null outputs even if - * this specific subclass claims otherwise. Such tools might still produce NPE for calls to this - * method. And that is one reason that we should be nervous about "lying" by extending Function in the first place. But for now, we're giving it a try, since extending Function<@Nullable - * A, @Nullable B> will cause issues *today*, whereas extending Function causes problems in - * various hypothetical futures. (Plus, a tool that were that smart would likely already introduce - * problems with LegacyConverter.) - */ - @SuppressWarnings("nullness") - @CheckForNull - public final B apply(@CheckForNull A a) { + @InlineMe(replacement = "this.convert(a)") + public final B apply(A a) { + /* + * Given that we declare this method as accepting and returning non-nullable values (because we + * implement Function, as discussed in a class-level comment), it would make some sense to + * perform runtime null checks on the input and output. (That would also make NullPointerTester + * happy!) However, since we didn't do that for many years, we're not about to start now. + * (Runtime checks could be particularly bad for users of LegacyConverter.) + * + * Luckily, our nullness checker is smart enough to realize that `convert` has @PolyNull-like + * behavior, so it knows that `convert(a)` returns a non-nullable value, and we don't need to + * perform even a cast, much less a runtime check. + * + * All that said, don't forget that everyone should call converter.convert() instead of + * converter.apply(), anyway. If clients use only converter.convert(), then their nullness + * checkers are unlikely to ever look at the annotations on this declaration. + * + * Historical note: At one point, we'd declared this method as accepting and returning nullable + * values. For details on that, see earlier revisions of this file. + */ return convert(a); } /** - * Indicates whether another object is equal to this converter. + * May return {@code true} if {@code object} is a {@code Converter} that behaves + * identically to this converter. + * + *

    Warning: do not depend on the behavior of this method. * - *

    Most implementations will have no reason to override the behavior of {@link Object#equals}. - * However, an implementation may also choose to return {@code true} whenever {@code object} is a - * {@link Converter} that it considers interchangeable with this one. "Interchangeable" - * typically means that {@code Objects.equal(this.convert(a), that.convert(a))} is true for - * all {@code a} of type {@code A} (and similarly for {@code reverse}). Note that a {@code false} - * result from this method does not imply that the converters are known not to be - * interchangeable. + *

    Historically, {@code Converter} instances in this library have implemented this method to + * recognize certain cases where distinct {@code Converter} instances would in fact behave + * identically. However, this is not true of {@code Converter} implementations in general. It is + * best not to depend on it. */ @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { return super.equals(object); } @@ -557,7 +530,7 @@ protected A doBackward(B b) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof FunctionBasedConverter) { FunctionBasedConverter that = (FunctionBasedConverter) object; return this.forwardFunction.equals(that.forwardFunction) @@ -588,7 +561,7 @@ public static Converter identity() { * "pass-through type". */ private static final class IdentityConverter extends Converter implements Serializable { - static final IdentityConverter INSTANCE = new IdentityConverter<>(); + static final Converter INSTANCE = new IdentityConverter<>(); @Override protected T doForward(T t) { @@ -624,6 +597,6 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 0L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0L; } } diff --git a/android/guava/src/com/google/common/base/Defaults.java b/android/guava/src/com/google/common/base/Defaults.java index 00adbdefa12f..8105badc59b3 100644 --- a/android/guava/src/com/google/common/base/Defaults.java +++ b/android/guava/src/com/google/common/base/Defaults.java @@ -17,7 +17,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * This class provides default values for all Java types, as defined by the JLS. @@ -25,12 +26,13 @@ * @author Ben Yu * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public final class Defaults { private Defaults() {} - private static final Double DOUBLE_DEFAULT = Double.valueOf(0d); - private static final Float FLOAT_DEFAULT = Float.valueOf(0f); + private static final Double DOUBLE_DEFAULT = 0d; + private static final Float FLOAT_DEFAULT = 0f; /** * Returns the default value of {@code type} as defined by JLS --- {@code 0} for numbers, {@code @@ -38,27 +40,27 @@ private Defaults() {} * {@code void}, {@code null} is returned. */ @SuppressWarnings("unchecked") - @NullableDecl - public static T defaultValue(Class type) { + public static @Nullable T defaultValue(Class type) { checkNotNull(type); - if (type == boolean.class) { - return (T) Boolean.FALSE; - } else if (type == char.class) { - return (T) Character.valueOf('\0'); - } else if (type == byte.class) { - return (T) Byte.valueOf((byte) 0); - } else if (type == short.class) { - return (T) Short.valueOf((short) 0); - } else if (type == int.class) { - return (T) Integer.valueOf(0); - } else if (type == long.class) { - return (T) Long.valueOf(0L); - } else if (type == float.class) { - return (T) FLOAT_DEFAULT; - } else if (type == double.class) { - return (T) DOUBLE_DEFAULT; - } else { - return null; + if (type.isPrimitive()) { + if (type == boolean.class) { + return (T) Boolean.FALSE; + } else if (type == char.class) { + return (T) Character.valueOf('\0'); + } else if (type == byte.class) { + return (T) Byte.valueOf((byte) 0); + } else if (type == short.class) { + return (T) Short.valueOf((short) 0); + } else if (type == int.class) { + return (T) Integer.valueOf(0); + } else if (type == long.class) { + return (T) Long.valueOf(0L); + } else if (type == float.class) { + return (T) FLOAT_DEFAULT; + } else if (type == double.class) { + return (T) DOUBLE_DEFAULT; + } } + return null; } } diff --git a/android/guava/src/com/google/common/base/Enums.java b/android/guava/src/com/google/common/base/Enums.java index 449b7e3a95f7..5587cbf9627d 100644 --- a/android/guava/src/com/google/common/base/Enums.java +++ b/android/guava/src/com/google/common/base/Enums.java @@ -16,8 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.lang.ref.WeakReference; import java.lang.reflect.Field; @@ -25,7 +25,7 @@ import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Utility methods for working with {@link Enum} instances. @@ -33,8 +33,8 @@ * @author Steve McKay * @since 9.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtIncompatible +@J2ktIncompatible public final class Enums { private Enums() {} @@ -46,9 +46,9 @@ private Enums() {} * * @since 12.0 */ - @GwtIncompatible // reflection public static Field getField(Enum enumValue) { - Class clazz = enumValue.getDeclaringClass(); + Class + clazz = enumValue.getDeclaringClass(); try { return clazz.getDeclaredField(enumValue.name()); } catch (NoSuchFieldException impossible) { @@ -70,11 +70,9 @@ public static > Optional getIfPresent(Class enumClass, S return Platform.getEnumIfPresent(enumClass, value); } - @GwtIncompatible // java.lang.ref.WeakReference private static final Map>, Map>>> enumConstantCache = new WeakHashMap<>(); - @GwtIncompatible // java.lang.ref.WeakReference private static > Map>> populateCache( Class enumClass) { Map>> result = new HashMap<>(); @@ -85,7 +83,6 @@ private static > Map>> return result; } - @GwtIncompatible // java.lang.ref.WeakReference static > Map>> getEnumConstants( Class enumClass) { synchronized (enumConstantCache) { @@ -98,15 +95,15 @@ static > Map>> getEnum } /** - * Returns a converter that converts between strings and {@code enum} values of type {@code - * enumClass} using {@link Enum#valueOf(Class, String)} and {@link Enum#name()}. The converter - * will throw an {@code IllegalArgumentException} if the argument is not the name of any enum - * constant in the specified enum. + * Returns a serializable converter that converts between strings and {@code enum} values of type + * {@code enumClass} using {@link Enum#valueOf(Class, String)} and {@link Enum#name()}. The + * converter will throw an {@code IllegalArgumentException} if the argument is not the name of any + * enum constant in the specified enum. * * @since 16.0 */ - public static > Converter stringConverter(final Class enumClass) { - return new StringConverter(enumClass); + public static > Converter stringConverter(Class enumClass) { + return new StringConverter<>(enumClass); } private static final class StringConverter> extends Converter @@ -129,9 +126,9 @@ protected String doBackward(T enumValue) { } @Override - public boolean equals(@CheckForNull Object object) { - if (object instanceof StringConverter) { - StringConverter that = (StringConverter) object; + public boolean equals(@Nullable Object obj) { + if (obj instanceof StringConverter) { + StringConverter that = (StringConverter) obj; return this.enumClass.equals(that.enumClass); } return false; @@ -147,6 +144,6 @@ public String toString() { return "Enums.stringConverter(" + enumClass.getName() + ".class)"; } - private static final long serialVersionUID = 0L; + @J2ktIncompatible private static final long serialVersionUID = 0L; } } diff --git a/android/guava/src/com/google/common/base/Equivalence.java b/android/guava/src/com/google/common/base/Equivalence.java index 9069ecf1a6cb..cf1b7443b74b 100644 --- a/android/guava/src/com/google/common/base/Equivalence.java +++ b/android/guava/src/com/google/common/base/Equivalence.java @@ -17,10 +17,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.ForOverride; import java.io.Serializable; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A strategy for determining whether two instances are considered equivalent, and for computing @@ -39,11 +42,11 @@ * source-compatible since 4.0) */ @GwtCompatible -@ElementTypesAreNonnullByDefault /* * The type parameter is rather than so that we can use T in the * doEquivalent and doHash methods to indicate that the parameter cannot be null. */ +@SuppressWarnings("UngroupedOverloads") public abstract class Equivalence { /** Constructor for use by subclasses. */ protected Equivalence() {} @@ -65,7 +68,7 @@ protected Equivalence() {} *

    Note that all calls to {@code equivalent(x, y)} are expected to return the same result as * long as neither {@code x} nor {@code y} is modified. */ - public final boolean equivalent(@CheckForNull T a, @CheckForNull T b) { + public final boolean equivalent(@Nullable T a, @Nullable T b) { if (a == b) { return true; } @@ -76,12 +79,27 @@ public final boolean equivalent(@CheckForNull T a, @CheckForNull T b) { } /** - * * @since 10.0 (previously, subclasses would override equivalent()) */ @ForOverride protected abstract boolean doEquivalent(T a, T b); + /** + * May return {@code true} if {@code object} is a {@code Equivalence} that behaves + * identically to this equivalence. + * + *

    Warning: do not depend on the behavior of this method. + * + *

    Historically, {@code Equivalence} instances in this library have implemented this method to + * recognize certain cases where distinct {@code Equivalence} instances would in fact behave + * identically. However, as code migrates to {@code java.util.function}, that behavior will + * disappear. It is best not to depend on it. + */ + @Override + public boolean equals(@Nullable Object object) { + return super.equals(object); + } + /** * Returns a hash code for {@code t}. * @@ -99,7 +117,7 @@ public final boolean equivalent(@CheckForNull T a, @CheckForNull T b) { *

  • {@code hash(null)} is {@code 0}. * */ - public final int hash(@CheckForNull T t) { + public final int hash(@Nullable T t) { if (t == null) { return 0; } @@ -127,9 +145,9 @@ public final int hash(@CheckForNull T t) { * *

    For example: * - *

    {@code
    +   * {@snippet :
        * Equivalence SAME_AGE = Equivalence.equals().onResultOf(GET_PERSON_AGE);
    -   * }
    + * } * *

    {@code function} will never be invoked with a null value. * @@ -150,10 +168,13 @@ public final Equivalence onResultOf(FunctionThe returned object is serializable if both this {@code Equivalence} and {@code reference} + * are serializable (including when {@code reference} is null). + * * @since 10.0 */ public final Wrapper wrap(@ParametricNullness S reference) { - return new Wrapper(this, reference); + return new Wrapper<>(this, reference); } /** @@ -163,24 +184,33 @@ public final Equivalence onResultOf(FunctionFor example, given an {@link Equivalence} for {@link String strings} named {@code equiv} * that tests equivalence using their lengths: * - *

    {@code
    +   * {@snippet :
        * equiv.wrap("a").equals(equiv.wrap("b")) // true
        * equiv.wrap("a").equals(equiv.wrap("hello")) // false
    -   * }
    + * } * *

    Note in particular that an equivalence wrapper is never equal to the object it wraps. * - *

    {@code
    +   * {@snippet :
        * equiv.wrap(obj).equals(obj) // always false
    -   * }
    + * } * * @since 10.0 */ public static final class Wrapper implements Serializable { - private final Equivalence equivalence; + /* + * Equivalence's type argument is always non-nullable: Equivalence, never + * Equivalence<@Nullable Number>. That can still produce wrappers of various types -- + * Wrapper, Wrapper, Wrapper<@Nullable Integer>, etc. If we used just + * Equivalence below, no type could satisfy both that bound and T's own + * bound. With this type, they have some overlap: in our example, Equivalence + * and Equivalence. + */ + private final Equivalence equivalence; + @ParametricNullness private final T reference; - private Wrapper(Equivalence equivalence, @ParametricNullness T reference) { + private Wrapper(Equivalence equivalence, @ParametricNullness T reference) { this.equivalence = checkNotNull(equivalence); this.reference = reference; } @@ -197,7 +227,7 @@ public T get() { * equivalence. */ @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } @@ -232,7 +262,7 @@ public String toString() { return equivalence + ".wrap(" + reference + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -244,13 +274,14 @@ public String toString() { *

    Note that this method performs a similar function for equivalences as {@link * com.google.common.collect.Ordering#lexicographical} does for orderings. * + *

    The returned object is serializable if this object is serializable. + * * @since 10.0 */ - @GwtCompatible(serializable = true) public final Equivalence> pairwise() { // Ideally, the returned equivalence would support Iterable. However, // the need for this is so rare that it's not worth making callers deal with the ugly wildcard. - return new PairwiseEquivalence(this); + return new PairwiseEquivalence<>(this); } /** @@ -259,41 +290,41 @@ public String toString() { * * @since 10.0 */ - public final Predicate<@Nullable T> equivalentTo(@CheckForNull T target) { - return new EquivalentToPredicate(this, target); + public final Predicate<@Nullable T> equivalentTo(@Nullable T target) { + return new EquivalentToPredicate<>(this, target); } private static final class EquivalentToPredicate implements Predicate<@Nullable T>, Serializable { private final Equivalence equivalence; - @CheckForNull private final T target; + private final @Nullable T target; - EquivalentToPredicate(Equivalence equivalence, @CheckForNull T target) { + EquivalentToPredicate(Equivalence equivalence, @Nullable T target) { this.equivalence = checkNotNull(equivalence); this.target = target; } @Override - public boolean apply(@CheckForNull T input) { + public boolean apply(@Nullable T input) { return equivalence.equivalent(input, target); } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj instanceof EquivalentToPredicate) { EquivalentToPredicate that = (EquivalentToPredicate) obj; - return equivalence.equals(that.equivalence) && Objects.equal(target, that.target); + return equivalence.equals(that.equivalence) && Objects.equals(target, that.target); } return false; } @Override public int hashCode() { - return Objects.hashCode(equivalence, target); + return Objects.hash(equivalence, target); } @Override @@ -301,7 +332,7 @@ public String toString() { return equivalence + ".equivalentTo(" + target + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -348,7 +379,7 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } static final class Identity extends Equivalence implements Serializable { @@ -369,6 +400,6 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } } diff --git a/android/guava/src/com/google/common/base/ExtraObjectsMethodsForWeb.java b/android/guava/src/com/google/common/base/ExtraObjectsMethodsForWeb.java index 677075522028..b29f194c6e64 100644 --- a/android/guava/src/com/google/common/base/ExtraObjectsMethodsForWeb.java +++ b/android/guava/src/com/google/common/base/ExtraObjectsMethodsForWeb.java @@ -20,6 +20,5 @@ * Holder for extra methods of {@code Objects} only in web. Intended to be empty for regular * version. */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible abstract class ExtraObjectsMethodsForWeb {} diff --git a/android/guava/src/com/google/common/base/FinalizablePhantomReference.java b/android/guava/src/com/google/common/base/FinalizablePhantomReference.java index f92057588a30..89b600f4fa13 100644 --- a/android/guava/src/com/google/common/base/FinalizablePhantomReference.java +++ b/android/guava/src/com/google/common/base/FinalizablePhantomReference.java @@ -15,8 +15,10 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; +import org.jspecify.annotations.Nullable; /** * Phantom reference with a {@code finalizeReferent()} method which a background thread invokes @@ -28,6 +30,7 @@ * @author Bob Lee * @since 2.0 */ +@J2ktIncompatible @GwtIncompatible public abstract class FinalizablePhantomReference extends PhantomReference implements FinalizableReference { @@ -37,7 +40,7 @@ public abstract class FinalizablePhantomReference extends PhantomReference * @param referent to phantom reference * @param queue that should finalize the referent */ - protected FinalizablePhantomReference(T referent, FinalizableReferenceQueue queue) { + protected FinalizablePhantomReference(@Nullable T referent, FinalizableReferenceQueue queue) { super(referent, queue.queue); queue.cleanUp(); } diff --git a/android/guava/src/com/google/common/base/FinalizableReference.java b/android/guava/src/com/google/common/base/FinalizableReference.java index 848e7ee586d2..d7e91e46e0ee 100644 --- a/android/guava/src/com/google/common/base/FinalizableReference.java +++ b/android/guava/src/com/google/common/base/FinalizableReference.java @@ -15,6 +15,7 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.DoNotMock; /** @@ -25,6 +26,7 @@ * @since 2.0 */ @DoNotMock("Use an instance of one of the Finalizable*Reference classes") +@J2ktIncompatible @GwtIncompatible public interface FinalizableReference { /** diff --git a/android/guava/src/com/google/common/base/FinalizableReferenceQueue.java b/android/guava/src/com/google/common/base/FinalizableReferenceQueue.java index 6b1e91ff7fed..ded9a259c6e5 100644 --- a/android/guava/src/com/google/common/base/FinalizableReferenceQueue.java +++ b/android/guava/src/com/google/common/base/FinalizableReferenceQueue.java @@ -15,6 +15,7 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import java.io.Closeable; import java.io.FileNotFoundException; @@ -27,11 +28,12 @@ import java.net.URLClassLoader; import java.util.logging.Level; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A reference queue with an associated background thread that dequeues references and invokes - * {@link FinalizableReference#finalizeReferent()} on them. + * {@link FinalizableReference#finalizeReferent()} on them. Java 9+ users should prefer {@link + * java.lang.ref.Cleaner Cleaner}; see example below. * *

    Keep a strong reference to this object until all of the associated referents have been * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code @@ -43,7 +45,7 @@ * its {@code close} method. You could use a finalizer to accomplish this, but that has a * number of well-known problems. Here is how you might use this class instead: * - *

    {@code
    + * {@snippet :
      * public class MyServer implements Closeable {
      *   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
      *   // You might also share this between several objects.
    @@ -61,8 +63,9 @@
      *
      *   public static MyServer create(...) {
      *     MyServer myServer = new MyServer(...);
    - *     final ServerSocket serverSocket = myServer.serverSocket;
    + *     ServerSocket serverSocket = myServer.serverSocket;
      *     Reference reference = new FinalizablePhantomReference(myServer, frq) {
    + *       @Override
      *       public void finalizeReferent() {
      *         references.remove(this):
      *         if (!serverSocket.isClosed()) {
    @@ -79,15 +82,61 @@
      *     return myServer;
      *   }
      *
    - *   public void close() {
    + *   @Override
    + *   public void close() throws IOException {
      *     serverSocket.close();
      *   }
      * }
    - * }
    + * } + * + *

    Here is how you might achieve the same thing using {@link java.lang.ref.Cleaner + * Cleaner}, if you are using a Java version where that is available: + * + * {@snippet : + * public class MyServer implements Closeable { + * private static final Cleaner cleaner = Cleaner.create(); + * // You might also share this between several objects. + * + * private final ServerSocket serverSocket; + * private final Cleaner.Cleanable cleanable; + * + * public MyServer(...) { + * ... + * this.serverSocket = new ServerSocket(...); + * this.cleanable = cleaner.register(this, closeServerSocketRunnable(serverSocket)); + * ... + * } + * + * private static Runnable closeServerSocketRunnable(ServerSocket serverSocket) { + * return () -> { + * if (!serverSocket.isClosed()) { + * ...log a message about how nobody called close()... + * try { + * serverSocket.close(); + * } catch (IOException e) { + * ... + * } + * } + * }; + * } + * + * @Override + * public void close() throws IOException { + * serverSocket.close(); + * cleanable.clean(); + * } + * } + * } + * + *

    Some care is needed when using {@code Cleaner} to ensure that the callback passed to {@code + * register} does not have a reference to the object (in this case, {@code MyServer}) that may be + * garbage-collected. That's why we are careful to make a {@code Runnable} that does not have a + * reference to any {@code MyServer} instance. * * @author Bob Lee * @since 2.0 */ +@J2ktIncompatible @GwtIncompatible public class FinalizableReferenceQueue implements Closeable { /* @@ -155,7 +204,7 @@ public class FinalizableReferenceQueue implements Closeable { public FinalizableReferenceQueue() { // We could start the finalizer lazily, but I'd rather it blow up early. queue = new ReferenceQueue<>(); - frqRef = new PhantomReference(this, queue); + frqRef = new PhantomReference<>(this, queue); boolean threadStarted = false; try { startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef); @@ -228,22 +277,20 @@ interface FinalizerLoader { * * @throws SecurityException if we don't have the appropriate privileges */ - @NullableDecl - Class loadFinalizer(); + @Nullable Class loadFinalizer(); } /** * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path, * we needn't create a separate loader. */ - static class SystemLoader implements FinalizerLoader { + static final class SystemLoader implements FinalizerLoader { // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable // finding Finalizer on the system class path even if it is there. @VisibleForTesting static boolean disabled; @Override - @NullableDecl - public Class loadFinalizer() { + public @Nullable Class loadFinalizer() { if (disabled) { return null; } @@ -280,8 +327,7 @@ static class DecoupledLoader implements FinalizerLoader { + "issue, or move Guava to your system class path."; @Override - @NullableDecl - public Class loadFinalizer() { + public @Nullable Class loadFinalizer() { try { /* * We use URLClassLoader because it's the only concrete class loader implementation in the @@ -331,7 +377,7 @@ URLClassLoader newLoader(URL base) { * Loads Finalizer directly using the current class loader. We won't be able to garbage collect * this class loader, but at least the world doesn't end. */ - static class DirectLoader implements FinalizerLoader { + private static final class DirectLoader implements FinalizerLoader { @Override public Class loadFinalizer() { try { diff --git a/android/guava/src/com/google/common/base/FinalizableSoftReference.java b/android/guava/src/com/google/common/base/FinalizableSoftReference.java index 45ecc656c0d4..c4f6baa3c7de 100644 --- a/android/guava/src/com/google/common/base/FinalizableSoftReference.java +++ b/android/guava/src/com/google/common/base/FinalizableSoftReference.java @@ -15,8 +15,10 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; +import org.jspecify.annotations.Nullable; /** * Soft reference with a {@code finalizeReferent()} method which a background thread invokes after @@ -26,6 +28,7 @@ * @author Bob Lee * @since 2.0 */ +@J2ktIncompatible @GwtIncompatible public abstract class FinalizableSoftReference extends SoftReference implements FinalizableReference { @@ -35,7 +38,7 @@ public abstract class FinalizableSoftReference extends SoftReference * @param referent to softly reference * @param queue that should finalize the referent */ - protected FinalizableSoftReference(T referent, FinalizableReferenceQueue queue) { + protected FinalizableSoftReference(@Nullable T referent, FinalizableReferenceQueue queue) { super(referent, queue.queue); queue.cleanUp(); } diff --git a/android/guava/src/com/google/common/base/FinalizableWeakReference.java b/android/guava/src/com/google/common/base/FinalizableWeakReference.java index fb3b09bb7dc4..aeea7c7f8508 100644 --- a/android/guava/src/com/google/common/base/FinalizableWeakReference.java +++ b/android/guava/src/com/google/common/base/FinalizableWeakReference.java @@ -15,8 +15,10 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import org.jspecify.annotations.Nullable; /** * Weak reference with a {@code finalizeReferent()} method which a background thread invokes after @@ -26,6 +28,7 @@ * @author Bob Lee * @since 2.0 */ +@J2ktIncompatible @GwtIncompatible public abstract class FinalizableWeakReference extends WeakReference implements FinalizableReference { @@ -35,7 +38,7 @@ public abstract class FinalizableWeakReference extends WeakReference * @param referent to weakly reference * @param queue that should finalize the referent */ - protected FinalizableWeakReference(T referent, FinalizableReferenceQueue queue) { + protected FinalizableWeakReference(@Nullable T referent, FinalizableReferenceQueue queue) { super(referent, queue.queue); queue.cleanUp(); } diff --git a/android/guava/src/com/google/common/base/Function.java b/android/guava/src/com/google/common/base/Function.java index 05831867fef8..ab9e6d9878ab 100644 --- a/android/guava/src/com/google/common/base/Function.java +++ b/android/guava/src/com/google/common/base/Function.java @@ -15,8 +15,8 @@ package com.google.common.base; import com.google.common.annotations.GwtCompatible; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * Determines an output value based on an input value; a pre-Java-8 version of {@link @@ -44,28 +44,27 @@ * @since 2.0 */ @GwtCompatible -public interface Function { +public interface Function { /** * Returns the result of applying this function to {@code input}. This method is generally * expected, but not absolutely required, to have the following properties: * *
      *
    • Its execution does not cause any observable side effects. - *
    • The computation is consistent with equals; that is, {@link Objects#equal - * Objects.equal}{@code (a, b)} implies that {@code Objects.equal(function.apply(a), + *
    • The computation is consistent with equals; that is, {@link Objects#equals + * Objects.equals}{@code (a, b)} implies that {@code Objects.equals(function.apply(a), * function.apply(b))}. *
    * * @throws NullPointerException if {@code input} is null and this function does not accept null * arguments */ - @CanIgnoreReturnValue // TODO(kevinb): remove this - @NullableDecl - T apply(@NullableDecl F input); + @ParametricNullness + T apply(@ParametricNullness F input); /** - * May return {@code true} if {@code object} is a {@code Function} that behaves identically - * to this function. + * May return {@code true} if {@code obj} is a {@code Function} that behaves identically to + * this function. * *

    Warning: do not depend on the behavior of this method. * @@ -75,5 +74,5 @@ public interface Function { * disappear. It is best not to depend on it. */ @Override - boolean equals(@NullableDecl Object object); + boolean equals(@Nullable Object obj); } diff --git a/android/guava/src/com/google/common/base/FunctionalEquivalence.java b/android/guava/src/com/google/common/base/FunctionalEquivalence.java index 228d34d04064..3c50db2d8530 100644 --- a/android/guava/src/com/google/common/base/FunctionalEquivalence.java +++ b/android/guava/src/com/google/common/base/FunctionalEquivalence.java @@ -16,10 +16,11 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Equivalence applied on functional result. @@ -27,17 +28,16 @@ * @author Bob Lee * @since 10.0 */ -@Beta @GwtCompatible final class FunctionalEquivalence extends Equivalence implements Serializable { - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; - private final Function function; + private final Function function; private final Equivalence resultEquivalence; FunctionalEquivalence( - Function function, Equivalence resultEquivalence) { + Function function, Equivalence resultEquivalence) { this.function = checkNotNull(function); this.resultEquivalence = checkNotNull(resultEquivalence); } @@ -53,7 +53,7 @@ protected int doHash(F a) { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } diff --git a/android/guava/src/com/google/common/base/Functions.java b/android/guava/src/com/google/common/base/Functions.java index 805f15c73727..3120e35e0d4f 100644 --- a/android/guava/src/com/google/common/base/Functions.java +++ b/android/guava/src/com/google/common/base/Functions.java @@ -14,13 +14,17 @@ package com.google.common.base; +import static com.google.common.base.NullnessCasts.uncheckedCastNullableTToT; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code com.google.common.base.Function} instances; see that @@ -40,9 +44,9 @@ public final class Functions { private Functions() {} /** - * A function equivalent to the method reference {@code Object::toString}, for users not yet using - * Java 8. The function simply invokes {@code toString} on its argument and returns the result. It - * throws a {@link NullPointerException} on null input. + * A function equivalent to the method reference {@code Object::toString}. The function simply + * invokes {@code toString} on its argument and returns the result. It throws a {@link + * NullPointerException} on null input. * *

    Warning: The returned function may not be consistent with equals (as * documented at {@link Function#apply}). For example, this function yields different results for @@ -52,9 +56,12 @@ private Functions() {} * {@code equals}, {@code hashCode} or {@code toString} behavior of the returned function. A * future migration to {@code java.util.function} will not preserve this behavior. * - *

    For Java 8 users: use the method reference {@code Object::toString} instead. In the - * future, when this class requires Java 8, this method will be deprecated. See {@link Function} - * for more important information about the Java 8 transition. + *

    As discussed above, prefer to use the method reference {@code Object::toString} instead, + * though note that it is not serializable unless you explicitly make it {@link Serializable}, + * typically by writing {@code (Function & Serializable) Object::toString}. + * + *

    For more important information about the transition from Guava's {@link Function} class to + * the JDK {@link java.util.function.Function} class, see {@link Function}. */ public static Function toStringFunction() { return ToStringFunction.INSTANCE; @@ -76,20 +83,24 @@ public String toString() { } } - /** Returns the identity function. */ + /** + * Returns the identity function. + * + *

    Discouraged: Prefer using a lambda like {@code v -> v}, which is shorter and often + * more readable. + */ // implementation is "fully variant"; E has become a "pass-through" type @SuppressWarnings("unchecked") - public static Function identity() { + public static Function identity() { return (Function) IdentityFunction.INSTANCE; } // enum singleton pattern - private enum IdentityFunction implements Function { + private enum IdentityFunction implements Function<@Nullable Object, @Nullable Object> { INSTANCE; @Override - @NullableDecl - public Object apply(@NullableDecl Object o) { + public @Nullable Object apply(@Nullable Object o) { return o; } @@ -108,11 +119,13 @@ public String toString() { * can use {@link com.google.common.collect.Maps#asConverter Maps.asConverter} instead to get a * function that also supports reverse conversion. * - *

    Java 8 users: if you are okay with {@code null} being returned for an unrecognized - * key (instead of an exception being thrown), you can use the method reference {@code map::get} - * instead. + *

    If you are okay with {@code null} being returned for an unrecognized key (instead of an + * exception being thrown), you can use the method reference {@code map::get} instead. Note that + * it is not serializable unless you explicitly make it {@link Serializable}, typically by writing + * {@code (Function & Serializable) map::get}. */ - public static Function forMap(Map map) { + public static Function forMap( + Map map) { return new FunctionForMapNoDefault<>(map); } @@ -121,20 +134,24 @@ public static Function forMap(Map map) { * this method returns {@code defaultValue} for all inputs that do not belong to the map's key * set. See also {@link #forMap(Map)}, which throws an exception in this case. * - *

    Java 8 users: you can just write the lambda expression {@code k -> - * map.getOrDefault(k, defaultValue)} instead. + *

    Prefer to write the lambda expression {@code k -> map.getOrDefault(k, defaultValue)} + * instead. Note that it is not serializable unless you explicitly make it {@link Serializable}, + * typically by writing {@code (Function & Serializable) k -> map.getOrDefault(k, + * defaultValue)}. * * @param map source map that determines the function behavior * @param defaultValue the value to return for inputs that aren't map keys * @return function that returns {@code map.get(a)} when {@code a} is a key, or {@code * defaultValue} otherwise */ - public static Function forMap( - Map map, @NullableDecl V defaultValue) { + public static Function forMap( + Map map, @ParametricNullness V defaultValue) { return new ForMapWithDefault<>(map, defaultValue); } - private static class FunctionForMapNoDefault implements Function, Serializable { + private static final class FunctionForMapNoDefault< + K extends @Nullable Object, V extends @Nullable Object> + implements Function, Serializable { final Map map; FunctionForMapNoDefault(Map map) { @@ -142,14 +159,16 @@ private static class FunctionForMapNoDefault implements Function, Se } @Override - public V apply(@NullableDecl K key) { + @ParametricNullness + public V apply(@ParametricNullness K key) { V result = map.get(key); checkArgument(result != null || map.containsKey(key), "Key '%s' not present in map", key); - return result; + // The unchecked cast is safe because of the containsKey check. + return uncheckedCastNullableTToT(result); } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof FunctionForMapNoDefault) { FunctionForMapNoDefault that = (FunctionForMapNoDefault) o; return map.equals(that.map); @@ -167,36 +186,42 @@ public String toString() { return "Functions.forMap(" + map + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static class ForMapWithDefault implements Function, Serializable { + private static final class ForMapWithDefault< + K extends @Nullable Object, V extends @Nullable Object> + implements Function, Serializable { final Map map; - @NullableDecl final V defaultValue; + @ParametricNullness final V defaultValue; - ForMapWithDefault(Map map, @NullableDecl V defaultValue) { + ForMapWithDefault(Map map, @ParametricNullness V defaultValue) { this.map = checkNotNull(map); this.defaultValue = defaultValue; } @Override - public V apply(@NullableDecl K key) { + @ParametricNullness + public V apply(@ParametricNullness K key) { V result = map.get(key); - return (result != null || map.containsKey(key)) ? result : defaultValue; + // The unchecked cast is safe because of the containsKey check. + return (result != null || map.containsKey(key)) + ? uncheckedCastNullableTToT(result) + : defaultValue; } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof ForMapWithDefault) { ForMapWithDefault that = (ForMapWithDefault) o; - return map.equals(that.map) && Objects.equal(defaultValue, that.defaultValue); + return map.equals(that.map) && Objects.equals(defaultValue, that.defaultValue); } return false; } @Override public int hashCode() { - return Objects.hashCode(map, defaultValue); + return Objects.hash(map, defaultValue); } @Override @@ -205,41 +230,46 @@ public String toString() { return "Functions.forMap(" + map + ", defaultValue=" + defaultValue + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns the composition of two functions. For {@code f: A->B} and {@code g: B->C}, composition * is defined as the function h such that {@code h(a) == g(f(a))} for each {@code a}. * - *

    Java 8 users: use {@code g.compose(f)} or (probably clearer) {@code f.andThen(g)} - * instead. + *

    JRE users and Android users who opt in to library desugaring: use {@code + * g.compose(f)} or (probably clearer) {@code f.andThen(g)} instead. Note that it is not + * serializable. * * @param g the second function to apply * @param f the first function to apply * @return the composition of {@code f} and {@code g} * @see function composition */ - public static Function compose(Function g, Function f) { + public static + Function compose(Function g, Function f) { return new FunctionComposition<>(g, f); } - private static class FunctionComposition implements Function, Serializable { + private static final class FunctionComposition< + A extends @Nullable Object, B extends @Nullable Object, C extends @Nullable Object> + implements Function, Serializable { private final Function g; private final Function f; - public FunctionComposition(Function g, Function f) { + FunctionComposition(Function g, Function f) { this.g = checkNotNull(g); this.f = checkNotNull(f); } @Override - public C apply(@NullableDecl A a) { + @ParametricNullness + public C apply(@ParametricNullness A a) { return g.apply(f.apply(a)); } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof FunctionComposition) { FunctionComposition that = (FunctionComposition) obj; return f.equals(that.f) && g.equals(that.g); @@ -258,7 +288,7 @@ public String toString() { return g + "(" + f + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -267,14 +297,20 @@ public String toString() { *

    The returned function is consistent with equals (as documented at {@link * Function#apply}) if and only if {@code predicate} is itself consistent with equals. * - *

    Java 8 users: use the method reference {@code predicate::test} instead. + *

    Prefer to use the method reference {@code predicate::test} instead. Note that it is not + * serializable unless you explicitly make it {@link Serializable}, typically by writing {@code + * (Function & Serializable) predicate::test}. */ - public static Function forPredicate(Predicate predicate) { - return new PredicateFunction(predicate); + public static Function forPredicate( + Predicate predicate) { + return new PredicateFunction<>(predicate); } - /** @see Functions#forPredicate */ - private static class PredicateFunction implements Function, Serializable { + /** + * @see Functions#forPredicate + */ + private static final class PredicateFunction + implements Function, Serializable { private final Predicate predicate; private PredicateFunction(Predicate predicate) { @@ -282,12 +318,12 @@ private PredicateFunction(Predicate predicate) { } @Override - public Boolean apply(@NullableDecl T t) { + public Boolean apply(@ParametricNullness T t) { return predicate.apply(t); } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof PredicateFunction) { PredicateFunction that = (PredicateFunction) obj; return predicate.equals(that.predicate); @@ -305,38 +341,43 @@ public String toString() { return "Functions.forPredicate(" + predicate + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns a function that ignores its input and always returns {@code value}. * - *

    Java 8 users: use the lambda expression {@code o -> value} instead. + *

    Prefer to use the lambda expression {@code o -> value} instead. Note that it is not + * serializable unless you explicitly make it {@link Serializable}, typically by writing {@code + * (Function & Serializable) o -> value}. * * @param value the constant value for the function to return * @return a function that always returns {@code value} */ - public static Function constant(@NullableDecl E value) { - return new ConstantFunction(value); + public static Function<@Nullable Object, E> constant( + @ParametricNullness E value) { + return new ConstantFunction<>(value); } - private static class ConstantFunction implements Function, Serializable { - @NullableDecl private final E value; + private static final class ConstantFunction + implements Function<@Nullable Object, E>, Serializable { + @ParametricNullness private final E value; - public ConstantFunction(@NullableDecl E value) { + ConstantFunction(@ParametricNullness E value) { this.value = value; } @Override - public E apply(@NullableDecl Object from) { + @ParametricNullness + public E apply(@Nullable Object from) { return value; } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof ConstantFunction) { ConstantFunction that = (ConstantFunction) obj; - return Objects.equal(value, that.value); + return Objects.equals(value, that.value); } return false; } @@ -351,22 +392,29 @@ public String toString() { return "Functions.constant(" + value + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns a function that ignores its input and returns the result of {@code supplier.get()}. * - *

    Java 8 users: use the lambda expression {@code o -> supplier.get()} instead. + *

    Prefer to use the lambda expression {@code o -> supplier.get()} instead. Note that it is not + * serializable unless you explicitly make it {@link Serializable}, typically by writing {@code + * (Function & Serializable) o -> supplier.get()}. * * @since 10.0 */ - public static Function forSupplier(Supplier supplier) { - return new SupplierFunction(supplier); + public static Function forSupplier( + Supplier supplier) { + return new SupplierFunction<>(supplier); } - /** @see Functions#forSupplier */ - private static class SupplierFunction implements Function, Serializable { + /** + * @see Functions#forSupplier + */ + private static final class SupplierFunction< + F extends @Nullable Object, T extends @Nullable Object> + implements Function, Serializable { private final Supplier supplier; @@ -375,14 +423,15 @@ private SupplierFunction(Supplier supplier) { } @Override - public T apply(@NullableDecl Object input) { + @ParametricNullness + public T apply(@ParametricNullness F input) { return supplier.get(); } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof SupplierFunction) { - SupplierFunction that = (SupplierFunction) obj; + SupplierFunction that = (SupplierFunction) obj; return this.supplier.equals(that.supplier); } return false; @@ -398,6 +447,6 @@ public String toString() { return "Functions.forSupplier(" + supplier + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/base/IgnoreJRERequirement.java b/android/guava/src/com/google/common/base/IgnoreJRERequirement.java new file mode 100644 index 000000000000..dbb0ccc5b4b9 --- /dev/null +++ b/android/guava/src/com/google/common/base/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/base/Internal.java b/android/guava/src/com/google/common/base/Internal.java new file mode 100644 index 000000000000..a048d89f9f7c --- /dev/null +++ b/android/guava/src/com/google/common/base/Internal.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.time.Duration; + +/** This class is for {@code com.google.common.base} use only! */ +@J2ktIncompatible +@GwtIncompatible // java.time.Duration +final class Internal { + + /** + * Returns the number of nanoseconds of the given duration without throwing or overflowing. + * + *

    Instead of throwing {@link ArithmeticException}, this method silently saturates to either + * {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE}. This behavior can be useful when decomposing + * a duration in order to call a legacy API which requires a {@code long, TimeUnit} pair. + */ + // We use this method only for cases in which we need to decompose to primitives. + @SuppressWarnings({"GoodTime-ApiWithNumericTimeUnit", "GoodTime-DecomposeToPrimitive"}) + @IgnoreJRERequirement + static long toNanosSaturated(Duration duration) { + // Using a try/catch seems lazy, but the catch block will rarely get invoked (except for + // durations longer than approximately +/- 292 years). + try { + return duration.toNanos(); + } catch (ArithmeticException tooBig) { + return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE; + } + } + + private Internal() {} +} diff --git a/android/guava/src/com/google/common/base/Java8Compatibility.java b/android/guava/src/com/google/common/base/Java8Compatibility.java new file mode 100644 index 000000000000..d3ee13968bc2 --- /dev/null +++ b/android/guava/src/com/google/common/base/Java8Compatibility.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.nio.Buffer; + +/** + * Wrappers around {@link Buffer} methods that are covariantly overridden in Java 9+. See + * https://github.com/google/guava/issues/3990 + */ +@J2ktIncompatible +@GwtIncompatible +final class Java8Compatibility { + static void clear(Buffer b) { + b.clear(); + } + + static void flip(Buffer b) { + b.flip(); + } + + static void limit(Buffer b, int limit) { + b.limit(limit); + } + + static void position(Buffer b, int position) { + b.position(position); + } + + private Java8Compatibility() {} +} diff --git a/android/guava/src/com/google/common/base/JdkPattern.java b/android/guava/src/com/google/common/base/JdkPattern.java index 4788398b7c20..66bf460e8bdc 100644 --- a/android/guava/src/com/google/common/base/JdkPattern.java +++ b/android/guava/src/com/google/common/base/JdkPattern.java @@ -15,12 +15,12 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.regex.Matcher; import java.util.regex.Pattern; /** A regex pattern implementation which is backed by the {@link Pattern}. */ -@ElementTypesAreNonnullByDefault @GwtIncompatible final class JdkPattern extends CommonPattern implements Serializable { private final Pattern pattern; @@ -87,5 +87,5 @@ public int start() { } } - private static final long serialVersionUID = 0; + @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/base/Joiner.java b/android/guava/src/com/google/common/base/Joiner.java index f74f02feba0a..0a2241620c43 100644 --- a/android/guava/src/com/google/common/base/Joiner.java +++ b/android/guava/src/com/google/common/base/Joiner.java @@ -15,28 +15,29 @@ package com.google.common.base; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.util.AbstractList; import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns * them as a {@link String}. Example: * - *

    {@code
    + * {@snippet :
      * Joiner joiner = Joiner.on("; ").skipNulls();
      *  . . .
      * return joiner.join("Harry", null, "Ron", "Hermione");
    - * }
    + * } * *

    This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are * converted to strings using {@link Object#toString()} before being appended. @@ -49,12 +50,12 @@ * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code * static final} constants. * - *

    {@code
    + * {@snippet :
      * // Bad! Do not do this!
      * Joiner joiner = Joiner.on(',');
      * joiner.skipNulls(); // does nothing!
      * return joiner.join("wrong", null, "wrong");
    - * }
    + * } * *

    See the Guava User Guide article on {@code Joiner}. @@ -117,14 +118,17 @@ public A appendTo(A appendable, Iterator parts) throws * separator between each, to {@code appendable}. */ @CanIgnoreReturnValue - public final A appendTo(A appendable, Object[] parts) throws IOException { - return appendTo(appendable, Arrays.asList(parts)); + public final A appendTo(A appendable, @Nullable Object[] parts) + throws IOException { + @SuppressWarnings("nullness") // TODO: b/316358623 - Remove suppression after fixing checker + List partsList = Arrays.<@Nullable Object>asList(parts); + return appendTo(appendable, partsList); } /** Appends to {@code appendable} the string representation of each of the remaining arguments. */ @CanIgnoreReturnValue public final A appendTo( - A appendable, @NullableDecl Object first, @NullableDecl Object second, Object... rest) + A appendable, @Nullable Object first, @Nullable Object second, @Nullable Object... rest) throws IOException { return appendTo(appendable, iterable(first, second, rest)); } @@ -162,8 +166,10 @@ public final StringBuilder appendTo(StringBuilder builder, Iterator parts) { * Iterable)}, except that it does not throw {@link IOException}. */ @CanIgnoreReturnValue - public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { - return appendTo(builder, Arrays.asList(parts)); + public final StringBuilder appendTo(StringBuilder builder, @Nullable Object[] parts) { + @SuppressWarnings("nullness") // TODO: b/316358623 - Remove suppression after fixing checker + List partsList = Arrays.<@Nullable Object>asList(parts); + return appendTo(builder, partsList); } /** @@ -174,9 +180,9 @@ public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { @CanIgnoreReturnValue public final StringBuilder appendTo( StringBuilder builder, - @NullableDecl Object first, - @NullableDecl Object second, - Object... rest) { + @Nullable Object first, + @Nullable Object second, + @Nullable Object... rest) { return appendTo(builder, iterable(first, second, rest)); } @@ -184,10 +190,20 @@ public final StringBuilder appendTo( * Returns a string containing the string representation of each of {@code parts}, using the * previously configured separator between each. */ - public final String join(Iterable parts) { + public String join(Iterable parts) { + // We don't use the same optimization here as in the JRE flavor. + // TODO: b/381289911 - Evaluate the performance impact of doing so. return join(parts.iterator()); } + /* + * TODO: b/381289911 - Make the Iterator overload use StringJoiner (including Android or not)—or + * some other optimization, given that StringJoiner can over-allocate: + * https://bugs.openjdk.org/browse/JDK-8305774 + */ + + // TODO: b/381289911 - Optimize MapJoiner similarly to Joiner (including Android or not). + /** * Returns a string containing the string representation of each of {@code parts}, using the * previously configured separator between each. @@ -202,8 +218,10 @@ public final String join(Iterator parts) { * Returns a string containing the string representation of each of {@code parts}, using the * previously configured separator between each. */ - public final String join(Object[] parts) { - return join(Arrays.asList(parts)); + public final String join(@Nullable Object[] parts) { + @SuppressWarnings("nullness") // TODO: b/316358623 - Remove suppression after fixing checker + List partsList = Arrays.<@Nullable Object>asList(parts); + return join(partsList); } /** @@ -211,7 +229,7 @@ public final String join(Object[] parts) { * configured separator between each. */ public final String join( - @NullableDecl Object first, @NullableDecl Object second, Object... rest) { + @Nullable Object first, @Nullable Object second, @Nullable Object... rest) { return join(iterable(first, second, rest)); } @@ -219,11 +237,11 @@ public final String join( * Returns a joiner with the same behavior as this one, except automatically substituting {@code * nullText} for any provided null elements. */ - public Joiner useForNull(final String nullText) { + public Joiner useForNull(String nullText) { checkNotNull(nullText); return new Joiner(this) { @Override - CharSequence toString(@NullableDecl Object part) { + CharSequence toString(@Nullable Object part) { return (part == null) ? nullText : Joiner.this.toString(part); } @@ -245,6 +263,12 @@ public Joiner skipNulls() { */ public Joiner skipNulls() { return new Joiner(this) { + @Override + @SuppressWarnings("JoinIterableIterator") // suggests infinite recursion + public String join(Iterable parts) { + return join(parts.iterator()); + } + @Override public A appendTo(A appendable, Iterator parts) throws IOException { checkNotNull(appendable, "appendable"); @@ -348,7 +372,6 @@ public StringBuilder appendTo(StringBuilder builder, Map map) { * * @since 10.0 */ - @Beta @CanIgnoreReturnValue public A appendTo(A appendable, Iterable> entries) throws IOException { @@ -361,7 +384,6 @@ public A appendTo(A appendable, Iterable A appendTo(A appendable, Iterator> parts) throws IOException { @@ -389,7 +411,6 @@ public A appendTo(A appendable, Iterator> entries) { return appendTo(builder, entries.iterator()); @@ -402,7 +423,6 @@ public StringBuilder appendTo(StringBuilder builder, Iterable> entries) { try { @@ -427,7 +447,6 @@ public String join(Map map) { * * @since 10.0 */ - @Beta public String join(Iterable> entries) { return join(entries.iterator()); } @@ -438,7 +457,6 @@ public String join(Iterable> entries) { * * @since 11.0 */ - @Beta public String join(Iterator> entries) { return appendTo(new StringBuilder(), entries).toString(); } @@ -452,22 +470,40 @@ public MapJoiner useForNull(String nullText) { } } - CharSequence toString(Object part) { - checkNotNull(part); // checkNotNull for GWT (do not optimize). + // TODO(cpovirk): Rename to "toCharSequence." + CharSequence toString(@Nullable Object part) { + /* + * requireNonNull is not safe: Joiner.on(...).join(somethingThatContainsNull) will indeed throw. + * However, Joiner.on(...).useForNull(...).join(somethingThatContainsNull) *is* safe -- because + * it returns a subclass of Joiner that overrides this method to tolerate null inputs. + * + * Unfortunately, we don't distinguish between these two cases in our public API: Joiner.on(...) + * and Joiner.on(...).useForNull(...) both declare the same return type: plain Joiner. To ensure + * that users *can* pass null arguments to Joiner, we annotate it as if it always tolerates null + * inputs, rather than as if it never tolerates them. + * + * We rely on checkers to implement special cases to catch dangerous calls to join(), etc. based + * on what they know about the particular Joiner instances the calls are performed on. + * + * (In addition to useForNull, we also offer skipNulls. It, too, tolerates null inputs, but its + * tolerance is implemented differently: Its implementation avoids calling this toString(Object) + * method in the first place.) + */ + requireNonNull(part); return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); } - private static Iterable iterable( - final Object first, final Object second, final Object[] rest) { + private static Iterable<@Nullable Object> iterable( + @Nullable Object first, @Nullable Object second, @Nullable Object[] rest) { checkNotNull(rest); - return new AbstractList() { + return new AbstractList<@Nullable Object>() { @Override public int size() { return rest.length + 2; } @Override - public Object get(int index) { + public @Nullable Object get(int index) { switch (index) { case 0: return first; @@ -479,4 +515,23 @@ public Object get(int index) { } }; } + + // cloned from ImmutableCollection + private static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new IllegalArgumentException("cannot store more than Integer.MAX_VALUE elements"); + } else if (minCapacity <= oldCapacity) { + return oldCapacity; + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; + // guaranteed to be >= newCapacity + } + return newCapacity; + } } diff --git a/android/guava/src/com/google/common/base/MoreObjects.java b/android/guava/src/com/google/common/base/MoreObjects.java index a56b2a69def0..064bb5d699ef 100644 --- a/android/guava/src/com/google/common/base/MoreObjects.java +++ b/android/guava/src/com/google/common/base/MoreObjects.java @@ -18,8 +18,11 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.reflect.Array; import java.util.Arrays; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Collection; +import java.util.Map; +import org.jspecify.annotations.Nullable; /** * Helper functions that operate on any {@code Object}, and are not already provided in {@link @@ -54,7 +57,7 @@ public final class MoreObjects { * @throws NullPointerException if both {@code first} and {@code second} are null * @since 18.0 (since 3.0 as {@code Objects.firstNonNull()}). */ - public static T firstNonNull(@NullableDecl T first, @NullableDecl T second) { + public static T firstNonNull(@Nullable T first, @Nullable T second) { if (first != null) { return first; } @@ -69,7 +72,7 @@ public static T firstNonNull(@NullableDecl T first, @NullableDecl T second) * *

    This is helpful for implementing {@link Object#toString()}. Specification by example: * - *

    {@code
    +   * {@snippet :
        * // Returns "ClassName{}"
        * MoreObjects.toStringHelper(this)
        *     .toString();
    @@ -96,7 +99,7 @@ public static  T firstNonNull(@NullableDecl T first, @NullableDecl T second)
        *     .add("x", 1)
        *     .add("y", null)
        *     .toString();
    -   * }
    + * } * *

    Note that in GWT, class names are often obfuscated. * @@ -145,6 +148,7 @@ public static final class ToStringHelper { private final ValueHolder holderHead = new ValueHolder(); private ValueHolder holderTail = holderHead; private boolean omitNullValues = false; + private boolean omitEmptyValues = false; /** Use {@link MoreObjects#toStringHelper(Object)} to create an instance. */ private ToStringHelper(String className) { @@ -164,13 +168,31 @@ public ToStringHelper omitNullValues() { return this; } + /** + * Configures the {@link ToStringHelper} so {@link #toString()} will ignore properties with + * empty values. The order of calling this method, relative to the {@code add()}/{@code + * addValue()} methods, is not significant. + * + *

    Note: in general, code should assume that the string form returned by {@code + * ToStringHelper} for a given object may change. In particular, the list of types which are + * checked for emptiness is subject to change. We currently check {@code CharSequence}s, {@code + * Collection}s, {@code Map}s, optionals (including Guava's), and arrays. + * + * @since 33.4.0 + */ + @CanIgnoreReturnValue + public ToStringHelper omitEmptyValues() { + omitEmptyValues = true; + return this; + } + /** * Adds a name/value pair to the formatted output in {@code name=value} format. If {@code value} * is {@code null}, the string {@code "null"} is used, unless {@link #omitNullValues()} is * called, in which case this name/value pair will not be added. */ @CanIgnoreReturnValue - public ToStringHelper add(String name, @NullableDecl Object value) { + public ToStringHelper add(String name, @Nullable Object value) { return addHolder(name, value); } @@ -181,7 +203,7 @@ public ToStringHelper add(String name, @NullableDecl Object value) { */ @CanIgnoreReturnValue public ToStringHelper add(String name, boolean value) { - return addHolder(name, String.valueOf(value)); + return addUnconditionalHolder(name, String.valueOf(value)); } /** @@ -191,7 +213,7 @@ public ToStringHelper add(String name, boolean value) { */ @CanIgnoreReturnValue public ToStringHelper add(String name, char value) { - return addHolder(name, String.valueOf(value)); + return addUnconditionalHolder(name, String.valueOf(value)); } /** @@ -201,7 +223,7 @@ public ToStringHelper add(String name, char value) { */ @CanIgnoreReturnValue public ToStringHelper add(String name, double value) { - return addHolder(name, String.valueOf(value)); + return addUnconditionalHolder(name, String.valueOf(value)); } /** @@ -211,7 +233,7 @@ public ToStringHelper add(String name, double value) { */ @CanIgnoreReturnValue public ToStringHelper add(String name, float value) { - return addHolder(name, String.valueOf(value)); + return addUnconditionalHolder(name, String.valueOf(value)); } /** @@ -221,7 +243,7 @@ public ToStringHelper add(String name, float value) { */ @CanIgnoreReturnValue public ToStringHelper add(String name, int value) { - return addHolder(name, String.valueOf(value)); + return addUnconditionalHolder(name, String.valueOf(value)); } /** @@ -231,7 +253,7 @@ public ToStringHelper add(String name, int value) { */ @CanIgnoreReturnValue public ToStringHelper add(String name, long value) { - return addHolder(name, String.valueOf(value)); + return addUnconditionalHolder(name, String.valueOf(value)); } /** @@ -241,7 +263,7 @@ public ToStringHelper add(String name, long value) { * readable name. */ @CanIgnoreReturnValue - public ToStringHelper addValue(@NullableDecl Object value) { + public ToStringHelper addValue(@Nullable Object value) { return addHolder(value); } @@ -255,7 +277,7 @@ public ToStringHelper addValue(@NullableDecl Object value) { */ @CanIgnoreReturnValue public ToStringHelper addValue(boolean value) { - return addHolder(String.valueOf(value)); + return addUnconditionalHolder(String.valueOf(value)); } /** @@ -268,7 +290,7 @@ public ToStringHelper addValue(boolean value) { */ @CanIgnoreReturnValue public ToStringHelper addValue(char value) { - return addHolder(String.valueOf(value)); + return addUnconditionalHolder(String.valueOf(value)); } /** @@ -281,7 +303,7 @@ public ToStringHelper addValue(char value) { */ @CanIgnoreReturnValue public ToStringHelper addValue(double value) { - return addHolder(String.valueOf(value)); + return addUnconditionalHolder(String.valueOf(value)); } /** @@ -294,7 +316,7 @@ public ToStringHelper addValue(double value) { */ @CanIgnoreReturnValue public ToStringHelper addValue(float value) { - return addHolder(String.valueOf(value)); + return addUnconditionalHolder(String.valueOf(value)); } /** @@ -307,7 +329,7 @@ public ToStringHelper addValue(float value) { */ @CanIgnoreReturnValue public ToStringHelper addValue(int value) { - return addHolder(String.valueOf(value)); + return addUnconditionalHolder(String.valueOf(value)); } /** @@ -320,7 +342,23 @@ public ToStringHelper addValue(int value) { */ @CanIgnoreReturnValue public ToStringHelper addValue(long value) { - return addHolder(String.valueOf(value)); + return addUnconditionalHolder(String.valueOf(value)); + } + + private static boolean isEmpty(Object value) { + // Put types estimated to be the most frequent first. + if (value instanceof CharSequence) { + return ((CharSequence) value).length() == 0; + } else if (value instanceof Collection) { + return ((Collection) value).isEmpty(); + } else if (value instanceof Map) { + return ((Map) value).isEmpty(); + } else if (value instanceof Optional) { + return !((Optional) value).isPresent(); + } else if (value.getClass().isArray()) { + return Array.getLength(value) == 0; + } + return false; } /** @@ -335,13 +373,17 @@ public ToStringHelper addValue(long value) { public String toString() { // create a copy to keep it consistent in case value changes boolean omitNullValuesSnapshot = omitNullValues; + boolean omitEmptyValuesSnapshot = omitEmptyValues; String nextSeparator = ""; StringBuilder builder = new StringBuilder(32).append(className).append('{'); for (ValueHolder valueHolder = holderHead.next; valueHolder != null; valueHolder = valueHolder.next) { Object value = valueHolder.value; - if (!omitNullValuesSnapshot || value != null) { + if (valueHolder instanceof UnconditionalValueHolder + || (value == null + ? !omitNullValuesSnapshot + : (!omitEmptyValuesSnapshot || !isEmpty(value)))) { builder.append(nextSeparator); nextSeparator = ", "; @@ -366,24 +408,55 @@ private ValueHolder addHolder() { return valueHolder; } - private ToStringHelper addHolder(@NullableDecl Object value) { + @CanIgnoreReturnValue + private ToStringHelper addHolder(@Nullable Object value) { ValueHolder valueHolder = addHolder(); valueHolder.value = value; return this; } - private ToStringHelper addHolder(String name, @NullableDecl Object value) { + @CanIgnoreReturnValue + private ToStringHelper addHolder(String name, @Nullable Object value) { ValueHolder valueHolder = addHolder(); valueHolder.value = value; valueHolder.name = checkNotNull(name); return this; } - private static final class ValueHolder { - @NullableDecl String name; - @NullableDecl Object value; - @NullableDecl ValueHolder next; + private UnconditionalValueHolder addUnconditionalHolder() { + UnconditionalValueHolder valueHolder = new UnconditionalValueHolder(); + holderTail = holderTail.next = valueHolder; + return valueHolder; } + + @CanIgnoreReturnValue + private ToStringHelper addUnconditionalHolder(Object value) { + UnconditionalValueHolder valueHolder = addUnconditionalHolder(); + valueHolder.value = value; + return this; + } + + @CanIgnoreReturnValue + private ToStringHelper addUnconditionalHolder(String name, Object value) { + UnconditionalValueHolder valueHolder = addUnconditionalHolder(); + valueHolder.value = value; + valueHolder.name = checkNotNull(name); + return this; + } + + // Holder object for values that might be null and/or empty. + static class ValueHolder { + @Nullable String name; + @Nullable Object value; + @Nullable ValueHolder next; + } + + /** + * Holder object for values that cannot be null or empty (will be printed unconditionally). This + * helps to shortcut most calls to isEmpty(), which is important because the check for emptiness + * is relatively expensive. Use a subtype so this also doesn't need any extra storage. + */ + private static final class UnconditionalValueHolder extends ValueHolder {} } private MoreObjects() {} diff --git a/android/guava/src/com/google/common/base/NullnessCasts.java b/android/guava/src/com/google/common/base/NullnessCasts.java old mode 100755 new mode 100644 index 1ada6bf26148..e46e8e7ac6fa --- a/android/guava/src/com/google/common/base/NullnessCasts.java +++ b/android/guava/src/com/google/common/base/NullnessCasts.java @@ -15,12 +15,10 @@ package com.google.common.base; import com.google.common.annotations.GwtCompatible; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** A utility method to perform unchecked casts to suppress errors produced by nullness analyses. */ @GwtCompatible -@ElementTypesAreNonnullByDefault final class NullnessCasts { /** * Accepts a {@code @Nullable T} and returns a plain {@code T}, without performing any check that @@ -46,13 +44,15 @@ final class NullnessCasts { * doesn't work: Because nullness analyses typically infer the nullness of local variables, * there's no way to assign a {@code @Nullable T} to a field {@code T foo;} and instruct the * analysis that that means "plain {@code T}" rather than the inferred type {@code @Nullable T}. - * (Even if supported added {@code @NonNull}, that would not help, since the problem case - * addressed by this method is the case in which {@code T} has parametric nullness -- and thus its - * value may be legitimately {@code null}.) + * (And even if annotations on local variables were permitted as an optional hint, no annotation + * would be the right tool for the job here: {@code @Nullable} is the annotation that we're trying + * to get rid of, and {@code @NonNull} would be wrong for our use case for the same reason as + * {@code requireNonNull}: Our use case is the one in which {@code T} has parametric nullness—and + * thus its value may be legitimately {@code null}.) */ @ParametricNullness @SuppressWarnings("nullness") - static T uncheckedCastNullableTToT(@CheckForNull T t) { + static T uncheckedCastNullableTToT(@Nullable T t) { return t; } diff --git a/android/guava/src/com/google/common/base/Objects.java b/android/guava/src/com/google/common/base/Objects.java index bd6b0d94c5f0..33fcb9033524 100644 --- a/android/guava/src/com/google/common/base/Objects.java +++ b/android/guava/src/com/google/common/base/Objects.java @@ -16,8 +16,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Arrays; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Helper functions that can operate on any {@code Object}. @@ -30,9 +29,7 @@ * @since 2.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Objects extends ExtraObjectsMethodsForWeb { - private Objects() {} /** * Determines whether two possibly-null objects are equal. Returns: @@ -47,11 +44,12 @@ private Objects() {} *

    This assumes that any non-null objects passed to this function conform to the {@code * equals()} contract. * - *

    Note for Java 7 and later: This method should be treated as deprecated; use {@link + *

    Note: this method is now unnecessary and should be treated as deprecated; use {@link * java.util.Objects#equals} instead. */ - public static boolean equal(@CheckForNull Object a, @CheckForNull Object b) { - return a == b || (a != null && a.equals(b)); + @SuppressWarnings("InlineMeSuggester") // would introduce fully qualified references to Objects + public static boolean equal(@Nullable Object a, @Nullable Object b) { + return java.util.Objects.equals(a, b); } /** @@ -63,19 +61,22 @@ public static boolean equal(@CheckForNull Object a, @CheckForNull Object b) { *

    This is useful for implementing {@link Object#hashCode()}. For example, in an object that * has three properties, {@code x}, {@code y}, and {@code z}, one could write: * - *

    {@code
    +   * {@snippet :
        * public int hashCode() {
        *   return Objects.hashCode(getX(), getY(), getZ());
        * }
    -   * }
    + * } * *

    Warning: When a single object is supplied, the returned hash code does not equal the * hash code of that object. * - *

    Note for Java 7 and later: This method should be treated as deprecated; use {@link + *

    Note: this method is now unnecessary and should be treated as deprecated; use {@link * java.util.Objects#hash} instead. */ - public static int hashCode(@CheckForNull @Nullable Object... objects) { - return Arrays.hashCode(objects); + @SuppressWarnings("InlineMeSuggester") // would introduce fully qualified references to Objects + public static int hashCode(@Nullable Object @Nullable ... objects) { + return java.util.Objects.hash(objects); } + + private Objects() {} } diff --git a/android/guava/src/com/google/common/base/Optional.java b/android/guava/src/com/google/common/base/Optional.java index 9558699b400f..d5f7f1ee2486 100644 --- a/android/guava/src/com/google/common/base/Optional.java +++ b/android/guava/src/com/google/common/base/Optional.java @@ -16,13 +16,14 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.DoNotMock; import java.io.Serializable; import java.util.Iterator; import java.util.Set; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * An immutable object that may contain a non-null reference to another object. Each instance of @@ -52,6 +53,9 @@ *

    This class is not intended as a direct analogue of any existing "option" or "maybe" construct * from other programming environments, though it may bear some similarities. * + *

    An instance of this class is serializable if its reference is absent or is a serializable + * object. + * *

    Comparison to {@code java.util.Optional} (JDK 8 and higher): A new {@code Optional} * class was added for Java 8. The two classes are extremely similar, but incompatible (they cannot * share a common supertype). All known differences are listed either here or with the @@ -81,8 +85,7 @@ * @since 10.0 */ @DoNotMock("Use Optional.of(value) or Optional.absent()") -@GwtCompatible(serializable = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public abstract class Optional implements Serializable { /** * Returns an {@code Optional} instance with no contained reference. @@ -103,7 +106,7 @@ public static Optional absent() { * @throws NullPointerException if {@code reference} is null */ public static Optional of(T reference) { - return new Present(checkNotNull(reference)); + return new Present<>(checkNotNull(reference)); } /** @@ -113,10 +116,63 @@ public static Optional of(T reference) { *

    Comparison to {@code java.util.Optional}: this method is equivalent to Java 8's * {@code Optional.ofNullable}. */ - public static Optional fromNullable(@CheckForNull T nullableReference) { + public static Optional fromNullable(@Nullable T nullableReference) { return (nullableReference == null) ? Optional.absent() : new Present(nullableReference); } + /** + * Returns the equivalent {@code com.google.common.base.Optional} value to the given {@code + * java.util.Optional}, or {@code null} if the argument is null. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ + @SuppressWarnings("NullableOptional") // Null passthrough is reasonable for type conversions + @IgnoreJRERequirement // Users will use this only if they're already using Optional. + public static @Nullable Optional fromJavaUtil( + java.util.@Nullable Optional javaUtilOptional) { + return (javaUtilOptional == null) ? null : fromNullable(javaUtilOptional.orElse(null)); + } + + /** + * Returns the equivalent {@code java.util.Optional} value to the given {@code + * com.google.common.base.Optional}, or {@code null} if the argument is null. + * + *

    If {@code googleOptional} is known to be non-null, use {@code googleOptional.toJavaUtil()} + * instead. + * + *

    Unfortunately, the method reference {@code Optional::toJavaUtil} will not work, because it + * could refer to either the static or instance version of this method. Write out the lambda + * expression {@code o -> Optional.toJavaUtil(o)} instead. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ + @SuppressWarnings({ + "AmbiguousMethodReference", // We chose the name despite knowing this risk. + "NullableOptional", // Null passthrough is reasonable for type conversions + }) + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Optional calls + @IgnoreJRERequirement + public static java.util.@Nullable Optional toJavaUtil( + @Nullable Optional googleOptional) { + return googleOptional == null ? null : googleOptional.toJavaUtil(); + } + + /** + * Returns the equivalent {@code java.util.Optional} value to this optional. + * + *

    Unfortunately, the method reference {@code Optional::toJavaUtil} will not work, because it + * could refer to either the static or instance version of this method. Write out the lambda + * expression {@code o -> o.toJavaUtil()} instead. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ + @SuppressWarnings("AmbiguousMethodReference") // We chose the name despite knowing this risk. + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Optional calls + @IgnoreJRERequirement + public java.util.Optional toJavaUtil() { + return java.util.Optional.ofNullable(orNull()); + } + Optional() {} /** @@ -131,7 +187,7 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { * {@link #or(Object)} or {@link #orNull} instead. * *

    Comparison to {@code java.util.Optional}: when the value is absent, this method - * throws {@link IllegalStateException}, whereas the Java 8 counterpart throws {@link + * throws {@link IllegalStateException}, whereas the {@code java.util} counterpart throws {@link * java.util.NoSuchElementException NoSuchElementException}. * * @throws IllegalStateException if the instance is absent ({@link #isPresent} returns {@code @@ -149,27 +205,27 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { * restrictive. However, the ideal signature, {@code public S or(S)}, is not legal * Java. As a result, some sensible operations involving subtypes are compile errors: * - *

    {@code
    +   * {@snippet :
        * Optional optionalInt = getSomeOptionalInt();
        * Number value = optionalInt.or(0.5); // error
        *
        * FluentIterable numbers = getSomeNumbers();
        * Optional first = numbers.first();
        * Number value = first.or(0.5); // error
    -   * }
    + * } * *

    As a workaround, it is always safe to cast an {@code Optional} to {@code * Optional}. Casting either of the above example {@code Optional} instances to {@code * Optional} (where {@code Number} is the desired output type) solves the problem: * - *

    {@code
    +   * {@snippet :
        * Optional optionalInt = (Optional) getSomeOptionalInt();
        * Number value = optionalInt.or(0.5); // fine
        *
        * FluentIterable numbers = getSomeNumbers();
        * Optional first = (Optional) numbers.first();
        * Number value = first.or(0.5); // fine
    -   * }
    + * } * *

    Comparison to {@code java.util.Optional}: this method is similar to Java 8's {@code * Optional.orElse}, but will not accept {@code null} as a {@code defaultValue} ({@link #orNull} @@ -192,12 +248,11 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { * *

    Comparison to {@code java.util.Optional}: this method is similar to Java 8's {@code * Optional.orElseGet}, except when {@code supplier} returns {@code null}. In this case this - * method throws an exception, whereas the Java 8 method returns the {@code null} to the caller. + * method throws an exception, whereas the Java 8+ method returns the {@code null} to the caller. * * @throws NullPointerException if this optional's value is absent and the supplier returns {@code * null} */ - @Beta public abstract T or(Supplier supplier); /** @@ -207,8 +262,7 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { *

    Comparison to {@code java.util.Optional}: this method is equivalent to Java 8's * {@code Optional.orElse(null)}. */ - @CheckForNull - public abstract T orNull(); + public abstract @Nullable T orNull(); /** * Returns an immutable singleton {@link Set} whose only element is the contained instance if it @@ -217,17 +271,17 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { *

    Comparison to {@code java.util.Optional}: this method has no equivalent in Java 8's * {@code Optional} class. However, this common usage: * - *

    {@code
    +   * {@snippet :
        * for (Foo foo : possibleFoo.asSet()) {
        *   doSomethingWith(foo);
        * }
    -   * }
    + * } * * ... can be replaced with: * - *
    {@code
    +   * {@snippet :
        * possibleFoo.ifPresent(foo -> doSomethingWith(foo));
    -   * }
    + * } * *

    Java 9 users: some use cases can be written with calls to {@code optional.stream()}. * @@ -241,7 +295,7 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { * *

    Comparison to {@code java.util.Optional}: this method is similar to Java 8's {@code * Optional.map}, except when {@code function} returns {@code null}. In this case this method - * throws an exception, whereas the Java 8 method returns {@code Optional.absent()}. + * throws an exception, whereas the Java 8+ method returns {@code Optional.absent()}. * * @throws NullPointerException if the function returns {@code null} * @since 12.0 @@ -256,13 +310,13 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { *

    Comparison to {@code java.util.Optional}: no differences. */ @Override - public abstract boolean equals(@CheckForNull Object object); + public abstract boolean equals(@Nullable Object object); /** * Returns a hash code for this instance. * *

    Comparison to {@code java.util.Optional}: this class leaves the specific choice of - * hash code unspecified, unlike the Java 8 equivalent. + * hash code unspecified, unlike the Java 8+ equivalent. */ @Override public abstract int hashCode(); @@ -271,7 +325,7 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { * Returns a string representation for this instance. * *

    Comparison to {@code java.util.Optional}: this class leaves the specific string - * representation unspecified, unlike the Java 8 equivalent. + * representation unspecified, unlike the Java 8+ equivalent. */ @Override public abstract String toString(); @@ -289,19 +343,16 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { * * @since 11.0 (generics widened in 13.0) */ - @Beta public static Iterable presentInstances( - final Iterable> optionals) { + Iterable> optionals) { checkNotNull(optionals); - return new Iterable() { - @Override - public Iterator iterator() { - return new AbstractIterator() { + return () -> + new AbstractIterator() { private final Iterator> iterator = checkNotNull(optionals.iterator()); @Override - protected T computeNext() { + protected @Nullable T computeNext() { while (iterator.hasNext()) { Optional optional = iterator.next(); if (optional.isPresent()) { @@ -311,9 +362,7 @@ protected T computeNext() { return endOfData(); } }; - } - }; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/base/PairwiseEquivalence.java b/android/guava/src/com/google/common/base/PairwiseEquivalence.java index cb7d784f016c..0c3bffe106fc 100644 --- a/android/guava/src/com/google/common/base/PairwiseEquivalence.java +++ b/android/guava/src/com/google/common/base/PairwiseEquivalence.java @@ -15,16 +15,18 @@ package com.google.common.base; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; -@GwtCompatible(serializable = true) -final class PairwiseEquivalence extends Equivalence> implements Serializable { +@GwtCompatible +final class PairwiseEquivalence extends Equivalence> + implements Serializable { + final Equivalence elementEquivalence; - final Equivalence elementEquivalence; - - PairwiseEquivalence(Equivalence elementEquivalence) { + PairwiseEquivalence(Equivalence elementEquivalence) { this.elementEquivalence = Preconditions.checkNotNull(elementEquivalence); } @@ -52,9 +54,10 @@ protected int doHash(Iterable iterable) { } @Override - public boolean equals(@NullableDecl Object object) { - if (object instanceof PairwiseEquivalence) { - PairwiseEquivalence that = (PairwiseEquivalence) object; + public boolean equals(@Nullable Object obj) { + if (obj instanceof PairwiseEquivalence) { + @SuppressWarnings("unchecked") + PairwiseEquivalence that = (PairwiseEquivalence) obj; return this.elementEquivalence.equals(that.elementEquivalence); } @@ -71,5 +74,5 @@ public String toString() { return elementEquivalence + ".pairwise()"; } - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } diff --git a/android/guava/src/com/google/common/base/ParametricNullness.java b/android/guava/src/com/google/common/base/ParametricNullness.java old mode 100755 new mode 100644 index c73605548f74..cdec346f42b5 --- a/android/guava/src/com/google/common/base/ParametricNullness.java +++ b/android/guava/src/com/google/common/base/ParametricNullness.java @@ -19,25 +19,54 @@ import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static javax.annotation.meta.When.UNKNOWN; +import static java.lang.annotation.RetentionPolicy.CLASS; import com.google.common.annotations.GwtCompatible; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierNickname; /** - * Marks a "top-level" type-variable usage as (a) a Kotlin platform type when the type argument is - * non-nullable and (b) nullable when the type argument is nullable. This is the closest we can get - * to "non-nullable when non-nullable; nullable when nullable" (like the Android {@code - * NullFromTypeParam}). We use this to "undo" {@link ElementTypesAreNonnullByDefault}. + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. */ @GwtCompatible -@Retention(RUNTIME) +@Retention(CLASS) @Target({FIELD, METHOD, PARAMETER}) -@TypeQualifierNickname -@Nonnull(when = UNKNOWN) @interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/base/PatternCompiler.java b/android/guava/src/com/google/common/base/PatternCompiler.java index 72a45faae963..90a565b1e470 100644 --- a/android/guava/src/com/google/common/base/PatternCompiler.java +++ b/android/guava/src/com/google/common/base/PatternCompiler.java @@ -15,6 +15,7 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; +import com.google.errorprone.annotations.RestrictedApi; /** * Pluggable interface for compiling a regex pattern. By default this package uses the {@code @@ -22,18 +23,23 @@ * java.util.ServiceLoader} mechanism. */ @GwtIncompatible -@ElementTypesAreNonnullByDefault interface PatternCompiler { /** * Compiles the given pattern. * * @throws IllegalArgumentException if the pattern is invalid */ + @RestrictedApi( + explanation = "PatternCompiler is an implementation detail of com.google.common.base", + allowedOnPath = ".*/com/google/common/base/.*") CommonPattern compile(String pattern); /** * Returns {@code true} if the regex implementation behaves like Perl -- notably, by supporting * possessive quantifiers but also being susceptible to catastrophic backtracking. */ + @RestrictedApi( + explanation = "PatternCompiler is an implementation detail of com.google.common.base", + allowedOnPath = ".*/com/google/common/base/.*") boolean isPcreLike(); } diff --git a/android/guava/src/com/google/common/base/Platform.java b/android/guava/src/com/google/common/base/Platform.java index 896e9db83033..1dd43f68459a 100644 --- a/android/guava/src/com/google/common/base/Platform.java +++ b/android/guava/src/com/google/common/base/Platform.java @@ -17,44 +17,44 @@ import com.google.common.annotations.GwtCompatible; import java.lang.ref.WeakReference; import java.util.Locale; -import java.util.ServiceConfigurationError; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Methods factored out so that they can be emulated differently in GWT. * * @author Jesse Wilson */ -@GwtCompatible(emulated = true) +@GwtCompatible final class Platform { - private static final Logger logger = Logger.getLogger(Platform.class.getName()); private static final PatternCompiler patternCompiler = loadPatternCompiler(); private Platform() {} - /** Calls {@link System#nanoTime()}. */ - @SuppressWarnings("GoodTime") // reading system time without TimeSource - static long systemNanoTime() { - return System.nanoTime(); - } - static CharMatcher precomputeCharMatcher(CharMatcher matcher) { return matcher.precomputedInternal(); } static > Optional getEnumIfPresent(Class enumClass, String value) { WeakReference> ref = Enums.getEnumConstants(enumClass).get(value); - return ref == null ? Optional.absent() : Optional.of(enumClass.cast(ref.get())); + /* + * We use `fromNullable` instead of `of` because `WeakReference.get()` has a nullable return + * type. + * + * In practice, we are very unlikely to see `null`: The `WeakReference` to the enum constant + * won't be cleared as long as the enum constant is referenced somewhere, and the enum constant + * is referenced somewhere for as long as the enum class is loaded. *Maybe in theory* the enum + * class could be unloaded after the above call to `getEnumConstants` but before we call + * `get()`, but that is vanishingly unlikely. + */ + return ref == null ? Optional.absent() : Optional.fromNullable(enumClass.cast(ref.get())); } static String formatCompact4Digits(double value) { return String.format(Locale.ROOT, "%.4g", value); } - static boolean stringIsNullOrEmpty(@NullableDecl String string) { + static boolean stringIsNullOrEmpty(@Nullable String string) { return string == null || string.isEmpty(); } @@ -64,7 +64,7 @@ static boolean stringIsNullOrEmpty(@NullableDecl String string) { * @param string the string to test and possibly return * @return {@code string} if it is not null; {@code ""} otherwise */ - static String nullToEmpty(@NullableDecl String string) { + static String nullToEmpty(@Nullable String string) { return (string == null) ? "" : string; } @@ -74,10 +74,18 @@ static String nullToEmpty(@NullableDecl String string) { * @param string the string to test and possibly return * @return {@code string} if it is not empty; {@code null} otherwise */ - static String emptyToNull(@NullableDecl String string) { + static @Nullable String emptyToNull(@Nullable String string) { return stringIsNullOrEmpty(string) ? null : string; } + static String lenientFormat(@Nullable String template, @Nullable Object @Nullable ... args) { + return Strings.lenientFormat(template, args); + } + + static String stringValueOf(@Nullable Object o) { + return String.valueOf(o); + } + static CommonPattern compilePattern(String pattern) { Preconditions.checkNotNull(pattern); return patternCompiler.compile(pattern); @@ -88,18 +96,14 @@ static boolean patternCompilerIsPcreLike() { } private static PatternCompiler loadPatternCompiler() { - /* - * We'd normally use ServiceLoader here, but it hurts Android startup performance. To avoid - * that, we hardcode the JDK Pattern compiler on Android (and, inadvertently, on App Engine and - * in Guava, at least for now). - */ + // We want the JDK Pattern compiler: + // - under Android (where it hurts startup performance) + // - even for the JVM in our open-source release (https://github.com/google/guava/issues/3147) + // If anyone in our monorepo uses the Android copy of Guava on a JVM, that would be unfortunate. + // But that is only likely to happen in Robolectric tests, where the risks of JDK regex are low. return new JdkPatternCompiler(); } - private static void logPatternCompilerError(ServiceConfigurationError e) { - logger.log(Level.WARNING, "Error loading regex compiler, falling back to next option", e); - } - private static final class JdkPatternCompiler implements PatternCompiler { @Override public CommonPattern compile(String pattern) { @@ -111,6 +115,4 @@ public boolean isPcreLike() { return true; } } - - static void checkGwtRpcEnabled() {} } diff --git a/android/guava/src/com/google/common/base/Preconditions.java b/android/guava/src/com/google/common/base/Preconditions.java index 995d3945e59b..62192fb18d3d 100644 --- a/android/guava/src/com/google/common/base/Preconditions.java +++ b/android/guava/src/com/google/common/base/Preconditions.java @@ -18,8 +18,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.checkerframework.checker.nullness.compatqual.NonNullDecl; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static convenience methods that help a method or constructor check whether it was invoked @@ -29,31 +28,31 @@ * of a specified type, which helps the method in which the exception was thrown communicate that * its caller has made a mistake. This allows constructs such as * - *

    {@code
    + * {@snippet :
      * public static double sqrt(double value) {
      *   if (value < 0) {
      *     throw new IllegalArgumentException("input is negative: " + value);
      *   }
      *   // calculate square root
      * }
    - * }
    + * } * *

    to be replaced with the more compact * - *

    {@code
    + * {@snippet :
      * public static double sqrt(double value) {
      *   checkArgument(value >= 0, "input is negative: %s", value);
      *   // calculate square root
      * }
    - * }
    + * } * *

    so that a hypothetical bad caller of this method, such as: * - *

    {@code
    + * {@snippet :
      * void exampleBadCaller() {
      *   double d = sqrt(-1.0);
      * }
    - * }
    + * } * *

    would be flagged as having called {@code sqrt()} with an illegal argument. * @@ -137,9 +136,9 @@ public static void checkArgument(boolean expression) { * string using {@link String#valueOf(Object)} * @throws IllegalArgumentException if {@code expression} is false */ - public static void checkArgument(boolean expression, @NullableDecl Object errorMessage) { + public static void checkArgument(boolean expression, @Nullable Object errorMessage) { if (!expression) { - throw new IllegalArgumentException(String.valueOf(errorMessage)); + throw new IllegalArgumentException(Platform.stringValueOf(errorMessage)); } } @@ -158,10 +157,11 @@ public static void checkArgument(boolean expression, @NullableDecl Object errorM */ public static void checkArgument( boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object... errorMessageArgs) { + String errorMessageTemplate, + @Nullable Object @Nullable ... errorMessageArgs) { if (!expression) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, errorMessageArgs)); + throw new IllegalArgumentException( + Platform.lenientFormat(errorMessageTemplate, errorMessageArgs)); } } @@ -172,8 +172,8 @@ public static void checkArgument( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkArgument(boolean b, @NullableDecl String errorMessageTemplate, char p1) { - if (!b) { + public static void checkArgument(boolean expression, String errorMessageTemplate, char p1) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); } } @@ -185,8 +185,8 @@ public static void checkArgument(boolean b, @NullableDecl String errorMessageTem * * @since 20.0 (varargs overload since 2.0) */ - public static void checkArgument(boolean b, @NullableDecl String errorMessageTemplate, int p1) { - if (!b) { + public static void checkArgument(boolean expression, String errorMessageTemplate, int p1) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); } } @@ -198,8 +198,8 @@ public static void checkArgument(boolean b, @NullableDecl String errorMessageTem * * @since 20.0 (varargs overload since 2.0) */ - public static void checkArgument(boolean b, @NullableDecl String errorMessageTemplate, long p1) { - if (!b) { + public static void checkArgument(boolean expression, String errorMessageTemplate, long p1) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); } } @@ -212,9 +212,9 @@ public static void checkArgument(boolean b, @NullableDecl String errorMessageTem * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); + boolean expression, String errorMessageTemplate, @Nullable Object p1) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1)); } } @@ -226,8 +226,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, char p1, char p2) { - if (!b) { + boolean expression, String errorMessageTemplate, char p1, char p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -240,8 +240,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, char p1, int p2) { - if (!b) { + boolean expression, String errorMessageTemplate, char p1, int p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -254,8 +254,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, char p1, long p2) { - if (!b) { + boolean expression, String errorMessageTemplate, char p1, long p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -268,9 +268,9 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, char p1, @NullableDecl Object p2) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, char p1, @Nullable Object p2) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -282,8 +282,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, int p1, char p2) { - if (!b) { + boolean expression, String errorMessageTemplate, int p1, char p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -296,8 +296,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, int p1, int p2) { - if (!b) { + boolean expression, String errorMessageTemplate, int p1, int p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -310,8 +310,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, int p1, long p2) { - if (!b) { + boolean expression, String errorMessageTemplate, int p1, long p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -324,9 +324,9 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, int p1, @NullableDecl Object p2) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, int p1, @Nullable Object p2) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -338,8 +338,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, long p1, char p2) { - if (!b) { + boolean expression, String errorMessageTemplate, long p1, char p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -352,8 +352,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, long p1, int p2) { - if (!b) { + boolean expression, String errorMessageTemplate, long p1, int p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -366,8 +366,8 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, long p1, long p2) { - if (!b) { + boolean expression, String errorMessageTemplate, long p1, long p2) { + if (!expression) { throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -380,9 +380,9 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, long p1, @NullableDecl Object p2) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, long p1, @Nullable Object p2) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -394,9 +394,9 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1, char p2) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, @Nullable Object p1, char p2) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -408,9 +408,9 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1, int p2) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, @Nullable Object p1, int p2) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -422,9 +422,9 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1, long p2) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, @Nullable Object p1, long p2) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -436,12 +436,13 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, + // TODO: cl/604933487 - Make errorMessageTemplate consistently @Nullable across overloads. + @Nullable String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -453,13 +454,13 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2, - @NullableDecl Object p3) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2, p3)); + boolean expression, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3) { + if (!expression) { + throw new IllegalArgumentException(Platform.lenientFormat(errorMessageTemplate, p1, p2, p3)); } } @@ -471,14 +472,15 @@ public static void checkArgument( * @since 20.0 (varargs overload since 2.0) */ public static void checkArgument( - boolean b, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2, - @NullableDecl Object p3, - @NullableDecl Object p4) { - if (!b) { - throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); + boolean expression, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3, + @Nullable Object p4) { + if (!expression) { + throw new IllegalArgumentException( + Platform.lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); } } @@ -506,9 +508,9 @@ public static void checkState(boolean expression) { * @throws IllegalStateException if {@code expression} is false * @see Verify#verify Verify.verify() */ - public static void checkState(boolean expression, @NullableDecl Object errorMessage) { + public static void checkState(boolean expression, @Nullable Object errorMessage) { if (!expression) { - throw new IllegalStateException(String.valueOf(errorMessage)); + throw new IllegalStateException(Platform.stringValueOf(errorMessage)); } } @@ -529,10 +531,19 @@ public static void checkState(boolean expression, @NullableDecl Object errorMess */ public static void checkState( boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object... errorMessageArgs) { + /* + * TODO(cpovirk): Consider removing @Nullable here, as we've done with the other methods' + * errorMessageTemplate parameters: It is unlikely that callers intend for their string + * template to be null (though we do handle that case gracefully at runtime). I've left this + * one as it is because one of our users has defined a wrapper API around Preconditions, + * declaring a checkState method that accepts a possibly null template. So we'd need to update + * that user first. + */ + @Nullable String errorMessageTemplate, + @Nullable Object @Nullable ... errorMessageArgs) { if (!expression) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, errorMessageArgs)); + throw new IllegalStateException( + Platform.lenientFormat(errorMessageTemplate, errorMessageArgs)); } } @@ -544,8 +555,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState(boolean b, @NullableDecl String errorMessageTemplate, char p1) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, char p1) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); } } @@ -558,8 +569,8 @@ public static void checkState(boolean b, @NullableDecl String errorMessageTempla * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState(boolean b, @NullableDecl String errorMessageTemplate, int p1) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, int p1) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); } } @@ -572,8 +583,8 @@ public static void checkState(boolean b, @NullableDecl String errorMessageTempla * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState(boolean b, @NullableDecl String errorMessageTemplate, long p1) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, long p1) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); } } @@ -587,9 +598,9 @@ public static void checkState(boolean b, @NullableDecl String errorMessageTempla * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); + boolean expression, String errorMessageTemplate, @Nullable Object p1) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1)); } } @@ -601,9 +612,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, char p1, char p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, char p1, char p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -616,9 +626,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, char p1, int p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, char p1, int p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -631,9 +640,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, char p1, long p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, char p1, long p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -647,9 +655,9 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, char p1, @NullableDecl Object p2) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, char p1, @Nullable Object p2) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -661,9 +669,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, int p1, char p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, int p1, char p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -676,9 +683,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, int p1, int p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, int p1, int p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -691,9 +697,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, int p1, long p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, int p1, long p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -707,9 +712,9 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, int p1, @NullableDecl Object p2) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, int p1, @Nullable Object p2) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -721,9 +726,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, long p1, char p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, long p1, char p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -736,9 +740,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, long p1, int p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, long p1, int p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -751,9 +754,8 @@ public static void checkState( * * @since 20.0 (varargs overload since 2.0) */ - public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, long p1, long p2) { - if (!b) { + public static void checkState(boolean expression, String errorMessageTemplate, long p1, long p2) { + if (!expression) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -767,9 +769,9 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, long p1, @NullableDecl Object p2) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, long p1, @Nullable Object p2) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -782,9 +784,9 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1, char p2) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, @Nullable Object p1, char p2) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -797,9 +799,9 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1, int p2) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, @Nullable Object p1, int p2) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -812,9 +814,9 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1, long p2) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, @Nullable Object p1, long p2) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -827,12 +829,9 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + boolean expression, String errorMessageTemplate, @Nullable Object p1, @Nullable Object p2) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } } @@ -845,13 +844,13 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2, - @NullableDecl Object p3) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2, p3)); + boolean expression, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2, p3)); } } @@ -864,17 +863,31 @@ public static void checkState( * @since 20.0 (varargs overload since 2.0) */ public static void checkState( - boolean b, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2, - @NullableDecl Object p3, - @NullableDecl Object p4) { - if (!b) { - throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); + boolean expression, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3, + @Nullable Object p4) { + if (!expression) { + throw new IllegalStateException(Platform.lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); } } + /* + * Preconditions.checkNotNull is *intended* for performing eager null checks on parameters that a + * nullness checker can already "prove" are non-null. That means that the first parameter to + * checkNotNull *should* be annotated to require it to be non-null. + * + * However, for a variety of reasons, Google developers have written a ton of code over the past + * decade that assumes that they can use checkNotNull for non-precondition checks. I had hoped to + * take a principled stand on this, but the amount of such code is simply overwhelming. To avoid + * creating a lot of compile errors that users would not find to be informative, we're giving in + * and allowing callers to pass arguments that a nullness checker believes could be null. + * + * We still encourage people to use requireNonNull over checkNotNull for non-precondition checks. + */ + /** * Ensures that an object reference passed as a parameter to the calling method is not null. * @@ -884,8 +897,7 @@ public static void checkState( * @see Verify#verifyNotNull Verify.verifyNotNull() */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull(@NonNullDecl T reference) { + public static T checkNotNull(@Nullable T reference) { if (reference == null) { throw new NullPointerException(); } @@ -903,11 +915,9 @@ public static T checkNotNull(@NonNullDecl T reference) { * @see Verify#verifyNotNull Verify.verifyNotNull() */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T reference, @NullableDecl Object errorMessage) { + public static T checkNotNull(@Nullable T reference, @Nullable Object errorMessage) { if (reference == null) { - throw new NullPointerException(String.valueOf(errorMessage)); + throw new NullPointerException(Platform.stringValueOf(errorMessage)); } return reference; } @@ -928,13 +938,13 @@ public static T checkNotNull( * @see Verify#verifyNotNull Verify.verifyNotNull() */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T reference, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object... errorMessageArgs) { + public static T checkNotNull( + @Nullable T reference, + String errorMessageTemplate, + @Nullable Object @Nullable ... errorMessageArgs) { if (reference == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, errorMessageArgs)); + throw new NullPointerException( + Platform.lenientFormat(errorMessageTemplate, errorMessageArgs)); } return reference; } @@ -947,13 +957,11 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, char p1) { - if (obj == null) { + public static T checkNotNull(@Nullable T reference, String errorMessageTemplate, char p1) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); } - return obj; + return reference; } /** @@ -964,13 +972,11 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, int p1) { - if (obj == null) { + public static T checkNotNull(@Nullable T reference, String errorMessageTemplate, int p1) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); } - return obj; + return reference; } /** @@ -981,13 +987,11 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, long p1) { - if (obj == null) { + public static T checkNotNull(@Nullable T reference, String errorMessageTemplate, long p1) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); } - return obj; + return reference; } /** @@ -998,13 +1002,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, @Nullable Object p1) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1)); } - return obj; + return reference; } /** @@ -1015,13 +1018,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, char p1, char p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, char p1, char p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1032,13 +1034,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, char p1, int p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, char p1, int p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1049,13 +1050,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, char p1, long p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, char p1, long p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1066,16 +1066,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - char p1, - @NullableDecl Object p2) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, char p1, @Nullable Object p2) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1086,13 +1082,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, int p1, char p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, int p1, char p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1103,13 +1098,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, int p1, int p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, int p1, int p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1120,13 +1114,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, int p1, long p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, int p1, long p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1137,16 +1130,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - int p1, - @NullableDecl Object p2) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, int p1, @Nullable Object p2) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1157,13 +1146,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, long p1, char p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, long p1, char p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1174,13 +1162,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, long p1, int p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, long p1, int p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1191,13 +1178,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, @NullableDecl String errorMessageTemplate, long p1, long p2) { - if (obj == null) { + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, long p1, long p2) { + if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1208,16 +1194,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - long p1, - @NullableDecl Object p2) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, long p1, @Nullable Object p2) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1228,16 +1210,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - char p2) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, @Nullable Object p1, char p2) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1248,16 +1226,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - int p2) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, @Nullable Object p1, int p2) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1268,16 +1242,12 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - long p2) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + public static T checkNotNull( + @Nullable T reference, String errorMessageTemplate, @Nullable Object p1, long p2) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1288,16 +1258,15 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + public static T checkNotNull( + @Nullable T reference, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2)); } - return obj; + return reference; } /** @@ -1308,17 +1277,16 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2, - @NullableDecl Object p3) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2, p3)); + public static T checkNotNull( + @Nullable T reference, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2, p3)); } - return obj; + return reference; } /** @@ -1329,18 +1297,17 @@ public static T checkNotNull( * @since 20.0 (varargs overload since 2.0) */ @CanIgnoreReturnValue - @NonNullDecl - public static T checkNotNull( - @NonNullDecl T obj, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2, - @NullableDecl Object p3, - @NullableDecl Object p4) { - if (obj == null) { - throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); + public static T checkNotNull( + @Nullable T reference, + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3, + @Nullable Object p4) { + if (reference == null) { + throw new NullPointerException(Platform.lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); } - return obj; + return reference; } /* @@ -1396,7 +1363,7 @@ public static int checkElementIndex(int index, int size) { * @throws IllegalArgumentException if {@code size} is negative */ @CanIgnoreReturnValue - public static int checkElementIndex(int index, int size, @NullableDecl String desc) { + public static int checkElementIndex(int index, int size, String desc) { // Carefully optimized for execution by hotspot (explanatory comment above) if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); @@ -1404,7 +1371,7 @@ public static int checkElementIndex(int index, int size, @NullableDecl String de return index; } - private static String badElementIndex(int index, int size, @NullableDecl String desc) { + private static String badElementIndex(int index, int size, String desc) { if (index < 0) { return lenientFormat("%s (%s) must not be negative", desc, index); } else if (size < 0) { @@ -1418,6 +1385,10 @@ private static String badElementIndex(int index, int size, @NullableDecl String * Ensures that {@code index} specifies a valid position in an array, list or string of * size {@code size}. A position index may range from zero to {@code size}, inclusive. * + *

    Java 9 users: consider using {@link java.util.Objects#checkIndex(index, size)} + * instead. However, note that {@code checkIndex()} throws {@code IndexOutOfBoundsException} when + * {@code size} is negative, while this method throws {@code IllegalArgumentException}. + * * @param index a user-supplied index identifying a position in an array, list or string * @param size the size of that array, list or string * @return the value of {@code index} @@ -1441,7 +1412,7 @@ public static int checkPositionIndex(int index, int size) { * @throws IllegalArgumentException if {@code size} is negative */ @CanIgnoreReturnValue - public static int checkPositionIndex(int index, int size, @NullableDecl String desc) { + public static int checkPositionIndex(int index, int size, String desc) { // Carefully optimized for execution by hotspot (explanatory comment above) if (index < 0 || index > size) { throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); @@ -1449,7 +1420,7 @@ public static int checkPositionIndex(int index, int size, @NullableDecl String d return index; } - private static String badPositionIndex(int index, int size, @NullableDecl String desc) { + private static String badPositionIndex(int index, int size, String desc) { if (index < 0) { return lenientFormat("%s (%s) must not be negative", desc, index); } else if (size < 0) { diff --git a/android/guava/src/com/google/common/base/Predicate.java b/android/guava/src/com/google/common/base/Predicate.java index 35d57a6018a2..eadfbb2a68a1 100644 --- a/android/guava/src/com/google/common/base/Predicate.java +++ b/android/guava/src/com/google/common/base/Predicate.java @@ -15,9 +15,7 @@ package com.google.common.base; import com.google.common.annotations.GwtCompatible; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Determines a true or false value for a given input; a pre-Java-8 version of {@link @@ -46,10 +44,9 @@ * @since 2.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public interface Predicate { /** - * Returns the result of applying this predicate to {@code input} (Java 8 users, see notes in the + * Returns the result of applying this predicate to {@code input} (Java 8+ users, see notes in the * class documentation above). This method is generally expected, but not absolutely * required, to have the following properties: * @@ -63,19 +60,18 @@ public interface Predicate { * @throws NullPointerException if {@code input} is null and this predicate does not accept null * arguments */ - @CanIgnoreReturnValue boolean apply(@ParametricNullness T input); /** * Indicates whether another object is equal to this predicate. * - *

    Most implementations will have no reason to override the behavior of {@link Object#equals}. - * However, an implementation may also choose to return {@code true} whenever {@code object} is a - * {@link Predicate} that it considers interchangeable with this one. "Interchangeable" - * typically means that {@code this.apply(t) == that.apply(t)} for all {@code t} of type - * {@code T}). Note that a {@code false} result from this method does not imply that the - * predicates are known not to be interchangeable. + *

    Warning: do not depend on the behavior of this method. + * + *

    Historically, {@code Predicate} instances in this library have implemented this method to + * recognize certain cases where distinct {@code Predicate} instances would in fact behave + * identically. However, as code migrates to {@code java.util.function}, that behavior will + * disappear. It is best not to depend on it. */ @Override - boolean equals(@CheckForNull Object object); + boolean equals(@Nullable Object obj); } diff --git a/android/guava/src/com/google/common/base/Predicates.java b/android/guava/src/com/google/common/base/Predicates.java index 9c8f1e5ea256..f758408a59bd 100644 --- a/android/guava/src/com/google/common/base/Predicates.java +++ b/android/guava/src/com/google/common/base/Predicates.java @@ -16,16 +16,17 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code Predicate} instances. @@ -38,49 +39,61 @@ * @author Kevin Bourrillion * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Predicates { - private Predicates() {} - - // TODO(kevinb): considering having these implement a VisitablePredicate - // interface which specifies an accept(PredicateVisitor) method. - /** Returns a predicate that always evaluates to {@code true}. */ - @GwtCompatible(serializable = true) - public static Predicate alwaysTrue() { + /** + * Returns a predicate that always evaluates to {@code true}. + * + *

    Discouraged: Prefer using {@code x -> true}, but note that lambdas do not have + * human-readable {@link #toString()} representations and are not serializable. + */ + public static Predicate alwaysTrue() { return ObjectPredicate.ALWAYS_TRUE.withNarrowedType(); } - /** Returns a predicate that always evaluates to {@code false}. */ - @GwtCompatible(serializable = true) - public static Predicate alwaysFalse() { + /** + * Returns a predicate that always evaluates to {@code false}. + * + *

    Discouraged: Prefer using {@code x -> false}, but note that lambdas do not have + * human-readable {@link #toString()} representations and are not serializable. + */ + public static Predicate alwaysFalse() { return ObjectPredicate.ALWAYS_FALSE.withNarrowedType(); } /** * Returns a predicate that evaluates to {@code true} if the object reference being tested is * null. + * + *

    Discouraged: Prefer using either {@code x -> x == null} or {@code Objects::isNull}, + * but note that lambdas and method references do not have human-readable {@link #toString()} + * representations and are not serializable. */ - @GwtCompatible(serializable = true) - public static Predicate isNull() { + public static Predicate isNull() { return ObjectPredicate.IS_NULL.withNarrowedType(); } /** * Returns a predicate that evaluates to {@code true} if the object reference being tested is not * null. + * + *

    Discouraged: Prefer using either {@code x -> x != null} or {@code Objects::nonNull}, + * but note that lambdas and method references do not have human-readable {@link #toString()} + * representations and are not serializable. */ - @GwtCompatible(serializable = true) - public static Predicate notNull() { + public static Predicate notNull() { return ObjectPredicate.NOT_NULL.withNarrowedType(); } /** * Returns a predicate that evaluates to {@code true} if the given predicate evaluates to {@code * false}. + * + *

    Discouraged: Prefer using {@code predicate.negate()}. */ - public static Predicate not(Predicate predicate) { - return new NotPredicate(predicate); + public static Predicate not(Predicate predicate) { + return new NotPredicate<>(predicate); } /** @@ -89,9 +102,12 @@ public static Predicate not(Predicate predicate) { * as soon as a false predicate is found. It defensively copies the iterable passed in, so future * changes to it won't alter the behavior of this predicate. If {@code components} is empty, the * returned predicate will always evaluate to {@code true}. + * + *

    Discouraged: Prefer using {@code first.and(second).and(third).and(...)}. */ - public static Predicate and(Iterable> components) { - return new AndPredicate(defensiveCopy(components)); + public static Predicate and( + Iterable> components) { + return new AndPredicate<>(defensiveCopy(components)); } /** @@ -100,9 +116,11 @@ public static Predicate and(Iterable> comp * as soon as a false predicate is found. It defensively copies the array passed in, so future * changes to it won't alter the behavior of this predicate. If {@code components} is empty, the * returned predicate will always evaluate to {@code true}. + * + *

    Discouraged: Prefer using {@code first.and(second).and(third).and(...)}. */ @SafeVarargs - public static Predicate and(Predicate... components) { + public static Predicate and(Predicate... components) { return new AndPredicate(defensiveCopy(components)); } @@ -110,9 +128,12 @@ public static Predicate and(Predicate... components) { * Returns a predicate that evaluates to {@code true} if both of its components evaluate to {@code * true}. The components are evaluated in order, and evaluation will be "short-circuited" as soon * as a false predicate is found. + * + *

    Discouraged: Prefer using {@code first.and(second)}. */ - public static Predicate and(Predicate first, Predicate second) { - return new AndPredicate(Predicates.asList(checkNotNull(first), checkNotNull(second))); + public static Predicate and( + Predicate first, Predicate second) { + return new AndPredicate<>(Predicates.asList(checkNotNull(first), checkNotNull(second))); } /** @@ -121,9 +142,12 @@ public static Predicate and(Predicate first, PredicateDiscouraged: Prefer using {@code first.or(second).or(third).or(...)}. */ - public static Predicate or(Iterable> components) { - return new OrPredicate(defensiveCopy(components)); + public static Predicate or( + Iterable> components) { + return new OrPredicate<>(defensiveCopy(components)); } /** @@ -132,9 +156,11 @@ public static Predicate or(Iterable> compo * as soon as a true predicate is found. It defensively copies the array passed in, so future * changes to it won't alter the behavior of this predicate. If {@code components} is empty, the * returned predicate will always evaluate to {@code false}. + * + *

    Discouraged: Prefer using {@code first.or(second).or(third).or(...)}. */ @SafeVarargs - public static Predicate or(Predicate... components) { + public static Predicate or(Predicate... components) { return new OrPredicate(defensiveCopy(components)); } @@ -142,17 +168,26 @@ public static Predicate or(Predicate... components) { * Returns a predicate that evaluates to {@code true} if either of its components evaluates to * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited" * as soon as a true predicate is found. + * + *

    Discouraged: Prefer using {@code first.or(second)}. */ - public static Predicate or(Predicate first, Predicate second) { - return new OrPredicate(Predicates.asList(checkNotNull(first), checkNotNull(second))); + public static Predicate or( + Predicate first, Predicate second) { + return new OrPredicate<>(Predicates.asList(checkNotNull(first), checkNotNull(second))); } /** * Returns a predicate that evaluates to {@code true} if the object being tested {@code equals()} * the given target or both are null. + * + *

    Discouraged: Prefer using {@code x -> Objects.equals(x, target)}, but note that + * lambdas do not have human-readable {@link #toString()} representations and are not + * serializable. */ - public static Predicate equalTo(@NullableDecl T target) { - return (target == null) ? Predicates.isNull() : new IsEqualToPredicate(target); + public static Predicate equalTo(@ParametricNullness T target) { + return (target == null) + ? Predicates.isNull() + : new IsEqualToPredicate(target).withNarrowedType(); } /** @@ -167,28 +202,36 @@ public static Predicate equalTo(@NullableDecl T target) { * {@link Predicate#apply}), the returned predicate may not be consistent with equals. For * example, {@code instanceOf(ArrayList.class)} will yield different results for the two equal * instances {@code Lists.newArrayList(1)} and {@code Arrays.asList(1)}. + * + *

    Discouraged: Prefer using {@code clazz::isInstance} or {@code x -> x instanceof + * Clazz}, but note that lambdas do not have human-readable {@link #toString()} representations + * and are not serializable. */ @GwtIncompatible // Class.isInstance - public static Predicate instanceOf(Class clazz) { - return new InstanceOfPredicate(clazz); + public static Predicate instanceOf(Class clazz) { + return new InstanceOfPredicate<>(clazz); } /** * Returns a predicate that evaluates to {@code true} if the class being tested is assignable to * (is a subtype of) {@code clazz}. Example: * - *
    {@code
    +   * {@snippet :
        * List> classes = Arrays.asList(
        *     Object.class, String.class, Number.class, Long.class);
        * return Iterables.filter(classes, subtypeOf(Number.class));
    -   * }
    + * } * * The code above returns an iterable containing {@code Number.class} and {@code Long.class}. * + *

    Discouraged: Prefer using {@code clazz::isAssignableFrom} or {@code x -> + * clazz.isAssignableFrom(x)}, but note that lambdas do not have human-readable {@link + * #toString()} representations and are not serializable. + * * @since 20.0 (since 10.0 under the incorrect name {@code assignableFrom}) */ + @J2ktIncompatible @GwtIncompatible // Class.isAssignableFrom - @Beta public static Predicate> subtypeOf(Class clazz) { return new SubtypeOfPredicate(clazz); } @@ -202,10 +245,28 @@ public static Predicate> subtypeOf(Class clazz) { * helps prevent bugs. This approach doesn't block any potential users since it is always possible * to use {@code Predicates.in()}. * + *

    You may prefer to use a method reference (e.g., {@code target::contains}) instead of this + * method. However, there are some subtle considerations: + * + *

      + *
    • The {@link Predicate} returned by this method is {@link Serializable}. + *
    • The {@link Predicate} returned by this method catches {@link ClassCastException} and + * {@link NullPointerException}. + *
    • Code that chains multiple predicates together (especially negations) may be more readable + * using this method. For example, {@code not(in(target))} is generally more readable than + * {@code not(target::contains)}. + *
    • This method's name conflicts with Kotlin's {@code in} operator. + *
    + * + *

    Discouraged: Prefer using either {@code target::contains} or {@code x -> + * target.contains(x)}, but note that lambdas do not have human-readable {@link #toString()} + * representations and are not serializable. + * * @param target the collection that may contain the function input */ - public static Predicate in(Collection target) { - return new InPredicate(target); + @SuppressWarnings("NoHardKeywords") // We're stuck with the name for compatibility reasons. + public static Predicate in(Collection target) { + return new InPredicate<>(target); } /** @@ -214,7 +275,7 @@ public static Predicate in(Collection target) { * * @return the composition of the provided function and predicate */ - public static Predicate compose( + public static Predicate compose( Predicate predicate, Function function) { return new CompositionPredicate<>(predicate, function); } @@ -246,12 +307,13 @@ public static Predicate contains(Pattern pattern) { // End public API, begin private implementation classes. - // Package private for GWT serialization. - enum ObjectPredicate implements Predicate { - /** @see Predicates#alwaysTrue() */ + private enum ObjectPredicate implements Predicate<@Nullable Object> { + /** + * @see Predicates#alwaysTrue() + */ ALWAYS_TRUE { @Override - public boolean apply(@NullableDecl Object o) { + public boolean apply(@Nullable Object o) { return true; } @@ -260,10 +322,12 @@ public String toString() { return "Predicates.alwaysTrue()"; } }, - /** @see Predicates#alwaysFalse() */ + /** + * @see Predicates#alwaysFalse() + */ ALWAYS_FALSE { @Override - public boolean apply(@NullableDecl Object o) { + public boolean apply(@Nullable Object o) { return false; } @@ -272,10 +336,12 @@ public String toString() { return "Predicates.alwaysFalse()"; } }, - /** @see Predicates#isNull() */ + /** + * @see Predicates#isNull() + */ IS_NULL { @Override - public boolean apply(@NullableDecl Object o) { + public boolean apply(@Nullable Object o) { return o == null; } @@ -284,10 +350,12 @@ public String toString() { return "Predicates.isNull()"; } }, - /** @see Predicates#notNull() */ + /** + * @see Predicates#notNull() + */ NOT_NULL { @Override - public boolean apply(@NullableDecl Object o) { + public boolean apply(@Nullable Object o) { return o != null; } @@ -298,13 +366,16 @@ public String toString() { }; @SuppressWarnings("unchecked") // safe contravariant cast - Predicate withNarrowedType() { + Predicate withNarrowedType() { return (Predicate) this; } } - /** @see Predicates#not(Predicate) */ - private static class NotPredicate implements Predicate, Serializable { + /** + * @see Predicates#not(Predicate) + */ + private static final class NotPredicate + implements Predicate, Serializable { final Predicate predicate; NotPredicate(Predicate predicate) { @@ -312,7 +383,7 @@ private static class NotPredicate implements Predicate, Serializable { } @Override - public boolean apply(@NullableDecl T t) { + public boolean apply(@ParametricNullness T t) { return !predicate.apply(t); } @@ -322,7 +393,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof NotPredicate) { NotPredicate that = (NotPredicate) obj; return predicate.equals(that.predicate); @@ -335,11 +406,14 @@ public String toString() { return "Predicates.not(" + predicate + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** @see Predicates#and(Iterable) */ - private static class AndPredicate implements Predicate, Serializable { + /** + * @see Predicates#and(Iterable) + */ + private static final class AndPredicate + implements Predicate, Serializable { private final List> components; private AndPredicate(List> components) { @@ -347,7 +421,7 @@ private AndPredicate(List> components) { } @Override - public boolean apply(@NullableDecl T t) { + public boolean apply(@ParametricNullness T t) { // Avoid using the Iterator to avoid generating garbage (issue 820). for (int i = 0; i < components.size(); i++) { if (!components.get(i).apply(t)) { @@ -364,7 +438,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof AndPredicate) { AndPredicate that = (AndPredicate) obj; return components.equals(that.components); @@ -377,11 +451,14 @@ public String toString() { return toStringHelper("and", components); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** @see Predicates#or(Iterable) */ - private static class OrPredicate implements Predicate, Serializable { + /** + * @see Predicates#or(Iterable) + */ + private static final class OrPredicate + implements Predicate, Serializable { private final List> components; private OrPredicate(List> components) { @@ -389,7 +466,7 @@ private OrPredicate(List> components) { } @Override - public boolean apply(@NullableDecl T t) { + public boolean apply(@ParametricNullness T t) { // Avoid using the Iterator to avoid generating garbage (issue 820). for (int i = 0; i < components.size(); i++) { if (components.get(i).apply(t)) { @@ -406,7 +483,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof OrPredicate) { OrPredicate that = (OrPredicate) obj; return components.equals(that.components); @@ -419,7 +496,7 @@ public String toString() { return toStringHelper("or", components); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } private static String toStringHelper(String methodName, Iterable components) { @@ -435,17 +512,20 @@ private static String toStringHelper(String methodName, Iterable components) return builder.append(')').toString(); } - /** @see Predicates#equalTo(Object) */ - private static class IsEqualToPredicate implements Predicate, Serializable { - private final T target; + /** + * @see Predicates#equalTo(Object) + */ + private static final class IsEqualToPredicate + implements Predicate<@Nullable Object>, Serializable { + private final Object target; - private IsEqualToPredicate(T target) { + private IsEqualToPredicate(Object target) { this.target = target; } @Override - public boolean apply(T t) { - return target.equals(t); + public boolean apply(@Nullable Object o) { + return target.equals(o); } @Override @@ -454,9 +534,9 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof IsEqualToPredicate) { - IsEqualToPredicate that = (IsEqualToPredicate) obj; + IsEqualToPredicate that = (IsEqualToPredicate) obj; return target.equals(that.target); } return false; @@ -467,12 +547,20 @@ public String toString() { return "Predicates.equalTo(" + target + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; + + @SuppressWarnings("unchecked") // safe contravariant cast + Predicate withNarrowedType() { + return (Predicate) this; + } } - /** @see Predicates#instanceOf(Class) */ + /** + * @see Predicates#instanceOf(Class) + */ @GwtIncompatible // Class.isInstance - private static class InstanceOfPredicate implements Predicate, Serializable { + private static final class InstanceOfPredicate + implements Predicate, Serializable { private final Class clazz; private InstanceOfPredicate(Class clazz) { @@ -480,7 +568,7 @@ private InstanceOfPredicate(Class clazz) { } @Override - public boolean apply(@NullableDecl Object o) { + public boolean apply(@ParametricNullness T o) { return clazz.isInstance(o); } @@ -490,9 +578,9 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof InstanceOfPredicate) { - InstanceOfPredicate that = (InstanceOfPredicate) obj; + InstanceOfPredicate that = (InstanceOfPredicate) obj; return clazz == that.clazz; } return false; @@ -503,12 +591,15 @@ public String toString() { return "Predicates.instanceOf(" + clazz.getName() + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** @see Predicates#subtypeOf(Class) */ + /** + * @see Predicates#subtypeOf(Class) + */ + @J2ktIncompatible @GwtIncompatible // Class.isAssignableFrom - private static class SubtypeOfPredicate implements Predicate>, Serializable { + private static final class SubtypeOfPredicate implements Predicate>, Serializable { private final Class clazz; private SubtypeOfPredicate(Class clazz) { @@ -526,7 +617,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof SubtypeOfPredicate) { SubtypeOfPredicate that = (SubtypeOfPredicate) obj; return clazz == that.clazz; @@ -539,11 +630,14 @@ public String toString() { return "Predicates.subtypeOf(" + clazz.getName() + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** @see Predicates#in(Collection) */ - private static class InPredicate implements Predicate, Serializable { + /** + * @see Predicates#in(Collection) + */ + private static final class InPredicate + implements Predicate, Serializable { private final Collection target; private InPredicate(Collection target) { @@ -551,7 +645,7 @@ private InPredicate(Collection target) { } @Override - public boolean apply(@NullableDecl T t) { + public boolean apply(@ParametricNullness T t) { try { return target.contains(t); } catch (NullPointerException | ClassCastException e) { @@ -560,7 +654,13 @@ public boolean apply(@NullableDecl T t) { } @Override - public boolean equals(@NullableDecl Object obj) { + /* + * We should probably not have implemented equals() at all, but given that we did, we can't + * provide a better implementation than the input Collection, at least without dramatic changes + * like copying it to a new Set—which might then test for element equality differently. + */ + @SuppressWarnings("UndefinedEquals") + public boolean equals(@Nullable Object obj) { if (obj instanceof InPredicate) { InPredicate that = (InPredicate) obj; return target.equals(that.target); @@ -578,11 +678,15 @@ public String toString() { return "Predicates.in(" + target + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** @see Predicates#compose(Predicate, Function) */ - private static class CompositionPredicate implements Predicate, Serializable { + /** + * @see Predicates#compose(Predicate, Function) + */ + private static final class CompositionPredicate< + A extends @Nullable Object, B extends @Nullable Object> + implements Predicate, Serializable { final Predicate p; final Function f; @@ -592,12 +696,12 @@ private CompositionPredicate(Predicate p, Function f) { } @Override - public boolean apply(@NullableDecl A a) { + public boolean apply(@ParametricNullness A a) { return p.apply(f.apply(a)); } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof CompositionPredicate) { CompositionPredicate that = (CompositionPredicate) obj; return f.equals(that.f) && p.equals(that.p); @@ -616,10 +720,12 @@ public String toString() { return p + "(" + f + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** @see Predicates#contains(Pattern) */ + /** + * @see Predicates#contains(Pattern) + */ @GwtIncompatible // Only used by other GWT-incompatible code. private static class ContainsPatternPredicate implements Predicate, Serializable { final CommonPattern pattern; @@ -637,18 +743,17 @@ public boolean apply(CharSequence t) { public int hashCode() { // Pattern uses Object.hashCode, so we have to reach // inside to build a hashCode consistent with equals. - - return Objects.hashCode(pattern.pattern(), pattern.flags()); + return Objects.hash(pattern.pattern(), pattern.flags()); } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof ContainsPatternPredicate) { ContainsPatternPredicate that = (ContainsPatternPredicate) obj; // Pattern uses Object (identity) equality, so we have to reach // inside to compare individual fields. - return Objects.equal(pattern.pattern(), that.pattern.pattern()) + return Objects.equals(pattern.pattern(), that.pattern.pattern()) && pattern.flags() == that.pattern.flags(); } return false; @@ -664,12 +769,14 @@ public String toString() { return "Predicates.contains(" + patternString + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** @see Predicates#containsPattern(String) */ + /** + * @see Predicates#containsPattern(String) + */ @GwtIncompatible // Only used by other GWT-incompatible code. - private static class ContainsPatternFromStringPredicate extends ContainsPatternPredicate { + private static final class ContainsPatternFromStringPredicate extends ContainsPatternPredicate { ContainsPatternFromStringPredicate(String string) { super(Platform.compilePattern(string)); @@ -680,10 +787,10 @@ public String toString() { return "Predicates.containsPattern(" + pattern.pattern() + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static List> asList( + private static List> asList( Predicate first, Predicate second) { // TODO(kevinb): understand why we still get a warning despite @SafeVarargs! return Arrays.>asList(first, second); @@ -694,10 +801,12 @@ private static List defensiveCopy(T... array) { } static List defensiveCopy(Iterable iterable) { - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); for (T element : iterable) { list.add(checkNotNull(element)); } return list; } + + private Predicates() {} } diff --git a/android/guava/src/com/google/common/base/Present.java b/android/guava/src/com/google/common/base/Present.java index d33eb8e38567..3e0b12eb41e9 100644 --- a/android/guava/src/com/google/common/base/Present.java +++ b/android/guava/src/com/google/common/base/Present.java @@ -17,9 +17,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.Collections; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** Implementation of an {@link Optional} containing a reference. */ @GwtCompatible @@ -70,16 +72,16 @@ public Set asSet() { @Override public Optional transform(Function function) { - return new Present( + return new Present<>( checkNotNull( function.apply(reference), "the Function passed to Optional.transform() must not return null.")); } @Override - public boolean equals(@NullableDecl Object object) { - if (object instanceof Present) { - Present other = (Present) object; + public boolean equals(@Nullable Object obj) { + if (obj instanceof Present) { + Present other = (Present) obj; return reference.equals(other.reference); } return false; @@ -95,5 +97,5 @@ public String toString() { return "Optional.of(" + reference + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/base/SmallCharMatcher.java b/android/guava/src/com/google/common/base/SmallCharMatcher.java index f0e801b67118..c92fb7065e99 100644 --- a/android/guava/src/com/google/common/base/SmallCharMatcher.java +++ b/android/guava/src/com/google/common/base/SmallCharMatcher.java @@ -26,7 +26,6 @@ * @author Christopher Swenson */ @GwtIncompatible // no precomputation is done in GWT -@ElementTypesAreNonnullByDefault final class SmallCharMatcher extends NamedFastMatcher { static final int MAX_SIZE = 1023; private final char[] table; @@ -56,7 +55,7 @@ static int smear(int hashCode) { } private boolean checkFilter(int c) { - return 1 == (1 & (filter >> c)); + return ((filter >> c) & 1) == 1; } // This is all essentially copied from ImmutableSet, but we have to duplicate because diff --git a/android/guava/src/com/google/common/base/SneakyThrows.java b/android/guava/src/com/google/common/base/SneakyThrows.java new file mode 100644 index 000000000000..33e2714ce43c --- /dev/null +++ b/android/guava/src/com/google/common/base/SneakyThrows.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +/** Static utility method for unchecked throwing of any {@link Throwable}. */ +@GwtCompatible +final class SneakyThrows { + /** + * Throws {@code t} as if it were an unchecked {@link Throwable}. + * + *

    This method is useful primarily when we make a reflective call to a method with no {@code + * throws} clause: Java forces us to handle an arbitrary {@link Throwable} from that method, + * rather than just the {@link RuntimeException} or {@link Error} that should be possible. (And in + * fact the static type of {@link Throwable} is occasionally justified even for a method with no + * {@code throws} clause: Some such methods can in fact throw a checked exception (e.g., by + * calling code written in Kotlin).) Typically, we want to let a {@link Throwable} from such a + * method propagate untouched, just as we'd typically let it do for a non-reflective call. + * However, we can't usually write {@code throw t;} when {@code t} has a static type of {@link + * Throwable}. But we can write {@code sneakyThrow(t);}. + * + *

    We sometimes also use {@code sneakyThrow} for testing how our code responds to + * sneaky checked exception. + * + * @return never; this method declares a return type of {@link Error} only so that callers can + * write {@code throw sneakyThrow(t);} to convince the compiler that the statement will always + * throw. + */ + @CanIgnoreReturnValue + static Error sneakyThrow(Throwable t) { + throw new SneakyThrows().throwIt(t); + } + + @SuppressWarnings("unchecked") // not really safe, but that's the point + private Error throwIt(Throwable t) throws T { + throw (T) t; + } + + private SneakyThrows() {} +} diff --git a/android/guava/src/com/google/common/base/Splitter.java b/android/guava/src/com/google/common/base/Splitter.java index bde2e0ec42f8..6211cb5ea7da 100644 --- a/android/guava/src/com/google/common/base/Splitter.java +++ b/android/guava/src/com/google/common/base/Splitter.java @@ -17,7 +17,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import java.util.ArrayList; @@ -27,7 +26,9 @@ import java.util.List; import java.util.Map; import java.util.regex.Pattern; -import javax.annotation.CheckForNull; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.jspecify.annotations.Nullable; /** * Extracts non-overlapping substrings from an input string, typically by recognizing appearances of @@ -38,9 +39,9 @@ * *

    For example, this expression: * - *

    {@code
    + * {@snippet :
      * Splitter.on(',').split("foo,bar,qux")
    - * }
    + * } * * ... produces an {@code Iterable} containing {@code "foo"}, {@code "bar"} and {@code "qux"}, in * that order. @@ -48,19 +49,19 @@ *

    By default, {@code Splitter}'s behavior is simplistic and unassuming. The following * expression: * - *

    {@code
    + * {@snippet :
      * Splitter.on(',').split(" foo,,,  bar ,")
    - * }
    + * } * * ... yields the substrings {@code [" foo", "", "", " bar ", ""]}. If this is not the desired * behavior, use configuration methods to obtain a new splitter instance with modified * behavior: * - *
    {@code
    + * {@snippet :
      * private static final Splitter MY_SPLITTER = Splitter.on(',')
      *     .trimResults()
      *     .omitEmptyStrings();
    - * }
    + * } * *

    Now {@code MY_SPLITTER.split("foo,,, bar ,")} returns just {@code ["foo", "bar"]}. Note that * the order in which these configuration methods are called is never significant. @@ -69,12 +70,12 @@ * effect on the receiving instance; you must store and use the new splitter instance it returns * instead. * - *

    {@code
    + * {@snippet :
      * // Do NOT do this
      * Splitter splitter = Splitter.on('/');
      * splitter.trimResults(); // does nothing!
      * return splitter.split("wrong / wrong / wrong");
    - * }
    + * } * *

    For separator-based splitters that do not use {@code omitEmptyStrings}, an input string * containing {@code n} occurrences of the separator naturally yields an iterable of size {@code n + @@ -97,8 +98,7 @@ * @author Louis Wasserman * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Splitter { private final CharMatcher trimmer; private final boolean omitEmptyStrings; @@ -137,14 +137,12 @@ public static Splitter on(char separator) { * separator * @return a splitter, with default settings, that uses this matcher */ - public static Splitter on(final CharMatcher separatorMatcher) { + public static Splitter on(CharMatcher separatorMatcher) { checkNotNull(separatorMatcher); return new Splitter( - new Strategy() { - @Override - public SplittingIterator iterator(Splitter splitter, final CharSequence toSplit) { - return new SplittingIterator(splitter, toSplit) { + (splitter, toSplit) -> + new SplittingIterator(splitter, toSplit) { @Override int separatorStart(int start) { return separatorMatcher.indexIn(toSplit, start); @@ -154,9 +152,7 @@ int separatorStart(int start) { int separatorEnd(int separatorPosition) { return separatorPosition + 1; } - }; - } - }); + }); } /** @@ -167,16 +163,14 @@ int separatorEnd(int separatorPosition) { * @param separator the literal, nonempty string to recognize as a separator * @return a splitter, with default settings, that recognizes that separator */ - public static Splitter on(final String separator) { + public static Splitter on(String separator) { checkArgument(separator.length() != 0, "The separator may not be the empty string."); if (separator.length() == 1) { return Splitter.on(separator.charAt(0)); } return new Splitter( - new Strategy() { - @Override - public SplittingIterator iterator(Splitter splitter, CharSequence toSplit) { - return new SplittingIterator(splitter, toSplit) { + (splitter, toSplit) -> + new SplittingIterator(splitter, toSplit) { @Override public int separatorStart(int start) { int separatorLength = separator.length(); @@ -197,9 +191,7 @@ public int separatorStart(int start) { public int separatorEnd(int separatorPosition) { return separatorPosition + separator.length(); } - }; - } - }); + }); } /** @@ -214,32 +206,30 @@ public int separatorEnd(int separatorPosition) { */ @GwtIncompatible // java.util.regex public static Splitter on(Pattern separatorPattern) { - return on(new JdkPattern(separatorPattern)); + return onPatternInternal(new JdkPattern(separatorPattern)); } - private static Splitter on(final CommonPattern separatorPattern) { + /** Internal utility; see {@link #on(Pattern)} instead. */ + static Splitter onPatternInternal(CommonPattern separatorPattern) { checkArgument( !separatorPattern.matcher("").matches(), "The pattern may not match the empty string: %s", separatorPattern); return new Splitter( - new Strategy() { - @Override - public SplittingIterator iterator(final Splitter splitter, CharSequence toSplit) { - final CommonMatcher matcher = separatorPattern.matcher(toSplit); - return new SplittingIterator(splitter, toSplit) { - @Override - public int separatorStart(int start) { - return matcher.find(start) ? matcher.start() : -1; - } - - @Override - public int separatorEnd(int separatorPosition) { - return matcher.end(); - } - }; - } + (splitter, toSplit) -> { + CommonMatcher matcher = separatorPattern.matcher(toSplit); + return new SplittingIterator(splitter, toSplit) { + @Override + public int separatorStart(int start) { + return matcher.find(start) ? matcher.start() : -1; + } + + @Override + public int separatorEnd(int separatorPosition) { + return matcher.end(); + } + }; }); } @@ -257,7 +247,7 @@ public int separatorEnd(int separatorPosition) { */ @GwtIncompatible // java.util.regex public static Splitter onPattern(String separatorPattern) { - return on(Platform.compilePattern(separatorPattern)); + return onPatternInternal(Platform.compilePattern(separatorPattern)); } /** @@ -278,14 +268,12 @@ public static Splitter onPattern(String separatorPattern) { * @return a splitter, with default settings, that can split into fixed sized pieces * @throws IllegalArgumentException if {@code length} is zero or negative */ - public static Splitter fixedLength(final int length) { + public static Splitter fixedLength(int length) { checkArgument(length > 0, "The length may not be less than 1"); return new Splitter( - new Strategy() { - @Override - public SplittingIterator iterator(final Splitter splitter, CharSequence toSplit) { - return new SplittingIterator(splitter, toSplit) { + (splitter, toSplit) -> + new SplittingIterator(splitter, toSplit) { @Override public int separatorStart(int start) { int nextChunkStart = start + length; @@ -296,9 +284,7 @@ public int separatorStart(int start) { public int separatorEnd(int separatorPosition) { return separatorPosition; } - }; - } - }); + }); } /** @@ -329,7 +315,7 @@ public Splitter omitEmptyStrings() { *

    For example, {@code Splitter.on(',').limit(3).split("a,b,c,d")} returns an iterable * containing {@code ["a", "b", "c,d"]}. When omitting empty strings, the omitted strings do not * count. Hence, {@code Splitter.on(',').limit(3).omitEmptyStrings().split("a,,,b,,,c,d")} returns - * an iterable containing {@code ["a", "b", "c,d"}. When trim is requested, all entries are + * an iterable containing {@code ["a", "b", "c,d"]}. When trim is requested, all entries are * trimmed, including the last. Hence {@code Splitter.on(',').limit(3).trimResults().split(" a , b * , c , d ")} results in {@code ["a", "b", "c , d"]}. * @@ -379,7 +365,7 @@ public Splitter trimResults(CharMatcher trimmer) { * @param sequence the sequence of characters to split * @return an iteration over the segments split from the parameter */ - public Iterable split(final CharSequence sequence) { + public Iterable split(CharSequence sequence) { checkNotNull(sequence); return new Iterable() { @@ -423,13 +409,28 @@ public List splitToList(CharSequence sequence) { return Collections.unmodifiableList(result); } + /** + * Splits {@code sequence} into string components and makes them available through an {@link + * Stream}, which may be lazily evaluated. If you want an eagerly computed {@link List}, use + * {@link #splitToList(CharSequence)}. + * + * @param sequence the sequence of characters to split + * @return a stream over the segments split from the parameter + * @since 33.4.0 (but since 28.2 in the JRE flavor) + */ + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Stream calls. + @IgnoreJRERequirement + public Stream splitToStream(CharSequence sequence) { + // Can't use Streams.stream() from base + return StreamSupport.stream(split(sequence).spliterator(), false); + } + /** * Returns a {@code MapSplitter} which splits entries based on this splitter, and splits entries * into keys and values using the specified separator. * * @since 10.0 */ - @Beta public MapSplitter withKeyValueSeparator(String separator) { return withKeyValueSeparator(on(separator)); } @@ -440,7 +441,6 @@ public MapSplitter withKeyValueSeparator(String separator) { * * @since 14.0 */ - @Beta public MapSplitter withKeyValueSeparator(char separator) { return withKeyValueSeparator(on(separator)); } @@ -454,17 +454,16 @@ public MapSplitter withKeyValueSeparator(char separator) { * *

    Example: * - *

    {@code
    +   * {@snippet :
        * String toSplit = " x -> y, z-> a ";
        * Splitter outerSplitter = Splitter.on(',').trimResults();
        * MapSplitter mapSplitter = outerSplitter.withKeyValueSeparator(Splitter.on("->"));
        * Map result = mapSplitter.split(toSplit);
        * assertThat(result).isEqualTo(ImmutableMap.of("x ", " y", "z", " a"));
    -   * }
    + * } * * @since 10.0 */ - @Beta public MapSplitter withKeyValueSeparator(Splitter keyValueSplitter) { return new MapSplitter(this, keyValueSplitter); } @@ -477,7 +476,6 @@ public MapSplitter withKeyValueSeparator(Splitter keyValueSplitter) { * * @since 10.0 */ - @Beta public static final class MapSplitter { private static final String INVALID_ENTRY_MESSAGE = "Chunk [%s] is not a valid entry"; private final Splitter outerSplitter; @@ -542,16 +540,15 @@ private abstract static class SplittingIterator extends AbstractIterator int offset = 0; int limit; - protected SplittingIterator(Splitter splitter, CharSequence toSplit) { + SplittingIterator(Splitter splitter, CharSequence toSplit) { this.trimmer = splitter.trimmer; this.omitEmptyStrings = splitter.omitEmptyStrings; this.limit = splitter.limit; this.toSplit = toSplit; } - @CheckForNull @Override - protected String computeNext() { + protected @Nullable String computeNext() { /* * The returned string will be from the end of the last match to the beginning of the next * one. nextStart is the start position of the returned substring, while offset is the place diff --git a/android/guava/src/com/google/common/base/StandardSystemProperty.java b/android/guava/src/com/google/common/base/StandardSystemProperty.java index dc29792de782..af3082d783b6 100644 --- a/android/guava/src/com/google/common/base/StandardSystemProperty.java +++ b/android/guava/src/com/google/common/base/StandardSystemProperty.java @@ -15,7 +15,7 @@ package com.google.common.base; import com.google.common.annotations.GwtIncompatible; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Represents a {@linkplain System#getProperties() standard system property}. @@ -24,7 +24,6 @@ * @since 15.0 */ @GwtIncompatible // java.lang.System#getProperty -@ElementTypesAreNonnullByDefault public enum StandardSystemProperty { /** Java Runtime Environment version. */ @@ -125,7 +124,7 @@ public enum StandardSystemProperty { this.key = key; } - /** Returns the key used to lookup this system property. */ + /** Returns the key used to look up this system property. */ public String key() { return key; } @@ -153,8 +152,7 @@ public String key() { *
  • {@code jdk.module.*} (added in Java 9, optional) * */ - @CheckForNull - public String value() { + public @Nullable String value() { return System.getProperty(key); } diff --git a/android/guava/src/com/google/common/base/Stopwatch.java b/android/guava/src/com/google/common/base/Stopwatch.java index 052200b41430..b954d7b583a6 100644 --- a/android/guava/src/com/google/common/base/Stopwatch.java +++ b/android/guava/src/com/google/common/base/Stopwatch.java @@ -25,7 +25,11 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.time.Duration; import java.util.concurrent.TimeUnit; /** @@ -33,12 +37,11 @@ * successive readings of "now" in the same process. * *

    In contrast, wall time is a reading of "now" as given by a method like - * {@link System#currentTimeMillis()}, best represented as an {@link Instant}. Such values - * - *

    can be subtracted to obtain a {@code Duration} (such as by {@code Duration.between}), - * but doing so does not give a reliable measurement of elapsed time, because wall time - * readings are inherently approximate, routinely affected by periodic clock corrections. Because - * this class (by default) uses {@link System#nanoTime}, it is unaffected by these changes. + * {@link System#currentTimeMillis()}, best represented as an {@link java.time.Instant}. Such values + * can be subtracted to obtain a {@link Duration} (such as by {@link Duration#between}), but + * doing so does not give a reliable measurement of elapsed time, because wall time readings + * are inherently approximate, routinely affected by periodic clock corrections. Because this class + * (by default) uses {@link System#nanoTime}, it is unaffected by these changes. * *

    Use this class instead of direct calls to {@link System#nanoTime} for two reasons: * @@ -49,17 +52,23 @@ * performance reasons, without affecting most of your code. * * + *

    The one downside of {@code Stopwatch} relative to {@link System#nanoTime()} is that {@code + * Stopwatch} requires object allocation and additional method calls, which can reduce the accuracy + * of the elapsed times reported. {@code Stopwatch} is still suitable for logging and metrics where + * reasonably accurate values are sufficient. If the uncommon case that you need to maximize + * accuracy, use {@code System.nanoTime()} directly instead. + * *

    Basic usage: * - *

    {@code
    + * {@snippet :
      * Stopwatch stopwatch = Stopwatch.createStarted();
      * doSomething();
      * stopwatch.stop(); // optional
      *
    - * long millis = stopwatch.elapsed(MILLISECONDS);
    + * Duration duration = stopwatch.elapsed();
      *
      * log.info("time: " + stopwatch); // formatted string like "12.3 ms"
    - * }
    + * } * *

    The state-changing methods are not idempotent; it is an error to start or stop a stopwatch * that is already in the desired state. @@ -73,21 +82,20 @@ *

    Warning for Android users: a stopwatch with default behavior may not continue to keep * time while the device is asleep. Instead, create one like this: * - *

    {@code
    + * {@snippet :
      * Stopwatch.createStarted(
      *      new Ticker() {
      *        public long read() {
      *          return android.os.SystemClock.elapsedRealtimeNanos();
      *        }
      *      });
    - * }
    + * } * * @author Kevin Bourrillion * @since 10.0 */ @GwtCompatible @SuppressWarnings("GoodTime") // lots of violations -@ElementTypesAreNonnullByDefault public final class Stopwatch { private final Ticker ticker; private boolean isRunning; @@ -196,8 +204,12 @@ private long elapsedNanos() { * Returns the current elapsed time shown on this stopwatch, expressed in the desired time unit, * with any fraction rounded down. * - *

    Note that the overhead of measurement can be more than a microsecond, so it is generally not - * useful to specify {@link TimeUnit#NANOSECONDS} precision here. + *

    Note: the overhead of measurement can be more than a microsecond, so it is generally + * not useful to specify {@link TimeUnit#NANOSECONDS} precision here. + * + *

    It is generally not a good idea to use an ambiguous, unitless {@code long} to represent + * elapsed time. Therefore, we recommend using {@link #elapsed()} instead, which returns a + * strongly-typed {@link Duration} instance. * * @since 14.0 (since 10.0 as {@code elapsedTime()}) */ @@ -205,6 +217,26 @@ public long elapsed(TimeUnit desiredUnit) { return desiredUnit.convert(elapsedNanos(), NANOSECONDS); } + /** + * Returns the current elapsed time shown on this stopwatch as a {@link Duration}. Unlike {@link + * #elapsed(TimeUnit)}, this method does not lose any precision due to rounding. + * + *

    Warning: do not call this method from Android code unless you are on Android API + * level 26+ or you opt in to + * library desugaring. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Duration calls + @IgnoreJRERequirement + @J2ktIncompatible + @GwtIncompatible + @J2ObjCIncompatible + public Duration elapsed() { + return Duration.ofNanos(elapsedNanos()); + } + /** Returns a string representation of the current elapsed time. */ @Override public String toString() { @@ -255,8 +287,7 @@ private static String abbreviate(TimeUnit unit) { return "h"; case DAYS: return "d"; - default: - throw new AssertionError(); } + throw new AssertionError(); } } diff --git a/android/guava/src/com/google/common/base/Strings.java b/android/guava/src/com/google/common/base/Strings.java index fa3626648d10..feb5915ca2f8 100644 --- a/android/guava/src/com/google/common/base/Strings.java +++ b/android/guava/src/com/google/common/base/Strings.java @@ -16,13 +16,13 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.Math.min; import static java.util.logging.Level.WARNING; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.VisibleForTesting; import java.util.logging.Logger; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code String} or {@code CharSequence} instances. @@ -31,7 +31,6 @@ * @since 3.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Strings { private Strings() {} @@ -41,7 +40,7 @@ private Strings() {} * @param string the string to test and possibly return * @return {@code string} itself if it is non-null; {@code ""} if it is null */ - public static String nullToEmpty(@CheckForNull String string) { + public static String nullToEmpty(@Nullable String string) { return Platform.nullToEmpty(string); } @@ -51,8 +50,7 @@ public static String nullToEmpty(@CheckForNull String string) { * @param string the string to test and possibly return * @return {@code string} itself if it is nonempty; {@code null} if it is empty or null */ - @CheckForNull - public static String emptyToNull(@CheckForNull String string) { + public static @Nullable String emptyToNull(@Nullable String string) { return Platform.emptyToNull(string); } @@ -67,7 +65,7 @@ public static String emptyToNull(@CheckForNull String string) { * @param string a string reference to check * @return {@code true} if the string is null or is the empty string */ - public static boolean isNullOrEmpty(@CheckForNull String string) { + public static boolean isNullOrEmpty(@Nullable String string) { return Platform.stringIsNullOrEmpty(string); } @@ -152,14 +150,14 @@ public static String repeat(String string, int count) { } // IF YOU MODIFY THE CODE HERE, you must update StringsRepeatBenchmark - final int len = string.length(); - final long longSize = (long) len * (long) count; - final int size = (int) longSize; + int len = string.length(); + long longSize = (long) len * (long) count; + int size = (int) longSize; if (size != longSize) { throw new ArrayIndexOutOfBoundsException("Required array size too large: " + longSize); } - final char[] array = new char[size]; + char[] array = new char[size]; string.getChars(0, len, array, 0); int n; for (n = len; n < size - n; n <<= 1) { @@ -180,7 +178,7 @@ public static String commonPrefix(CharSequence a, CharSequence b) { checkNotNull(a); checkNotNull(b); - int maxPrefixLength = Math.min(a.length(), b.length()); + int maxPrefixLength = min(a.length(), b.length()); int p = 0; while (p < maxPrefixLength && a.charAt(p) == b.charAt(p)) { p++; @@ -202,7 +200,7 @@ public static String commonSuffix(CharSequence a, CharSequence b) { checkNotNull(a); checkNotNull(b); - int maxSuffixLength = Math.min(a.length(), b.length()); + int maxSuffixLength = min(a.length(), b.length()); int s = 0; while (s < maxSuffixLength && a.charAt(a.length() - s - 1) == b.charAt(b.length() - s - 1)) { s++; @@ -260,15 +258,11 @@ static boolean validSurrogatePairAt(CharSequence string, int index) { */ // TODO(diamondm) consider using Arrays.toString() for array parameters public static String lenientFormat( - @CheckForNull String template, @CheckForNull @Nullable Object... args) { + @Nullable String template, @Nullable Object @Nullable ... args) { template = String.valueOf(template); // null -> "null" if (args == null) { args = new Object[] {"(Object[])null"}; - } else { - for (int i = 0; i < args.length; i++) { - args[i] = lenientToString(args[i]); - } } // start substituting the arguments into the '%s' placeholders @@ -281,18 +275,18 @@ public static String lenientFormat( break; } builder.append(template, templateStart, placeholderStart); - builder.append(args[i++]); + builder.append(lenientToString(args[i++])); templateStart = placeholderStart + 2; } builder.append(template, templateStart, template.length()); - // if we run out of placeholders, append the extra args in square braces + // if we run out of placeholders, append the extra args in square brackets if (i < args.length) { - builder.append(" ["); - builder.append(args[i++]); - while (i < args.length) { - builder.append(", "); - builder.append(args[i++]); + String prefix = " ["; + for (; i < args.length; i++) { + builder.append(prefix); + builder.append(lenientToString(args[i])); + prefix = ", "; } builder.append(']'); } @@ -300,13 +294,14 @@ public static String lenientFormat( return builder.toString(); } - private static String lenientToString(@CheckForNull Object o) { + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception + private static String lenientToString(@Nullable Object o) { if (o == null) { return "null"; } try { return o.toString(); - } catch (Exception e) { + } catch (Exception e) { // sneaky checked exception // Default toString() behavior - see Object.toString() String objectToString = o.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(o)); diff --git a/android/guava/src/com/google/common/base/Supplier.java b/android/guava/src/com/google/common/base/Supplier.java index f9e1e34f17a0..8041ad156d8f 100644 --- a/android/guava/src/com/google/common/base/Supplier.java +++ b/android/guava/src/com/google/common/base/Supplier.java @@ -15,8 +15,7 @@ package com.google.common.base; import com.google.common.annotations.GwtCompatible; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A class that can supply objects of a single type; a pre-Java-8 version of {@link @@ -46,7 +45,6 @@ * @since 2.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public interface Supplier { /** * Retrieves an instance of the appropriate type. The returned object may or may not be a new @@ -54,7 +52,20 @@ public interface Supplier { * * @return an instance of the appropriate type */ - @CanIgnoreReturnValue @ParametricNullness T get(); + + /** + * May return {@code true} if {@code object} is a {@code Supplier} that behaves identically + * to this supplier. + * + *

    Warning: do not depend on the behavior of this method. + * + *

    Historically, {@code Supplier} instances in this library have implemented this method to + * recognize certain cases where distinct {@code Supplier} instances would in fact behave + * identically. However, as code migrates to {@code java.util.function}, that behavior will + * disappear. It is best not to depend on it. + */ + @Override + boolean equals(@Nullable Object object); } diff --git a/android/guava/src/com/google/common/base/Suppliers.java b/android/guava/src/com/google/common/base/Suppliers.java index da1490da2d7c..7426d71b01da 100644 --- a/android/guava/src/com/google/common/base/Suppliers.java +++ b/android/guava/src/com/google/common/base/Suppliers.java @@ -14,14 +14,22 @@ package com.google.common.base; +import static com.google.common.base.Internal.toNanosSaturated; +import static com.google.common.base.NullnessCasts.uncheckedCastNullableTToT; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.io.ObjectInputStream; import java.io.Serializable; +import java.time.Duration; +import java.util.Objects; import java.util.concurrent.TimeUnit; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Useful suppliers. @@ -42,11 +50,14 @@ private Suppliers() {} * and then applying {@code function} to that value. Note that the resulting supplier will not * call {@code supplier} or invoke {@code function} until it is called. */ - public static Supplier compose(Function function, Supplier supplier) { + public static Supplier compose( + Function function, Supplier supplier) { return new SupplierComposition<>(function, supplier); } - private static class SupplierComposition implements Supplier, Serializable { + private static final class SupplierComposition< + F extends @Nullable Object, T extends @Nullable Object> + implements Supplier, Serializable { final Function function; final Supplier supplier; @@ -56,12 +67,13 @@ private static class SupplierComposition implements Supplier, Serializa } @Override + @ParametricNullness public T get() { return function.apply(supplier.get()); } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof SupplierComposition) { SupplierComposition that = (SupplierComposition) obj; return function.equals(that.function) && supplier.equals(that.supplier); @@ -71,7 +83,7 @@ public boolean equals(@NullableDecl Object obj) { @Override public int hashCode() { - return Objects.hashCode(function, supplier); + return Objects.hash(function, supplier); } @Override @@ -79,7 +91,7 @@ public String toString() { return "Suppliers.compose(" + function + ", " + supplier + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -90,7 +102,7 @@ public String toString() { *

    The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at * most once unless the underlying {@code get()} throws an exception. The supplier's serialized * form does not contain the cached value, which will be recalculated when {@code get()} is called - * on the reserialized instance. + * on the deserialized instance. * *

    When the underlying delegate throws an exception then this memoizing supplier will keep * delegating calls until it returns valid data. @@ -98,7 +110,7 @@ public String toString() { *

    If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is * returned directly. */ - public static Supplier memoize(Supplier delegate) { + public static Supplier memoize(Supplier delegate) { if (delegate instanceof NonSerializableMemoizingSupplier || delegate instanceof MemoizingSupplier) { return delegate; @@ -109,22 +121,28 @@ public static Supplier memoize(Supplier delegate) { } @VisibleForTesting - static class MemoizingSupplier implements Supplier, Serializable { + static final class MemoizingSupplier + implements Supplier, Serializable { + private transient Object lock = new Object(); + final Supplier delegate; transient volatile boolean initialized; // "value" does not need to be volatile; visibility piggy-backs // on volatile read of "initialized". - @NullableDecl transient T value; + transient @Nullable T value; MemoizingSupplier(Supplier delegate) { this.delegate = checkNotNull(delegate); } @Override + @ParametricNullness + // We set the field only once (during construction or deserialization). + @SuppressWarnings("SynchronizeOnNonFinalField") public T get() { // A 2-field variant of Double Checked Locking. if (!initialized) { - synchronized (this) { + synchronized (lock) { if (!initialized) { T t = delegate.get(); value = t; @@ -133,7 +151,8 @@ public T get() { } } } - return value; + // This is safe because we checked `initialized`. + return uncheckedCastNullableTToT(value); } @Override @@ -143,44 +162,60 @@ public String toString() { + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + lock = new Object(); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - @VisibleForTesting - static class NonSerializableMemoizingSupplier implements Supplier { - volatile Supplier delegate; - volatile boolean initialized; - // "value" does not need to be volatile; visibility piggy-backs - // on volatile read of "initialized". - @NullableDecl T value; + private static final class NonSerializableMemoizingSupplier + implements Supplier { + private final Object lock = new Object(); + + @SuppressWarnings("UnnecessaryLambda") // Must be a fixed singleton object + private static final Supplier<@Nullable Void> SUCCESSFULLY_COMPUTED = + () -> { + throw new IllegalStateException(); // Should never get called. + }; + + private volatile Supplier delegate; + // "value" does not need to be volatile; visibility piggy-backs on volatile read of "delegate". + private @Nullable T value; NonSerializableMemoizingSupplier(Supplier delegate) { this.delegate = checkNotNull(delegate); } @Override + @ParametricNullness + @SuppressWarnings("unchecked") // Cast from Supplier to Supplier is always valid public T get() { - // A 2-field variant of Double Checked Locking. - if (!initialized) { - synchronized (this) { - if (!initialized) { + // Because Supplier is read-heavy, we use the "double-checked locking" pattern. + if (delegate != SUCCESSFULLY_COMPUTED) { + synchronized (lock) { + if (delegate != SUCCESSFULLY_COMPUTED) { T t = delegate.get(); value = t; - initialized = true; - // Release the delegate to GC. - delegate = null; + delegate = (Supplier) SUCCESSFULLY_COMPUTED; return t; } } } - return value; + // This is safe because we checked `delegate`. + return uncheckedCastNullableTToT(value); } @Override public String toString() { Supplier delegate = this.delegate; return "Suppliers.memoize(" - + (delegate == null ? "" : delegate) + + (delegate == SUCCESSFULLY_COMPUTED + ? "" + : delegate) + ")"; } } @@ -206,28 +241,67 @@ public String toString() { * @throws IllegalArgumentException if {@code duration} is not positive * @since 2.0 */ - @SuppressWarnings("GoodTime") // should accept a java.time.Duration - public static Supplier memoizeWithExpiration( + @SuppressWarnings("GoodTime") // Prefer the Duration overload + public static Supplier memoizeWithExpiration( Supplier delegate, long duration, TimeUnit unit) { - return new ExpiringMemoizingSupplier(delegate, duration, unit); + checkNotNull(delegate); + checkArgument(duration > 0, "duration (%s %s) must be > 0", duration, unit); + return new ExpiringMemoizingSupplier<>(delegate, unit.toNanos(duration)); + } + + /** + * Returns a supplier that caches the instance supplied by the delegate and removes the cached + * value after the specified time has passed. Subsequent calls to {@code get()} return the cached + * value if the expiration time has not passed. After the expiration time, a new value is + * retrieved, cached, and returned. See: memoization + * + *

    The returned supplier is thread-safe. The supplier's serialized form does not contain the + * cached value, which will be recalculated when {@code get()} is called on the reserialized + * instance. The actual memoization does not happen when the underlying delegate throws an + * exception. + * + *

    When the underlying delegate throws an exception then this memoizing supplier will keep + * delegating calls until it returns valid data. + * + * @param duration the length of time after a value is created that it should stop being returned + * by subsequent {@code get()} calls + * @throws IllegalArgumentException if {@code duration} is not positive + * @since 33.1.0 + */ + @J2ktIncompatible + @GwtIncompatible // java.time.Duration + @IgnoreJRERequirement + public static Supplier memoizeWithExpiration( + Supplier delegate, Duration duration) { + checkNotNull(delegate); + // The alternative of `duration.compareTo(Duration.ZERO) > 0` causes J2ObjC trouble. + checkArgument( + !duration.isNegative() && !duration.isZero(), "duration (%s) must be > 0", duration); + return new ExpiringMemoizingSupplier<>(delegate, toNanosSaturated(duration)); } @VisibleForTesting @SuppressWarnings("GoodTime") // lots of violations - static class ExpiringMemoizingSupplier implements Supplier, Serializable { + static final class ExpiringMemoizingSupplier + implements Supplier, Serializable { + private transient Object lock = new Object(); + final Supplier delegate; final long durationNanos; - @NullableDecl transient volatile T value; + transient volatile @Nullable T value; // The special value 0 means "not yet initialized". transient volatile long expirationNanos; - ExpiringMemoizingSupplier(Supplier delegate, long duration, TimeUnit unit) { - this.delegate = checkNotNull(delegate); - this.durationNanos = unit.toNanos(duration); - checkArgument(duration > 0, "duration (%s %s) must be > 0", duration, unit); + ExpiringMemoizingSupplier(Supplier delegate, long durationNanos) { + this.delegate = delegate; + this.durationNanos = durationNanos; } @Override + @ParametricNullness + // We set the field only once (during construction or deserialization). + @SuppressWarnings("SynchronizeOnNonFinalField") public T get() { // Another variant of Double Checked Locking. // @@ -236,9 +310,9 @@ public T get() { // the extra memory consumption and indirection are more // expensive than the extra volatile reads. long nanos = expirationNanos; - long now = Platform.systemNanoTime(); + long now = System.nanoTime(); if (nanos == 0 || now - nanos >= 0) { - synchronized (this) { + synchronized (lock) { if (nanos == expirationNanos) { // recheck for lost race T t = delegate.get(); value = t; @@ -250,7 +324,8 @@ public T get() { } } } - return value; + // This is safe because we checked `expirationNanos`. + return uncheckedCastNullableTToT(value); } @Override @@ -260,38 +335,54 @@ public String toString() { return "Suppliers.memoizeWithExpiration(" + delegate + ", " + durationNanos + ", NANOS)"; } - private static final long serialVersionUID = 0; + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + lock = new Object(); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** Returns a supplier that always supplies {@code instance}. */ - public static Supplier ofInstance(@NullableDecl T instance) { - return new SupplierOfInstance(instance); + /** + * Returns a supplier that always supplies {@code instance}. + * + *

    Discouraged: Prefer using {@code () -> instance}, but note that lambdas do not have + * human-readable {@link #toString()} representations and are not serializable. If you need a + * supplier that is serializable, use {@code (Supplier & Serializable) () -> instance}. + */ + public static Supplier ofInstance( + @ParametricNullness T instance) { + return new SupplierOfInstance<>(instance); } - private static class SupplierOfInstance implements Supplier, Serializable { - @NullableDecl final T instance; + private static final class SupplierOfInstance + implements Supplier, Serializable { + @ParametricNullness final T instance; - SupplierOfInstance(@NullableDecl T instance) { + SupplierOfInstance(@ParametricNullness T instance) { this.instance = instance; } @Override + @ParametricNullness public T get() { return instance; } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof SupplierOfInstance) { SupplierOfInstance that = (SupplierOfInstance) obj; - return Objects.equal(instance, that.instance); + return Objects.equals(instance, that.instance); } return false; } @Override public int hashCode() { - return Objects.hashCode(instance); + return Objects.hash(instance); } @Override @@ -299,18 +390,22 @@ public String toString() { return "Suppliers.ofInstance(" + instance + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns a supplier whose {@code get()} method synchronizes on {@code delegate} before calling * it, making it thread-safe. */ - public static Supplier synchronizedSupplier(Supplier delegate) { - return new ThreadSafeSupplier(delegate); + @J2ktIncompatible + public static Supplier synchronizedSupplier( + Supplier delegate) { + return new ThreadSafeSupplier<>(delegate); } - private static class ThreadSafeSupplier implements Supplier, Serializable { + @J2ktIncompatible + private static final class ThreadSafeSupplier + implements Supplier, Serializable { final Supplier delegate; ThreadSafeSupplier(Supplier delegate) { @@ -318,6 +413,7 @@ private static class ThreadSafeSupplier implements Supplier, Serializable } @Override + @ParametricNullness public T get() { synchronized (delegate) { return delegate.get(); @@ -329,31 +425,33 @@ public String toString() { return "Suppliers.synchronizedSupplier(" + delegate + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns a function that accepts a supplier and returns the result of invoking {@link * Supplier#get} on that supplier. * - *

    Java 8 users: use the method reference {@code Supplier::get} instead. + *

    Prefer to use the method reference {@code Supplier::get} instead, though note that it is not + * serializable unless you explicitly make it {@link Serializable}, typically by writing {@code + * (Function, T> & Serializable) Supplier::get}. * * @since 8.0 */ - public static Function, T> supplierFunction() { + public static Function, T> supplierFunction() { @SuppressWarnings("unchecked") // implementation is "fully variant" SupplierFunction sf = (SupplierFunction) SupplierFunctionImpl.INSTANCE; return sf; } - private interface SupplierFunction extends Function, T> {} + private interface SupplierFunction extends Function, T> {} - private enum SupplierFunctionImpl implements SupplierFunction { + private enum SupplierFunctionImpl implements SupplierFunction<@Nullable Object> { INSTANCE; // Note: This makes T a "pass-through type" @Override - public Object apply(Supplier input) { + public @Nullable Object apply(Supplier<@Nullable Object> input) { return input.get(); } diff --git a/android/guava/src/com/google/common/base/Throwables.java b/android/guava/src/com/google/common/base/Throwables.java index c72a8c93b71d..f8b4ae47f00c 100644 --- a/android/guava/src/com/google/common/base/Throwables.java +++ b/android/guava/src/com/google/common/base/Throwables.java @@ -19,9 +19,9 @@ import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; @@ -33,7 +33,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to instances of {@link Throwable}. @@ -45,8 +45,7 @@ * @author Ben Yu * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Throwables { private Throwables() {} @@ -99,9 +98,10 @@ public static void throwIfInstanceOf( * null}. */ @Deprecated + @J2ktIncompatible @GwtIncompatible // throwIfInstanceOf public static void propagateIfInstanceOf( - @CheckForNull Throwable throwable, Class declaredType) throws X { + @Nullable Throwable throwable, Class declaredType) throws X { if (throwable != null) { throwIfInstanceOf(throwable, declaredType); } @@ -138,25 +138,15 @@ public static void throwIfUnchecked(Throwable throwable) { /** * Propagates {@code throwable} exactly as-is, if and only if it is an instance of {@link - * RuntimeException} or {@link Error}. Example usage: - * - *
    -   * try {
    -   *   someMethodThatCouldThrowAnything();
    -   * } catch (IKnowWhatToDoWithThisException e) {
    -   *   handle(e);
    -   * } catch (Throwable t) {
    -   *   Throwables.propagateIfPossible(t);
    -   *   throw new RuntimeException("unexpected", t);
    -   * }
    -   * 
    + * RuntimeException} or {@link Error}. * * @deprecated Use {@link #throwIfUnchecked}, which has the same behavior but rejects {@code * null}. */ @Deprecated + @J2ktIncompatible @GwtIncompatible - public static void propagateIfPossible(@CheckForNull Throwable throwable) { + public static void propagateIfPossible(@Nullable Throwable throwable) { if (throwable != null) { throwIfUnchecked(throwable); } @@ -164,43 +154,41 @@ public static void propagateIfPossible(@CheckForNull Throwable throwable) { /** * Propagates {@code throwable} exactly as-is, if and only if it is an instance of {@link - * RuntimeException}, {@link Error}, or {@code declaredType}. Example usage: + * RuntimeException}, {@link Error}, or {@code declaredType}. * - *
    -   * try {
    -   *   someMethodThatCouldThrowAnything();
    -   * } catch (IKnowWhatToDoWithThisException e) {
    -   *   handle(e);
    -   * } catch (Throwable t) {
    -   *   Throwables.propagateIfPossible(t, OtherException.class);
    -   *   throw new RuntimeException("unexpected", t);
    -   * }
    -   * 
    + *

    Discouraged in favor of calling {@link #throwIfInstanceOf} and {@link + * #throwIfUnchecked}. * * @param throwable the Throwable to possibly propagate * @param declaredType the single checked exception type declared by the calling method + * @deprecated Use a combination of {@link #throwIfInstanceOf} and {@link #throwIfUnchecked}, + * which togther provide the same behavior except that they reject {@code null}. */ + @Deprecated + @J2ktIncompatible @GwtIncompatible // propagateIfInstanceOf public static void propagateIfPossible( - @CheckForNull Throwable throwable, Class declaredType) throws X { + @Nullable Throwable throwable, Class declaredType) throws X { propagateIfInstanceOf(throwable, declaredType); propagateIfPossible(throwable); } /** * Propagates {@code throwable} exactly as-is, if and only if it is an instance of {@link - * RuntimeException}, {@link Error}, {@code declaredType1}, or {@code declaredType2}. In the - * unlikely case that you have three or more declared checked exception types, you can handle them - * all by invoking these methods repeatedly. See usage example in {@link - * #propagateIfPossible(Throwable, Class)}. + * RuntimeException}, {@link Error}, {@code declaredType1}, or {@code declaredType2}. * * @param throwable the Throwable to possibly propagate * @param declaredType1 any checked exception type declared by the calling method * @param declaredType2 any other checked exception type declared by the calling method + * @deprecated Use a combination of two calls to {@link #throwIfInstanceOf} and one call to {@link + * #throwIfUnchecked}, which togther provide the same behavior except that they reject {@code + * null}. */ + @Deprecated + @J2ktIncompatible @GwtIncompatible // propagateIfInstanceOf public static void propagateIfPossible( - @CheckForNull Throwable throwable, Class declaredType1, Class declaredType2) + @Nullable Throwable throwable, Class declaredType1, Class declaredType2) throws X1, X2 { checkNotNull(declaredType2); propagateIfInstanceOf(throwable, declaredType1); @@ -230,12 +218,15 @@ public static void propagateIfPossi * @param throwable the Throwable to propagate * @return nothing will ever be returned; this return type is only for your convenience, as * illustrated in the example above - * @deprecated Use {@code throw e} or {@code throw new RuntimeException(e)} directly, or use a - * combination of {@link #throwIfUnchecked} and {@code throw new RuntimeException(e)}. For - * background on the deprecation, read Why we deprecated - * {@code Throwables.propagate}. + * @deprecated To preserve behavior, use {@code throw e} or {@code throw new RuntimeException(e)} + * directly, or use a combination of {@link #throwIfUnchecked} and {@code throw new + * RuntimeException(e)}. But consider whether users would be better off if your API threw a + * different type of exception. For background on the deprecation, read Why we + * deprecated {@code Throwables.propagate}. */ @CanIgnoreReturnValue + @J2ktIncompatible @GwtIncompatible @Deprecated public static RuntimeException propagate(Throwable throwable) { @@ -290,7 +281,6 @@ public static Throwable getRootCause(Throwable throwable) { * @return an unmodifiable list containing the cause chain starting with {@code throwable} * @throws IllegalArgumentException if there is a loop in the causal chain */ - @Beta // TODO(kevinb): decide best return type public static List getCausalChain(Throwable throwable) { checkNotNull(throwable); List causes = new ArrayList<>(4); @@ -330,11 +320,8 @@ public static List getCausalChain(Throwable throwable) { * ClassCastException}'s cause is {@code throwable}. * @since 22.0 */ - @Beta @GwtIncompatible // Class.cast(Object) - @SuppressWarnings("nullness") - // TODO(cpovirk): Add @CheckForNull after updating callers. - public static X getCauseAs( + public static @Nullable X getCauseAs( Throwable throwable, Class expectedCauseType) { try { return expectedCauseType.cast(throwable.getCause()); @@ -383,11 +370,13 @@ public static String getStackTraceAsString(Throwable throwable) { * exception's creation. * * @since 19.0 + * @deprecated This method is equivalent to {@link Throwable#getStackTrace()} on JDK versions past + * JDK 8 and on all Android versions. Use {@link Throwable#getStackTrace()} directly, or where + * possible use the {@code java.lang.StackWalker.walk} method introduced in JDK 9. */ - // TODO(cpovirk): Say something about the possibility that List access could fail at runtime? - @Beta + @Deprecated + @J2ktIncompatible @GwtIncompatible // lazyStackTraceIsLazy, jlaStackTrace - // TODO(cpovirk): Consider making this available under GWT (slow implementation only). public static List lazyStackTrace(Throwable throwable) { return lazyStackTraceIsLazy() ? jlaStackTrace(throwable) @@ -399,15 +388,19 @@ public static List lazyStackTrace(Throwable throwable) { * documentation. * * @since 19.0 + * @deprecated This method always returns false on JDK versions past JDK 8 and on all Android + * versions. */ - @Beta + @Deprecated + @J2ktIncompatible @GwtIncompatible // getStackTraceElementMethod public static boolean lazyStackTraceIsLazy() { return getStackTraceElementMethod != null && getStackTraceDepthMethod != null; } + @J2ktIncompatible @GwtIncompatible // invokeAccessibleNonThrowingMethod - private static List jlaStackTrace(final Throwable t) { + private static List jlaStackTrace(Throwable t) { checkNotNull(t); /* * TODO(cpovirk): Consider optimizing iterator() to catch IOOBE instead of doing bounds checks. @@ -436,6 +429,7 @@ public int size() { }; } + @J2ktIncompatible @GwtIncompatible // java.lang.reflect private static Object invokeAccessibleNonThrowingMethod( Method method, Object receiver, Object... params) { @@ -449,42 +443,43 @@ private static Object invokeAccessibleNonThrowingMethod( } /** JavaLangAccess class name to load using reflection */ - @GwtIncompatible // not used by GWT emulation + @J2ktIncompatible @GwtIncompatible // not used by GWT emulation private static final String JAVA_LANG_ACCESS_CLASSNAME = "sun.misc.JavaLangAccess"; /** SharedSecrets class name to load using reflection */ + @J2ktIncompatible @GwtIncompatible // not used by GWT emulation @VisibleForTesting static final String SHARED_SECRETS_CLASSNAME = "sun.misc.SharedSecrets"; /** Access to some fancy internal JVM internals. */ - @GwtIncompatible // java.lang.reflect - @CheckForNull - private static final Object jla = getJLA(); + @J2ktIncompatible @GwtIncompatible // java.lang.reflect + private static final @Nullable Object jla = getJla(); /** * The "getStackTraceElementMethod" method, only available on some JDKs so we use reflection to * find it when available. When this is null, use the slow way. */ - @GwtIncompatible // java.lang.reflect - @CheckForNull - private static final Method getStackTraceElementMethod = (jla == null) ? null : getGetMethod(); + @J2ktIncompatible @GwtIncompatible // java.lang.reflect + private static final @Nullable Method getStackTraceElementMethod = + (jla == null) ? null : getGetMethod(); /** * The "getStackTraceDepth" method, only available on some JDKs so we use reflection to find it * when available. When this is null, use the slow way. */ - @GwtIncompatible // java.lang.reflect - @CheckForNull - private static final Method getStackTraceDepthMethod = (jla == null) ? null : getSizeMethod(jla); + @J2ktIncompatible @GwtIncompatible // java.lang.reflect + private static final @Nullable Method getStackTraceDepthMethod = + (jla == null) ? null : getSizeMethod(jla); /** * Returns the JavaLangAccess class that is present in all Sun JDKs. It is not allowed in * AppEngine, and not present in non-Sun JDKs. */ + @SuppressWarnings("removal") // b/318391980 + @J2ktIncompatible @GwtIncompatible // java.lang.reflect - @CheckForNull - private static Object getJLA() { + private static @Nullable Object getJla() { try { /* * We load sun.misc.* classes using reflection since Android doesn't support these classes and @@ -508,24 +503,24 @@ private static Object getJLA() { * Returns the Method that can be used to resolve an individual StackTraceElement, or null if that * method cannot be found (it is only to be found in fairly recent JDKs). */ + @J2ktIncompatible @GwtIncompatible // java.lang.reflect - @CheckForNull - private static Method getGetMethod() { + private static @Nullable Method getGetMethod() { return getJlaMethod("getStackTraceElement", Throwable.class, int.class); } /** * Returns the Method that can be used to return the size of a stack, or null if that method * cannot be found (it is only to be found in fairly recent JDKs). Tries to test method {@link - * sun.misc.JavaLangAccess#getStackTraceDepth(Throwable)} getStackTraceDepth} prior to return it + * sun.misc.JavaLangAccess#getStackTraceDepth(Throwable) getStackTraceDepth} prior to return it * (might fail some JDKs). * *

    See Throwables#lazyStackTrace throws * UnsupportedOperationException. */ + @J2ktIncompatible @GwtIncompatible // java.lang.reflect - @CheckForNull - private static Method getSizeMethod(Object jla) { + private static @Nullable Method getSizeMethod(Object jla) { try { Method getStackTraceDepth = getJlaMethod("getStackTraceDepth", Throwable.class); if (getStackTraceDepth == null) { @@ -538,9 +533,11 @@ private static Method getSizeMethod(Object jla) { } } + @SuppressWarnings("removal") // b/318391980 + @J2ktIncompatible @GwtIncompatible // java.lang.reflect - @CheckForNull - private static Method getJlaMethod(String name, Class... parameterTypes) throws ThreadDeath { + private static @Nullable Method getJlaMethod(String name, Class... parameterTypes) + throws ThreadDeath { try { return Class.forName(JAVA_LANG_ACCESS_CLASSNAME, false, null).getMethod(name, parameterTypes); } catch (ThreadDeath death) { diff --git a/android/guava/src/com/google/common/base/Ticker.java b/android/guava/src/com/google/common/base/Ticker.java index d898735c028f..e327a4cc907d 100644 --- a/android/guava/src/com/google/common/base/Ticker.java +++ b/android/guava/src/com/google/common/base/Ticker.java @@ -28,7 +28,6 @@ * source-compatible since 9.0) */ @GwtCompatible -@ElementTypesAreNonnullByDefault public abstract class Ticker { /** Constructor for use by subclasses. */ protected Ticker() {} @@ -48,8 +47,9 @@ public static Ticker systemTicker() { private static final Ticker SYSTEM_TICKER = new Ticker() { @Override + @SuppressWarnings("GoodTime") // reading system time without TimeSource public long read() { - return Platform.systemNanoTime(); + return System.nanoTime(); } }; } diff --git a/android/guava/src/com/google/common/base/Utf8.java b/android/guava/src/com/google/common/base/Utf8.java index bb945a35f095..0a54460cddec 100644 --- a/android/guava/src/com/google/common/base/Utf8.java +++ b/android/guava/src/com/google/common/base/Utf8.java @@ -18,7 +18,6 @@ import static java.lang.Character.MAX_SURROGATE; import static java.lang.Character.MIN_SURROGATE; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; /** @@ -36,9 +35,7 @@ * @author Clément Roux * @since 16.0 */ -@Beta -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Utf8 { /** * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string, this @@ -63,7 +60,7 @@ public static int encodedLength(CharSequence sequence) { for (; i < utf16Length; i++) { char c = sequence.charAt(i); if (c < 0x800) { - utf8Length += ((0x7f - c) >>> 31); // branch free! + utf8Length += (0x7f - c) >>> 31; // branch free! } else { utf8Length += encodedLengthGeneral(sequence, i); break; @@ -87,7 +84,7 @@ private static int encodedLengthGeneral(CharSequence sequence, int start) { utf8Length += (0x7f - c) >>> 31; // branch free! } else { utf8Length += 2; - // jdk7+: if (Character.isSurrogate(c)) { + // We can't use Character.isSurrogate(c) here and below because of GWT. if (MIN_SURROGATE <= c && c <= MAX_SURROGATE) { // Check that we have a well-formed surrogate pair. if (Character.codePointAt(sequence, i) == c) { @@ -167,7 +164,7 @@ private static boolean isWellFormedSlowPath(byte[] bytes, int off, int end) { // Overlong? 5 most significant bits must not all be zero. || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) // Check for illegal surrogate codepoints. - || (byte1 == (byte) 0xED && (byte) 0xA0 <= byte2) + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) // Third byte trailing-byte test. || bytes[index++] > (byte) 0xBF) { return false; diff --git a/android/guava/src/com/google/common/base/Verify.java b/android/guava/src/com/google/common/base/Verify.java index 843a77fe91f0..663814e664c7 100644 --- a/android/guava/src/com/google/common/base/Verify.java +++ b/android/guava/src/com/google/common/base/Verify.java @@ -18,7 +18,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static convenience methods that serve the same purpose as Java language {@code + * {@snippet : * Bill bill = remoteService.getLastUnpaidBill(); * * // In case bug 12345 happens again we'd rather just die * Verify.verify(bill.status() == Status.UNPAID, * "Unexpected bill status: %s", bill.status()); - * } + * } * *

    Comparison to alternatives

    * @@ -63,12 +63,12 @@ * the message ends up unneeded. Performance-sensitive verification checks should continue to use * usual form: * - *
    {@code
    + * {@snippet :
      * Bill bill = remoteService.getLastUnpaidBill();
      * if (bill.status() != Status.UNPAID) {
      *   throw new VerifyException("Unexpected bill status: " + bill.status());
      * }
    - * }
    + * } * *

    Only {@code %s} is supported

    * @@ -118,8 +118,8 @@ public static void verify(boolean expression) { */ public static void verify( boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object... errorMessageArgs) { + String errorMessageTemplate, + @Nullable Object @Nullable ... errorMessageArgs) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, errorMessageArgs)); } @@ -133,8 +133,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, char p1) { + public static void verify(boolean expression, String errorMessageTemplate, char p1) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1)); } @@ -148,7 +147,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify(boolean expression, @NullableDecl String errorMessageTemplate, int p1) { + public static void verify(boolean expression, String errorMessageTemplate, int p1) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1)); } @@ -162,8 +161,7 @@ public static void verify(boolean expression, @NullableDecl String errorMessageT * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, long p1) { + public static void verify(boolean expression, String errorMessageTemplate, long p1) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1)); } @@ -177,8 +175,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, @NullableDecl Object p1) { + public static void verify(boolean expression, String errorMessageTemplate, @Nullable Object p1) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1)); } @@ -192,8 +189,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, char p1, char p2) { + public static void verify(boolean expression, String errorMessageTemplate, char p1, char p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -207,8 +203,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, int p1, char p2) { + public static void verify(boolean expression, String errorMessageTemplate, int p1, char p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -222,8 +217,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, long p1, char p2) { + public static void verify(boolean expression, String errorMessageTemplate, long p1, char p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -238,10 +232,7 @@ public static void verify( * @since 23.1 (varargs overload since 17.0) */ public static void verify( - boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - char p2) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, char p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -255,8 +246,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, char p1, int p2) { + public static void verify(boolean expression, String errorMessageTemplate, char p1, int p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -270,8 +260,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, int p1, int p2) { + public static void verify(boolean expression, String errorMessageTemplate, int p1, int p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -285,8 +274,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, long p1, int p2) { + public static void verify(boolean expression, String errorMessageTemplate, long p1, int p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -301,10 +289,7 @@ public static void verify( * @since 23.1 (varargs overload since 17.0) */ public static void verify( - boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - int p2) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, int p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -318,8 +303,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, char p1, long p2) { + public static void verify(boolean expression, String errorMessageTemplate, char p1, long p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -333,8 +317,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, int p1, long p2) { + public static void verify(boolean expression, String errorMessageTemplate, int p1, long p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -348,8 +331,7 @@ public static void verify( * * @since 23.1 (varargs overload since 17.0) */ - public static void verify( - boolean expression, @NullableDecl String errorMessageTemplate, long p1, long p2) { + public static void verify(boolean expression, String errorMessageTemplate, long p1, long p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -364,10 +346,7 @@ public static void verify( * @since 23.1 (varargs overload since 17.0) */ public static void verify( - boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - long p2) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, long p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -382,10 +361,7 @@ public static void verify( * @since 23.1 (varargs overload since 17.0) */ public static void verify( - boolean expression, - @NullableDecl String errorMessageTemplate, - char p1, - @NullableDecl Object p2) { + boolean expression, String errorMessageTemplate, char p1, @Nullable Object p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -400,10 +376,7 @@ public static void verify( * @since 23.1 (varargs overload since 17.0) */ public static void verify( - boolean expression, - @NullableDecl String errorMessageTemplate, - int p1, - @NullableDecl Object p2) { + boolean expression, String errorMessageTemplate, int p1, @Nullable Object p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -418,10 +391,7 @@ public static void verify( * @since 23.1 (varargs overload since 17.0) */ public static void verify( - boolean expression, - @NullableDecl String errorMessageTemplate, - long p1, - @NullableDecl Object p2) { + boolean expression, String errorMessageTemplate, long p1, @Nullable Object p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -436,10 +406,7 @@ public static void verify( * @since 23.1 (varargs overload since 17.0) */ public static void verify( - boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2) { + boolean expression, String errorMessageTemplate, @Nullable Object p1, @Nullable Object p2) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); } @@ -455,10 +422,10 @@ public static void verify( */ public static void verify( boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2, - @NullableDecl Object p3) { + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2, p3)); } @@ -474,16 +441,25 @@ public static void verify( */ public static void verify( boolean expression, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object p1, - @NullableDecl Object p2, - @NullableDecl Object p3, - @NullableDecl Object p4) { + String errorMessageTemplate, + @Nullable Object p1, + @Nullable Object p2, + @Nullable Object p3, + @Nullable Object p4) { if (!expression) { throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); } } + /* + * For a discussion of the signature of verifyNotNull, see the discussion above + * Preconditions.checkNotNull. + * + * (verifyNotNull has many fewer "problem" callers, so we could try to be stricter. On the other + * hand, verifyNotNull arguably has more reason to accept nullable arguments in the first + * place....) + */ + /** * Ensures that {@code reference} is non-null, throwing a {@code VerifyException} with a default * message otherwise. @@ -493,7 +469,7 @@ public static void verify( * @see Preconditions#checkNotNull Preconditions.checkNotNull() */ @CanIgnoreReturnValue - public static T verifyNotNull(@NullableDecl T reference) { + public static T verifyNotNull(@Nullable T reference) { return verifyNotNull(reference, "expected a non-null reference"); } @@ -514,10 +490,12 @@ public static T verifyNotNull(@NullableDecl T reference) { */ @CanIgnoreReturnValue public static T verifyNotNull( - @NullableDecl T reference, - @NullableDecl String errorMessageTemplate, - @NullableDecl Object... errorMessageArgs) { - verify(reference != null, errorMessageTemplate, errorMessageArgs); + @Nullable T reference, + String errorMessageTemplate, + @Nullable Object @Nullable ... errorMessageArgs) { + if (reference == null) { + throw new VerifyException(lenientFormat(errorMessageTemplate, errorMessageArgs)); + } return reference; } diff --git a/android/guava/src/com/google/common/base/VerifyException.java b/android/guava/src/com/google/common/base/VerifyException.java index eed5c6df2996..e40f5f1294bc 100644 --- a/android/guava/src/com/google/common/base/VerifyException.java +++ b/android/guava/src/com/google/common/base/VerifyException.java @@ -15,7 +15,7 @@ package com.google.common.base; import com.google.common.annotations.GwtCompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Exception thrown upon the failure of a
    bigThreadConstructor = getBigThreadConstructor(); + private static final @Nullable Constructor bigThreadConstructor = + getBigThreadConstructor(); - @NullableDecl - private static final Field inheritableThreadLocals = + private static final @Nullable Field inheritableThreadLocals = (bigThreadConstructor == null) ? getInheritableThreadLocalsField() : null; /** Constructs a new finalizer thread. */ @@ -130,8 +129,7 @@ private Finalizer( PhantomReference frqReference) { this.queue = queue; - this.finalizableReferenceClassReference = - new WeakReference>(finalizableReferenceClass); + this.finalizableReferenceClassReference = new WeakReference<>(finalizableReferenceClass); // Keep track of the FRQ that started us so we know when to stop. this.frqReference = frqReference; @@ -153,47 +151,67 @@ public void run() { } /** - * Cleans up a single reference. Catches and logs all throwables. + * Cleans up the given reference and any other references already in the queue. Catches and logs + * all throwables. * - * @return true if the caller should continue, false if the associated FinalizableReferenceQueue - * is no longer referenced. + * @return true if the caller should continue to wait for more references to be added to the + * queue, false if the associated FinalizableReferenceQueue is no longer referenced. */ - private boolean cleanUp(Reference reference) { + private boolean cleanUp(Reference firstReference) { Method finalizeReferentMethod = getFinalizeReferentMethod(); if (finalizeReferentMethod == null) { return false; } - do { - /* - * This is for the benefit of phantom references. Weak and soft references will have already - * been cleared by this point. - */ - reference.clear(); - if (reference == frqReference) { - /* - * The client no longer has a reference to the FinalizableReferenceQueue. We can stop. - */ + if (!finalizeReference(firstReference, finalizeReferentMethod)) { + return false; + } + + /* + * Loop as long as we have references available so as not to waste CPU looking up the Method + * over and over again. + */ + while (true) { + Reference furtherReference = queue.poll(); + if (furtherReference == null) { + return true; + } + if (!finalizeReference(furtherReference, finalizeReferentMethod)) { return false; } + } + } - try { - finalizeReferentMethod.invoke(reference); - } catch (Throwable t) { - logger.log(Level.SEVERE, "Error cleaning up after reference.", t); - } + /** + * Cleans up the given reference. Catches and logs all throwables. + * + * @return true if the caller should continue to clean up references from the queue, false if the + * associated FinalizableReferenceQueue is no longer referenced. + */ + private boolean finalizeReference(Reference reference, Method finalizeReferentMethod) { + /* + * This is for the benefit of phantom references. Weak and soft references will have already + * been cleared by this point. + */ + reference.clear(); + if (reference == frqReference) { /* - * Loop as long as we have references available so as not to waste CPU looking up the Method - * over and over again. + * The client no longer has a reference to the FinalizableReferenceQueue. We can stop. */ - } while ((reference = queue.poll()) != null); + return false; + } + + try { + finalizeReferentMethod.invoke(reference); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } return true; } /** Looks up FinalizableReference.finalizeReferent() method. */ - @NullableDecl - private Method getFinalizeReferentMethod() { + private @Nullable Method getFinalizeReferentMethod() { Class finalizableReferenceClass = finalizableReferenceClassReference.get(); if (finalizableReferenceClass == null) { /* @@ -211,8 +229,7 @@ private Method getFinalizeReferentMethod() { } } - @NullableDecl - private static Field getInheritableThreadLocalsField() { + private static @Nullable Field getInheritableThreadLocalsField() { try { Field inheritableThreadLocals = Thread.class.getDeclaredField("inheritableThreadLocals"); inheritableThreadLocals.setAccessible(true); @@ -226,8 +243,7 @@ private static Field getInheritableThreadLocalsField() { } } - @NullableDecl - private static Constructor getBigThreadConstructor() { + private static @Nullable Constructor getBigThreadConstructor() { try { return Thread.class.getConstructor( ThreadGroup.class, Runnable.class, String.class, long.class, boolean.class); diff --git a/android/guava/src/com/google/common/base/package-info.java b/android/guava/src/com/google/common/base/package-info.java index f2218562e82b..c73391c3b4e4 100644 --- a/android/guava/src/com/google/common/base/package-info.java +++ b/android/guava/src/com/google/common/base/package-info.java @@ -15,49 +15,50 @@ /** * Basic utility libraries and interfaces. * - *

    This package is a part of the open-source Guava + *

    This package is a part of the open-source Guava * library. * *

    Contents

    * - *

    String-related utilities

    + * The classes in this package that are most commonly useful are: + * + *

    String utilities

    * *
      - *
    • {@link com.google.common.base.Ascii} - *
    • {@link com.google.common.base.CaseFormat} - *
    • {@link com.google.common.base.CharMatcher} - *
    • {@link com.google.common.base.Charsets} - *
    • {@link com.google.common.base.Joiner} - *
    • {@link com.google.common.base.Splitter} - *
    • {@link com.google.common.base.Strings} + *
    • {@link Ascii} + *
    • {@link CaseFormat} + *
    • {@link CharMatcher} + *
    • {@link Splitter} + *
    • {@link Strings} *
    * *

    Function types

    * *
      - *
    • {@link com.google.common.base.Function}, {@link com.google.common.base.Functions} - *
    • {@link com.google.common.base.Predicate}, {@link com.google.common.base.Predicates} - *
    • {@link com.google.common.base.Equivalence} - *
    • {@link com.google.common.base.Converter} - *
    • {@link com.google.common.base.Supplier}, {@link com.google.common.base.Suppliers} + *
    • {@link Converter} + *
    • {@link Equivalence} *
    * *

    Other

    * *
      - *
    • {@link com.google.common.base.Defaults} - *
    • {@link com.google.common.base.Enums} - *
    • {@link com.google.common.base.Objects} - *
    • {@link com.google.common.base.Optional} - *
    • {@link com.google.common.base.Preconditions} - *
    • {@link com.google.common.base.Stopwatch} - *
    • {@link com.google.common.base.Throwables} + *
    • {@link Enums} + *
    • {@link MoreObjects} + *
    • {@link Preconditions} + *
    • {@link StandardSystemProperty} + *
    • {@link Stopwatch} + *
    • {@link Throwables} + *
    • {@link Verify} *
    * + *

    The rest

    + * + * This package also contains some classes with niche use cases (e.g., {@link Utf8} and {@link + * Defaults}), as well as a number of classes that have been superseded by additions to the JDK. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.base; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/cache/AbstractCache.java b/android/guava/src/com/google/common/cache/AbstractCache.java index d8ef0323a81d..e42745122b21 100644 --- a/android/guava/src/com/google/common/cache/AbstractCache.java +++ b/android/guava/src/com/google/common/cache/AbstractCache.java @@ -16,7 +16,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Callable; @@ -43,7 +43,9 @@ public abstract class AbstractCache implements Cache { /** Constructor for use by subclasses. */ protected AbstractCache() {} - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override public V get(K key, Callable valueLoader) throws ExecutionException { throw new UnsupportedOperationException(); @@ -58,9 +60,13 @@ public V get(K key, Callable valueLoader) throws ExecutionException * * @since 11.0 */ + /* + * is mostly the same as to plain Java. But to nullness checkers, they + * differ: means "non-null types," while means "all types." + */ @Override - public ImmutableMap getAllPresent(Iterable keys) { - Map result = Maps.newLinkedHashMap(); + public ImmutableMap getAllPresent(Iterable keys) { + Map result = new LinkedHashMap<>(); for (Object key : keys) { if (!result.containsKey(key)) { @SuppressWarnings("unchecked") @@ -74,13 +80,17 @@ public ImmutableMap getAllPresent(Iterable keys) { return ImmutableMap.copyOf(result); } - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override public void put(K key, V value) { throw new UnsupportedOperationException(); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @Override public void putAll(Map m) { for (Entry entry : m.entrySet()) { @@ -101,9 +111,12 @@ public void invalidate(Object key) { throw new UnsupportedOperationException(); } - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override - public void invalidateAll(Iterable keys) { + // For discussion of , see getAllPresent. + public void invalidateAll(Iterable keys) { for (Object key : keys) { invalidate(key); } @@ -204,13 +217,17 @@ public static final class SimpleStatsCounter implements StatsCounter { /** Constructs an instance with all counts initialized to zero. */ public SimpleStatsCounter() {} - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override public void recordHits(int count) { hitCount.add(count); } - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override public void recordMisses(int count) { missCount.add(count); diff --git a/android/guava/src/com/google/common/cache/AbstractLoadingCache.java b/android/guava/src/com/google/common/cache/AbstractLoadingCache.java index 38b97747915e..7cf6c7b8dae8 100644 --- a/android/guava/src/com/google/common/cache/AbstractLoadingCache.java +++ b/android/guava/src/com/google/common/cache/AbstractLoadingCache.java @@ -16,8 +16,9 @@ import com.google.common.annotations.GwtIncompatible; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -44,6 +45,7 @@ public abstract class AbstractLoadingCache extends AbstractCache /** Constructor for use by subclasses. */ protected AbstractLoadingCache() {} + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this? @Override public V getUnchecked(K key) { try { @@ -55,7 +57,7 @@ public V getUnchecked(K key) { @Override public ImmutableMap getAll(Iterable keys) throws ExecutionException { - Map result = Maps.newLinkedHashMap(); + Map result = new LinkedHashMap<>(); for (K key : keys) { if (!result.containsKey(key)) { result.put(key, get(key)); diff --git a/android/guava/src/com/google/common/cache/Cache.java b/android/guava/src/com/google/common/cache/Cache.java index a47c4fe47b3e..2a6925d31f1d 100644 --- a/android/guava/src/com/google/common/cache/Cache.java +++ b/android/guava/src/com/google/common/cache/Cache.java @@ -18,14 +18,14 @@ import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ExecutionError; import com.google.common.util.concurrent.UncheckedExecutionException; -import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CompatibleWith; import com.google.errorprone.annotations.DoNotMock; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A semi-persistent mapping from keys to values. Cache entries are manually added using {@link @@ -35,6 +35,8 @@ *

    Implementations of this interface are expected to be thread-safe, and can be safely accessed * by multiple concurrent threads. * + * @param the type of the cache's keys, which are not permitted to be null + * @param the type of the cache's values, which are not permitted to be null * @author Charles Fry * @since 10.0 */ @@ -48,8 +50,8 @@ public interface Cache { * * @since 11.0 */ - @NullableDecl - V getIfPresent(@CompatibleWith("K") Object key); + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this? + @Nullable V getIfPresent(@CompatibleWith("K") Object key); /** * Returns the value associated with {@code key} in this cache, obtaining that value from {@code @@ -97,6 +99,7 @@ public interface Cache { * @throws ExecutionError if an error was thrown while loading the value * @since 11.0 */ + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this V get(K key, Callable loader) throws ExecutionException; /** @@ -105,7 +108,11 @@ public interface Cache { * * @since 11.0 */ - ImmutableMap getAllPresent(Iterable keys); + /* + * is mostly the same as to plain Java. But to nullness checkers, they + * differ: means "non-null types," while means "all types." + */ + ImmutableMap getAllPresent(Iterable keys); /** * Associates {@code value} with {@code key} in this cache. If the cache previously contained a @@ -136,13 +143,13 @@ public interface Cache { * * @since 11.0 */ - void invalidateAll(Iterable keys); + // For discussion of , see getAllPresent. + void invalidateAll(Iterable keys); /** Discards all entries in the cache. */ void invalidateAll(); /** Returns the approximate number of entries in this cache. */ - @CheckReturnValue long size(); /** @@ -156,7 +163,6 @@ public interface Cache { * all values is returned. * */ - @CheckReturnValue CacheStats stats(); /** @@ -172,7 +178,6 @@ public interface Cache { * {@code ConcurrentMap} documentation. They will not function correctly and it is impossible for * Guava to fix them until Guava is ready to require Java 8 for all users. */ - @CheckReturnValue ConcurrentMap asMap(); /** diff --git a/android/guava/src/com/google/common/cache/CacheBuilder.java b/android/guava/src/com/google/common/cache/CacheBuilder.java index edd10bbfb357..012cc2e6be5b 100644 --- a/android/guava/src/com/google/common/cache/CacheBuilder.java +++ b/android/guava/src/com/google/common/cache/CacheBuilder.java @@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; @@ -29,33 +30,60 @@ import com.google.common.cache.AbstractCache.SimpleStatsCounter; import com.google.common.cache.AbstractCache.StatsCounter; import com.google.common.cache.LocalCache.Strength; -import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.j2objc.annotations.J2ObjCIncompatible; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; +import java.time.Duration; import java.util.ConcurrentModificationException; import java.util.IdentityHashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A builder of {@link LoadingCache} and {@link Cache} instances. * - *

    Prefer Caffeine over {@code - * common.cache}

    + *

    Prefer Caffeine over Guava's caching + * API

    * - *

    The successor to {@code common.cache} is The successor to Guava's caching API is Caffeine. Its API is designed to make it a - * nearly drop-in replacement (though it requires Java 8 APIs). (Its equivalent to {@code + * nearly drop-in replacement. Note that it is not available for Android or GWT/J2CL and that it may + * have different (usually better) + * behavior when multiple threads attempt concurrent mutations. Its equivalent to {@code * CacheBuilder} is its {@code - * Caffeine} class.) It offers better performance, more features (including asynchronous + * href="https://www.javadoc.io/doc/com.github.ben-manes.caffeine/caffeine/latest/com.github.benmanes.caffeine/com/github/benmanes/caffeine/cache/Caffeine.html">{@code + * Caffeine} class. Caffeine offers better performance, more features (including asynchronous * loading), and fewer bugs. * + *

    Caffeine defines its own interfaces ({@code + * Cache}, {@code + * LoadingCache}, {@code + * CacheLoader}, etc.), so you can use Caffeine without needing to use any Guava types. + * Caffeine's types are better than Guava's, especially for their + * deep support for asynchronous operations. But if you want to migrate to Caffeine with minimal + * code changes, you can use its + * {@code CaffeinatedGuava} adapter class, which lets you build a Guava {@code Cache} or a Guava + * {@code LoadingCache} backed by a Guava {@code CacheLoader}. + * + *

    Caffeine's API for asynchronous operations uses {@code CompletableFuture}: {@code + * AsyncLoadingCache.get} returns a {@code CompletableFuture}, and implementations of {@code + * AsyncCacheLoader.asyncLoad} must return a {@code CompletableFuture}. Users of Guava's {@link + * com.google.common.util.concurrent.ListenableFuture} can adapt between the two {@code Future} + * types by using {@code + * net.javacrumbs.futureconverter.java8guava.FutureConverter}. + * *

    More on {@code CacheBuilder}

    * * {@code CacheBuilder} builds caches with any combination of the following features: @@ -65,21 +93,22 @@ *
  • least-recently-used eviction when a maximum size is exceeded (note that the cache is * divided into segments, each of which does LRU internally) *
  • time-based expiration of entries, measured since last access or last write - *
  • keys automatically wrapped in {@code WeakReference} - *
  • values automatically wrapped in {@code WeakReference} or {@code SoftReference} + *
  • keys automatically wrapped in {@linkplain WeakReference weak} references + *
  • values automatically wrapped in {@linkplain WeakReference weak} or {@linkplain + * SoftReference soft} references *
  • notification of evicted (or otherwise removed) entries *
  • accumulation of cache access statistics * * - *

    These features are all optional; caches can be created using all or none of them. By default + *

    These features are all optional; caches can be created using all or none of them. By default, * cache instances created by {@code CacheBuilder} will not perform any type of eviction. * *

    Usage example: * - *

    {@code
    + * {@snippet :
      * LoadingCache graphs = CacheBuilder.newBuilder()
      *     .maximumSize(10000)
    - *     .expireAfterWrite(10, TimeUnit.MINUTES)
    + *     .expireAfterWrite(Duration.ofMinutes(10))
      *     .removalListener(MY_LISTENER)
      *     .build(
      *         new CacheLoader() {
    @@ -87,11 +116,11 @@
      *             return createExpensiveGraph(key);
      *           }
      *         });
    - * }
    + * } * *

    Or equivalently, * - *

    {@code
    + * {@snippet :
      * // In real life this would come from a command-line flag or config file
      * String spec = "maximumSize=10000,expireAfterWrite=10m";
      *
    @@ -103,15 +132,13 @@
      *             return createExpensiveGraph(key);
      *           }
      *         });
    - * }
    + * } * - *

    The returned cache is implemented as a hash table with similar performance characteristics to - * {@link ConcurrentHashMap}. It implements all optional operations of the {@link LoadingCache} and - * {@link Cache} interfaces. The {@code asMap} view (and its collection views) have weakly - * consistent iterators. This means that they are safe for concurrent use, but if other threads - * modify the cache after the iterator is created, it is undefined which of these changes, if any, - * are reflected in that iterator. These iterators never throw {@link - * ConcurrentModificationException}. + *

    The returned cache implements all optional operations of the {@link LoadingCache} and {@link + * Cache} interfaces. The {@code asMap} view (and its collection views) have weakly consistent + * iterators. This means that they are safe for concurrent use, but if other threads modify the + * cache after the iterator is created, it is undefined which of these changes, if any, are + * reflected in that iterator. These iterators never throw {@link ConcurrentModificationException}. * *

    Note: by default, the returned cache uses equality comparisons (the {@link * Object#equals equals} method) to determine equality for keys or values. However, if {@link @@ -119,34 +146,33 @@ * Likewise, if {@link #weakValues} or {@link #softValues} was specified, the cache uses identity * comparisons for values. * - *

    Entries are automatically evicted from the cache when any of {@linkplain #maximumSize(long) - * maximumSize}, {@linkplain #maximumWeight(long) maximumWeight}, {@linkplain #expireAfterWrite - * expireAfterWrite}, {@linkplain #expireAfterAccess expireAfterAccess}, {@linkplain #weakKeys - * weakKeys}, {@linkplain #weakValues weakValues}, or {@linkplain #softValues softValues} are - * requested. + *

    Entries are automatically evicted from the cache when any of {@link #maximumSize(long) + * maximumSize}, {@link #maximumWeight(long) maximumWeight}, {@link #expireAfterWrite + * expireAfterWrite}, {@link #expireAfterAccess expireAfterAccess}, {@link #weakKeys weakKeys}, + * {@link #weakValues weakValues}, or {@link #softValues softValues} are requested. * - *

    If {@linkplain #maximumSize(long) maximumSize} or {@linkplain #maximumWeight(long) - * maximumWeight} is requested entries may be evicted on each cache modification. + *

    If {@link #maximumSize(long) maximumSize} or {@link #maximumWeight(long) maximumWeight} is + * requested entries may be evicted on each cache modification. * - *

    If {@linkplain #expireAfterWrite expireAfterWrite} or {@linkplain #expireAfterAccess - * expireAfterAccess} is requested entries may be evicted on each cache modification, on occasional - * cache accesses, or on calls to {@link Cache#cleanUp}. Expired entries may be counted by {@link - * Cache#size}, but will never be visible to read or write operations. + *

    If {@link #expireAfterWrite expireAfterWrite} or {@link #expireAfterAccess expireAfterAccess} + * is requested entries may be evicted on each cache modification, on occasional cache accesses, or + * on calls to {@link Cache#cleanUp}. Expired entries may be counted by {@link Cache#size}, but will + * never be visible to read or write operations. * - *

    If {@linkplain #weakKeys weakKeys}, {@linkplain #weakValues weakValues}, or {@linkplain - * #softValues softValues} are requested, it is possible for a key or value present in the cache to - * be reclaimed by the garbage collector. Entries with reclaimed keys or values may be removed from - * the cache on each cache modification, on occasional cache accesses, or on calls to {@link - * Cache#cleanUp}; such entries may be counted in {@link Cache#size}, but will never be visible to - * read or write operations. + *

    If {@link #weakKeys weakKeys}, {@link #weakValues weakValues}, or {@link #softValues + * softValues} are requested, it is possible for a key or value present in the cache to be reclaimed + * by the garbage collector. Entries with reclaimed keys or values may be removed from the cache on + * each cache modification, on occasional cache accesses, or on calls to {@link Cache#cleanUp}; such + * entries may be counted in {@link Cache#size}, but will never be visible to read or write + * operations. * *

    Certain cache configurations will result in the accrual of periodic maintenance tasks which * will be performed during write operations, or during occasional read operations in the absence of * writes. The {@link Cache#cleanUp} method of the returned cache will also perform maintenance, but - * calling it should not be necessary with a high throughput cache. Only caches built with - * {@linkplain #removalListener removalListener}, {@linkplain #expireAfterWrite expireAfterWrite}, - * {@linkplain #expireAfterAccess expireAfterAccess}, {@linkplain #weakKeys weakKeys}, {@linkplain - * #weakValues weakValues}, or {@linkplain #softValues softValues} perform periodic maintenance. + * calling it should not be necessary with a high throughput cache. Only caches built with {@link + * #removalListener removalListener}, {@link #expireAfterWrite expireAfterWrite}, {@link + * #expireAfterAccess expireAfterAccess}, {@link #weakKeys weakKeys}, {@link #weakValues + * weakValues}, or {@link #softValues softValues} perform periodic maintenance. * *

    The caches produced by {@code CacheBuilder} are serializable, and the deserialized caches * retain all the configuration properties of the original cache. Note that the serialized form does @@ -157,24 +183,24 @@ * explanation. * * @param the most general key type this builder will be able to create caches for. This is - * normally {@code Object} unless it is constrained by using a method like {@code - * #removalListener} + * normally {@code Object} unless it is constrained by using a method like {@link + * #removalListener}. Cache keys may not be null. * @param the most general value type this builder will be able to create caches for. This is - * normally {@code Object} unless it is constrained by using a method like {@code - * #removalListener} + * normally {@code Object} unless it is constrained by using a method like {@link + * #removalListener}. Cache values may not be null. * @author Charles Fry * @author Kevin Bourrillion * @since 10.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class CacheBuilder { private static final int DEFAULT_INITIAL_CAPACITY = 16; private static final int DEFAULT_CONCURRENCY_LEVEL = 4; - @SuppressWarnings("GoodTime") // should be a java.time.Duration + @SuppressWarnings("GoodTime") // should be a Duration private static final int DEFAULT_EXPIRATION_NANOS = 0; - @SuppressWarnings("GoodTime") // should be a java.time.Duration + @SuppressWarnings("GoodTime") // should be a Duration private static final int DEFAULT_REFRESH_NANOS = 0; static final Supplier NULL_STATS_COUNTER = @@ -204,6 +230,18 @@ public CacheStats snapshot() { }); static final CacheStats EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0); + /* + * We avoid using a method reference or lambda here for now: + * + * - method reference: Inside Google, CacheBuilder is used from the implementation of a custom + * ClassLoader that is sometimes used as a system classloader. That's a problem because + * method-reference linking tries to look up the system classloader, and it fails because there + * isn't one yet. + * + * - lambda: Outside Google, we got a report of a similar problem in + * https://github.com/google/guava/issues/6565 + */ + @SuppressWarnings("AnonymousToLambda") static final Supplier CACHE_STATS_COUNTER = new Supplier() { @Override @@ -236,7 +274,10 @@ public long read() { } }; - private static final Logger logger = Logger.getLogger(CacheBuilder.class.getName()); + // We use a holder class to delay initialization: https://github.com/google/guava/issues/6566 + private static final class LoggerHolder { + static final Logger logger = Logger.getLogger(CacheBuilder.class.getName()); + } static final int UNSET_INT = -1; @@ -246,25 +287,25 @@ public long read() { int concurrencyLevel = UNSET_INT; long maximumSize = UNSET_INT; long maximumWeight = UNSET_INT; - @NullableDecl Weigher weigher; + @Nullable Weigher weigher; - @NullableDecl Strength keyStrength; - @NullableDecl Strength valueStrength; + @Nullable Strength keyStrength; + @Nullable Strength valueStrength; - @SuppressWarnings("GoodTime") // should be a java.time.Duration + @SuppressWarnings("GoodTime") // should be a Duration long expireAfterWriteNanos = UNSET_INT; - @SuppressWarnings("GoodTime") // should be a java.time.Duration + @SuppressWarnings("GoodTime") // should be a Duration long expireAfterAccessNanos = UNSET_INT; - @SuppressWarnings("GoodTime") // should be a java.time.Duration + @SuppressWarnings("GoodTime") // should be a Duration long refreshNanos = UNSET_INT; - @NullableDecl Equivalence keyEquivalence; - @NullableDecl Equivalence valueEquivalence; + @Nullable Equivalence keyEquivalence; + @Nullable Equivalence valueEquivalence; - @NullableDecl RemovalListener removalListener; - @NullableDecl Ticker ticker; + @Nullable RemovalListener removalListener; + @Nullable Ticker ticker; Supplier statsCounterSupplier = NULL_STATS_COUNTER; @@ -277,7 +318,6 @@ private CacheBuilder() {} *

    Note that while this return type is {@code CacheBuilder}, type parameters on * the {@link #build} methods allow you to create a cache of any key and value type desired. */ - @CheckReturnValue public static CacheBuilder newBuilder() { return new CacheBuilder<>(); } @@ -288,7 +328,6 @@ public static CacheBuilder newBuilder() { * @since 12.0 */ @GwtIncompatible // To be supported - @CheckReturnValue public static CacheBuilder from(CacheBuilderSpec spec) { return spec.toCacheBuilder().lenientParsing(); } @@ -301,7 +340,6 @@ public static CacheBuilder from(CacheBuilderSpec spec) { * @since 12.0 */ @GwtIncompatible // To be supported - @CheckReturnValue public static CacheBuilder from(String spec) { return from(CacheBuilderSpec.parse(spec)); } @@ -312,6 +350,7 @@ public static CacheBuilder from(String spec) { * @return this {@code CacheBuilder} instance (for chaining) */ @GwtIncompatible // To be supported + @CanIgnoreReturnValue CacheBuilder lenientParsing() { strictParsing = false; return this; @@ -326,6 +365,7 @@ CacheBuilder lenientParsing() { * @return this {@code CacheBuilder} instance (for chaining) */ @GwtIncompatible // To be supported + @CanIgnoreReturnValue CacheBuilder keyEquivalence(Equivalence equivalence) { checkState(keyEquivalence == null, "key equivalence was already set to %s", keyEquivalence); keyEquivalence = checkNotNull(equivalence); @@ -346,6 +386,7 @@ Equivalence getKeyEquivalence() { * @return this {@code CacheBuilder} instance (for chaining) */ @GwtIncompatible // To be supported + @CanIgnoreReturnValue CacheBuilder valueEquivalence(Equivalence equivalence) { checkState( valueEquivalence == null, "value equivalence was already set to %s", valueEquivalence); @@ -368,6 +409,7 @@ Equivalence getValueEquivalence() { * @throws IllegalArgumentException if {@code initialCapacity} is negative * @throws IllegalStateException if an initial capacity was already set */ + @CanIgnoreReturnValue public CacheBuilder initialCapacity(int initialCapacity) { checkState( this.initialCapacity == UNSET_INT, @@ -413,6 +455,7 @@ int getInitialCapacity() { * @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive * @throws IllegalStateException if a concurrency level was already set */ + @CanIgnoreReturnValue public CacheBuilder concurrencyLevel(int concurrencyLevel) { checkState( this.concurrencyLevel == UNSET_INT, @@ -448,6 +491,7 @@ int getConcurrencyLevel() { * @throws IllegalArgumentException if {@code maximumSize} is negative * @throws IllegalStateException if a maximum size or weight was already set */ + @CanIgnoreReturnValue public CacheBuilder maximumSize(long maximumSize) { checkState( this.maximumSize == UNSET_INT, "maximum size was already set to %s", this.maximumSize); @@ -489,6 +533,7 @@ public CacheBuilder maximumSize(long maximumSize) { * @since 11.0 */ @GwtIncompatible // To be supported + @CanIgnoreReturnValue public CacheBuilder maximumWeight(long maximumWeight) { checkState( this.maximumWeight == UNSET_INT, @@ -526,18 +571,19 @@ public CacheBuilder maximumWeight(long maximumWeight) { * * @param weigher the weigher to use in calculating the weight of cache entries * @return this {@code CacheBuilder} instance (for chaining) - * @throws IllegalArgumentException if {@code size} is negative - * @throws IllegalStateException if a maximum size was already set + * @throws IllegalStateException if a weigher was already set or {@link #maximumSize(long)} was + * previously called * @since 11.0 */ @GwtIncompatible // To be supported + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this public CacheBuilder weigher( Weigher weigher) { checkState(this.weigher == null); if (strictParsing) { checkState( this.maximumSize == UNSET_INT, - "weigher can not be combined with maximum size", + "weigher can not be combined with maximum size (%s provided)", this.maximumSize); } @@ -578,10 +624,12 @@ Weigher getWeigher() { * @throws IllegalStateException if the key strength was already set */ @GwtIncompatible // java.lang.ref.WeakReference + @CanIgnoreReturnValue public CacheBuilder weakKeys() { return setKeyStrength(Strength.WEAK); } + @CanIgnoreReturnValue CacheBuilder setKeyStrength(Strength strength) { checkState(keyStrength == null, "Key strength was already set to %s", keyStrength); keyStrength = checkNotNull(strength); @@ -610,6 +658,7 @@ Strength getKeyStrength() { * @throws IllegalStateException if the value strength was already set */ @GwtIncompatible // java.lang.ref.WeakReference + @CanIgnoreReturnValue public CacheBuilder weakValues() { return setValueStrength(Strength.WEAK); } @@ -635,10 +684,12 @@ public CacheBuilder weakValues() { * @throws IllegalStateException if the value strength was already set */ @GwtIncompatible // java.lang.ref.SoftReference + @CanIgnoreReturnValue public CacheBuilder softValues() { return setValueStrength(Strength.SOFT); } + @CanIgnoreReturnValue CacheBuilder setValueStrength(Strength strength) { checkState(valueStrength == null, "Value strength was already set to %s", valueStrength); valueStrength = checkNotNull(strength); @@ -663,12 +714,47 @@ Strength getValueStrength() { * * @param duration the length of time after an entry is created that it should be automatically * removed + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if {@link #expireAfterWrite} was already set + * @throws ArithmeticException for durations greater than +/- approximately 292 years + * @since 33.3.0 (but since 25.0 in the JRE flavor) + */ + @J2ObjCIncompatible + @GwtIncompatible // Duration + @SuppressWarnings("GoodTime") // Duration decomposition + @IgnoreJRERequirement // No more dangerous than wherever the caller got the Duration from + @CanIgnoreReturnValue + public CacheBuilder expireAfterWrite(Duration duration) { + return expireAfterWrite(toNanosSaturated(duration), NANOSECONDS); + } + + /** + * Specifies that each entry should be automatically removed from the cache once a fixed duration + * has elapsed after the entry's creation, or the most recent replacement of its value. + * + *

    When {@code duration} is zero, this method hands off to {@link #maximumSize(long) + * maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be + * useful in testing, or to disable caching temporarily without a code change. + * + *

    Expired entries may be counted in {@link Cache#size}, but will never be visible to read or + * write operations. Expired entries are cleaned up as part of the routine maintenance described + * in the class javadoc. + * + *

    If you can represent the duration as a {@link Duration} (which should be preferred when + * feasible), use {@link #expireAfterWrite(Duration)} instead. + * + * @param duration the length of time after an entry is created that it should be automatically + * removed * @param unit the unit that {@code duration} is expressed in * @return this {@code CacheBuilder} instance (for chaining) * @throws IllegalArgumentException if {@code duration} is negative * @throws IllegalStateException if {@link #expireAfterWrite} was already set + * @deprecated Use {@link #expireAfterWrite(Duration)} instead. */ - @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @Deprecated // GoodTime + @CanIgnoreReturnValue public CacheBuilder expireAfterWrite(long duration, TimeUnit unit) { checkState( expireAfterWriteNanos == UNSET_INT, @@ -684,6 +770,41 @@ long getExpireAfterWriteNanos() { return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos; } + /** + * Specifies that each entry should be automatically removed from the cache once a fixed duration + * has elapsed after the entry's creation, the most recent replacement of its value, or its last + * access. Access time is reset by all cache read and write operations (including {@code + * Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by {@code + * containsKey(Object)}, nor by operations on the collection-views of {@link Cache#asMap}}. So, + * for example, iterating through {@code Cache.asMap().entrySet()} does not reset access time for + * the entries you retrieve. + * + *

    When {@code duration} is zero, this method hands off to {@link #maximumSize(long) + * maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be + * useful in testing, or to disable caching temporarily without a code change. + * + *

    Expired entries may be counted in {@link Cache#size}, but will never be visible to read or + * write operations. Expired entries are cleaned up as part of the routine maintenance described + * in the class javadoc. + * + * @param duration the length of time after an entry is last accessed that it should be + * automatically removed + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if {@link #expireAfterAccess} was already set + * @throws ArithmeticException for durations greater than +/- approximately 292 years + * @since 33.3.0 (but since 25.0 in the JRE flavor) + */ + @J2ObjCIncompatible + @GwtIncompatible // Duration + @SuppressWarnings("GoodTime") // Duration decomposition + @IgnoreJRERequirement // No more dangerous than wherever the caller got the Duration from + @CanIgnoreReturnValue + public CacheBuilder expireAfterAccess(Duration duration) { + return expireAfterAccess(toNanosSaturated(duration), NANOSECONDS); + } + /** * Specifies that each entry should be automatically removed from the cache once a fixed duration * has elapsed after the entry's creation, the most recent replacement of its value, or its last @@ -701,14 +822,19 @@ long getExpireAfterWriteNanos() { * write operations. Expired entries are cleaned up as part of the routine maintenance described * in the class javadoc. * + *

    If you can represent the duration as a {@link Duration} (which should be preferred when + * feasible), use {@link #expireAfterAccess(Duration)} instead. + * * @param duration the length of time after an entry is last accessed that it should be * automatically removed * @param unit the unit that {@code duration} is expressed in * @return this {@code CacheBuilder} instance (for chaining) * @throws IllegalArgumentException if {@code duration} is negative * @throws IllegalStateException if {@link #expireAfterAccess} was already set + * @deprecated Use {@link #expireAfterAccess(Duration)} instead. */ - @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @Deprecated // GoodTime + @CanIgnoreReturnValue public CacheBuilder expireAfterAccess(long duration, TimeUnit unit) { checkState( expireAfterAccessNanos == UNSET_INT, @@ -738,12 +864,53 @@ long getExpireAfterAccessNanos() { * operations. * *

    Currently automatic refreshes are performed when the first stale request for an entry - * occurs. The request triggering refresh will make a blocking call to {@link CacheLoader#reload} + * occurs. The request triggering refresh will make a synchronous call to {@link + * CacheLoader#reload} + * to obtain a future of the new value. If the returned future is already complete, it is returned + * immediately. Otherwise, the old value is returned. + * + *

    Note: all exceptions thrown during refresh will be logged and then swallowed. + * + * @param duration the length of time after an entry is created that it should be considered + * stale, and thus eligible for refresh + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if {@link #refreshAfterWrite} was already set + * @throws ArithmeticException for durations greater than +/- approximately 292 years + * @since 33.3.0 (but since 25.0 in the JRE flavor) + */ + @J2ObjCIncompatible + @GwtIncompatible // Duration + @SuppressWarnings("GoodTime") // Duration decomposition + @IgnoreJRERequirement // No more dangerous than wherever the caller got the Duration from + @CanIgnoreReturnValue + public CacheBuilder refreshAfterWrite(Duration duration) { + return refreshAfterWrite(toNanosSaturated(duration), NANOSECONDS); + } + + /** + * Specifies that active entries are eligible for automatic refresh once a fixed duration has + * elapsed after the entry's creation, or the most recent replacement of its value. The semantics + * of refreshes are specified in {@link LoadingCache#refresh}, and are performed by calling {@link + * CacheLoader#reload}. + * + *

    As the default implementation of {@link CacheLoader#reload} is synchronous, it is + * recommended that users of this method override {@link CacheLoader#reload} with an asynchronous + * implementation; otherwise refreshes will be performed during unrelated cache read and write + * operations. + * + *

    Currently automatic refreshes are performed when the first stale request for an entry + * occurs. The request triggering refresh will make a synchronous call to {@link + * CacheLoader#reload} * and immediately return the new value if the returned future is complete, and the old value * otherwise. * *

    Note: all exceptions thrown during refresh will be logged and then swallowed. * + *

    If you can represent the duration as a {@link Duration} (which should be preferred when + * feasible), use {@link #refreshAfterWrite(Duration)} instead. + * * @param duration the length of time after an entry is created that it should be considered * stale, and thus eligible for refresh * @param unit the unit that {@code duration} is expressed in @@ -751,9 +918,11 @@ long getExpireAfterAccessNanos() { * @throws IllegalArgumentException if {@code duration} is negative * @throws IllegalStateException if {@link #refreshAfterWrite} was already set * @since 11.0 + * @deprecated Use {@link #refreshAfterWrite(Duration)} instead. */ @GwtIncompatible // To be supported (synchronously). - @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @Deprecated // GoodTime + @CanIgnoreReturnValue public CacheBuilder refreshAfterWrite(long duration, TimeUnit unit) { checkNotNull(unit); checkState(refreshNanos == UNSET_INT, "refresh was already set to %s ns", refreshNanos); @@ -777,6 +946,7 @@ long getRefreshNanos() { * @return this {@code CacheBuilder} instance (for chaining) * @throws IllegalStateException if a ticker was already set */ + @CanIgnoreReturnValue public CacheBuilder ticker(Ticker ticker) { checkState(this.ticker == null); this.ticker = checkNotNull(ticker); @@ -797,21 +967,19 @@ Ticker getTicker(boolean recordsTime) { * *

    Warning: after invoking this method, do not continue to use this cache builder * reference; instead use the reference this method returns. At runtime, these point to the - * same instance, but only the returned reference has the correct generic type information so as - * to ensure type safety. For best results, use the standard method-chaining idiom illustrated in - * the class documentation above, configuring a builder and building your cache in a single - * statement. Failure to heed this advice can result in a {@link ClassCastException} being thrown - * by a cache operation at some undefined point in the future. + * same instance, but only the returned reference has the correct generic type information to + * ensure type safety. For best results, use the standard method-chaining idiom illustrated in the + * class documentation above, configuring a builder and building your cache in a single statement. + * Failure to heed this advice can result in a {@link ClassCastException} being thrown by a cache + * operation at some undefined point in the future. * *

    Warning: any exception thrown by {@code listener} will not be propagated to * the {@code Cache} user, only logged via a {@link Logger}. * * @return the cache builder reference that should be used instead of {@code this} for any * remaining configuration and cache building - * @return this {@code CacheBuilder} instance (for chaining) * @throws IllegalStateException if a removal listener was already set */ - @CheckReturnValue public CacheBuilder removalListener( RemovalListener listener) { checkState(this.removalListener == null); @@ -839,6 +1007,7 @@ RemovalListener getRemovalListener() { * @return this {@code CacheBuilder} instance (for chaining) * @since 12.0 (previously, stats collection was automatic) */ + @CanIgnoreReturnValue public CacheBuilder recordStats() { statsCounterSupplier = CACHE_STATS_COUNTER; return this; @@ -864,7 +1033,6 @@ Supplier getStatsCounterSupplier() { * @param loader the cache loader used to obtain new values * @return a cache having the requested features */ - @CheckReturnValue public LoadingCache build( CacheLoader loader) { checkWeightWithWeigher(); @@ -883,7 +1051,6 @@ public LoadingCache build( * @return a cache having the requested features * @since 11.0 */ - @CheckReturnValue public Cache build() { checkWeightWithWeigher(); checkNonLoadingCache(); @@ -902,7 +1069,8 @@ private void checkWeightWithWeigher() { checkState(maximumWeight != UNSET_INT, "weigher requires maximumWeight"); } else { if (maximumWeight == UNSET_INT) { - logger.log(Level.WARNING, "ignoring weigher specified without maximumWeight"); + LoggerHolder.logger.log( + Level.WARNING, "ignoring weigher specified without maximumWeight"); } } } @@ -950,4 +1118,24 @@ public String toString() { } return s.toString(); } + + /** + * Returns the number of nanoseconds of the given duration without throwing or overflowing. + * + *

    Instead of throwing {@link ArithmeticException}, this method silently saturates to either + * {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE}. This behavior can be useful when decomposing + * a duration in order to call a legacy API which requires a {@code long, TimeUnit} pair. + */ + @GwtIncompatible // Duration + @SuppressWarnings("GoodTime") // Duration decomposition + @IgnoreJRERequirement // No more dangerous than wherever the caller got the Duration from + private static long toNanosSaturated(Duration duration) { + // Using a try/catch seems lazy, but the catch block will rarely get invoked (except for + // durations longer than approximately +/- 292 years). + try { + return duration.toNanos(); + } catch (ArithmeticException tooBig) { + return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE; + } + } } diff --git a/android/guava/src/com/google/common/cache/CacheBuilderSpec.java b/android/guava/src/com/google/common/cache/CacheBuilderSpec.java index 8f98222bcd47..afcb23e36381 100644 --- a/android/guava/src/com/google/common/cache/CacheBuilderSpec.java +++ b/android/guava/src/com/google/common/cache/CacheBuilderSpec.java @@ -15,19 +15,24 @@ package com.google.common.cache; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; import com.google.common.base.Splitter; import com.google.common.cache.LocalCache.Strength; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.TimeUnit; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A specification of a {@link CacheBuilder} configuration. @@ -81,7 +86,7 @@ public final class CacheBuilderSpec { /** Parses a single value. */ private interface ValueParser { - void parse(CacheBuilderSpec spec, String key, @NullableDecl String value); + void parse(CacheBuilderSpec spec, String key, @Nullable String value); } /** Splits each key-value pair. */ @@ -105,21 +110,22 @@ private interface ValueParser { .put("expireAfterWrite", new WriteDurationParser()) .put("refreshAfterWrite", new RefreshDurationParser()) .put("refreshInterval", new RefreshDurationParser()) - .build(); - - @VisibleForTesting @NullableDecl Integer initialCapacity; - @VisibleForTesting @NullableDecl Long maximumSize; - @VisibleForTesting @NullableDecl Long maximumWeight; - @VisibleForTesting @NullableDecl Integer concurrencyLevel; - @VisibleForTesting @NullableDecl Strength keyStrength; - @VisibleForTesting @NullableDecl Strength valueStrength; - @VisibleForTesting @NullableDecl Boolean recordStats; + .buildOrThrow(); + + @VisibleForTesting @Nullable Integer initialCapacity; + @VisibleForTesting @Nullable Long maximumSize; + @VisibleForTesting @Nullable Long maximumWeight; + @VisibleForTesting @Nullable Integer concurrencyLevel; + @VisibleForTesting @Nullable Strength keyStrength; + @VisibleForTesting @Nullable Strength valueStrength; + @VisibleForTesting @Nullable Boolean recordStats; @VisibleForTesting long writeExpirationDuration; - @VisibleForTesting @NullableDecl TimeUnit writeExpirationTimeUnit; + @VisibleForTesting @Nullable TimeUnit writeExpirationTimeUnit; @VisibleForTesting long accessExpirationDuration; - @VisibleForTesting @NullableDecl TimeUnit accessExpirationTimeUnit; + @VisibleForTesting @Nullable TimeUnit accessExpirationTimeUnit; @VisibleForTesting long refreshDuration; - @VisibleForTesting @NullableDecl TimeUnit refreshTimeUnit; + @VisibleForTesting @Nullable TimeUnit refreshTimeUnit; + /** Specification; used for toParseableString(). */ private final String specification; @@ -234,7 +240,7 @@ public String toString() { @Override public int hashCode() { - return Objects.hashCode( + return Objects.hash( initialCapacity, maximumSize, maximumWeight, @@ -248,7 +254,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } @@ -256,20 +262,20 @@ public boolean equals(@NullableDecl Object obj) { return false; } CacheBuilderSpec that = (CacheBuilderSpec) obj; - return Objects.equal(initialCapacity, that.initialCapacity) - && Objects.equal(maximumSize, that.maximumSize) - && Objects.equal(maximumWeight, that.maximumWeight) - && Objects.equal(concurrencyLevel, that.concurrencyLevel) - && Objects.equal(keyStrength, that.keyStrength) - && Objects.equal(valueStrength, that.valueStrength) - && Objects.equal(recordStats, that.recordStats) - && Objects.equal( + return Objects.equals(initialCapacity, that.initialCapacity) + && Objects.equals(maximumSize, that.maximumSize) + && Objects.equals(maximumWeight, that.maximumWeight) + && Objects.equals(concurrencyLevel, that.concurrencyLevel) + && Objects.equals(keyStrength, that.keyStrength) + && Objects.equals(valueStrength, that.valueStrength) + && Objects.equals(recordStats, that.recordStats) + && Objects.equals( durationInNanos(writeExpirationDuration, writeExpirationTimeUnit), durationInNanos(that.writeExpirationDuration, that.writeExpirationTimeUnit)) - && Objects.equal( + && Objects.equals( durationInNanos(accessExpirationDuration, accessExpirationTimeUnit), durationInNanos(that.accessExpirationDuration, that.accessExpirationTimeUnit)) - && Objects.equal( + && Objects.equals( durationInNanos(refreshDuration, refreshTimeUnit), durationInNanos(that.refreshDuration, that.refreshTimeUnit)); } @@ -278,8 +284,7 @@ public boolean equals(@NullableDecl Object obj) { * Converts an expiration duration/unit pair into a single Long for hashing and equality. Uses * nanos to match CacheBuilder implementation. */ - @NullableDecl - private static Long durationInNanos(long duration, @NullableDecl TimeUnit unit) { + private static @Nullable Long durationInNanos(long duration, @Nullable TimeUnit unit) { return (unit == null) ? null : unit.toNanos(duration); } @@ -288,8 +293,10 @@ abstract static class IntegerParser implements ValueParser { protected abstract void parseInteger(CacheBuilderSpec spec, int value); @Override - public void parse(CacheBuilderSpec spec, String key, String value) { - checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { + if (isNullOrEmpty(value)) { + throw new IllegalArgumentException("value of key " + key + " omitted"); + } try { parseInteger(spec, Integer.parseInt(value)); } catch (NumberFormatException e) { @@ -304,8 +311,10 @@ abstract static class LongParser implements ValueParser { protected abstract void parseLong(CacheBuilderSpec spec, long value); @Override - public void parse(CacheBuilderSpec spec, String key, String value) { - checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { + if (isNullOrEmpty(value)) { + throw new IllegalArgumentException("value of key " + key + " omitted"); + } try { parseLong(spec, Long.parseLong(value)); } catch (NumberFormatException e) { @@ -316,53 +325,55 @@ public void parse(CacheBuilderSpec spec, String key, String value) { } /** Parse initialCapacity */ - static class InitialCapacityParser extends IntegerParser { + private static final class InitialCapacityParser extends IntegerParser { @Override protected void parseInteger(CacheBuilderSpec spec, int value) { checkArgument( spec.initialCapacity == null, - "initial capacity was already set to ", + "initial capacity was already set to %s", spec.initialCapacity); spec.initialCapacity = value; } } /** Parse maximumSize */ - static class MaximumSizeParser extends LongParser { + private static final class MaximumSizeParser extends LongParser { @Override protected void parseLong(CacheBuilderSpec spec, long value) { - checkArgument(spec.maximumSize == null, "maximum size was already set to ", spec.maximumSize); checkArgument( - spec.maximumWeight == null, "maximum weight was already set to ", spec.maximumWeight); + spec.maximumSize == null, "maximum size was already set to %s", spec.maximumSize); + checkArgument( + spec.maximumWeight == null, "maximum weight was already set to %s", spec.maximumWeight); spec.maximumSize = value; } } /** Parse maximumWeight */ - static class MaximumWeightParser extends LongParser { + private static final class MaximumWeightParser extends LongParser { @Override protected void parseLong(CacheBuilderSpec spec, long value) { checkArgument( - spec.maximumWeight == null, "maximum weight was already set to ", spec.maximumWeight); - checkArgument(spec.maximumSize == null, "maximum size was already set to ", spec.maximumSize); + spec.maximumWeight == null, "maximum weight was already set to %s", spec.maximumWeight); + checkArgument( + spec.maximumSize == null, "maximum size was already set to %s", spec.maximumSize); spec.maximumWeight = value; } } /** Parse concurrencyLevel */ - static class ConcurrencyLevelParser extends IntegerParser { + private static final class ConcurrencyLevelParser extends IntegerParser { @Override protected void parseInteger(CacheBuilderSpec spec, int value) { checkArgument( spec.concurrencyLevel == null, - "concurrency level was already set to ", + "concurrency level was already set to %s", spec.concurrencyLevel); spec.concurrencyLevel = value; } } /** Parse weakKeys */ - static class KeyStrengthParser implements ValueParser { + private static final class KeyStrengthParser implements ValueParser { private final Strength strength; public KeyStrengthParser(Strength strength) { @@ -370,7 +381,7 @@ public KeyStrengthParser(Strength strength) { } @Override - public void parse(CacheBuilderSpec spec, String key, @NullableDecl String value) { + public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { checkArgument(value == null, "key %s does not take values", key); checkArgument(spec.keyStrength == null, "%s was already set to %s", key, spec.keyStrength); spec.keyStrength = strength; @@ -378,7 +389,7 @@ public void parse(CacheBuilderSpec spec, String key, @NullableDecl String value) } /** Parse weakValues and softValues */ - static class ValueStrengthParser implements ValueParser { + private static final class ValueStrengthParser implements ValueParser { private final Strength strength; public ValueStrengthParser(Strength strength) { @@ -386,7 +397,7 @@ public ValueStrengthParser(Strength strength) { } @Override - public void parse(CacheBuilderSpec spec, String key, @NullableDecl String value) { + public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { checkArgument(value == null, "key %s does not take values", key); checkArgument( spec.valueStrength == null, "%s was already set to %s", key, spec.valueStrength); @@ -396,10 +407,10 @@ public void parse(CacheBuilderSpec spec, String key, @NullableDecl String value) } /** Parse recordStats */ - static class RecordStatsParser implements ValueParser { + private static final class RecordStatsParser implements ValueParser { @Override - public void parse(CacheBuilderSpec spec, String key, @NullableDecl String value) { + public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { checkArgument(value == null, "recordStats does not take values"); checkArgument(spec.recordStats == null, "recordStats already set"); spec.recordStats = true; @@ -411,23 +422,25 @@ abstract static class DurationParser implements ValueParser { protected abstract void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit); @Override - public void parse(CacheBuilderSpec spec, String key, String value) { - checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + public void parse(CacheBuilderSpec spec, String key, @Nullable String value) { + if (isNullOrEmpty(value)) { + throw new IllegalArgumentException("value of key " + key + " omitted"); + } try { char lastChar = value.charAt(value.length() - 1); TimeUnit timeUnit; switch (lastChar) { case 'd': - timeUnit = TimeUnit.DAYS; + timeUnit = DAYS; break; case 'h': - timeUnit = TimeUnit.HOURS; + timeUnit = HOURS; break; case 'm': - timeUnit = TimeUnit.MINUTES; + timeUnit = MINUTES; break; case 's': - timeUnit = TimeUnit.SECONDS; + timeUnit = SECONDS; break; default: throw new IllegalArgumentException( @@ -444,7 +457,7 @@ public void parse(CacheBuilderSpec spec, String key, String value) { } /** Parse expireAfterAccess */ - static class AccessDurationParser extends DurationParser { + private static final class AccessDurationParser extends DurationParser { @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { checkArgument(spec.accessExpirationTimeUnit == null, "expireAfterAccess already set"); @@ -454,7 +467,7 @@ protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit } /** Parse expireAfterWrite */ - static class WriteDurationParser extends DurationParser { + private static final class WriteDurationParser extends DurationParser { @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { checkArgument(spec.writeExpirationTimeUnit == null, "expireAfterWrite already set"); @@ -464,7 +477,7 @@ protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit } /** Parse refreshAfterWrite */ - static class RefreshDurationParser extends DurationParser { + private static final class RefreshDurationParser extends DurationParser { @Override protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { checkArgument(spec.refreshTimeUnit == null, "refreshAfterWrite already set"); diff --git a/android/guava/src/com/google/common/cache/CacheLoader.java b/android/guava/src/com/google/common/cache/CacheLoader.java index aa1da922b90b..bf1235f9f5ea 100644 --- a/android/guava/src/com/google/common/cache/CacheLoader.java +++ b/android/guava/src/com/google/common/cache/CacheLoader.java @@ -15,18 +15,17 @@ package com.google.common.cache; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Futures.immediateFuture; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Supplier; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; -import com.google.errorprone.annotations.CheckReturnValue; import java.io.Serializable; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.Executor; /** @@ -37,26 +36,26 @@ * *

    Usage example: * - *

    {@code
    + * {@snippet :
      * CacheLoader loader = new CacheLoader() {
      *   public Graph load(Key key) throws AnyException {
      *     return createExpensiveGraph(key);
      *   }
      * };
      * LoadingCache cache = CacheBuilder.newBuilder().build(loader);
    - * }
    + * } * *

    Since this example doesn't support reloading or bulk loading, if you're able to use lambda * expressions it can be specified even more easily: * - *

    {@code
    + * {@snippet :
      * CacheLoader loader = CacheLoader.from(key -> createExpensiveGraph(key));
    - * }
    + * } * * @author Charles Fry * @since 10.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public abstract class CacheLoader { /** Constructor for use by subclasses. */ protected CacheLoader() {} @@ -98,7 +97,7 @@ protected CacheLoader() {} public ListenableFuture reload(K key, V oldValue) throws Exception { checkNotNull(key); checkNotNull(oldValue); - return Futures.immediateFuture(load(key)); + return immediateFuture(load(key)); } /** @@ -134,10 +133,11 @@ public Map loadAll(Iterable keys) throws Exception { * reloading or bulk loading. This is most useful when you can pass a lambda expression. Otherwise * it is useful mostly when you already have an existing function instance. * + *

    The returned object is serializable if {@code function} is serializable. + * * @param function the function to be used for loading values; must never return {@code null} * @return a cache loader that loads values by passing each key to {@code function} */ - @CheckReturnValue public static CacheLoader from(Function function) { return new FunctionToCacheLoader<>(function); } @@ -147,20 +147,21 @@ public static CacheLoader from(Function function) { * to create a new supplier just to pass it in here; just subclass {@code CacheLoader} and * implement {@link #load load} instead. * + *

    The returned object is serializable if {@code supplier} is serializable. + * * @param supplier the supplier to be used for loading values; must never return {@code null} * @return a cache loader that loads values by calling {@link Supplier#get}, irrespective of the * key */ - @CheckReturnValue public static CacheLoader from(Supplier supplier) { - return new SupplierToCacheLoader(supplier); + return new SupplierToCacheLoader<>(supplier); } private static final class FunctionToCacheLoader extends CacheLoader implements Serializable { private final Function computingFunction; - public FunctionToCacheLoader(Function computingFunction) { + FunctionToCacheLoader(Function computingFunction) { this.computingFunction = checkNotNull(computingFunction); } @@ -169,7 +170,7 @@ public V load(K key) { return computingFunction.apply(checkNotNull(key)); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -181,10 +182,9 @@ public V load(K key) { * * @since 17.0 */ - @CheckReturnValue @GwtIncompatible // Executor + Futures public static CacheLoader asyncReloading( - final CacheLoader loader, final Executor executor) { + CacheLoader loader, Executor executor) { checkNotNull(loader); checkNotNull(executor); return new CacheLoader() { @@ -194,15 +194,9 @@ public V load(K key) throws Exception { } @Override - public ListenableFuture reload(final K key, final V oldValue) throws Exception { + public ListenableFuture reload(K key, V oldValue) { ListenableFutureTask task = - ListenableFutureTask.create( - new Callable() { - @Override - public V call() throws Exception { - return loader.reload(key, oldValue).get(); - } - }); + ListenableFutureTask.create(() -> loader.reload(key, oldValue).get()); executor.execute(task); return task; } @@ -218,7 +212,7 @@ private static final class SupplierToCacheLoader extends CacheLoader computingSupplier; - public SupplierToCacheLoader(Supplier computingSupplier) { + SupplierToCacheLoader(Supplier computingSupplier) { this.computingSupplier = checkNotNull(computingSupplier); } @@ -228,7 +222,7 @@ public V load(Object key) { return computingSupplier.get(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** diff --git a/android/guava/src/com/google/common/cache/CacheStats.java b/android/guava/src/com/google/common/cache/CacheStats.java index e0c39c3d7e67..b9b5c861b51e 100644 --- a/android/guava/src/com/google/common/cache/CacheStats.java +++ b/android/guava/src/com/google/common/cache/CacheStats.java @@ -17,12 +17,13 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.math.LongMath.saturatedAdd; import static com.google.common.math.LongMath.saturatedSubtract; +import static java.lang.Math.max; import com.google.common.annotations.GwtCompatible; import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; +import java.util.Objects; import java.util.concurrent.Callable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Statistics about the performance of a {@link Cache}. Instances of this class are immutable. @@ -139,7 +140,7 @@ public long missCount() { * requestCount}, or {@code 0.0} when {@code requestCount == 0}. Note that {@code hitRate + * missRate =~ 1.0}. Cache misses include all requests which weren't cache hits, including * requests which resulted in either successful or failed loading attempts, and requests which - * waited for other threads to finish loading. It is thus the case that {@code missCount >= + * waited for other threads to finish loading. It is thus the case that {@code missCount >= * loadSuccessCount + loadExceptionCount}. Multiple concurrent misses for the same key will result * in a single load operation. */ @@ -150,8 +151,8 @@ public double missRate() { /** * Returns the total number of times that {@link Cache} lookup methods attempted to load new - * values. This includes both successful load operations, as well as those that threw exceptions. - * This is defined as {@code loadSuccessCount + loadExceptionCount}. + * values. This includes both successful load operations and those that threw exceptions. This is + * defined as {@code loadSuccessCount + loadExceptionCount}. * *

    Note: the values of the metrics are undefined in case of overflow (though it is * guaranteed not to throw an exception). If you require specific handling, we recommend @@ -241,12 +242,12 @@ public long evictionCount() { */ public CacheStats minus(CacheStats other) { return new CacheStats( - Math.max(0, saturatedSubtract(hitCount, other.hitCount)), - Math.max(0, saturatedSubtract(missCount, other.missCount)), - Math.max(0, saturatedSubtract(loadSuccessCount, other.loadSuccessCount)), - Math.max(0, saturatedSubtract(loadExceptionCount, other.loadExceptionCount)), - Math.max(0, saturatedSubtract(totalLoadTime, other.totalLoadTime)), - Math.max(0, saturatedSubtract(evictionCount, other.evictionCount))); + max(0, saturatedSubtract(hitCount, other.hitCount)), + max(0, saturatedSubtract(missCount, other.missCount)), + max(0, saturatedSubtract(loadSuccessCount, other.loadSuccessCount)), + max(0, saturatedSubtract(loadExceptionCount, other.loadExceptionCount)), + max(0, saturatedSubtract(totalLoadTime, other.totalLoadTime)), + max(0, saturatedSubtract(evictionCount, other.evictionCount))); } /** @@ -271,12 +272,12 @@ public CacheStats plus(CacheStats other) { @Override public int hashCode() { - return Objects.hashCode( + return Objects.hash( hitCount, missCount, loadSuccessCount, loadExceptionCount, totalLoadTime, evictionCount); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof CacheStats) { CacheStats other = (CacheStats) object; return hitCount == other.hitCount diff --git a/android/guava/src/com/google/common/cache/ForwardingCache.java b/android/guava/src/com/google/common/cache/ForwardingCache.java index 217042b099e1..be7df89a3566 100644 --- a/android/guava/src/com/google/common/cache/ForwardingCache.java +++ b/android/guava/src/com/google/common/cache/ForwardingCache.java @@ -22,7 +22,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A cache which forwards all its method calls to another cache. Subclasses should override one or @@ -41,32 +41,45 @@ protected ForwardingCache() {} @Override protected abstract Cache delegate(); - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override - @NullableDecl - public V getIfPresent(Object key) { + public @Nullable V getIfPresent(Object key) { return delegate().getIfPresent(key); } - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override public V get(K key, Callable valueLoader) throws ExecutionException { return delegate().get(key, valueLoader); } - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override - public ImmutableMap getAllPresent(Iterable keys) { + /* + * is mostly the same as to plain Java. But to nullness checkers, they + * differ: means "non-null types," while means "all types." + */ + public ImmutableMap getAllPresent(Iterable keys) { return delegate().getAllPresent(keys); } - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override public void put(K key, V value) { delegate().put(key, value); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @Override public void putAll(Map m) { delegate().putAll(m); @@ -77,9 +90,12 @@ public void invalidate(Object key) { delegate().invalidate(key); } - /** @since 11.0 */ + /** + * @since 11.0 + */ @Override - public void invalidateAll(Iterable keys) { + // For discussion of , see getAllPresent. + public void invalidateAll(Iterable keys) { delegate().invalidateAll(keys); } diff --git a/android/guava/src/com/google/common/cache/ForwardingLoadingCache.java b/android/guava/src/com/google/common/cache/ForwardingLoadingCache.java index ba88ded9bbc1..296c44f484e2 100644 --- a/android/guava/src/com/google/common/cache/ForwardingLoadingCache.java +++ b/android/guava/src/com/google/common/cache/ForwardingLoadingCache.java @@ -17,6 +17,7 @@ import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.concurrent.ExecutionException; /** @@ -40,16 +41,19 @@ protected ForwardingLoadingCache() {} @Override protected abstract LoadingCache delegate(); + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this @Override public V get(K key) throws ExecutionException { return delegate().get(key); } + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this @Override public V getUnchecked(K key) { return delegate().getUnchecked(key); } + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this @Override public ImmutableMap getAll(Iterable keys) throws ExecutionException { return delegate().getAll(keys); diff --git a/android/guava/src/com/google/common/cache/IgnoreJRERequirement.java b/android/guava/src/com/google/common/cache/IgnoreJRERequirement.java new file mode 100644 index 000000000000..5c2f743d1582 --- /dev/null +++ b/android/guava/src/com/google/common/cache/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/cache/LoadingCache.java b/android/guava/src/com/google/common/cache/LoadingCache.java index 9e812c3ac9bf..d60e9df8720b 100644 --- a/android/guava/src/com/google/common/cache/LoadingCache.java +++ b/android/guava/src/com/google/common/cache/LoadingCache.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ExecutionError; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; @@ -33,6 +34,8 @@ *

    When evaluated as a {@link Function}, a cache yields the same result as invoking {@link * #getUnchecked}. * + * @param the type of the cache's keys, which are not permitted to be null + * @param the type of the cache's values, which are not permitted to be null * @author Charles Fry * @since 11.0 */ @@ -64,6 +67,7 @@ public interface LoadingCache extends Cache, Function { * value * @throws ExecutionError if an error was thrown while loading the value */ + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this? V get(K key) throws ExecutionException; /** @@ -90,6 +94,7 @@ public interface LoadingCache extends Cache, Function { * explained in the last paragraph above, this should be an unchecked exception only.) * @throws ExecutionError if an error was thrown while loading the value */ + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this? V getUnchecked(K key); /** @@ -116,6 +121,7 @@ public interface LoadingCache extends Cache, Function { * @throws ExecutionError if an error was thrown while loading the values * @since 11.0 */ + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this ImmutableMap getAll(Iterable keys) throws ExecutionException; /** diff --git a/android/guava/src/com/google/common/cache/LocalCache.java b/android/guava/src/com/google/common/cache/LocalCache.java index 4ec9320153cb..1acbe7982ed3 100644 --- a/android/guava/src/com/google/common/cache/LocalCache.java +++ b/android/guava/src/com/google/common/cache/LocalCache.java @@ -18,16 +18,20 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.cache.CacheBuilder.NULL_TICKER; import static com.google.common.cache.CacheBuilder.UNSET_INT; +import static com.google.common.util.concurrent.Futures.immediateFailedFuture; +import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.Futures.transform; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static java.lang.Math.min; +import static java.util.Collections.unmodifiableSet; import static java.util.concurrent.TimeUnit.NANOSECONDS; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Equivalence; -import com.google.common.base.Function; import com.google.common.base.Stopwatch; import com.google.common.base.Ticker; import com.google.common.cache.AbstractCache.SimpleStatsCounter; @@ -36,23 +40,23 @@ import com.google.common.cache.CacheBuilder.OneWeigher; import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.cache.CacheLoader.UnsupportedLoadingOperationException; +import com.google.common.cache.LocalCache.AbstractCacheSet; import com.google.common.collect.AbstractSequentialIterator; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterators; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import com.google.common.util.concurrent.ExecutionError; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.common.util.concurrent.Uninterruptibles; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.concurrent.GuardedBy; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedWith; import com.google.j2objc.annotations.Weak; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.ref.Reference; @@ -63,11 +67,11 @@ import java.util.AbstractMap; import java.util.AbstractQueue; import java.util.AbstractSet; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; -import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Set; @@ -75,13 +79,13 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * The concurrent hash map implementation built by {@link CacheBuilder}. @@ -93,9 +97,13 @@ * @author Bob Lee ({@code com.google.common.collect.MapMaker}) * @author Doug Lea ({@code ConcurrentHashMap}) */ -@SuppressWarnings("GoodTime") // lots of violations (nanosecond math) -@GwtCompatible(emulated = true) -class LocalCache extends AbstractMap implements ConcurrentMap { +@SuppressWarnings({ + "GoodTime", // lots of violations (nanosecond math) + "nullness", // too much trouble for the payoff +}) +@GwtCompatible +@NullUnmarked // TODO(cpovirk): Annotate for nullness. +final class LocalCache extends AbstractMap implements ConcurrentMap { /* * The basic strategy is to subdivide the table among Segments, each of which itself is a @@ -226,14 +234,14 @@ class LocalCache extends AbstractMap implements ConcurrentMap final StatsCounter globalStatsCounter; /** The default cache loader to use on loading operations. */ - @NullableDecl final CacheLoader defaultLoader; + final @Nullable CacheLoader defaultLoader; /** * Creates a new, empty map with the specified strategy, initial capacity and concurrency level. */ LocalCache( - CacheBuilder builder, @NullableDecl CacheLoader loader) { - concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); + CacheBuilder builder, @Nullable CacheLoader loader) { + concurrencyLevel = min(builder.getConcurrencyLevel(), MAX_SEGMENTS); keyStrength = builder.getKeyStrength(); valueStrength = builder.getValueStrength(); @@ -250,17 +258,17 @@ class LocalCache extends AbstractMap implements ConcurrentMap removalListener = builder.getRemovalListener(); removalNotificationQueue = (removalListener == NullListener.INSTANCE) - ? LocalCache.>discardingQueue() - : new ConcurrentLinkedQueue>(); + ? LocalCache.discardingQueue() + : new ConcurrentLinkedQueue<>(); ticker = builder.getTicker(recordsTime()); entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries()); globalStatsCounter = builder.getStatsCounterSupplier().get(); defaultLoader = loader; - int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); + int initialCapacity = min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); if (evictsBySize() && !customWeigher()) { - initialCapacity = (int) Math.min(initialCapacity, maxWeight); + initialCapacity = (int) min(initialCapacity, maxWeight); } // Find the lowest power-of-two segmentCount that exceeds concurrencyLevel, unless @@ -270,7 +278,8 @@ class LocalCache extends AbstractMap implements ConcurrentMap // will result in random eviction behavior. int segmentShift = 0; int segmentCount = 1; - while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) { + while (segmentCount < concurrencyLevel + && (!evictsBySize() || segmentCount * 20L <= maxWeight)) { ++segmentShift; segmentCount <<= 1; } @@ -436,21 +445,24 @@ enum EntryFactory { STRONG { @Override ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next) { + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { return new StrongEntry<>(key, hash, next); } }, STRONG_ACCESS { @Override ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next) { + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { return new StrongAccessEntry<>(key, hash, next); } @Override ReferenceEntry copyEntry( - Segment segment, ReferenceEntry original, ReferenceEntry newNext) { - ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + Segment segment, + ReferenceEntry original, + ReferenceEntry newNext, + K key) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key); copyAccessEntry(original, newEntry); return newEntry; } @@ -458,14 +470,17 @@ ReferenceEntry copyEntry( STRONG_WRITE { @Override ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next) { + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { return new StrongWriteEntry<>(key, hash, next); } @Override ReferenceEntry copyEntry( - Segment segment, ReferenceEntry original, ReferenceEntry newNext) { - ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + Segment segment, + ReferenceEntry original, + ReferenceEntry newNext, + K key) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key); copyWriteEntry(original, newEntry); return newEntry; } @@ -473,14 +488,17 @@ ReferenceEntry copyEntry( STRONG_ACCESS_WRITE { @Override ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next) { + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { return new StrongAccessWriteEntry<>(key, hash, next); } @Override ReferenceEntry copyEntry( - Segment segment, ReferenceEntry original, ReferenceEntry newNext) { - ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + Segment segment, + ReferenceEntry original, + ReferenceEntry newNext, + K key) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key); copyAccessEntry(original, newEntry); copyWriteEntry(original, newEntry); return newEntry; @@ -489,21 +507,24 @@ ReferenceEntry copyEntry( WEAK { @Override ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next) { + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { return new WeakEntry<>(segment.keyReferenceQueue, key, hash, next); } }, WEAK_ACCESS { @Override ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next) { + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { return new WeakAccessEntry<>(segment.keyReferenceQueue, key, hash, next); } @Override ReferenceEntry copyEntry( - Segment segment, ReferenceEntry original, ReferenceEntry newNext) { - ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + Segment segment, + ReferenceEntry original, + ReferenceEntry newNext, + K key) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key); copyAccessEntry(original, newEntry); return newEntry; } @@ -511,14 +532,17 @@ ReferenceEntry copyEntry( WEAK_WRITE { @Override ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next) { + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { return new WeakWriteEntry<>(segment.keyReferenceQueue, key, hash, next); } @Override ReferenceEntry copyEntry( - Segment segment, ReferenceEntry original, ReferenceEntry newNext) { - ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + Segment segment, + ReferenceEntry original, + ReferenceEntry newNext, + K key) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key); copyWriteEntry(original, newEntry); return newEntry; } @@ -526,14 +550,17 @@ ReferenceEntry copyEntry( WEAK_ACCESS_WRITE { @Override ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next) { + Segment segment, K key, int hash, @Nullable ReferenceEntry next) { return new WeakAccessWriteEntry<>(segment.keyReferenceQueue, key, hash, next); } @Override ReferenceEntry copyEntry( - Segment segment, ReferenceEntry original, ReferenceEntry newNext) { - ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + Segment segment, + ReferenceEntry original, + ReferenceEntry newNext, + K key) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key); copyAccessEntry(original, newEntry); copyWriteEntry(original, newEntry); return newEntry; @@ -576,18 +603,23 @@ static EntryFactory getFactory( * @param next entry in the same bucket */ abstract ReferenceEntry newEntry( - Segment segment, K key, int hash, @NullableDecl ReferenceEntry next); + Segment segment, K key, int hash, @Nullable ReferenceEntry next); /** * Copies an entry, assigning it a new {@code next} entry. * - * @param original the entry to copy + * @param original the entry to copy. But avoid calling {@code getKey} on it: Instead, use the + * {@code key} parameter. That way, we prevent the key from being garbage collected in the + * case of weak keys. If we create a new entry with a key that is null at construction time, + * we're not sure if entry will necessarily ever be garbage collected. * @param newNext entry in the same bucket + * @param key the key to copy from the original entry to the new one. Use this in preference to + * {@code original.getKey()}. */ // Guarded By Segment.this ReferenceEntry copyEntry( - Segment segment, ReferenceEntry original, ReferenceEntry newNext) { - return newEntry(segment, original.getKey(), original.getHash(), newNext); + Segment segment, ReferenceEntry original, ReferenceEntry newNext, K key) { + return newEntry(segment, key, original.getHash(), newNext); } // Guarded By Segment.this @@ -618,8 +650,7 @@ void copyWriteEntry(ReferenceEntry original, ReferenceEntry n /** A reference to a value. */ interface ValueReference { /** Returns the value. Does not block or throw exceptions. */ - @NullableDecl - V get(); + @Nullable V get(); /** * Waits for a value that may still be loading. Unlike get(), this method can block (in the case @@ -637,8 +668,7 @@ interface ValueReference { * Returns the entry associated with this value reference, or {@code null} if this value * reference is independent of any entry. */ - @NullableDecl - ReferenceEntry getEntry(); + @Nullable ReferenceEntry getEntry(); /** * Creates a copy of this reference for the given entry. @@ -646,17 +676,17 @@ interface ValueReference { *

    {@code value} may be null only for a loading reference. */ ValueReference copyFor( - ReferenceQueue queue, @NullableDecl V value, ReferenceEntry entry); + ReferenceQueue queue, @Nullable V value, ReferenceEntry entry); /** * Notify pending loads that a new value was set. This is only relevant to loading value * references. */ - void notifyNewValue(@NullableDecl V newValue); + void notifyNewValue(@Nullable V newValue); /** - * Returns true if a new value is currently loading, regardless of whether or not there is an - * existing value. It is assumed that the return value of this method is constant for any given + * Returns true if a new value is currently loading, regardless of whether there is an existing + * value. It is assumed that the return value of this method is constant for any given * ValueReference instance. */ boolean isLoading(); @@ -675,7 +705,7 @@ ValueReference copyFor( static final ValueReference UNSET = new ValueReference() { @Override - public Object get() { + public @Nullable Object get() { return null; } @@ -685,14 +715,14 @@ public int getWeight() { } @Override - public ReferenceEntry getEntry() { + public @Nullable ReferenceEntry getEntry() { return null; } @Override public ValueReference copyFor( ReferenceQueue queue, - @NullableDecl Object value, + @Nullable Object value, ReferenceEntry entry) { return this; } @@ -708,7 +738,7 @@ public boolean isActive() { } @Override - public Object waitForValue() { + public @Nullable Object waitForValue() { return null; } @@ -726,7 +756,7 @@ private enum NullEntry implements ReferenceEntry { INSTANCE; @Override - public ValueReference getValueReference() { + public @Nullable ValueReference getValueReference() { return null; } @@ -734,7 +764,7 @@ public ValueReference getValueReference() { public void setValueReference(ValueReference valueReference) {} @Override - public ReferenceEntry getNext() { + public @Nullable ReferenceEntry getNext() { return null; } @@ -744,7 +774,7 @@ public int getHash() { } @Override - public Object getKey() { + public @Nullable Object getKey() { return null; } @@ -897,12 +927,12 @@ public boolean offer(Object o) { } @Override - public Object peek() { + public @Nullable Object peek() { return null; } @Override - public Object poll() { + public @Nullable Object poll() { return null; } @@ -932,10 +962,10 @@ static Queue discardingQueue() { */ /** Used for strongly-referenced keys. */ - static class StrongEntry extends AbstractReferenceEntry { + private static class StrongEntry extends AbstractReferenceEntry { final K key; - StrongEntry(K key, int hash, @NullableDecl ReferenceEntry next) { + StrongEntry(K key, int hash, @Nullable ReferenceEntry next) { this.key = key; this.hash = hash; this.next = next; @@ -949,7 +979,7 @@ public K getKey() { // The code below is exactly the same for each entry type. final int hash; - @NullableDecl final ReferenceEntry next; + final @Nullable ReferenceEntry next; volatile ValueReference valueReference = unset(); @Override @@ -974,7 +1004,7 @@ public ReferenceEntry getNext() { } static final class StrongAccessEntry extends StrongEntry { - StrongAccessEntry(K key, int hash, @NullableDecl ReferenceEntry next) { + StrongAccessEntry(K key, int hash, @Nullable ReferenceEntry next) { super(key, hash, next); } @@ -1020,7 +1050,7 @@ public void setPreviousInAccessQueue(ReferenceEntry previous) { } static final class StrongWriteEntry extends StrongEntry { - StrongWriteEntry(K key, int hash, @NullableDecl ReferenceEntry next) { + StrongWriteEntry(K key, int hash, @Nullable ReferenceEntry next) { super(key, hash, next); } @@ -1066,7 +1096,7 @@ public void setPreviousInWriteQueue(ReferenceEntry previous) { } static final class StrongAccessWriteEntry extends StrongEntry { - StrongAccessWriteEntry(K key, int hash, @NullableDecl ReferenceEntry next) { + StrongAccessWriteEntry(K key, int hash, @Nullable ReferenceEntry next) { super(key, hash, next); } @@ -1152,8 +1182,8 @@ public void setPreviousInWriteQueue(ReferenceEntry previous) { } /** Used for weakly-referenced keys. */ - static class WeakEntry extends WeakReference implements ReferenceEntry { - WeakEntry(ReferenceQueue queue, K key, int hash, @NullableDecl ReferenceEntry next) { + private static class WeakEntry extends WeakReference implements ReferenceEntry { + WeakEntry(ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { super(key, queue); this.hash = hash; this.next = next; @@ -1236,7 +1266,7 @@ public void setPreviousInWriteQueue(ReferenceEntry previous) { // The code below is exactly the same for each entry type. final int hash; - @NullableDecl final ReferenceEntry next; + final @Nullable ReferenceEntry next; volatile ValueReference valueReference = unset(); @Override @@ -1261,8 +1291,7 @@ public ReferenceEntry getNext() { } static final class WeakAccessEntry extends WeakEntry { - WeakAccessEntry( - ReferenceQueue queue, K key, int hash, @NullableDecl ReferenceEntry next) { + WeakAccessEntry(ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { super(queue, key, hash, next); } @@ -1308,8 +1337,7 @@ public void setPreviousInAccessQueue(ReferenceEntry previous) { } static final class WeakWriteEntry extends WeakEntry { - WeakWriteEntry( - ReferenceQueue queue, K key, int hash, @NullableDecl ReferenceEntry next) { + WeakWriteEntry(ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { super(queue, key, hash, next); } @@ -1356,7 +1384,7 @@ public void setPreviousInWriteQueue(ReferenceEntry previous) { static final class WeakAccessWriteEntry extends WeakEntry { WeakAccessWriteEntry( - ReferenceQueue queue, K key, int hash, @NullableDecl ReferenceEntry next) { + ReferenceQueue queue, K key, int hash, @Nullable ReferenceEntry next) { super(queue, key, hash, next); } @@ -1442,7 +1470,8 @@ public void setPreviousInWriteQueue(ReferenceEntry previous) { } /** References a weak value. */ - static class WeakValueReference extends WeakReference implements ValueReference { + private static class WeakValueReference extends WeakReference + implements ValueReference { final ReferenceEntry entry; WeakValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry) { @@ -1486,7 +1515,8 @@ public V waitForValue() { } /** References a soft value. */ - static class SoftValueReference extends SoftReference implements ValueReference { + private static class SoftValueReference extends SoftReference + implements ValueReference { final ReferenceEntry entry; SoftValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry) { @@ -1530,7 +1560,7 @@ public V waitForValue() { } /** References a strong value. */ - static class StrongValueReference implements ValueReference { + private static class StrongValueReference implements ValueReference { final V referent; StrongValueReference(V referent) { @@ -1649,9 +1679,9 @@ static int rehash(int h) { // using variant of single-word Wang/Jenkins hash. // TODO(kevinb): use Hashing/move this to Hashing? h += (h << 15) ^ 0xffffcd7d; - h ^= (h >>> 10); - h += (h << 3); - h ^= (h >>> 6); + h ^= h >>> 10; + h += h << 3; + h ^= h >>> 6; h += (h << 2) + (h << 14); return h ^ (h >>> 16); } @@ -1660,7 +1690,7 @@ static int rehash(int h) { * This method is a convenience for testing. Code should call {@link Segment#newEntry} directly. */ @VisibleForTesting - ReferenceEntry newEntry(K key, int hash, @NullableDecl ReferenceEntry next) { + ReferenceEntry newEntry(K key, int hash, @Nullable ReferenceEntry next) { Segment segment = segmentFor(hash); segment.lock(); try { @@ -1691,7 +1721,7 @@ ValueReference newValueReference(ReferenceEntry entry, V value, int return valueStrength.referenceValue(segmentFor(hash), entry, checkNotNull(value), weight); } - int hash(@NullableDecl Object key) { + int hash(@Nullable Object key) { int h = keyEquivalence.hash(key); return rehash(h); } @@ -1734,12 +1764,11 @@ Segment createSegment( /** * Gets the value from an entry. Returns null if the entry is invalid, partially-collected, - * loading, or expired. Unlike {@link Segment#getLiveValue} this method does not attempt to - * cleanup stale entries. As such it should only be called outside of a segment context, such as - * during iteration. + * loading, or expired. Unlike {@link Segment#getLiveValue} this method does not attempt to clean + * up stale entries. As such it should only be called outside a segment context, such as during + * iteration. */ - @NullableDecl - V getLiveValue(ReferenceEntry entry, long now) { + @Nullable V getLiveValue(ReferenceEntry entry, long now) { if (entry.getKey() == null) { return null; } @@ -1814,7 +1843,7 @@ void processPendingNotifications() { @SuppressWarnings("unchecked") final Segment[] newSegmentArray(int ssize) { - return new Segment[ssize]; + return (Segment[]) new Segment[ssize]; } // Inner Classes @@ -1824,7 +1853,7 @@ final Segment[] newSegmentArray(int ssize) { * opportunistically, just to simplify some locking and avoid separate construction. */ @SuppressWarnings("serial") // This class is never serialized. - static class Segment extends ReentrantLock { + static final class Segment extends ReentrantLock { /* * TODO(fry): Consider copying variables (like evictsBySize) from outer class into this class. @@ -1883,7 +1912,7 @@ static class Segment extends ReentrantLock { int threshold; /** The per-segment table. */ - @NullableDecl volatile AtomicReferenceArray> table; + volatile @Nullable AtomicReferenceArray> table; /** The maximum weight of this segment. UNSET_INT if there is no maximum. */ final long maxSegmentWeight; @@ -1892,13 +1921,13 @@ static class Segment extends ReentrantLock { * The key reference queue contains entries whose keys have been garbage collected, and which * need to be cleaned up internally. */ - @NullableDecl final ReferenceQueue keyReferenceQueue; + final @Nullable ReferenceQueue keyReferenceQueue; /** * The value reference queue contains value references whose values have been garbage collected, * and which need to be cleaned up internally. */ - @NullableDecl final ReferenceQueue valueReferenceQueue; + final @Nullable ReferenceQueue valueReferenceQueue; /** * The recency queue is used to record which entries were accessed for updating the access @@ -1940,24 +1969,16 @@ static class Segment extends ReentrantLock { this.statsCounter = checkNotNull(statsCounter); initTable(newEntryArray(initialCapacity)); - keyReferenceQueue = map.usesKeyReferences() ? new ReferenceQueue() : null; + keyReferenceQueue = map.usesKeyReferences() ? new ReferenceQueue<>() : null; - valueReferenceQueue = map.usesValueReferences() ? new ReferenceQueue() : null; + valueReferenceQueue = map.usesValueReferences() ? new ReferenceQueue<>() : null; recencyQueue = - map.usesAccessQueue() - ? new ConcurrentLinkedQueue>() - : LocalCache.>discardingQueue(); + map.usesAccessQueue() ? new ConcurrentLinkedQueue<>() : LocalCache.discardingQueue(); - writeQueue = - map.usesWriteQueue() - ? new WriteQueue() - : LocalCache.>discardingQueue(); + writeQueue = map.usesWriteQueue() ? new WriteQueue<>() : LocalCache.discardingQueue(); - accessQueue = - map.usesAccessQueue() - ? new AccessQueue() - : LocalCache.>discardingQueue(); + accessQueue = map.usesAccessQueue() ? new AccessQueue<>() : LocalCache.discardingQueue(); } AtomicReferenceArray> newEntryArray(int size) { @@ -1974,7 +1995,7 @@ void initTable(AtomicReferenceArray> newTable) { } @GuardedBy("this") - ReferenceEntry newEntry(K key, int hash, @NullableDecl ReferenceEntry next) { + ReferenceEntry newEntry(K key, int hash, @Nullable ReferenceEntry next) { return map.entryFactory.newEntry(this, checkNotNull(key), hash, next); } @@ -1983,8 +2004,10 @@ ReferenceEntry newEntry(K key, int hash, @NullableDecl ReferenceEntry copyEntry(ReferenceEntry original, ReferenceEntry newNext) { - if (original.getKey() == null) { + @Nullable ReferenceEntry copyEntry( + ReferenceEntry original, ReferenceEntry newNext) { + K key = original.getKey(); + if (key == null) { // key collected return null; } @@ -1996,7 +2019,7 @@ ReferenceEntry copyEntry(ReferenceEntry original, ReferenceEntry newEntry = map.entryFactory.copyEntry(this, original, newNext); + ReferenceEntry newEntry = map.entryFactory.copyEntry(this, original, newNext, key); newEntry.setValueReference(valueReference.copyFor(this.valueReferenceQueue, value, newEntry)); return newEntry; } @@ -2017,6 +2040,7 @@ void setValue(ReferenceEntry entry, K key, V value, long now) { // loading + @CanIgnoreReturnValue V get(K key, int hash, CacheLoader loader) throws ExecutionException { checkNotNull(key); checkNotNull(loader); @@ -2054,8 +2078,7 @@ V get(K key, int hash, CacheLoader loader) throws ExecutionExcepti } } - @NullableDecl - V get(Object key, int hash) { + @Nullable V get(Object key, int hash) { try { if (count != 0) { // read-volatile long now = map.ticker.read(); @@ -2196,21 +2219,18 @@ V loadSync( } ListenableFuture loadAsync( - final K key, - final int hash, - final LoadingValueReference loadingValueReference, + K key, + int hash, + LoadingValueReference loadingValueReference, CacheLoader loader) { - final ListenableFuture loadingFuture = loadingValueReference.loadFuture(key, loader); + ListenableFuture loadingFuture = loadingValueReference.loadFuture(key, loader); loadingFuture.addListener( - new Runnable() { - @Override - public void run() { - try { - getAndRecordStats(key, hash, loadingValueReference, loadingFuture); - } catch (Throwable t) { - logger.log(Level.WARNING, "Exception thrown during refresh", t); - loadingValueReference.setException(t); - } + () -> { + try { + getAndRecordStats(key, hash, loadingValueReference, loadingFuture); + } catch (Throwable t) { + logger.log(Level.WARNING, "Exception thrown during refresh", t); + loadingValueReference.setException(t); } }, directExecutor()); @@ -2218,6 +2238,7 @@ public void run() { } /** Waits uninterruptibly for {@code newValue} to be loaded, and then records loading stats. */ + @CanIgnoreReturnValue V getAndRecordStats( K key, int hash, @@ -2265,9 +2286,9 @@ V scheduleRefresh( * {@code null} if another thread is performing the refresh or if an error occurs during * refresh. */ - @NullableDecl - V refresh(K key, int hash, CacheLoader loader, boolean checkTime) { - final LoadingValueReference loadingValueReference = + @CanIgnoreReturnValue + @Nullable V refresh(K key, int hash, CacheLoader loader, boolean checkTime) { + LoadingValueReference loadingValueReference = insertLoadingValueReference(key, hash, checkTime); if (loadingValueReference == null) { return null; @@ -2288,9 +2309,8 @@ V refresh(K key, int hash, CacheLoader loader, boolean checkTime) * Returns a newly inserted {@code LoadingValueReference}, or null if the live value reference * is already loading. */ - @NullableDecl - LoadingValueReference insertLoadingValueReference( - final K key, final int hash, boolean checkTime) { + @Nullable LoadingValueReference insertLoadingValueReference( + K key, int hash, boolean checkTime) { ReferenceEntry e = null; lock(); try { @@ -2476,7 +2496,7 @@ void drainRecencyQueue() { // An entry may be in the recency queue despite it being removed from // the map . This can occur when the entry was concurrently read while a // writer is removing it from the segment or after a clear has removed - // all of the segment's entries. + // all the segment's entries. if (accessQueue.contains(e)) { accessQueue.add(e); } @@ -2518,7 +2538,7 @@ void expireEntries(long now) { @GuardedBy("this") void enqueueNotification( - @NullableDecl K key, int hash, @NullableDecl V value, int weight, RemovalCause cause) { + @Nullable K key, int hash, @Nullable V value, int weight, RemovalCause cause) { totalWeight -= weight; if (cause.wasEvicted()) { statsCounter.recordEviction(); @@ -2580,8 +2600,7 @@ ReferenceEntry getFirst(int hash) { // Specialized implementations of map methods - @NullableDecl - ReferenceEntry getEntry(Object key, int hash) { + @Nullable ReferenceEntry getEntry(Object key, int hash) { for (ReferenceEntry e = getFirst(hash); e != null; e = e.getNext()) { if (e.getHash() != hash) { continue; @@ -2601,8 +2620,7 @@ ReferenceEntry getEntry(Object key, int hash) { return null; } - @NullableDecl - ReferenceEntry getLiveEntry(Object key, int hash, long now) { + @Nullable ReferenceEntry getLiveEntry(Object key, int hash, long now) { ReferenceEntry e = getEntry(key, hash); if (e == null) { return null; @@ -2682,8 +2700,8 @@ boolean containsValue(Object value) { } } - @NullableDecl - V put(K key, int hash, V value, boolean onlyIfAbsent) { + @CanIgnoreReturnValue + @Nullable V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); try { long now = map.ticker.read(); @@ -2888,8 +2906,7 @@ boolean replace(K key, int hash, V oldValue, V newValue) { } } - @NullableDecl - V replace(K key, int hash, V newValue) { + @Nullable V replace(K key, int hash, V newValue) { lock(); try { long now = map.ticker.read(); @@ -2943,8 +2960,7 @@ V replace(K key, int hash, V newValue) { } } - @NullableDecl - V remove(Object key, int hash) { + @Nullable V remove(Object key, int hash) { lock(); try { long now = map.ticker.read(); @@ -3036,6 +3052,7 @@ boolean remove(Object key, int hash, Object value) { } } + @CanIgnoreReturnValue boolean storeLoadedValue( K key, int hash, LoadingValueReference oldValueReference, V newValue) { lock(); @@ -3135,11 +3152,10 @@ void clear() { } @GuardedBy("this") - @NullableDecl - ReferenceEntry removeValueFromChain( + @Nullable ReferenceEntry removeValueFromChain( ReferenceEntry first, ReferenceEntry entry, - @NullableDecl K key, + @Nullable K key, int hash, V value, ValueReference valueReference, @@ -3157,8 +3173,7 @@ ReferenceEntry removeValueFromChain( } @GuardedBy("this") - @NullableDecl - ReferenceEntry removeEntryFromChain( + @Nullable ReferenceEntry removeEntryFromChain( ReferenceEntry first, ReferenceEntry entry) { int newCount = count; ReferenceEntry newFirst = entry.getNext(); @@ -3188,6 +3203,7 @@ void removeCollectedEntry(ReferenceEntry entry) { } /** Removes an entry whose key has been garbage collected. */ + @CanIgnoreReturnValue boolean reclaimKey(ReferenceEntry entry, int hash) { lock(); try { @@ -3223,6 +3239,7 @@ boolean reclaimKey(ReferenceEntry entry, int hash) { } /** Removes an entry whose value has been garbage collected. */ + @CanIgnoreReturnValue boolean reclaimValue(K key, int hash, ValueReference valueReference) { lock(); try { @@ -3260,12 +3277,13 @@ boolean reclaimValue(K key, int hash, ValueReference valueReference) { return false; } finally { unlock(); - if (!isHeldByCurrentThread()) { // don't cleanup inside of put + if (!isHeldByCurrentThread()) { // don't clean up inside of put postWriteCleanup(); } } } + @CanIgnoreReturnValue boolean removeLoadingValue(K key, int hash, LoadingValueReference valueReference) { lock(); try { @@ -3301,6 +3319,7 @@ boolean removeLoadingValue(K key, int hash, LoadingValueReference valueRef @VisibleForTesting @GuardedBy("this") + @CanIgnoreReturnValue boolean removeEntry(ReferenceEntry entry, int hash, RemovalCause cause) { int newCount = this.count - 1; AtomicReferenceArray> table = this.table; @@ -3392,6 +3411,11 @@ public LoadingValueReference() { this(LocalCache.unset()); } + /* + * TODO(cpovirk): Consider making this implementation closer to the mainline implementation. + * (The difference was introduced as part of Java-8-specific changes in cl/132882204, but we + * could probably make *some* of those changes here in the backport, too.) + */ public LoadingValueReference(ValueReference oldValue) { this.oldValue = oldValue; } @@ -3411,20 +3435,22 @@ public int getWeight() { return oldValue.getWeight(); } - public boolean set(@NullableDecl V newValue) { + @CanIgnoreReturnValue + public boolean set(@Nullable V newValue) { return futureValue.set(newValue); } + @CanIgnoreReturnValue public boolean setException(Throwable t) { return futureValue.setException(t); } private ListenableFuture fullyFailedFuture(Throwable t) { - return Futures.immediateFailedFuture(t); + return immediateFailedFuture(t); } @Override - public void notifyNewValue(@NullableDecl V newValue) { + public void notifyNewValue(@Nullable V newValue) { if (newValue != null) { // The pending load was clobbered by a manual write. // Unblock all pending gets, and have them return the new value. @@ -3443,22 +3469,19 @@ public ListenableFuture loadFuture(K key, CacheLoader loader) { V previousValue = oldValue.get(); if (previousValue == null) { V newValue = loader.load(key); - return set(newValue) ? futureValue : Futures.immediateFuture(newValue); + return set(newValue) ? futureValue : immediateFuture(newValue); } ListenableFuture newValue = loader.reload(key, previousValue); if (newValue == null) { - return Futures.immediateFuture(null); + return immediateFuture(null); } // To avoid a race, make sure the refreshed value is set into loadingValueReference // *before* returning newValue from the cache query. return transform( newValue, - new Function() { - @Override - public V apply(V newValue) { - LoadingValueReference.this.set(newValue); - return newValue; - } + newResult -> { + LoadingValueReference.this.set(newResult); + return newResult; }, directExecutor()); } catch (Throwable t) { @@ -3495,7 +3518,7 @@ public ReferenceEntry getEntry() { @Override public ValueReference copyFor( - ReferenceQueue queue, @NullableDecl V value, ReferenceEntry entry) { + ReferenceQueue queue, @Nullable V value, ReferenceEntry entry) { return this; } } @@ -3565,13 +3588,13 @@ public boolean offer(ReferenceEntry entry) { } @Override - public ReferenceEntry peek() { + public @Nullable ReferenceEntry peek() { ReferenceEntry next = head.getNextInWriteQueue(); return (next == head) ? null : next; } @Override - public ReferenceEntry poll() { + public @Nullable ReferenceEntry poll() { ReferenceEntry next = head.getNextInWriteQueue(); if (next == head) { return null; @@ -3583,6 +3606,7 @@ public ReferenceEntry poll() { @Override @SuppressWarnings("unchecked") + @CanIgnoreReturnValue public boolean remove(Object o) { ReferenceEntry e = (ReferenceEntry) o; ReferenceEntry previous = e.getPreviousInWriteQueue(); @@ -3633,7 +3657,7 @@ public void clear() { public Iterator> iterator() { return new AbstractSequentialIterator>(peek()) { @Override - protected ReferenceEntry computeNext(ReferenceEntry previous) { + protected @Nullable ReferenceEntry computeNext(ReferenceEntry previous) { ReferenceEntry next = previous.getNextInWriteQueue(); return (next == head) ? null : next; } @@ -3704,13 +3728,13 @@ public boolean offer(ReferenceEntry entry) { } @Override - public ReferenceEntry peek() { + public @Nullable ReferenceEntry peek() { ReferenceEntry next = head.getNextInAccessQueue(); return (next == head) ? null : next; } @Override - public ReferenceEntry poll() { + public @Nullable ReferenceEntry poll() { ReferenceEntry next = head.getNextInAccessQueue(); if (next == head) { return null; @@ -3722,6 +3746,7 @@ public ReferenceEntry poll() { @Override @SuppressWarnings("unchecked") + @CanIgnoreReturnValue public boolean remove(Object o) { ReferenceEntry e = (ReferenceEntry) o; ReferenceEntry previous = e.getPreviousInAccessQueue(); @@ -3772,7 +3797,7 @@ public void clear() { public Iterator> iterator() { return new AbstractSequentialIterator>(peek()) { @Override - protected ReferenceEntry computeNext(ReferenceEntry previous) { + protected @Nullable ReferenceEntry computeNext(ReferenceEntry previous) { ReferenceEntry next = previous.getNextInAccessQueue(); return (next == head) ? null : next; } @@ -3801,19 +3826,19 @@ public boolean isEmpty() { */ long sum = 0L; Segment[] segments = this.segments; - for (int i = 0; i < segments.length; ++i) { - if (segments[i].count != 0) { + for (Segment segment : segments) { + if (segment.count != 0) { return false; } - sum += segments[i].modCount; + sum += segment.modCount; } if (sum != 0L) { // recheck unless no modifications - for (int i = 0; i < segments.length; ++i) { - if (segments[i].count != 0) { + for (Segment segment : segments) { + if (segment.count != 0) { return false; } - sum -= segments[i].modCount; + sum -= segment.modCount; } return sum == 0L; } @@ -3823,8 +3848,8 @@ public boolean isEmpty() { long longSize() { Segment[] segments = this.segments; long sum = 0; - for (int i = 0; i < segments.length; ++i) { - sum += Math.max(0, segments[i].count); // see https://github.com/google/guava/issues/2108 + for (Segment segment : segments) { + sum += Math.max(0, segment.count); // see https://github.com/google/guava/issues/2108 } return sum; } @@ -3834,9 +3859,9 @@ public int size() { return Ints.saturatedCast(longSize()); } + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this @Override - @NullableDecl - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { if (key == null) { return null; } @@ -3844,13 +3869,13 @@ public V get(@NullableDecl Object key) { return segmentFor(hash).get(key, hash); } + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this V get(K key, CacheLoader loader) throws ExecutionException { int hash = hash(checkNotNull(key)); return segmentFor(hash).get(key, hash, loader); } - @NullableDecl - public V getIfPresent(Object key) { + public @Nullable V getIfPresent(Object key) { int hash = hash(checkNotNull(key)); V value = segmentFor(hash).get(key, hash); if (value == null) { @@ -3861,9 +3886,8 @@ public V getIfPresent(Object key) { return value; } - @SuppressWarnings("MissingOverride") // Supermethod will not exist if we build with --release 7. - @NullableDecl - public V getOrDefault(@NullableDecl Object key, @NullableDecl V defaultValue) { + @Override + public @Nullable V getOrDefault(@Nullable Object key, @Nullable V defaultValue) { V result = get(key); return (result != null) ? result : defaultValue; } @@ -3876,7 +3900,7 @@ ImmutableMap getAllPresent(Iterable keys) { int hits = 0; int misses = 0; - Map result = Maps.newLinkedHashMap(); + ImmutableMap.Builder result = ImmutableMap.builder(); for (Object key : keys) { V value = get(key); if (value == null) { @@ -3891,15 +3915,15 @@ ImmutableMap getAllPresent(Iterable keys) { } globalStatsCounter.recordHits(hits); globalStatsCounter.recordMisses(misses); - return ImmutableMap.copyOf(result); + return result.buildKeepingLast(); } ImmutableMap getAll(Iterable keys) throws ExecutionException { int hits = 0; int misses = 0; - Map result = Maps.newLinkedHashMap(); - Set keysToLoad = Sets.newLinkedHashSet(); + Map result = new LinkedHashMap<>(); + Set keysToLoad = new LinkedHashSet<>(); for (K key : keys) { V value = get(key); if (!result.containsKey(key)) { @@ -3916,7 +3940,7 @@ ImmutableMap getAll(Iterable keys) throws ExecutionException try { if (!keysToLoad.isEmpty()) { try { - Map newEntries = loadAll(keysToLoad, defaultLoader); + Map newEntries = loadAll(unmodifiableSet(keysToLoad), defaultLoader); for (K key : keysToLoad) { V value = newEntries.get(key); if (value == null) { @@ -3943,8 +3967,7 @@ ImmutableMap getAll(Iterable keys) throws ExecutionException * Returns the result of calling {@link CacheLoader#loadAll}, or null if {@code loader} doesn't * implement {@code loadAll}. */ - @NullableDecl - Map loadAll(Set keys, CacheLoader loader) + @Nullable Map loadAll(Set keys, CacheLoader loader) throws ExecutionException { checkNotNull(loader); checkNotNull(keys); @@ -4007,7 +4030,7 @@ Map loadAll(Set keys, CacheLoader loader) * Returns the internal entry for the specified key. The entry may be loading, expired, or * partially collected. */ - ReferenceEntry getEntry(@NullableDecl Object key) { + @Nullable ReferenceEntry getEntry(@Nullable Object key) { // does not impact recency ordering if (key == null) { return null; @@ -4022,7 +4045,7 @@ void refresh(K key) { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { // does not impact recency ordering if (key == null) { return false; @@ -4032,7 +4055,7 @@ public boolean containsKey(@NullableDecl Object key) { } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { // does not impact recency ordering if (value == null) { return false; @@ -4044,7 +4067,7 @@ public boolean containsValue(@NullableDecl Object value) { // in time it was present somewhere int the map. This becomes increasingly unlikely as // CONTAINS_VALUE_RETRIES increases, though without locking it is theoretically possible. long now = ticker.read(); - final Segment[] segments = this.segments; + Segment[] segments = this.segments; long last = -1L; for (int i = 0; i < CONTAINS_VALUE_RETRIES; i++) { long sum = 0L; @@ -4071,8 +4094,9 @@ public boolean containsValue(@NullableDecl Object value) { return false; } + @CanIgnoreReturnValue @Override - public V put(K key, V value) { + public @Nullable V put(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); @@ -4080,7 +4104,7 @@ public V put(K key, V value) { } @Override - public V putIfAbsent(K key, V value) { + public @Nullable V putIfAbsent(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); @@ -4094,8 +4118,9 @@ public void putAll(Map m) { } } + @CanIgnoreReturnValue @Override - public V remove(@NullableDecl Object key) { + public @Nullable V remove(@Nullable Object key) { if (key == null) { return null; } @@ -4103,8 +4128,9 @@ public V remove(@NullableDecl Object key) { return segmentFor(hash).remove(key, hash); } + @CanIgnoreReturnValue @Override - public boolean remove(@NullableDecl Object key, @NullableDecl Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { if (key == null || value == null) { return false; } @@ -4112,8 +4138,9 @@ public boolean remove(@NullableDecl Object key, @NullableDecl Object value) { return segmentFor(hash).remove(key, hash, value); } + @CanIgnoreReturnValue @Override - public boolean replace(K key, @NullableDecl V oldValue, V newValue) { + public boolean replace(K key, @Nullable V oldValue, V newValue) { checkNotNull(key); checkNotNull(newValue); if (oldValue == null) { @@ -4123,8 +4150,9 @@ public boolean replace(K key, @NullableDecl V oldValue, V newValue) { return segmentFor(hash).replace(key, hash, oldValue, newValue); } + @CanIgnoreReturnValue @Override - public V replace(K key, V value) { + public @Nullable V replace(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); @@ -4145,7 +4173,7 @@ void invalidateAll(Iterable keys) { } } - @RetainedWith @NullableDecl Set keySet; + @LazyInit @RetainedWith @Nullable Set keySet; @Override public Set keySet() { @@ -4154,7 +4182,7 @@ public Set keySet() { return (ks != null) ? ks : (keySet = new KeySet()); } - @RetainedWith @NullableDecl Collection values; + @LazyInit @RetainedWith @Nullable Collection values; @Override public Collection values() { @@ -4163,7 +4191,7 @@ public Collection values() { return (vs != null) ? vs : (values = new Values()); } - @RetainedWith @NullableDecl Set> entrySet; + @LazyInit @RetainedWith @Nullable Set> entrySet; @Override @GwtIncompatible // Not supported. @@ -4179,11 +4207,11 @@ abstract class HashIterator implements Iterator { int nextSegmentIndex; int nextTableIndex; - @NullableDecl Segment currentSegment; - @NullableDecl AtomicReferenceArray> currentTable; - @NullableDecl ReferenceEntry nextEntry; - @NullableDecl WriteThroughEntry nextExternal; - @NullableDecl WriteThroughEntry lastReturned; + @Nullable Segment currentSegment; + @Nullable AtomicReferenceArray> currentTable; + @Nullable ReferenceEntry nextEntry; + @Nullable WriteThroughEntry nextExternal; + @Nullable WriteThroughEntry lastReturned; HashIterator() { nextSegmentIndex = segments.length - 1; @@ -4324,7 +4352,7 @@ public V getValue() { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { // Cannot use key and value equivalence if (object instanceof Entry) { Entry that = (Entry) object; @@ -4375,26 +4403,6 @@ public boolean isEmpty() { public void clear() { LocalCache.this.clear(); } - - // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. - // https://code.google.com/p/android/issues/detail?id=36519 / http://r.android.com/47508 - - @Override - public Object[] toArray() { - return toArrayList(this).toArray(); - } - - @Override - public E[] toArray(E[] a) { - return toArrayList(this).toArray(a); - } - } - - private static ArrayList toArrayList(Collection c) { - // Avoid calling ArrayList(Collection), which may call back into toArray. - ArrayList result = new ArrayList(c.size()); - Iterators.addAll(result, c.iterator()); - return result; } final class KeySet extends AbstractCacheSet { @@ -4440,19 +4448,6 @@ public Iterator iterator() { public boolean contains(Object o) { return LocalCache.this.containsValue(o); } - - // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. - // https://code.google.com/p/android/issues/detail?id=36519 / http://r.android.com/47508 - - @Override - public Object[] toArray() { - return toArrayList(this).toArray(); - } - - @Override - public E[] toArray(E[] a) { - return toArrayList(this).toArray(a); - } } final class EntrySet extends AbstractCacheSet> { @@ -4498,9 +4493,9 @@ public boolean remove(Object o) { *

    Unfortunately, readResolve() doesn't get called when a circular dependency is present, so * the proxy must be able to behave as the cache itself. */ - static class ManualSerializationProxy extends ForwardingCache + private static class ManualSerializationProxy extends ForwardingCache implements Serializable { - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; final Strength keyStrength; final Strength valueStrength; @@ -4512,10 +4507,10 @@ static class ManualSerializationProxy extends ForwardingCache final Weigher weigher; final int concurrencyLevel; final RemovalListener removalListener; - @NullableDecl final Ticker ticker; + final @Nullable Ticker ticker; final CacheLoader loader; - @NullableDecl transient Cache delegate; + transient @Nullable Cache delegate; ManualSerializationProxy(LocalCache cache) { this( @@ -4571,13 +4566,13 @@ CacheBuilder recreateCacheBuilder() { .removalListener(removalListener); builder.strictParsing = false; if (expireAfterWriteNanos > 0) { - builder.expireAfterWrite(expireAfterWriteNanos, TimeUnit.NANOSECONDS); + builder.expireAfterWrite(expireAfterWriteNanos, NANOSECONDS); } if (expireAfterAccessNanos > 0) { - builder.expireAfterAccess(expireAfterAccessNanos, TimeUnit.NANOSECONDS); + builder.expireAfterAccess(expireAfterAccessNanos, NANOSECONDS); } if (weigher != OneWeigher.INSTANCE) { - builder.weigher(weigher); + Object unused = builder.weigher(weigher); if (maxWeight != UNSET_INT) { builder.maximumWeight(maxWeight); } @@ -4617,10 +4612,10 @@ protected Cache delegate() { * the proxy must be able to behave as the cache itself. */ static final class LoadingSerializationProxy extends ManualSerializationProxy - implements LoadingCache, Serializable { - private static final long serialVersionUID = 1; + implements LoadingCache { + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; - @NullableDecl transient LoadingCache autoDelegate; + transient @Nullable LoadingCache autoDelegate; LoadingSerializationProxy(LocalCache cache) { super(cache); @@ -4648,7 +4643,7 @@ public ImmutableMap getAll(Iterable keys) throws ExecutionExc } @Override - public final V apply(K key) { + public V apply(K key) { return autoDelegate.apply(key); } @@ -4666,7 +4661,7 @@ static class LocalManualCache implements Cache, Serializable { final LocalCache localCache; LocalManualCache(CacheBuilder builder) { - this(new LocalCache(builder, null)); + this(new LocalCache<>(builder, null)); } private LocalManualCache(LocalCache localCache) { @@ -4676,13 +4671,12 @@ private LocalManualCache(LocalCache localCache) { // Cache methods @Override - @NullableDecl - public V getIfPresent(Object key) { + public @Nullable V getIfPresent(Object key) { return localCache.getIfPresent(key); } @Override - public V get(K key, final Callable valueLoader) throws ExecutionException { + public V get(K key, Callable valueLoader) throws ExecutionException { checkNotNull(valueLoader); return localCache.get( key, @@ -4752,19 +4746,24 @@ public void cleanUp() { // Serialization Support - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; Object writeReplace() { return new ManualSerializationProxy<>(localCache); } + + private void readObject(ObjectInputStream in) throws InvalidObjectException { + throw new InvalidObjectException("Use ManualSerializationProxy"); + } } + // TODO(cpovirk): Make this final (but that may break proxies). static class LocalLoadingCache extends LocalManualCache implements LoadingCache { LocalLoadingCache( CacheBuilder builder, CacheLoader loader) { - super(new LocalCache(builder, checkNotNull(loader))); + super(new LocalCache<>(builder, checkNotNull(loader))); } // LoadingCache methods @@ -4774,6 +4773,7 @@ public V get(K key) throws ExecutionException { return localCache.getOrLoad(key); } + @CanIgnoreReturnValue // TODO(b/27479612): consider removing this @Override public V getUnchecked(K key) { try { @@ -4800,11 +4800,15 @@ public final V apply(K key) { // Serialization Support - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; @Override Object writeReplace() { return new LoadingSerializationProxy<>(localCache); } + + private void readObject(ObjectInputStream in) throws InvalidObjectException { + throw new InvalidObjectException("Use LoadingSerializationProxy"); + } } } diff --git a/android/guava/src/com/google/common/cache/LongAddables.java b/android/guava/src/com/google/common/cache/LongAddables.java index 203d2ef731a8..c370f1975204 100644 --- a/android/guava/src/com/google/common/cache/LongAddables.java +++ b/android/guava/src/com/google/common/cache/LongAddables.java @@ -23,14 +23,15 @@ * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible final class LongAddables { private static final Supplier SUPPLIER; static { Supplier supplier; try { - new LongAdder(); // trigger static initialization of the LongAdder class, which may fail + // trigger static initialization of the LongAdder class, which may fail + LongAdder unused = new LongAdder(); supplier = new Supplier() { @Override @@ -70,4 +71,6 @@ public long sum() { return get(); } } + + private LongAddables() {} } diff --git a/android/guava/src/com/google/common/cache/LongAdder.java b/android/guava/src/com/google/common/cache/LongAdder.java index f0c44ffbc697..19ac65fb5f33 100644 --- a/android/guava/src/com/google/common/cache/LongAdder.java +++ b/android/guava/src/com/google/common/cache/LongAdder.java @@ -12,6 +12,8 @@ package com.google.common.cache; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -39,9 +41,9 @@ * @since 1.8 * @author Doug Lea */ -@GwtCompatible(emulated = true) +@GwtCompatible final class LongAdder extends Striped64 implements Serializable, LongAddable { - private static final long serialVersionUID = 7249069246863182397L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 7249069246863182397L; /** Version of plus for use in retryUpdate */ @Override diff --git a/android/guava/src/com/google/common/cache/ParametricNullness.java b/android/guava/src/com/google/common/cache/ParametricNullness.java new file mode 100644 index 000000000000..affbfc511840 --- /dev/null +++ b/android/guava/src/com/google/common/cache/ParametricNullness.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.cache; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. + */ +@GwtCompatible +@Retention(CLASS) +@Target({FIELD, METHOD, PARAMETER}) +@interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/cache/ReferenceEntry.java b/android/guava/src/com/google/common/cache/ReferenceEntry.java index f9027ab641d9..3c78679dba19 100644 --- a/android/guava/src/com/google/common/cache/ReferenceEntry.java +++ b/android/guava/src/com/google/common/cache/ReferenceEntry.java @@ -16,7 +16,7 @@ import com.google.common.annotations.GwtIncompatible; import com.google.common.cache.LocalCache.ValueReference; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An entry in a reference map. @@ -41,21 +41,19 @@ @GwtIncompatible interface ReferenceEntry { /** Returns the value reference from this entry. */ - ValueReference getValueReference(); + @Nullable ValueReference getValueReference(); /** Sets the value reference for this entry. */ void setValueReference(ValueReference valueReference); /** Returns the next entry in the chain. */ - @NullableDecl - ReferenceEntry getNext(); + @Nullable ReferenceEntry getNext(); /** Returns the entry's hash. */ int getHash(); /** Returns the key for this entry. */ - @NullableDecl - K getKey(); + @Nullable K getKey(); /* * Used by entries that use access order. Access entries are maintained in a doubly-linked list. @@ -89,8 +87,8 @@ interface ReferenceEntry { * expired from the head of the list. */ - @SuppressWarnings("GoodTime") /** Returns the time that this entry was last written, in ns. */ + @SuppressWarnings("GoodTime") long getWriteTime(); /** Sets the entry write time in ns. */ diff --git a/android/guava/src/com/google/common/cache/RemovalListeners.java b/android/guava/src/com/google/common/cache/RemovalListeners.java index c82b0941207f..e5999a4e80e4 100644 --- a/android/guava/src/com/google/common/cache/RemovalListeners.java +++ b/android/guava/src/com/google/common/cache/RemovalListeners.java @@ -38,20 +38,10 @@ private RemovalListeners() {} * @param executor the executor with which removal notifications are asynchronously executed */ public static RemovalListener asynchronous( - final RemovalListener listener, final Executor executor) { + RemovalListener listener, Executor executor) { checkNotNull(listener); checkNotNull(executor); - return new RemovalListener() { - @Override - public void onRemoval(final RemovalNotification notification) { - executor.execute( - new Runnable() { - @Override - public void run() { - listener.onRemoval(notification); - } - }); - } - }; + return (RemovalNotification notification) -> + executor.execute(() -> listener.onRemoval(notification)); } } diff --git a/android/guava/src/com/google/common/cache/RemovalNotification.java b/android/guava/src/com/google/common/cache/RemovalNotification.java index e30ec0c05dec..e95c5e202140 100644 --- a/android/guava/src/com/google/common/cache/RemovalNotification.java +++ b/android/guava/src/com/google/common/cache/RemovalNotification.java @@ -17,8 +17,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.AbstractMap.SimpleImmutableEntry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A notification of the removal of a single entry. The key and/or value may be null if they were @@ -32,7 +34,8 @@ * @since 10.0 */ @GwtCompatible -public final class RemovalNotification extends SimpleImmutableEntry { +public final class RemovalNotification + extends SimpleImmutableEntry<@Nullable K, @Nullable V> { private final RemovalCause cause; /** @@ -43,11 +46,11 @@ public final class RemovalNotification extends SimpleImmutableEntry * @since 19.0 */ public static RemovalNotification create( - @NullableDecl K key, @NullableDecl V value, RemovalCause cause) { - return new RemovalNotification(key, value, cause); + @Nullable K key, @Nullable V value, RemovalCause cause) { + return new RemovalNotification<>(key, value, cause); } - private RemovalNotification(@NullableDecl K key, @NullableDecl V value, RemovalCause cause) { + private RemovalNotification(@Nullable K key, @Nullable V value, RemovalCause cause) { super(key, value); this.cause = checkNotNull(cause); } @@ -65,5 +68,5 @@ public boolean wasEvicted() { return cause.wasEvicted(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/cache/Striped64.java b/android/guava/src/com/google/common/cache/Striped64.java index 676e09aa6bc5..a5241528f035 100644 --- a/android/guava/src/com/google/common/cache/Striped64.java +++ b/android/guava/src/com/google/common/cache/Striped64.java @@ -12,14 +12,20 @@ package com.google.common.cache; import com.google.common.annotations.GwtIncompatible; +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.Random; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; +import sun.misc.Unsafe; /** * A package-local class holding common representation and mechanics for classes supporting dynamic * striping on 64bit values. The class extends Number so that concrete subclasses must publicly do * so. */ +@SuppressWarnings("SunApi") // b/345822163 @GwtIncompatible abstract class Striped64 extends Number { /* @@ -102,18 +108,18 @@ static final class Cell { } final boolean cas(long cmp, long val) { - return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); + return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, cmp, val); } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long valueOffset; + private static final Unsafe UNSAFE; + private static final long VALUE_OFFSET; static { try { UNSAFE = getUnsafe(); Class ak = Cell.class; - valueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField("value")); + VALUE_OFFSET = UNSAFE.objectFieldOffset(ak.getDeclaredField("value")); } catch (Exception e) { throw new Error(e); } @@ -125,7 +131,7 @@ final boolean cas(long cmp, long val) { * class, we use a suboptimal int[] representation to avoid introducing a new type that can impede * class-unloading when ThreadLocals are not removed. */ - static final ThreadLocal threadHashCode = new ThreadLocal<>(); + static final ThreadLocal threadHashCode = new ThreadLocal<>(); /** Generator of new random hash codes */ static final Random rng = new Random(); @@ -134,7 +140,7 @@ final boolean cas(long cmp, long val) { static final int NCPU = Runtime.getRuntime().availableProcessors(); /** Table of cells. When non-null, size is a power of 2. */ - @NullableDecl transient volatile Cell[] cells; + transient volatile Cell @Nullable [] cells; /** * Base value, used mainly when there is no contention, but also as a fallback during table @@ -150,12 +156,12 @@ final boolean cas(long cmp, long val) { /** CASes the base field. */ final boolean casBase(long cmp, long val) { - return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); + return UNSAFE.compareAndSwapLong(this, BASE_OFFSET, cmp, val); } /** CASes the busy field from 0 to 1 to acquire lock. */ final boolean casBusy() { - return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); + return UNSAFE.compareAndSwapInt(this, BUSY_OFFSET, 0, 1); } /** @@ -177,7 +183,7 @@ final boolean casBusy() { * @param hc the hash code holder * @param wasUncontended false if CAS failed before call */ - final void retryUpdate(long x, int[] hc, boolean wasUncontended) { + final void retryUpdate(long x, int @Nullable [] hc, boolean wasUncontended) { int h; if (hc == null) { threadHashCode.set(hc = new int[1]); // Initialize randomly @@ -264,16 +270,16 @@ final void internalReset(long initialValue) { } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long baseOffset; - private static final long busyOffset; + private static final Unsafe UNSAFE; + private static final long BASE_OFFSET; + private static final long BUSY_OFFSET; static { try { UNSAFE = getUnsafe(); Class sk = Striped64.class; - baseOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField("base")); - busyOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField("busy")); + BASE_OFFSET = UNSAFE.objectFieldOffset(sk.getDeclaredField("base")); + BUSY_OFFSET = UNSAFE.objectFieldOffset(sk.getDeclaredField("busy")); } catch (Exception e) { throw new Error(e); } @@ -285,18 +291,18 @@ final void internalReset(long initialValue) { * * @return a sun.misc.Unsafe */ - private static sun.misc.Unsafe getUnsafe() { + private static Unsafe getUnsafe() { try { - return sun.misc.Unsafe.getUnsafe(); + return Unsafe.getUnsafe(); } catch (SecurityException tryReflectionInstead) { } try { - return java.security.AccessController.doPrivileged( - new java.security.PrivilegedExceptionAction() { + return AccessController.doPrivileged( + new PrivilegedExceptionAction() { @Override - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { + public Unsafe run() throws Exception { + Class k = Unsafe.class; + for (Field f : k.getDeclaredFields()) { f.setAccessible(true); Object x = f.get(null); if (k.isInstance(x)) return k.cast(x); @@ -304,7 +310,7 @@ public sun.misc.Unsafe run() throws Exception { throw new NoSuchFieldError("the Unsafe"); } }); - } catch (java.security.PrivilegedActionException e) { + } catch (PrivilegedActionException e) { throw new RuntimeException("Could not initialize intrinsics", e.getCause()); } } diff --git a/android/guava/src/com/google/common/cache/package-info.java b/android/guava/src/com/google/common/cache/package-info.java index a7791de494a3..5bd416f5cfc5 100644 --- a/android/guava/src/com/google/common/cache/package-info.java +++ b/android/guava/src/com/google/common/cache/package-info.java @@ -13,23 +13,24 @@ */ /** - * This package contains caching utilities. + * {@linkplain CacheBuilder Discouraged} (in favor of Caffeine) caching utilities. * - *

    The core interface used to represent caches is {@link com.google.common.cache.Cache}. - * In-memory caches can be configured and created using {@link - * com.google.common.cache.CacheBuilder}, with cache entries being loaded by {@link - * com.google.common.cache.CacheLoader}. Statistics about cache performance are exposed using {@link - * com.google.common.cache.CacheStats}. + *

    The core interface used to represent caches is {@link Cache}. In-memory caches can be + * configured and created using {@link CacheBuilder}, with cache entries being loaded by {@link + * CacheLoader}. Statistics about cache performance are exposed using {@link CacheStats}. * *

    See the Guava User Guide article on caches. * - *

    This package is a part of the open-source Guava + *

    This package is a part of the open-source Guava * library. * * @author Charles Fry */ -@ParametersAreNonnullByDefault +@CheckReturnValue +@NullMarked package com.google.common.cache; -import javax.annotation.ParametersAreNonnullByDefault; +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/collect/AbstractBiMap.java b/android/guava/src/com/google/common/collect/AbstractBiMap.java index 0b314ae4bf3f..82297c800adc 100644 --- a/android/guava/src/com/google/common/collect/AbstractBiMap.java +++ b/android/guava/src/com/google/common/collect/AbstractBiMap.java @@ -18,12 +18,14 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedWith; import com.google.j2objc.annotations.WeakOuter; import java.io.IOException; @@ -33,8 +35,9 @@ import java.util.Collection; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A general-purpose bimap implementation using any two backing {@code Map} instances. @@ -45,16 +48,18 @@ * @author Kevin Bourrillion * @author Mike Bostock */ -@GwtCompatible(emulated = true) -abstract class AbstractBiMap extends ForwardingMap - implements BiMap, Serializable { +@GwtCompatible +abstract class AbstractBiMap + extends ForwardingMap implements BiMap, Serializable { - @NullableDecl private transient Map delegate; - @RetainedWith @NullableDecl transient AbstractBiMap inverse; + private transient Map delegate; + + @RetainedWith private transient AbstractBiMap inverse; /** Package-private constructor for creating a map-backed bimap. */ AbstractBiMap(Map forward, Map backward) { - setDelegates(forward, backward); + inverse = checkMapsAndMakeInverse(forward, backward); + delegate = forward; } /** Private constructor for inverse bimap. */ @@ -70,28 +75,32 @@ protected Map delegate() { /** Returns its input, or throws an exception if this is not a valid key. */ @CanIgnoreReturnValue - K checkKey(@NullableDecl K key) { + @ParametricNullness + K checkKey(@ParametricNullness K key) { return key; } /** Returns its input, or throws an exception if this is not a valid value. */ @CanIgnoreReturnValue - V checkValue(@NullableDecl V value) { + @ParametricNullness + V checkValue(@ParametricNullness V value) { return value; } /** - * Specifies the delegate maps going in each direction. Called by the constructor and by - * subclasses during deserialization. + * Specifies the delegate maps going in each direction. Called by subclasses during + * deserialization. */ void setDelegates(Map forward, Map backward) { - checkState(delegate == null); - checkState(inverse == null); + inverse = checkMapsAndMakeInverse(forward, backward); + delegate = forward; + } + + private AbstractBiMap checkMapsAndMakeInverse(Map forward, Map backward) { checkArgument(forward.isEmpty()); checkArgument(backward.isEmpty()); checkArgument(forward != backward); - delegate = forward; - inverse = makeInverse(backward); + return makeInverse(backward); } AbstractBiMap makeInverse(Map backward) { @@ -105,7 +114,7 @@ void setInverse(AbstractBiMap inverse) { // Query Operations (optimizations) @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return inverse.containsKey(value); } @@ -113,21 +122,22 @@ public boolean containsValue(@NullableDecl Object value) { @CanIgnoreReturnValue @Override - public V put(@NullableDecl K key, @NullableDecl V value) { + public @Nullable V put(@ParametricNullness K key, @ParametricNullness V value) { return putInBothMaps(key, value, false); } @CanIgnoreReturnValue @Override - public V forcePut(@NullableDecl K key, @NullableDecl V value) { + public @Nullable V forcePut(@ParametricNullness K key, @ParametricNullness V value) { return putInBothMaps(key, value, true); } - private V putInBothMaps(@NullableDecl K key, @NullableDecl V value, boolean force) { + private @Nullable V putInBothMaps( + @ParametricNullness K key, @ParametricNullness V value, boolean force) { checkKey(key); checkValue(value); boolean containedKey = containsKey(key); - if (containedKey && Objects.equal(value, get(key))) { + if (containedKey && Objects.equals(value, get(key))) { return value; } if (force) { @@ -140,27 +150,34 @@ private V putInBothMaps(@NullableDecl K key, @NullableDecl V value, boolean forc return oldValue; } - private void updateInverseMap(K key, boolean containedKey, V oldValue, V newValue) { + private void updateInverseMap( + @ParametricNullness K key, + boolean containedKey, + @Nullable V oldValue, + @ParametricNullness V newValue) { if (containedKey) { - removeFromInverseMap(oldValue); + // The cast is safe because of the containedKey check. + removeFromInverseMap(uncheckedCastNullableTToT(oldValue)); } inverse.delegate.put(newValue, key); } @CanIgnoreReturnValue @Override - public V remove(@NullableDecl Object key) { + public @Nullable V remove(@Nullable Object key) { return containsKey(key) ? removeFromBothMaps(key) : null; } @CanIgnoreReturnValue - private V removeFromBothMaps(Object key) { - V oldValue = delegate.remove(key); + @ParametricNullness + private V removeFromBothMaps(@Nullable Object key) { + // The cast is safe because the callers of this method first check that the key is present. + V oldValue = uncheckedCastNullableTToT(delegate.remove(key)); removeFromInverseMap(oldValue); return oldValue; } - private void removeFromInverseMap(V oldValue) { + private void removeFromInverseMap(@ParametricNullness V oldValue) { inverse.delegate.remove(oldValue); } @@ -186,7 +203,7 @@ public BiMap inverse() { return inverse; } - @NullableDecl private transient Set keySet; + @LazyInit private transient @Nullable Set keySet; @Override public Set keySet() { @@ -195,7 +212,7 @@ public Set keySet() { } @WeakOuter - private class KeySet extends ForwardingSet { + private final class KeySet extends ForwardingSet { @Override protected Set delegate() { return delegate.keySet(); @@ -207,7 +224,7 @@ public void clear() { } @Override - public boolean remove(Object key) { + public boolean remove(@Nullable Object key) { if (!contains(key)) { return false; } @@ -231,7 +248,7 @@ public Iterator iterator() { } } - @NullableDecl private transient Set valueSet; + @LazyInit private transient @Nullable Set valueSet; @Override public Set values() { @@ -244,7 +261,7 @@ public Set values() { } @WeakOuter - private class ValueSet extends ForwardingSet { + private final class ValueSet extends ForwardingSet { final Set valuesDelegate = inverse.keySet(); @Override @@ -258,12 +275,13 @@ public Iterator iterator() { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { return standardToArray(); } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // bug in our checker's handling of toArray signatures + public T[] toArray(T[] array) { return standardToArray(array); } @@ -273,7 +291,7 @@ public String toString() { } } - @NullableDecl private transient Set> entrySet; + @LazyInit private transient @Nullable Set> entrySet; @Override public Set> entrySet() { @@ -281,7 +299,7 @@ public Set> entrySet() { return (result == null) ? entrySet = new EntrySet() : result; } - class BiMapEntry extends ForwardingMapEntry { + private final class BiMapEntry extends ForwardingMapEntry { private final Entry delegate; BiMapEntry(Entry delegate) { @@ -299,21 +317,21 @@ public V setValue(V value) { // Preconditions keep the map and inverse consistent. checkState(entrySet().contains(this), "entry no longer in map"); // similar to putInBothMaps, but set via entry - if (Objects.equal(value, getValue())) { + if (Objects.equals(value, getValue())) { return value; } checkArgument(!containsValue(value), "value already present: %s", value); V oldValue = delegate.setValue(value); - checkState(Objects.equal(value, get(getKey())), "entry no longer in map"); + checkState(Objects.equals(value, get(getKey())), "entry no longer in map"); updateInverseMap(getKey(), true, oldValue, value); return oldValue; } } Iterator> entrySetIterator() { - final Iterator> iterator = delegate.entrySet().iterator(); + Iterator> iterator = delegate.entrySet().iterator(); return new Iterator>() { - @NullableDecl Entry entry; + @Nullable Entry entry; @Override public boolean hasNext() { @@ -328,7 +346,9 @@ public Entry next() { @Override public void remove() { - checkRemove(entry != null); + if (entry == null) { + throw new IllegalStateException("no calls to next() since the last call to remove()"); + } V value = entry.getValue(); iterator.remove(); removeFromInverseMap(value); @@ -338,7 +358,7 @@ public void remove() { } @WeakOuter - private class EntrySet extends ForwardingSet> { + private final class EntrySet extends ForwardingSet> { final Set> esDelegate = delegate.entrySet(); @Override @@ -352,12 +372,15 @@ public void clear() { } @Override - public boolean remove(Object object) { - if (!esDelegate.contains(object)) { + public boolean remove(@Nullable Object object) { + /* + * `o instanceof Entry` is guaranteed by `contains`, but we check it here to satisfy our + * nullness checker. + */ + if (!esDelegate.contains(object) || !(object instanceof Entry)) { return false; } - // safe because esDelegate.contains(object). Entry entry = (Entry) object; inverse.delegate.remove(entry.getValue()); /* @@ -377,17 +400,18 @@ public Iterator> iterator() { // See java.util.Collections.CheckedEntrySet for details on attacks. @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { return standardToArray(); } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // bug in our checker's handling of toArray signatures + public T[] toArray(T[] array) { return standardToArray(array); } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { return Maps.containsEntryImpl(delegate(), o); } @@ -408,7 +432,8 @@ public boolean retainAll(Collection c) { } /** The inverse of any other {@code AbstractBiMap} subclass. */ - static class Inverse extends AbstractBiMap { + private static final class Inverse + extends AbstractBiMap { Inverse(Map backward, AbstractBiMap forward) { super(backward, forward); } @@ -423,38 +448,43 @@ static class Inverse extends AbstractBiMap { */ @Override - K checkKey(K key) { - return inverse.checkValue(key); + @ParametricNullness + K checkKey(@ParametricNullness K key) { + return super.inverse.checkValue(key); } @Override - V checkValue(V value) { - return inverse.checkKey(value); + @ParametricNullness + V checkValue(@ParametricNullness V value) { + return super.inverse.checkKey(value); } - /** @serialData the forward bimap */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + /** + * @serialData the forward bimap + */ + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(inverse()); } - @GwtIncompatible // java.io.ObjectInputStream - @SuppressWarnings("unchecked") // reading data stored by writeObject + @GwtIncompatible + @J2ktIncompatible + @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - setInverse((AbstractBiMap) stream.readObject()); + setInverse((AbstractBiMap) requireNonNull(stream.readObject())); } @GwtIncompatible // Not needed in the emulated source. + @J2ktIncompatible Object readResolve() { return inverse().inverse(); } - @GwtIncompatible // Not needed in emulated source. - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - @GwtIncompatible // Not needed in emulated source. - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/AbstractIndexedListIterator.java b/android/guava/src/com/google/common/collect/AbstractIndexedListIterator.java index 855fb1c5fd0b..552a1bc2ca9e 100644 --- a/android/guava/src/com/google/common/collect/AbstractIndexedListIterator.java +++ b/android/guava/src/com/google/common/collect/AbstractIndexedListIterator.java @@ -21,6 +21,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.ListIterator; import java.util.NoSuchElementException; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of the {@link ListIterator} interface across a @@ -30,11 +31,13 @@ * @author Jared Levy */ @GwtCompatible -abstract class AbstractIndexedListIterator extends UnmodifiableListIterator { +abstract class AbstractIndexedListIterator + extends UnmodifiableListIterator { private final int size; private int position; /** Returns the element with the specified index. This method is called by {@link #next()}. */ + @ParametricNullness protected abstract E get(int index); /** @@ -70,6 +73,7 @@ public final boolean hasNext() { } @Override + @ParametricNullness public final E next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -88,6 +92,7 @@ public final boolean hasPrevious() { } @Override + @ParametricNullness public final E previous() { if (!hasPrevious()) { throw new NoSuchElementException(); diff --git a/android/guava/src/com/google/common/collect/AbstractIterator.java b/android/guava/src/com/google/common/collect/AbstractIterator.java index ea5ea7a58326..8281233a1099 100644 --- a/android/guava/src/com/google/common/collect/AbstractIterator.java +++ b/android/guava/src/com/google/common/collect/AbstractIterator.java @@ -17,11 +17,12 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of the {@code Iterator} interface, to make this @@ -37,7 +38,7 @@ *

    Another example is an iterator that skips over null elements in a backing iterator. This could * be implemented as: * - *

    {@code
    + * {@snippet :
      * public static Iterator skipNulls(final Iterator in) {
      *   return new AbstractIterator() {
      *     protected String computeNext() {
    @@ -51,7 +52,7 @@
      *     }
      *   };
      * }
    - * }
    + * } * *

    This class supports iterators that include null elements. * @@ -61,7 +62,7 @@ // When making changes to this class, please also update the copy at // com.google.common.base.AbstractIterator @GwtCompatible -public abstract class AbstractIterator extends UnmodifiableIterator { +public abstract class AbstractIterator extends UnmodifiableIterator { private State state = State.NOT_READY; /** Constructor for use by subclasses. */ @@ -81,7 +82,7 @@ private enum State { FAILED, } - @NullableDecl private T next; + private @Nullable T next; /** * Returns the next element. Note: the implementation must call {@link #endOfData()} when @@ -107,7 +108,7 @@ private enum State { * this method. Any further attempts to use the iterator will result in an {@link * IllegalStateException}. */ - protected abstract T computeNext(); + protected abstract @Nullable T computeNext(); /** * Implementations of {@link #computeNext} must invoke this method when there are no @@ -117,12 +118,11 @@ private enum State { * simple statement {@code return endOfData();} */ @CanIgnoreReturnValue - protected final T endOfData() { + protected final @Nullable T endOfData() { state = State.DONE; return null; } - @CanIgnoreReturnValue // TODO(kak): Should we remove this? Some people are using it to prefetch? @Override public final boolean hasNext() { checkState(state != State.FAILED); @@ -148,12 +148,14 @@ private boolean tryToComputeNext() { @CanIgnoreReturnValue // TODO(kak): Should we remove this? @Override + @ParametricNullness public final T next() { if (!hasNext()) { throw new NoSuchElementException(); } state = State.NOT_READY; - T result = next; + // Safe because hasNext() ensures that tryToComputeNext() has put a T into `next`. + T result = uncheckedCastNullableTToT(next); next = null; return result; } @@ -165,10 +167,12 @@ public final T next() { *

    Implementations of {@code AbstractIterator} that wish to expose this functionality should * implement {@code PeekingIterator}. */ + @ParametricNullness public final T peek() { if (!hasNext()) { throw new NoSuchElementException(); } - return next; + // Safe because hasNext() ensures that tryToComputeNext() has put a T into `next`. + return uncheckedCastNullableTToT(next); } } diff --git a/android/guava/src/com/google/common/collect/AbstractListMultimap.java b/android/guava/src/com/google/common/collect/AbstractListMultimap.java index 4f075d1aebb6..08f170fbaba9 100644 --- a/android/guava/src/com/google/common/collect/AbstractListMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractListMultimap.java @@ -16,13 +16,17 @@ package com.google.common.collect; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Basic implementation of the {@link ListMultimap} interface. It's a wrapper around {@link @@ -33,8 +37,8 @@ * @since 2.0 */ @GwtCompatible -abstract class AbstractListMultimap extends AbstractMapBasedMultimap - implements ListMultimap { +abstract class AbstractListMultimap + extends AbstractMapBasedMultimap implements ListMultimap { /** * Creates a new multimap that uses the provided map. * @@ -47,18 +51,20 @@ protected AbstractListMultimap(Map> map) { @Override abstract List createCollection(); + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types @Override List createUnmodifiableEmptyCollection() { - return Collections.emptyList(); + return emptyList(); } @Override - Collection unmodifiableCollectionSubclass(Collection collection) { - return Collections.unmodifiableList((List) collection); + Collection unmodifiableCollectionSubclass( + Collection collection) { + return unmodifiableList((List) collection); } @Override - Collection wrapCollection(K key, Collection collection) { + Collection wrapCollection(@ParametricNullness K key, Collection collection) { return wrapList(key, (List) collection, null); } @@ -72,7 +78,7 @@ Collection wrapCollection(K key, Collection collection) { * Multimap} interface. */ @Override - public List get(@NullableDecl K key) { + public List get(@ParametricNullness K key) { return (List) super.get(key); } @@ -85,7 +91,7 @@ public List get(@NullableDecl K key) { */ @CanIgnoreReturnValue @Override - public List removeAll(@NullableDecl Object key) { + public List removeAll(@Nullable Object key) { return (List) super.removeAll(key); } @@ -98,7 +104,7 @@ public List removeAll(@NullableDecl Object key) { */ @CanIgnoreReturnValue @Override - public List replaceValues(@NullableDecl K key, Iterable values) { + public List replaceValues(@ParametricNullness K key, Iterable values) { return (List) super.replaceValues(key, values); } @@ -111,7 +117,7 @@ public List replaceValues(@NullableDecl K key, Iterable values) */ @CanIgnoreReturnValue @Override - public boolean put(@NullableDecl K key, @NullableDecl V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { return super.put(key, value); } @@ -133,9 +139,9 @@ public Map> asMap() { * in the same order. If the value orderings disagree, the multimaps will not be considered equal. */ @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return super.equals(object); } - private static final long serialVersionUID = 6588350623831699109L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 6588350623831699109L; } diff --git a/android/guava/src/com/google/common/collect/AbstractMapBasedMultimap.java b/android/guava/src/com/google/common/collect/AbstractMapBasedMultimap.java index b72f13559331..78b4f0275d5a 100644 --- a/android/guava/src/com/google/common/collect/AbstractMapBasedMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractMapBasedMultimap.java @@ -18,9 +18,15 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Maps.safeGet; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Maps.ViewCachingAbstractMap; import com.google.j2objc.annotations.WeakOuter; import java.io.Serializable; @@ -40,7 +46,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Basic implementation of the {@link Multimap} interface. This class represents a multimap as a map @@ -81,8 +87,9 @@ * @author Louis Wasserman */ @GwtCompatible -abstract class AbstractMapBasedMultimap extends AbstractMultimap - implements Serializable { +@SuppressWarnings("WrongCommentType") // false positive +abstract class AbstractMapBasedMultimap + extends AbstractMultimap implements Serializable { /* * Here's an outline of the overall design. * @@ -155,7 +162,7 @@ Collection createUnmodifiableEmptyCollection() { * @param key key to associate with values in the collection * @return an empty collection of values */ - Collection createCollection(@NullableDecl K key) { + Collection createCollection(@ParametricNullness K key) { return createCollection(); } @@ -171,14 +178,14 @@ public int size() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return map.containsKey(key); } // Modification Operations @Override - public boolean put(@NullableDecl K key, @NullableDecl V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { Collection collection = map.get(key); if (collection == null) { collection = createCollection(key); @@ -197,7 +204,7 @@ public boolean put(@NullableDecl K key, @NullableDecl V value) { } } - private Collection getOrCreateCollection(@NullableDecl K key) { + private Collection getOrCreateCollection(@ParametricNullness K key) { Collection collection = map.get(key); if (collection == null) { collection = createCollection(key); @@ -214,7 +221,7 @@ private Collection getOrCreateCollection(@NullableDecl K key) { *

    The returned collection is immutable. */ @Override - public Collection replaceValues(@NullableDecl K key, Iterable values) { + public Collection replaceValues(@ParametricNullness K key, Iterable values) { Iterator iterator = values.iterator(); if (!iterator.hasNext()) { return removeAll(key); @@ -243,7 +250,7 @@ public Collection replaceValues(@NullableDecl K key, Iterable va *

    The returned collection is immutable. */ @Override - public Collection removeAll(@NullableDecl Object key) { + public Collection removeAll(@Nullable Object key) { Collection collection = map.remove(key); if (collection == null) { @@ -258,7 +265,8 @@ public Collection removeAll(@NullableDecl Object key) { return unmodifiableCollectionSubclass(output); } - Collection unmodifiableCollectionSubclass(Collection collection) { + Collection unmodifiableCollectionSubclass( + Collection collection) { return Collections.unmodifiableCollection(collection); } @@ -280,7 +288,7 @@ public void clear() { *

    The returned collection is not serializable. */ @Override - public Collection get(@NullableDecl K key) { + public Collection get(@ParametricNullness K key) { Collection collection = map.get(key); if (collection == null) { collection = createCollection(key); @@ -292,12 +300,12 @@ public Collection get(@NullableDecl K key) { * Generates a decorated collection that remains consistent with the values in the multimap for * the provided key. Changes to the multimap may alter the returned collection, and vice versa. */ - Collection wrapCollection(@NullableDecl K key, Collection collection) { + Collection wrapCollection(@ParametricNullness K key, Collection collection) { return new WrappedCollection(key, collection, null); } final List wrapList( - @NullableDecl K key, List list, @NullableDecl WrappedCollection ancestor) { + @ParametricNullness K key, List list, @Nullable WrappedCollection ancestor) { return (list instanceof RandomAccess) ? new RandomAccessWrappedList(key, list, ancestor) : new WrappedList(key, list, ancestor); @@ -320,13 +328,13 @@ final List wrapList( */ @WeakOuter class WrappedCollection extends AbstractCollection { - @NullableDecl final K key; + @ParametricNullness final K key; Collection delegate; - @NullableDecl final WrappedCollection ancestor; - @NullableDecl final Collection ancestorDelegate; + final @Nullable WrappedCollection ancestor; + final @Nullable Collection ancestorDelegate; WrappedCollection( - @NullableDecl K key, Collection delegate, @NullableDecl WrappedCollection ancestor) { + @ParametricNullness K key, Collection delegate, @Nullable WrappedCollection ancestor) { this.key = key; this.delegate = delegate; this.ancestor = ancestor; @@ -366,6 +374,7 @@ void removeIfEmpty() { } } + @ParametricNullness K getKey() { return key; } @@ -391,7 +400,14 @@ public int size() { } @Override - public boolean equals(@NullableDecl Object object) { + /* + * Most Multimap implementations use a List or Set (or even Multiset) for their values, in which + * case Multimap equality works as expected. Users who use a Collection type that does not + * implement equals(), such as most Queue implementations, will get the same behavior from our + * value-collection wrappers (and from Multimap.equals) as from the underlying Collection. + */ + @SuppressWarnings("UndefinedEquals") + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -451,6 +467,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public V next() { validateIterator(); return delegateIterator.next(); @@ -470,7 +487,7 @@ Iterator getDelegateIterator() { } @Override - public boolean add(V value) { + public boolean add(@ParametricNullness V value) { refreshIfEmpty(); boolean wasEmpty = delegate.isEmpty(); boolean changed = delegate.add(value); @@ -483,7 +500,7 @@ public boolean add(V value) { return changed; } - WrappedCollection getAncestor() { + @Nullable WrappedCollection getAncestor() { return ancestor; } @@ -498,7 +515,7 @@ public boolean addAll(Collection collection) { boolean changed = delegate.addAll(collection); if (changed) { int newSize = delegate.size(); - totalSize += (newSize - oldSize); + totalSize += newSize - oldSize; if (oldSize == 0) { addToMap(); } @@ -507,7 +524,7 @@ public boolean addAll(Collection collection) { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { refreshIfEmpty(); return delegate.contains(o); } @@ -530,7 +547,7 @@ public void clear() { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { refreshIfEmpty(); boolean changed = delegate.remove(o); if (changed) { @@ -549,7 +566,7 @@ public boolean removeAll(Collection c) { boolean changed = delegate.removeAll(c); if (changed) { int newSize = delegate.size(); - totalSize += (newSize - oldSize); + totalSize += newSize - oldSize; removeIfEmpty(); } return changed; @@ -562,14 +579,15 @@ public boolean retainAll(Collection c) { boolean changed = delegate.retainAll(c); if (changed) { int newSize = delegate.size(); - totalSize += (newSize - oldSize); + totalSize += newSize - oldSize; removeIfEmpty(); } return changed; } } - private static Iterator iteratorOrListIterator(Collection collection) { + private static Iterator iteratorOrListIterator( + Collection collection) { return (collection instanceof List) ? ((List) collection).listIterator() : collection.iterator(); @@ -577,8 +595,8 @@ private static Iterator iteratorOrListIterator(Collection collection) /** Set decorator that stays in sync with the multimap values for a key. */ @WeakOuter - class WrappedSet extends WrappedCollection implements Set { - WrappedSet(@NullableDecl K key, Set delegate) { + final class WrappedSet extends WrappedCollection implements Set { + WrappedSet(@ParametricNullness K key, Set delegate) { super(key, delegate, null); } @@ -595,7 +613,7 @@ public boolean removeAll(Collection c) { boolean changed = Sets.removeAllImpl((Set) delegate, c); if (changed) { int newSize = delegate.size(); - totalSize += (newSize - oldSize); + totalSize += newSize - oldSize; removeIfEmpty(); } return changed; @@ -606,7 +624,7 @@ public boolean removeAll(Collection c) { @WeakOuter class WrappedSortedSet extends WrappedCollection implements SortedSet { WrappedSortedSet( - @NullableDecl K key, SortedSet delegate, @NullableDecl WrappedCollection ancestor) { + @ParametricNullness K key, SortedSet delegate, @Nullable WrappedCollection ancestor) { super(key, delegate, ancestor); } @@ -615,24 +633,26 @@ SortedSet getSortedSetDelegate() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return getSortedSetDelegate().comparator(); } @Override + @ParametricNullness public V first() { refreshIfEmpty(); return getSortedSetDelegate().first(); } @Override + @ParametricNullness public V last() { refreshIfEmpty(); return getSortedSetDelegate().last(); } @Override - public SortedSet headSet(V toElement) { + public SortedSet headSet(@ParametricNullness V toElement) { refreshIfEmpty(); return new WrappedSortedSet( getKey(), @@ -641,7 +661,7 @@ public SortedSet headSet(V toElement) { } @Override - public SortedSet subSet(V fromElement, V toElement) { + public SortedSet subSet(@ParametricNullness V fromElement, @ParametricNullness V toElement) { refreshIfEmpty(); return new WrappedSortedSet( getKey(), @@ -650,7 +670,7 @@ public SortedSet subSet(V fromElement, V toElement) { } @Override - public SortedSet tailSet(V fromElement) { + public SortedSet tailSet(@ParametricNullness V fromElement) { refreshIfEmpty(); return new WrappedSortedSet( getKey(), @@ -660,9 +680,9 @@ public SortedSet tailSet(V fromElement) { } @WeakOuter - class WrappedNavigableSet extends WrappedSortedSet implements NavigableSet { + final class WrappedNavigableSet extends WrappedSortedSet implements NavigableSet { WrappedNavigableSet( - @NullableDecl K key, NavigableSet delegate, @NullableDecl WrappedCollection ancestor) { + @ParametricNullness K key, NavigableSet delegate, @Nullable WrappedCollection ancestor) { super(key, delegate, ancestor); } @@ -672,32 +692,32 @@ NavigableSet getSortedSetDelegate() { } @Override - public V lower(V v) { + public @Nullable V lower(@ParametricNullness V v) { return getSortedSetDelegate().lower(v); } @Override - public V floor(V v) { + public @Nullable V floor(@ParametricNullness V v) { return getSortedSetDelegate().floor(v); } @Override - public V ceiling(V v) { + public @Nullable V ceiling(@ParametricNullness V v) { return getSortedSetDelegate().ceiling(v); } @Override - public V higher(V v) { + public @Nullable V higher(@ParametricNullness V v) { return getSortedSetDelegate().higher(v); } @Override - public V pollFirst() { + public @Nullable V pollFirst() { return Iterators.pollNext(iterator()); } @Override - public V pollLast() { + public @Nullable V pollLast() { return Iterators.pollNext(descendingIterator()); } @@ -717,26 +737,29 @@ public Iterator descendingIterator() { @Override public NavigableSet subSet( - V fromElement, boolean fromInclusive, V toElement, boolean toInclusive) { + @ParametricNullness V fromElement, + boolean fromInclusive, + @ParametricNullness V toElement, + boolean toInclusive) { return wrap( getSortedSetDelegate().subSet(fromElement, fromInclusive, toElement, toInclusive)); } @Override - public NavigableSet headSet(V toElement, boolean inclusive) { + public NavigableSet headSet(@ParametricNullness V toElement, boolean inclusive) { return wrap(getSortedSetDelegate().headSet(toElement, inclusive)); } @Override - public NavigableSet tailSet(V fromElement, boolean inclusive) { + public NavigableSet tailSet(@ParametricNullness V fromElement, boolean inclusive) { return wrap(getSortedSetDelegate().tailSet(fromElement, inclusive)); } } /** List decorator that stays in sync with the multimap values for a key. */ @WeakOuter - class WrappedList extends WrappedCollection implements List { - WrappedList(@NullableDecl K key, List delegate, @NullableDecl WrappedCollection ancestor) { + private class WrappedList extends WrappedCollection implements List { + WrappedList(@ParametricNullness K key, List delegate, @Nullable WrappedCollection ancestor) { super(key, delegate, ancestor); } @@ -753,7 +776,7 @@ public boolean addAll(int index, Collection c) { boolean changed = getListDelegate().addAll(index, c); if (changed) { int newSize = getDelegate().size(); - totalSize += (newSize - oldSize); + totalSize += newSize - oldSize; if (oldSize == 0) { addToMap(); } @@ -762,19 +785,21 @@ public boolean addAll(int index, Collection c) { } @Override + @ParametricNullness public V get(int index) { refreshIfEmpty(); return getListDelegate().get(index); } @Override - public V set(int index, V element) { + @ParametricNullness + public V set(int index, @ParametricNullness V element) { refreshIfEmpty(); return getListDelegate().set(index, element); } @Override - public void add(int index, V element) { + public void add(int index, @ParametricNullness V element) { refreshIfEmpty(); boolean wasEmpty = getDelegate().isEmpty(); getListDelegate().add(index, element); @@ -785,6 +810,7 @@ public void add(int index, V element) { } @Override + @ParametricNullness public V remove(int index) { refreshIfEmpty(); V value = getListDelegate().remove(index); @@ -794,13 +820,13 @@ public V remove(int index) { } @Override - public int indexOf(Object o) { + public int indexOf(@Nullable Object o) { refreshIfEmpty(); return getListDelegate().indexOf(o); } @Override - public int lastIndexOf(Object o) { + public int lastIndexOf(@Nullable Object o) { refreshIfEmpty(); return getListDelegate().lastIndexOf(o); } @@ -827,10 +853,10 @@ public List subList(int fromIndex, int toIndex) { } /** ListIterator decorator. */ - private class WrappedListIterator extends WrappedIterator implements ListIterator { + private final class WrappedListIterator extends WrappedIterator implements ListIterator { WrappedListIterator() {} - public WrappedListIterator(int index) { + WrappedListIterator(int index) { super(getListDelegate().listIterator(index)); } @@ -844,6 +870,7 @@ public boolean hasPrevious() { } @Override + @ParametricNullness public V previous() { return getDelegateListIterator().previous(); } @@ -859,12 +886,12 @@ public int previousIndex() { } @Override - public void set(V value) { + public void set(@ParametricNullness V value) { getDelegateListIterator().set(value); } @Override - public void add(V value) { + public void add(@ParametricNullness V value) { boolean wasEmpty = isEmpty(); getDelegateListIterator().add(value); totalSize++; @@ -879,9 +906,9 @@ public void add(V value) { * List decorator that stays in sync with the multimap values for a key and supports rapid random * access. */ - private class RandomAccessWrappedList extends WrappedList implements RandomAccess { + private final class RandomAccessWrappedList extends WrappedList implements RandomAccess { RandomAccessWrappedList( - @NullableDecl K key, List delegate, @NullableDecl WrappedCollection ancestor) { + @ParametricNullness K key, List delegate, @Nullable WrappedCollection ancestor) { super(key, delegate, ancestor); } } @@ -903,15 +930,15 @@ final Set createMaybeNavigableKeySet() { @WeakOuter private class KeySet extends Maps.KeySet> { - KeySet(final Map> subMap) { + KeySet(Map> subMap) { super(subMap); } @Override public Iterator iterator() { - final Iterator>> entryIterator = map().entrySet().iterator(); + Iterator>> entryIterator = map().entrySet().iterator(); return new Iterator() { - @NullableDecl Entry> entry; + @Nullable Entry> entry; @Override public boolean hasNext() { @@ -919,6 +946,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public K next() { entry = entryIterator.next(); return entry.getKey(); @@ -926,7 +954,7 @@ public K next() { @Override public void remove() { - checkRemove(entry != null); + checkState(entry != null, "no calls to next() since the last call to remove()"); Collection collection = entry.getValue(); entryIterator.remove(); totalSize -= collection.size(); @@ -939,7 +967,7 @@ public void remove() { // The following methods are included for better performance. @Override - public boolean remove(Object key) { + public boolean remove(@Nullable Object key) { int count = 0; Collection collection = map().remove(key); if (collection != null) { @@ -961,7 +989,7 @@ public boolean containsAll(Collection c) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return this == object || this.map().keySet().equals(object); } @@ -983,38 +1011,40 @@ SortedMap> sortedMap() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return sortedMap().comparator(); } @Override + @ParametricNullness public K first() { return sortedMap().firstKey(); } @Override - public SortedSet headSet(K toElement) { + public SortedSet headSet(@ParametricNullness K toElement) { return new SortedKeySet(sortedMap().headMap(toElement)); } @Override + @ParametricNullness public K last() { return sortedMap().lastKey(); } @Override - public SortedSet subSet(K fromElement, K toElement) { + public SortedSet subSet(@ParametricNullness K fromElement, @ParametricNullness K toElement) { return new SortedKeySet(sortedMap().subMap(fromElement, toElement)); } @Override - public SortedSet tailSet(K fromElement) { + public SortedSet tailSet(@ParametricNullness K fromElement) { return new SortedKeySet(sortedMap().tailMap(fromElement)); } } @WeakOuter - class NavigableKeySet extends SortedKeySet implements NavigableSet { + private final class NavigableKeySet extends SortedKeySet implements NavigableSet { NavigableKeySet(NavigableMap> subMap) { super(subMap); } @@ -1025,32 +1055,32 @@ NavigableMap> sortedMap() { } @Override - public K lower(K k) { + public @Nullable K lower(@ParametricNullness K k) { return sortedMap().lowerKey(k); } @Override - public K floor(K k) { + public @Nullable K floor(@ParametricNullness K k) { return sortedMap().floorKey(k); } @Override - public K ceiling(K k) { + public @Nullable K ceiling(@ParametricNullness K k) { return sortedMap().ceilingKey(k); } @Override - public K higher(K k) { + public @Nullable K higher(@ParametricNullness K k) { return sortedMap().higherKey(k); } @Override - public K pollFirst() { + public @Nullable K pollFirst() { return Iterators.pollNext(iterator()); } @Override - public K pollLast() { + public @Nullable K pollLast() { return Iterators.pollNext(descendingIterator()); } @@ -1065,40 +1095,44 @@ public Iterator descendingIterator() { } @Override - public NavigableSet headSet(K toElement) { + public NavigableSet headSet(@ParametricNullness K toElement) { return headSet(toElement, false); } @Override - public NavigableSet headSet(K toElement, boolean inclusive) { + public NavigableSet headSet(@ParametricNullness K toElement, boolean inclusive) { return new NavigableKeySet(sortedMap().headMap(toElement, inclusive)); } @Override - public NavigableSet subSet(K fromElement, K toElement) { + public NavigableSet subSet( + @ParametricNullness K fromElement, @ParametricNullness K toElement) { return subSet(fromElement, true, toElement, false); } @Override public NavigableSet subSet( - K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { + @ParametricNullness K fromElement, + boolean fromInclusive, + @ParametricNullness K toElement, + boolean toInclusive) { return new NavigableKeySet( sortedMap().subMap(fromElement, fromInclusive, toElement, toInclusive)); } @Override - public NavigableSet tailSet(K fromElement) { + public NavigableSet tailSet(@ParametricNullness K fromElement) { return tailSet(fromElement, true); } @Override - public NavigableSet tailSet(K fromElement, boolean inclusive) { + public NavigableSet tailSet(@ParametricNullness K fromElement, boolean inclusive) { return new NavigableKeySet(sortedMap().tailMap(fromElement, inclusive)); } } /** Removes all values for the provided key. */ - private void removeValuesForKey(Object key) { + private void removeValuesForKey(@Nullable Object key) { Collection collection = Maps.safeRemove(map, key); if (collection != null) { @@ -1108,10 +1142,10 @@ private void removeValuesForKey(Object key) { } } - private abstract class Itr implements Iterator { + private abstract class Itr implements Iterator { final Iterator>> keyIterator; - @NullableDecl K key; - @NullableDecl Collection collection; + @Nullable K key; + @Nullable Collection collection; Iterator valueIterator; Itr() { @@ -1121,7 +1155,7 @@ private abstract class Itr implements Iterator { valueIterator = Iterators.emptyModifiableIterator(); } - abstract T output(K key, V value); + abstract T output(@ParametricNullness K key, @ParametricNullness V value); @Override public boolean hasNext() { @@ -1129,6 +1163,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { if (!valueIterator.hasNext()) { Entry> mapEntry = keyIterator.next(); @@ -1136,13 +1171,21 @@ public T next() { collection = mapEntry.getValue(); valueIterator = collection.iterator(); } - return output(key, valueIterator.next()); + /* + * uncheckedCastNullableTToT is safe: The first call to this method always enters the !hasNext() case and + * populates key, after which it's never cleared. + */ + return output(uncheckedCastNullableTToT(key), valueIterator.next()); } @Override public void remove() { valueIterator.remove(); - if (collection.isEmpty()) { + /* + * requireNonNull is safe because we've already initialized `collection`. If we hadn't, then + * valueIterator.remove() would have failed. + */ + if (requireNonNull(collection).isEmpty()) { keyIterator.remove(); } totalSize--; @@ -1169,7 +1212,8 @@ Collection createValues() { Iterator valueIterator() { return new Itr() { @Override - V output(K key, V value) { + @ParametricNullness + V output(@ParametricNullness K key, @ParametricNullness V value) { return value; } }; @@ -1221,8 +1265,8 @@ Collection> createEntries() { Iterator> entryIterator() { return new Itr>() { @Override - Entry output(K key, V value) { - return Maps.immutableEntry(key, value); + Entry output(@ParametricNullness K key, @ParametricNullness V value) { + return immutableEntry(key, value); } }; } @@ -1262,13 +1306,13 @@ protected Set>> createEntrySet() { // The following methods are included for performance. @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return Maps.safeContainsKey(submap, key); } @Override - public Collection get(Object key) { - Collection collection = Maps.safeGet(submap, key); + public @Nullable Collection get(@Nullable Object key) { + Collection collection = safeGet(submap, key); if (collection == null) { return null; } @@ -1288,7 +1332,7 @@ public int size() { } @Override - public Collection remove(Object key) { + public @Nullable Collection remove(@Nullable Object key) { Collection collection = submap.remove(key); if (collection == null) { return null; @@ -1302,7 +1346,7 @@ public Collection remove(Object key) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return this == object || submap.equals(object); } @@ -1327,11 +1371,11 @@ public void clear() { Entry> wrapEntry(Entry> entry) { K key = entry.getKey(); - return Maps.immutableEntry(key, wrapCollection(key, entry.getValue())); + return immutableEntry(key, wrapCollection(key, entry.getValue())); } @WeakOuter - class AsMapEntries extends Maps.EntrySet> { + final class AsMapEntries extends Maps.EntrySet> { @Override Map> map() { return AsMap.this; @@ -1345,25 +1389,26 @@ public Iterator>> iterator() { // The following methods are included for performance. @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { return Collections2.safeContains(submap.entrySet(), o); } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { if (!contains(o)) { return false; } - Entry entry = (Entry) o; + // requireNonNull is safe because of the contains check. + Entry entry = requireNonNull((Entry) o); removeValuesForKey(entry.getKey()); return true; } } /** Iterator across all keys and value collections. */ - class AsMapIterator implements Iterator>> { + final class AsMapIterator implements Iterator>> { final Iterator>> delegateIterator = submap.entrySet().iterator(); - @NullableDecl Collection collection; + @Nullable Collection collection; @Override public boolean hasNext() { @@ -1379,7 +1424,7 @@ public Entry> next() { @Override public void remove() { - checkRemove(collection != null); + checkState(collection != null, "no calls to next() since the last call to remove()"); delegateIterator.remove(); totalSize -= collection.size(); collection.clear(); @@ -1399,36 +1444,39 @@ SortedMap> sortedMap() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return sortedMap().comparator(); } @Override + @ParametricNullness public K firstKey() { return sortedMap().firstKey(); } @Override + @ParametricNullness public K lastKey() { return sortedMap().lastKey(); } @Override - public SortedMap> headMap(K toKey) { + public SortedMap> headMap(@ParametricNullness K toKey) { return new SortedAsMap(sortedMap().headMap(toKey)); } @Override - public SortedMap> subMap(K fromKey, K toKey) { + public SortedMap> subMap( + @ParametricNullness K fromKey, @ParametricNullness K toKey) { return new SortedAsMap(sortedMap().subMap(fromKey, toKey)); } @Override - public SortedMap> tailMap(K fromKey) { + public SortedMap> tailMap(@ParametricNullness K fromKey) { return new SortedAsMap(sortedMap().tailMap(fromKey)); } - @NullableDecl SortedSet sortedKeySet; + @Nullable SortedSet sortedKeySet; // returns a SortedSet, even though returning a Set would be sufficient to // satisfy the SortedMap.keySet() interface @@ -1444,7 +1492,7 @@ SortedSet createKeySet() { } } - class NavigableAsMap extends SortedAsMap implements NavigableMap> { + private final class NavigableAsMap extends SortedAsMap implements NavigableMap> { NavigableAsMap(NavigableMap> submap) { super(submap); @@ -1456,72 +1504,73 @@ NavigableMap> sortedMap() { } @Override - public Entry> lowerEntry(K key) { + public @Nullable Entry> lowerEntry(@ParametricNullness K key) { Entry> entry = sortedMap().lowerEntry(key); return (entry == null) ? null : wrapEntry(entry); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(@ParametricNullness K key) { return sortedMap().lowerKey(key); } @Override - public Entry> floorEntry(K key) { + public @Nullable Entry> floorEntry(@ParametricNullness K key) { Entry> entry = sortedMap().floorEntry(key); return (entry == null) ? null : wrapEntry(entry); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(@ParametricNullness K key) { return sortedMap().floorKey(key); } @Override - public Entry> ceilingEntry(K key) { + public @Nullable Entry> ceilingEntry(@ParametricNullness K key) { Entry> entry = sortedMap().ceilingEntry(key); return (entry == null) ? null : wrapEntry(entry); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(@ParametricNullness K key) { return sortedMap().ceilingKey(key); } @Override - public Entry> higherEntry(K key) { + public @Nullable Entry> higherEntry(@ParametricNullness K key) { Entry> entry = sortedMap().higherEntry(key); return (entry == null) ? null : wrapEntry(entry); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(@ParametricNullness K key) { return sortedMap().higherKey(key); } @Override - public Entry> firstEntry() { + public @Nullable Entry> firstEntry() { Entry> entry = sortedMap().firstEntry(); return (entry == null) ? null : wrapEntry(entry); } @Override - public Entry> lastEntry() { + public @Nullable Entry> lastEntry() { Entry> entry = sortedMap().lastEntry(); return (entry == null) ? null : wrapEntry(entry); } @Override - public Entry> pollFirstEntry() { + public @Nullable Entry> pollFirstEntry() { return pollAsMapEntry(entrySet().iterator()); } @Override - public Entry> pollLastEntry() { + public @Nullable Entry> pollLastEntry() { return pollAsMapEntry(descendingMap().entrySet().iterator()); } - Entry> pollAsMapEntry(Iterator>> entryIterator) { + @Nullable Entry> pollAsMapEntry( + Iterator>> entryIterator) { if (!entryIterator.hasNext()) { return null; } @@ -1529,7 +1578,7 @@ Entry> pollAsMapEntry(Iterator>> entryIt Collection output = createCollection(); output.addAll(entry.getValue()); entryIterator.remove(); - return Maps.immutableEntry(entry.getKey(), unmodifiableCollectionSubclass(output)); + return immutableEntry(entry.getKey(), unmodifiableCollectionSubclass(output)); } @Override @@ -1558,36 +1607,41 @@ public NavigableSet descendingKeySet() { } @Override - public NavigableMap> subMap(K fromKey, K toKey) { + public NavigableMap> subMap( + @ParametricNullness K fromKey, @ParametricNullness K toKey) { return subMap(fromKey, true, toKey, false); } @Override public NavigableMap> subMap( - K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + @ParametricNullness K fromKey, + boolean fromInclusive, + @ParametricNullness K toKey, + boolean toInclusive) { return new NavigableAsMap(sortedMap().subMap(fromKey, fromInclusive, toKey, toInclusive)); } @Override - public NavigableMap> headMap(K toKey) { + public NavigableMap> headMap(@ParametricNullness K toKey) { return headMap(toKey, false); } @Override - public NavigableMap> headMap(K toKey, boolean inclusive) { + public NavigableMap> headMap(@ParametricNullness K toKey, boolean inclusive) { return new NavigableAsMap(sortedMap().headMap(toKey, inclusive)); } @Override - public NavigableMap> tailMap(K fromKey) { + public NavigableMap> tailMap(@ParametricNullness K fromKey) { return tailMap(fromKey, true); } @Override - public NavigableMap> tailMap(K fromKey, boolean inclusive) { + public NavigableMap> tailMap( + @ParametricNullness K fromKey, boolean inclusive) { return new NavigableAsMap(sortedMap().tailMap(fromKey, inclusive)); } } - private static final long serialVersionUID = 2447537837011683357L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 2447537837011683357L; } diff --git a/android/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java b/android/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java index 3ff472a9fc25..885dfbfb229a 100644 --- a/android/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java +++ b/android/guava/src/com/google/common/collect/AbstractMapBasedMultiset.java @@ -22,6 +22,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; @@ -31,31 +32,32 @@ import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Basic implementation of {@code Multiset} backed by an instance of {@code - * AbstractObjectCountMap}. + * ObjectCountHashMap}. * *

    For serialization to work, the subclass must specify explicit {@code readObject} and {@code * writeObject} methods. * * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) -abstract class AbstractMapBasedMultiset extends AbstractMultiset implements Serializable { +@GwtCompatible +abstract class AbstractMapBasedMultiset extends AbstractMultiset + implements Serializable { transient ObjectCountHashMap backingMap; transient long size; AbstractMapBasedMultiset(int distinctElements) { - init(distinctElements); + backingMap = newBackingMap(distinctElements); } - abstract void init(int distinctElements); + abstract ObjectCountHashMap newBackingMap(int distinctElements); @Override - public final int count(@NullableDecl Object element) { + public final int count(@Nullable Object element) { return backingMap.get(element); } @@ -69,7 +71,7 @@ public final int count(@NullableDecl Object element) { */ @CanIgnoreReturnValue @Override - public final int add(@NullableDecl E element, int occurrences) { + public final int add(@ParametricNullness E element, int occurrences) { if (occurrences == 0) { return count(element); } @@ -90,7 +92,7 @@ public final int add(@NullableDecl E element, int occurrences) { @CanIgnoreReturnValue @Override - public final int remove(@NullableDecl Object element, int occurrences) { + public final int remove(@Nullable Object element, int occurrences) { if (occurrences == 0) { return count(element); } @@ -114,7 +116,7 @@ public final int remove(@NullableDecl Object element, int occurrences) { @CanIgnoreReturnValue @Override - public final int setCount(@NullableDecl E element, int count) { + public final int setCount(@ParametricNullness E element, int count) { checkNonnegative(count, "count"); int oldCount = (count == 0) ? backingMap.remove(element) : backingMap.put(element, count); size += (count - oldCount); @@ -122,7 +124,7 @@ public final int setCount(@NullableDecl E element, int count) { } @Override - public final boolean setCount(@NullableDecl E element, int oldCount, int newCount) { + public final boolean setCount(@ParametricNullness E element, int oldCount, int newCount) { checkNonnegative(oldCount, "oldCount"); checkNonnegative(newCount, "newCount"); int entryIndex = backingMap.indexOf(element); @@ -160,11 +162,12 @@ public final void clear() { * Skeleton of per-entry iterators. We could push this down and win a few bytes, but it's complex * enough it's not especially worth it. */ - abstract class Itr implements Iterator { + abstract class Itr implements Iterator { int entryIndex = backingMap.firstIndex(); int toRemove = -1; int expectedModCount = backingMap.modCount; + @ParametricNullness abstract T result(int entryIndex); private void checkForConcurrentModification() { @@ -180,6 +183,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -205,6 +209,7 @@ public void remove() { final Iterator elementIterator() { return new Itr() { @Override + @ParametricNullness E result(int entryIndex) { return backingMap.getKey(entryIndex); } @@ -248,20 +253,21 @@ public final int size() { * @serialData the number of distinct elements, the first element, its count, the second element, * its count, and so on */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMultiset(this, stream); } - @GwtIncompatible // java.io.ObjectInputStream - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int distinctElements = Serialization.readCount(stream); - init(ObjectCountHashMap.DEFAULT_SIZE); + backingMap = newBackingMap(ObjectCountHashMap.DEFAULT_SIZE); Serialization.populateMultiset(this, stream, distinctElements); } - @GwtIncompatible // Not needed in emulated source. - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/AbstractMapEntry.java b/android/guava/src/com/google/common/collect/AbstractMapEntry.java index 27ea432ba3de..aa8be1e87a15 100644 --- a/android/guava/src/com/google/common/collect/AbstractMapEntry.java +++ b/android/guava/src/com/google/common/collect/AbstractMapEntry.java @@ -17,9 +17,9 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} methods of {@code @@ -28,25 +28,29 @@ * @author Jared Levy */ @GwtCompatible -abstract class AbstractMapEntry implements Entry { +abstract class AbstractMapEntry + implements Entry { @Override + @ParametricNullness public abstract K getKey(); @Override + @ParametricNullness public abstract V getValue(); @Override - public V setValue(V value) { + @ParametricNullness + public V setValue(@ParametricNullness V value) { throw new UnsupportedOperationException(); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof Entry) { Entry that = (Entry) object; - return Objects.equal(this.getKey(), that.getKey()) - && Objects.equal(this.getValue(), that.getValue()); + return Objects.equals(this.getKey(), that.getKey()) + && Objects.equals(this.getValue(), that.getValue()); } return false; } diff --git a/android/guava/src/com/google/common/collect/AbstractMultimap.java b/android/guava/src/com/google/common/collect/AbstractMultimap.java index a9952172d104..086b8c73aaa8 100644 --- a/android/guava/src/com/google/common/collect/AbstractMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractMultimap.java @@ -28,7 +28,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A skeleton {@code Multimap} implementation, not necessarily in terms of a {@code Map}. @@ -36,14 +36,15 @@ * @author Louis Wasserman */ @GwtCompatible -abstract class AbstractMultimap implements Multimap { +abstract class AbstractMultimap + implements Multimap { @Override public boolean isEmpty() { return size() == 0; } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { for (Collection collection : asMap().values()) { if (collection.contains(value)) { return true; @@ -54,27 +55,27 @@ public boolean containsValue(@NullableDecl Object value) { } @Override - public boolean containsEntry(@NullableDecl Object key, @NullableDecl Object value) { + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { Collection collection = asMap().get(key); return collection != null && collection.contains(value); } @CanIgnoreReturnValue @Override - public boolean remove(@NullableDecl Object key, @NullableDecl Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { Collection collection = asMap().get(key); return collection != null && collection.remove(value); } @CanIgnoreReturnValue @Override - public boolean put(@NullableDecl K key, @NullableDecl V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { return get(key).add(value); } @CanIgnoreReturnValue @Override - public boolean putAll(@NullableDecl K key, Iterable values) { + public boolean putAll(@ParametricNullness K key, Iterable values) { checkNotNull(values); // make sure we only call values.iterator() once // and we only call get(key) if values is nonempty @@ -99,14 +100,14 @@ public boolean putAll(Multimap multimap) { @CanIgnoreReturnValue @Override - public Collection replaceValues(@NullableDecl K key, Iterable values) { + public Collection replaceValues(@ParametricNullness K key, Iterable values) { checkNotNull(values); Collection result = removeAll(key); putAll(key, values); return result; } - @LazyInit @NullableDecl private transient Collection> entries; + @LazyInit private transient @Nullable Collection> entries; @Override public Collection> entries() { @@ -130,21 +131,21 @@ public Iterator> iterator() { } @WeakOuter - class EntrySet extends Entries implements Set> { + final class EntrySet extends Entries implements Set> { @Override public int hashCode() { return Sets.hashCodeImpl(this); } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { return Sets.equalsImpl(this, obj); } } abstract Iterator> entryIterator(); - @LazyInit @NullableDecl private transient Set keySet; + @LazyInit private transient @Nullable Set keySet; @Override public Set keySet() { @@ -154,7 +155,7 @@ public Set keySet() { abstract Set createKeySet(); - @LazyInit @NullableDecl private transient Multiset keys; + @LazyInit private transient @Nullable Multiset keys; @Override public Multiset keys() { @@ -164,7 +165,7 @@ public Multiset keys() { abstract Multiset createKeys(); - @LazyInit @NullableDecl private transient Collection values; + @LazyInit private transient @Nullable Collection values; @Override public Collection values() { @@ -175,7 +176,7 @@ public Collection values() { abstract Collection createValues(); @WeakOuter - class Values extends AbstractCollection { + final class Values extends AbstractCollection { @Override public Iterator iterator() { return valueIterator(); @@ -187,7 +188,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { return AbstractMultimap.this.containsValue(o); } @@ -201,7 +202,7 @@ Iterator valueIterator() { return Maps.valueIterator(entries().iterator()); } - @LazyInit @NullableDecl private transient Map> asMap; + @LazyInit private transient @Nullable Map> asMap; @Override public Map> asMap() { @@ -214,7 +215,7 @@ public Map> asMap() { // Comparison and hashing @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return Multimaps.equalsImpl(this, object); } diff --git a/android/guava/src/com/google/common/collect/AbstractMultiset.java b/android/guava/src/com/google/common/collect/AbstractMultiset.java index c0a7f5e384ce..89bd10343d3d 100644 --- a/android/guava/src/com/google/common/collect/AbstractMultiset.java +++ b/android/guava/src/com/google/common/collect/AbstractMultiset.java @@ -26,7 +26,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of the {@link Multiset} interface. A new multiset @@ -42,7 +42,8 @@ * @author Louis Wasserman */ @GwtCompatible -abstract class AbstractMultiset extends AbstractCollection implements Multiset { +abstract class AbstractMultiset extends AbstractCollection + implements Multiset { // Query Operations @Override @@ -51,45 +52,45 @@ public boolean isEmpty() { } @Override - public boolean contains(@NullableDecl Object element) { + public boolean contains(@Nullable Object element) { return count(element) > 0; } // Modification Operations @CanIgnoreReturnValue @Override - public final boolean add(@NullableDecl E element) { + public final boolean add(@ParametricNullness E element) { add(element, 1); return true; } @CanIgnoreReturnValue @Override - public int add(@NullableDecl E element, int occurrences) { + public int add(@ParametricNullness E element, int occurrences) { throw new UnsupportedOperationException(); } @CanIgnoreReturnValue @Override - public final boolean remove(@NullableDecl Object element) { + public final boolean remove(@Nullable Object element) { return remove(element, 1) > 0; } @CanIgnoreReturnValue @Override - public int remove(@NullableDecl Object element, int occurrences) { + public int remove(@Nullable Object element, int occurrences) { throw new UnsupportedOperationException(); } @CanIgnoreReturnValue @Override - public int setCount(@NullableDecl E element, int count) { + public int setCount(@ParametricNullness E element, int count) { return setCountImpl(this, element, count); } @CanIgnoreReturnValue @Override - public boolean setCount(@NullableDecl E element, int oldCount, int newCount) { + public boolean setCount(@ParametricNullness E element, int oldCount, int newCount) { return setCountImpl(this, element, oldCount, newCount); } @@ -124,7 +125,7 @@ public final boolean retainAll(Collection elementsToRetain) { // Views - @LazyInit @NullableDecl private transient Set elementSet; + @LazyInit private transient @Nullable Set elementSet; @Override public Set elementSet() { @@ -144,7 +145,7 @@ Set createElementSet() { } @WeakOuter - class ElementSet extends Multisets.ElementSet { + final class ElementSet extends Multisets.ElementSet { @Override Multiset multiset() { return AbstractMultiset.this; @@ -158,7 +159,7 @@ public Iterator iterator() { abstract Iterator elementIterator(); - @LazyInit @NullableDecl private transient Set> entrySet; + @LazyInit private transient @Nullable Set> entrySet; @Override public Set> entrySet() { @@ -204,7 +205,7 @@ Set> createEntrySet() { * and if, for each element, the two multisets have the same count. */ @Override - public final boolean equals(@NullableDecl Object object) { + public final boolean equals(@Nullable Object object) { return Multisets.equalsImpl(this, object); } diff --git a/android/guava/src/com/google/common/collect/AbstractNavigableMap.java b/android/guava/src/com/google/common/collect/AbstractNavigableMap.java index e5259e829e4e..ef2f20b04fa4 100644 --- a/android/guava/src/com/google/common/collect/AbstractNavigableMap.java +++ b/android/guava/src/com/google/common/collect/AbstractNavigableMap.java @@ -24,7 +24,7 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Skeletal implementation of {@link NavigableMap}. @@ -32,38 +32,34 @@ * @author Louis Wasserman */ @GwtIncompatible -abstract class AbstractNavigableMap extends IteratorBasedAbstractMap - implements NavigableMap { +abstract class AbstractNavigableMap + extends IteratorBasedAbstractMap implements NavigableMap { @Override - @NullableDecl - public abstract V get(@NullableDecl Object key); + public abstract @Nullable V get(@Nullable Object key); @Override - @NullableDecl - public Entry firstEntry() { - return Iterators.getNext(entryIterator(), null); + public @Nullable Entry firstEntry() { + return Iterators.<@Nullable Entry>getNext(entryIterator(), null); } @Override - @NullableDecl - public Entry lastEntry() { - return Iterators.getNext(descendingEntryIterator(), null); + public @Nullable Entry lastEntry() { + return Iterators.<@Nullable Entry>getNext(descendingEntryIterator(), null); } @Override - @NullableDecl - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return Iterators.pollNext(entryIterator()); } @Override - @NullableDecl - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return Iterators.pollNext(descendingEntryIterator()); } @Override + @ParametricNullness public K firstKey() { Entry entry = firstEntry(); if (entry == null) { @@ -74,6 +70,7 @@ public K firstKey() { } @Override + @ParametricNullness public K lastKey() { Entry entry = lastEntry(); if (entry == null) { @@ -84,63 +81,59 @@ public K lastKey() { } @Override - @NullableDecl - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(@ParametricNullness K key) { return headMap(key, false).lastEntry(); } @Override - @NullableDecl - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(@ParametricNullness K key) { return headMap(key, true).lastEntry(); } @Override - @NullableDecl - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(@ParametricNullness K key) { return tailMap(key, true).firstEntry(); } @Override - @NullableDecl - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(@ParametricNullness K key) { return tailMap(key, false).firstEntry(); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(@ParametricNullness K key) { return Maps.keyOrNull(lowerEntry(key)); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(@ParametricNullness K key) { return Maps.keyOrNull(floorEntry(key)); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(@ParametricNullness K key) { return Maps.keyOrNull(ceilingEntry(key)); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(@ParametricNullness K key) { return Maps.keyOrNull(higherEntry(key)); } abstract Iterator> descendingEntryIterator(); @Override - public SortedMap subMap(K fromKey, K toKey) { + public SortedMap subMap(@ParametricNullness K fromKey, @ParametricNullness K toKey) { return subMap(fromKey, true, toKey, false); } @Override - public SortedMap headMap(K toKey) { + public SortedMap headMap(@ParametricNullness K toKey) { return headMap(toKey, false); } @Override - public SortedMap tailMap(K fromKey) { + public SortedMap tailMap(@ParametricNullness K fromKey) { return tailMap(fromKey, true); } diff --git a/android/guava/src/com/google/common/collect/AbstractRangeSet.java b/android/guava/src/com/google/common/collect/AbstractRangeSet.java index 42ae89380186..c84d126ce1c4 100644 --- a/android/guava/src/com/google/common/collect/AbstractRangeSet.java +++ b/android/guava/src/com/google/common/collect/AbstractRangeSet.java @@ -15,13 +15,14 @@ package com.google.common.collect; import com.google.common.annotations.GwtIncompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A skeletal implementation of {@code RangeSet}. * * @author Louis Wasserman */ +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 @GwtIncompatible abstract class AbstractRangeSet implements RangeSet { AbstractRangeSet() {} @@ -32,7 +33,7 @@ public boolean contains(C value) { } @Override - public abstract Range rangeContaining(C value); + public abstract @Nullable Range rangeContaining(C value); @Override public boolean isEmpty() { @@ -51,7 +52,7 @@ public void remove(Range range) { @Override public void clear() { - remove(Range.all()); + remove(Range.all()); } @Override @@ -102,7 +103,7 @@ public boolean intersects(Range otherRange) { public abstract boolean encloses(Range otherRange); @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } else if (obj instanceof RangeSet) { diff --git a/android/guava/src/com/google/common/collect/AbstractSequentialIterator.java b/android/guava/src/com/google/common/collect/AbstractSequentialIterator.java index bda06924fcaa..96e888dc3e65 100644 --- a/android/guava/src/com/google/common/collect/AbstractSequentialIterator.java +++ b/android/guava/src/com/google/common/collect/AbstractSequentialIterator.java @@ -18,7 +18,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of the {@code Iterator} interface for sequences @@ -27,27 +27,27 @@ * *

    Example: * - *

    {@code
    + * {@snippet :
      * Iterator powersOfTwo =
      *     new AbstractSequentialIterator(1) {
      *       protected Integer computeNext(Integer previous) {
      *         return (previous == 1 << 30) ? null : previous * 2;
      *       }
      *     };
    - * }
    + * } * * @author Chris Povirk * @since 12.0 (in Guava as {@code AbstractLinkedIterator} since 8.0) */ @GwtCompatible public abstract class AbstractSequentialIterator extends UnmodifiableIterator { - @NullableDecl private T nextOrNull; + private @Nullable T nextOrNull; /** * Creates a new iterator with the given first element, or, if {@code firstOrNull} is null, * creates a new empty iterator. */ - protected AbstractSequentialIterator(@NullableDecl T firstOrNull) { + protected AbstractSequentialIterator(@Nullable T firstOrNull) { this.nextOrNull = firstOrNull; } @@ -56,8 +56,7 @@ protected AbstractSequentialIterator(@NullableDecl T firstOrNull) { * remain. This method is invoked during each call to {@link #next()} in order to compute the * result of a future call to {@code next()}. */ - @NullableDecl - protected abstract T computeNext(T previous); + protected abstract @Nullable T computeNext(T previous); @Override public final boolean hasNext() { @@ -66,13 +65,11 @@ public final boolean hasNext() { @Override public final T next() { - if (!hasNext()) { + if (nextOrNull == null) { throw new NoSuchElementException(); } - try { - return nextOrNull; - } finally { - nextOrNull = computeNext(nextOrNull); - } + T oldNext = nextOrNull; + nextOrNull = computeNext(oldNext); + return oldNext; } } diff --git a/android/guava/src/com/google/common/collect/AbstractSetMultimap.java b/android/guava/src/com/google/common/collect/AbstractSetMultimap.java index 2779d1cbde5d..ffb956798d71 100644 --- a/android/guava/src/com/google/common/collect/AbstractSetMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractSetMultimap.java @@ -16,14 +16,18 @@ package com.google.common.collect; +import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableSet; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; -import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Basic implementation of the {@link SetMultimap} interface. It's a wrapper around {@link @@ -33,8 +37,8 @@ * @author Jared Levy */ @GwtCompatible -abstract class AbstractSetMultimap extends AbstractMapBasedMultimap - implements SetMultimap { +abstract class AbstractSetMultimap + extends AbstractMapBasedMultimap implements SetMultimap { /** * Creates a new multimap that uses the provided map. * @@ -49,16 +53,17 @@ protected AbstractSetMultimap(Map> map) { @Override Set createUnmodifiableEmptyCollection() { - return Collections.emptySet(); + return emptySet(); } @Override - Collection unmodifiableCollectionSubclass(Collection collection) { - return Collections.unmodifiableSet((Set) collection); + Collection unmodifiableCollectionSubclass( + Collection collection) { + return unmodifiableSet((Set) collection); } @Override - Collection wrapCollection(K key, Collection collection) { + Collection wrapCollection(@ParametricNullness K key, Collection collection) { return new WrappedSet(key, (Set) collection); } @@ -71,7 +76,7 @@ Collection wrapCollection(K key, Collection collection) { * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. */ @Override - public Set get(@NullableDecl K key) { + public Set get(@ParametricNullness K key) { return (Set) super.get(key); } @@ -94,7 +99,7 @@ public Set> entries() { */ @CanIgnoreReturnValue @Override - public Set removeAll(@NullableDecl Object key) { + public Set removeAll(@Nullable Object key) { return (Set) super.removeAll(key); } @@ -108,7 +113,7 @@ public Set removeAll(@NullableDecl Object key) { */ @CanIgnoreReturnValue @Override - public Set replaceValues(@NullableDecl K key, Iterable values) { + public Set replaceValues(@ParametricNullness K key, Iterable values) { return (Set) super.replaceValues(key, values); } @@ -133,7 +138,7 @@ public Map> asMap() { */ @CanIgnoreReturnValue @Override - public boolean put(@NullableDecl K key, @NullableDecl V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { return super.put(key, value); } @@ -144,9 +149,9 @@ public boolean put(@NullableDecl K key, @NullableDecl V value) { * Equality does not depend on the ordering of keys or values. */ @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return super.equals(object); } - private static final long serialVersionUID = 7431625294878419160L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 7431625294878419160L; } diff --git a/android/guava/src/com/google/common/collect/AbstractSortedKeySortedSetMultimap.java b/android/guava/src/com/google/common/collect/AbstractSortedKeySortedSetMultimap.java index 0ee6edb1e090..b07e226df411 100644 --- a/android/guava/src/com/google/common/collect/AbstractSortedKeySortedSetMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractSortedKeySortedSetMultimap.java @@ -21,6 +21,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; +import org.jspecify.annotations.Nullable; /** * Basic implementation of a {@link SortedSetMultimap} with a sorted key set. @@ -31,7 +32,9 @@ * @author Louis Wasserman */ @GwtCompatible -abstract class AbstractSortedKeySortedSetMultimap extends AbstractSortedSetMultimap { +abstract class AbstractSortedKeySortedSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends AbstractSortedSetMultimap { AbstractSortedKeySortedSetMultimap(SortedMap> map) { super(map); diff --git a/android/guava/src/com/google/common/collect/AbstractSortedMultiset.java b/android/guava/src/com/google/common/collect/AbstractSortedMultiset.java index 091bb8cbce47..aae18799738a 100644 --- a/android/guava/src/com/google/common/collect/AbstractSortedMultiset.java +++ b/android/guava/src/com/google/common/collect/AbstractSortedMultiset.java @@ -17,11 +17,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.WeakOuter; import java.util.Comparator; import java.util.Iterator; import java.util.NavigableSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of the {@link SortedMultiset} interface. @@ -32,9 +33,10 @@ * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) -abstract class AbstractSortedMultiset extends AbstractMultiset implements SortedMultiset { - @GwtTransient final Comparator comparator; +@GwtCompatible +abstract class AbstractSortedMultiset extends AbstractMultiset + implements SortedMultiset { + private final Comparator comparator; // needed for serialization @SuppressWarnings("unchecked") @@ -53,7 +55,7 @@ public NavigableSet elementSet() { @Override NavigableSet createElementSet() { - return new SortedMultisets.NavigableElementSet(this); + return new SortedMultisets.NavigableElementSet<>(this); } @Override @@ -62,19 +64,19 @@ public Comparator comparator() { } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { Iterator> entryIterator = entryIterator(); return entryIterator.hasNext() ? entryIterator.next() : null; } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { Iterator> entryIterator = descendingEntryIterator(); return entryIterator.hasNext() ? entryIterator.next() : null; } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { Iterator> entryIterator = entryIterator(); if (entryIterator.hasNext()) { Entry result = entryIterator.next(); @@ -86,7 +88,7 @@ public Entry pollFirstEntry() { } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { Iterator> entryIterator = descendingEntryIterator(); if (entryIterator.hasNext()) { Entry result = entryIterator.next(); @@ -99,9 +101,9 @@ public Entry pollLastEntry() { @Override public SortedMultiset subMultiset( - @NullableDecl E fromElement, + @ParametricNullness E fromElement, BoundType fromBoundType, - @NullableDecl E toElement, + @ParametricNullness E toElement, BoundType toBoundType) { // These are checked elsewhere, but NullPointerTester wants them checked eagerly. checkNotNull(fromBoundType); @@ -115,7 +117,7 @@ Iterator descendingIterator() { return Multisets.iteratorImpl(descendingMultiset()); } - @NullableDecl private transient SortedMultiset descendingMultiset; + @LazyInit private transient @Nullable SortedMultiset descendingMultiset; @Override public SortedMultiset descendingMultiset() { @@ -125,7 +127,7 @@ public SortedMultiset descendingMultiset() { SortedMultiset createDescendingMultiset() { @WeakOuter - class DescendingMultisetImpl extends DescendingMultiset { + final class DescendingMultisetImpl extends DescendingMultiset { @Override SortedMultiset forwardMultiset() { return AbstractSortedMultiset.this; diff --git a/android/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java b/android/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java index 6254a6e62980..e76b7cbf4fb6 100644 --- a/android/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java +++ b/android/guava/src/com/google/common/collect/AbstractSortedSetMultimap.java @@ -16,14 +16,18 @@ package com.google.common.collect; +import static com.google.common.collect.Sets.unmodifiableNavigableSet; +import static java.util.Collections.unmodifiableSortedSet; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; -import java.util.Collections; import java.util.Map; import java.util.NavigableSet; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Basic implementation of the {@link SortedSetMultimap} interface. It's a wrapper around {@link @@ -33,8 +37,8 @@ * @author Jared Levy */ @GwtCompatible -abstract class AbstractSortedSetMultimap extends AbstractSetMultimap - implements SortedSetMultimap { +abstract class AbstractSortedSetMultimap + extends AbstractSetMultimap implements SortedSetMultimap { /** * Creates a new multimap that uses the provided map. * @@ -53,16 +57,17 @@ SortedSet createUnmodifiableEmptyCollection() { } @Override - SortedSet unmodifiableCollectionSubclass(Collection collection) { + SortedSet unmodifiableCollectionSubclass( + Collection collection) { if (collection instanceof NavigableSet) { - return Sets.unmodifiableNavigableSet((NavigableSet) collection); + return unmodifiableNavigableSet((NavigableSet) collection); } else { - return Collections.unmodifiableSortedSet((SortedSet) collection); + return unmodifiableSortedSet((SortedSet) collection); } } @Override - Collection wrapCollection(K key, Collection collection) { + Collection wrapCollection(@ParametricNullness K key, Collection collection) { if (collection instanceof NavigableSet) { return new WrappedNavigableSet(key, (NavigableSet) collection, null); } else { @@ -83,7 +88,7 @@ Collection wrapCollection(K key, Collection collection) { * Multimap} interface. */ @Override - public SortedSet get(@NullableDecl K key) { + public SortedSet get(@ParametricNullness K key) { return (SortedSet) super.get(key); } @@ -96,7 +101,7 @@ public SortedSet get(@NullableDecl K key) { */ @CanIgnoreReturnValue @Override - public SortedSet removeAll(@NullableDecl Object key) { + public SortedSet removeAll(@Nullable Object key) { return (SortedSet) super.removeAll(key); } @@ -112,7 +117,7 @@ public SortedSet removeAll(@NullableDecl Object key) { */ @CanIgnoreReturnValue @Override - public SortedSet replaceValues(@NullableDecl K key, Iterable values) { + public SortedSet replaceValues(@ParametricNullness K key, Iterable values) { return (SortedSet) super.replaceValues(key, values); } @@ -144,5 +149,5 @@ public Collection values() { return super.values(); } - private static final long serialVersionUID = 430848587173315748L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 430848587173315748L; } diff --git a/android/guava/src/com/google/common/collect/AbstractTable.java b/android/guava/src/com/google/common/collect/AbstractTable.java index 7cb34a61bb9a..0b82add873a6 100644 --- a/android/guava/src/com/google/common/collect/AbstractTable.java +++ b/android/guava/src/com/google/common/collect/AbstractTable.java @@ -14,6 +14,9 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Maps.safeGet; + import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.concurrent.LazyInit; @@ -24,7 +27,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Skeletal, implementation-agnostic implementation of the {@link Table} interface. @@ -32,15 +35,17 @@ * @author Louis Wasserman */ @GwtCompatible -abstract class AbstractTable implements Table { +abstract class AbstractTable< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + implements Table { @Override - public boolean containsRow(@NullableDecl Object rowKey) { + public boolean containsRow(@Nullable Object rowKey) { return Maps.safeContainsKey(rowMap(), rowKey); } @Override - public boolean containsColumn(@NullableDecl Object columnKey) { + public boolean containsColumn(@Nullable Object columnKey) { return Maps.safeContainsKey(columnMap(), columnKey); } @@ -55,7 +60,7 @@ public Set columnKeySet() { } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { for (Map row : rowMap().values()) { if (row.containsValue(value)) { return true; @@ -65,15 +70,15 @@ public boolean containsValue(@NullableDecl Object value) { } @Override - public boolean contains(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { - Map row = Maps.safeGet(rowMap(), rowKey); + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { + Map row = safeGet(rowMap(), rowKey); return row != null && Maps.safeContainsKey(row, columnKey); } @Override - public V get(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { - Map row = Maps.safeGet(rowMap(), rowKey); - return (row == null) ? null : Maps.safeGet(row, columnKey); + public @Nullable V get(@Nullable Object rowKey, @Nullable Object columnKey) { + Map row = safeGet(rowMap(), rowKey); + return (row == null) ? null : safeGet(row, columnKey); } @Override @@ -88,14 +93,15 @@ public void clear() { @CanIgnoreReturnValue @Override - public V remove(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { - Map row = Maps.safeGet(rowMap(), rowKey); + public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { + Map row = safeGet(rowMap(), rowKey); return (row == null) ? null : Maps.safeRemove(row, columnKey); } @CanIgnoreReturnValue @Override - public V put(R rowKey, C columnKey, V value) { + public @Nullable V put( + @ParametricNullness R rowKey, @ParametricNullness C columnKey, @ParametricNullness V value) { return row(rowKey).put(columnKey, value); } @@ -106,7 +112,7 @@ public void putAll(Table table) { } } - @LazyInit @NullableDecl private transient Set> cellSet; + @LazyInit private transient @Nullable Set> cellSet; @Override public Set> cellSet() { @@ -121,27 +127,27 @@ Set> createCellSet() { abstract Iterator> cellIterator(); @WeakOuter - class CellSet extends AbstractSet> { + private final class CellSet extends AbstractSet> { @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Cell) { Cell cell = (Cell) o; - Map row = Maps.safeGet(rowMap(), cell.getRowKey()); + Map row = safeGet(rowMap(), cell.getRowKey()); return row != null && Collections2.safeContains( - row.entrySet(), Maps.immutableEntry(cell.getColumnKey(), cell.getValue())); + row.entrySet(), immutableEntry(cell.getColumnKey(), cell.getValue())); } return false; } @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { if (o instanceof Cell) { Cell cell = (Cell) o; - Map row = Maps.safeGet(rowMap(), cell.getRowKey()); + Map row = safeGet(rowMap(), cell.getRowKey()); return row != null && Collections2.safeRemove( - row.entrySet(), Maps.immutableEntry(cell.getColumnKey(), cell.getValue())); + row.entrySet(), immutableEntry(cell.getColumnKey(), cell.getValue())); } return false; } @@ -162,7 +168,7 @@ public int size() { } } - @LazyInit @NullableDecl private transient Collection values; + @LazyInit private transient @Nullable Collection values; @Override public Collection values() { @@ -177,6 +183,7 @@ Collection createValues() { Iterator valuesIterator() { return new TransformedIterator, V>(cellSet().iterator()) { @Override + @ParametricNullness V transform(Cell cell) { return cell.getValue(); } @@ -184,14 +191,14 @@ V transform(Cell cell) { } @WeakOuter - class Values extends AbstractCollection { + private final class Values extends AbstractCollection { @Override public Iterator iterator() { return valuesIterator(); } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { return containsValue(o); } @@ -207,7 +214,7 @@ public int size() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { return Tables.equalsImpl(this, obj); } diff --git a/android/guava/src/com/google/common/collect/AllEqualOrdering.java b/android/guava/src/com/google/common/collect/AllEqualOrdering.java index bbcd19e01f85..70d14669e475 100644 --- a/android/guava/src/com/google/common/collect/AllEqualOrdering.java +++ b/android/guava/src/com/google/common/collect/AllEqualOrdering.java @@ -17,26 +17,29 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An ordering that treats all references as equals, even nulls. * * @author Emily Soldal */ -@GwtCompatible(serializable = true) -final class AllEqualOrdering extends Ordering implements Serializable { +@GwtCompatible +final class AllEqualOrdering extends Ordering<@Nullable Object> implements Serializable { static final AllEqualOrdering INSTANCE = new AllEqualOrdering(); @Override - public int compare(@NullableDecl Object left, @NullableDecl Object right) { + @SuppressWarnings("UnusedVariable") // intentionally weird Comparator + public int compare(@Nullable Object left, @Nullable Object right) { return 0; } @Override - public List sortedCopy(Iterable iterable) { + public List sortedCopy(Iterable iterable) { return Lists.newArrayList(iterable); } @@ -47,7 +50,7 @@ public ImmutableList immutableSortedCopy(Iterable iterable) { @SuppressWarnings("unchecked") @Override - public Ordering reverse() { + public Ordering reverse() { return (Ordering) this; } @@ -60,5 +63,5 @@ public String toString() { return "Ordering.allEqual()"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ArrayListMultimap.java b/android/guava/src/com/google/common/collect/ArrayListMultimap.java index 1faf476c6576..ed3d205f41a7 100644 --- a/android/guava/src/com/google/common/collect/ArrayListMultimap.java +++ b/android/guava/src/com/google/common/collect/ArrayListMultimap.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.io.ObjectInputStream; @@ -29,6 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; /** * Implementation of {@code Multimap} that uses an {@code ArrayList} to store the values for a given @@ -52,15 +54,14 @@ * with a call to {@link Multimaps#synchronizedListMultimap}. * *

    See the Guava User Guide article on {@code - * Multimap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}. * * @author Jared Levy * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) -public final class ArrayListMultimap - extends ArrayListMultimapGwtSerializationDependencies { +@GwtCompatible +public final class ArrayListMultimap + extends AbstractListMultimap { // Default from ArrayList private static final int DEFAULT_VALUES_PER_KEY = 3; @@ -69,10 +70,12 @@ public final class ArrayListMultimap /** * Creates a new, empty {@code ArrayListMultimap} with the default initial capacities. * - *

    This method will soon be deprecated in favor of {@code - * MultimapBuilder.hashKeys().arrayListValues().build()}. + *

    You may also consider the equivalent {@code + * MultimapBuilder.hashKeys().arrayListValues().build()}, which provides more control over the + * underlying data structure. */ - public static ArrayListMultimap create() { + public static + ArrayListMultimap create() { return new ArrayListMultimap<>(); } @@ -80,27 +83,31 @@ public static ArrayListMultimap create() { * Constructs an empty {@code ArrayListMultimap} with enough capacity to hold the specified * numbers of keys and values without resizing. * - *

    This method will soon be deprecated in favor of {@code - * MultimapBuilder.hashKeys(expectedKeys).arrayListValues(expectedValuesPerKey).build()}. + *

    You may also consider the equivalent {@code + * MultimapBuilder.hashKeys(expectedKeys).arrayListValues(expectedValuesPerKey).build()}, which + * provides more control over the underlying data structure. * * @param expectedKeys the expected number of distinct keys * @param expectedValuesPerKey the expected average number of values per key * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is * negative */ - public static ArrayListMultimap create(int expectedKeys, int expectedValuesPerKey) { + public static + ArrayListMultimap create(int expectedKeys, int expectedValuesPerKey) { return new ArrayListMultimap<>(expectedKeys, expectedValuesPerKey); } /** * Constructs an {@code ArrayListMultimap} with the same mappings as the specified multimap. * - *

    This method will soon be deprecated in favor of {@code - * MultimapBuilder.hashKeys().arrayListValues().build(multimap)}. + *

    You may also consider the equivalent {@code + * MultimapBuilder.hashKeys().arrayListValues().build(multimap)}, which provides more control over + * the underlying data structure. * * @param multimap the multimap whose contents are copied to this multimap */ - public static ArrayListMultimap create(Multimap multimap) { + public static + ArrayListMultimap create(Multimap multimap) { return new ArrayListMultimap<>(multimap); } @@ -109,7 +116,7 @@ private ArrayListMultimap() { } private ArrayListMultimap(int expectedKeys, int expectedValuesPerKey) { - super(Platform.>newHashMapWithExpectedSize(expectedKeys)); + super(Platform.newHashMapWithExpectedSize(expectedKeys)); checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); this.expectedValuesPerKey = expectedValuesPerKey; } @@ -128,7 +135,7 @@ private ArrayListMultimap(Multimap multimap) { */ @Override List createCollection() { - return new ArrayList(expectedValuesPerKey); + return new ArrayList<>(expectedValuesPerKey); } /** @@ -150,14 +157,16 @@ public void trimToSize() { * @serialData expectedValuesPerKey, number of distinct keys, and then for each distinct key: the * key, number of values for that key, and the key's values */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMultimap(this, stream); } - @GwtIncompatible // java.io.ObjectOutputStream - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; int distinctKeys = Serialization.readCount(stream); @@ -166,6 +175,5 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo Serialization.populateMultimap(this, stream, distinctKeys); } - @GwtIncompatible // Not needed in emulated source. - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java b/android/guava/src/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java deleted file mode 100644 index 9a8cdfbdbd13..000000000000 --- a/android/guava/src/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2016 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import com.google.common.annotations.GwtCompatible; -import java.util.Collection; -import java.util.Map; - -/** - * A dummy superclass to support GWT serialization of the element types of an {@link - * ArrayListMultimap}. The GWT supersource for this class contains a field for each type. - * - *

    For details about this hack, see {@code GwtSerializationDependencies}, which takes the same - * approach but with a subclass rather than a superclass. - * - *

    TODO(cpovirk): Consider applying this subclass approach to our other types. - */ -@GwtCompatible(emulated = true) -abstract class ArrayListMultimapGwtSerializationDependencies - extends AbstractListMultimap { - ArrayListMultimapGwtSerializationDependencies(Map> map) { - super(map); - } - // TODO(cpovirk): Maybe I should have just one shared superclass for AbstractMultimap itself? -} diff --git a/android/guava/src/com/google/common/collect/ArrayTable.java b/android/guava/src/com/google/common/collect/ArrayTable.java index 2859085c7c7b..666ac356df40 100644 --- a/android/guava/src/com/google/common/collect/ArrayTable.java +++ b/android/guava/src/com/google/common/collect/ArrayTable.java @@ -19,14 +19,16 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.System.arraycopy; +import static java.util.Collections.emptyMap; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Maps.IteratorBasedAbstractMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.WeakOuter; import java.io.Serializable; import java.lang.reflect.Array; @@ -34,8 +36,9 @@ import java.util.Collection; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Fixed-size {@link Table} implementation backed by a two-dimensional array. @@ -81,14 +84,16 @@ * thread that reads from another. * *

    See the Guava User Guide article on {@code Table}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#table">{@code Table}. * * @author Jared Levy * @since 10.0 */ -@Beta -@GwtCompatible(emulated = true) -public final class ArrayTable extends AbstractTable implements Serializable { +// We explicitly list `implements Table<...>` so that its `@Nullable V` appears in Javadoc. +@SuppressWarnings("RedundancyRemover") +@GwtCompatible +public final class ArrayTable extends AbstractTable + implements Table, Serializable { /** * Creates an {@code ArrayTable} filled with {@code null}. @@ -128,8 +133,9 @@ public static ArrayTable create( * * @throws NullPointerException if {@code table} has a null key */ - public static ArrayTable create(Table table) { - return (table instanceof ArrayTable) + @SuppressWarnings("unchecked") // TODO(cpovirk): Make constructor accept wildcard types? + public static ArrayTable create(Table table) { + return (table instanceof ArrayTable) ? new ArrayTable((ArrayTable) table) : new ArrayTable(table); } @@ -140,7 +146,7 @@ public static ArrayTable create(Table table) { // TODO(jlevy): Add getters returning rowKeyToIndex and columnKeyToIndex? private final ImmutableMap rowKeyToIndex; private final ImmutableMap columnKeyToIndex; - private final V[][] array; + private final @Nullable V[][] array; private ArrayTable(Iterable rowKeys, Iterable columnKeys) { this.rowList = ImmutableList.copyOf(rowKeys); @@ -157,13 +163,13 @@ private ArrayTable(Iterable rowKeys, Iterable columnKe columnKeyToIndex = Maps.indexMap(columnList); @SuppressWarnings("unchecked") - V[][] tmpArray = (V[][]) new Object[rowList.size()][columnList.size()]; + @Nullable V[][] tmpArray = (@Nullable V[][]) new Object[rowList.size()][columnList.size()]; array = tmpArray; // Necessary because in GWT the arrays are initialized with "undefined" instead of null. eraseAll(); } - private ArrayTable(Table table) { + private ArrayTable(Table table) { this(table.rowKeySet(), table.columnKeySet()); putAll(table); } @@ -174,14 +180,15 @@ private ArrayTable(ArrayTable table) { rowKeyToIndex = table.rowKeyToIndex; columnKeyToIndex = table.columnKeyToIndex; @SuppressWarnings("unchecked") - V[][] copy = (V[][]) new Object[rowList.size()][columnList.size()]; + @Nullable V[][] copy = (@Nullable V[][]) new Object[rowList.size()][columnList.size()]; array = copy; for (int i = 0; i < rowList.size(); i++) { - System.arraycopy(table.array[i], 0, copy[i], 0, table.array[i].length); + arraycopy(table.array[i], 0, copy[i], 0, table.array[i].length); } } - private abstract static class ArrayMap extends IteratorBasedAbstractMap { + private abstract static class ArrayMap + extends IteratorBasedAbstractMap { private final ImmutableMap keyIndex; private ArrayMap(ImmutableMap keyIndex) { @@ -199,11 +206,11 @@ K getKey(int index) { abstract String getKeyRole(); - @NullableDecl + @ParametricNullness abstract V getValue(int index); - @NullableDecl - abstract V setValue(int index, V newValue); + @ParametricNullness + abstract V setValue(int index, @ParametricNullness V newValue); @Override public int size() { @@ -215,7 +222,7 @@ public boolean isEmpty() { return keyIndex.isEmpty(); } - Entry getEntry(final int index) { + Entry getEntry(int index) { checkElementIndex(index, size()); return new AbstractMapEntry() { @Override @@ -224,12 +231,14 @@ public K getKey() { } @Override + @ParametricNullness public V getValue() { return ArrayMap.this.getValue(index); } @Override - public V setValue(V value) { + @ParametricNullness + public V setValue(@ParametricNullness V value) { return ArrayMap.this.setValue(index, value); } }; @@ -239,7 +248,7 @@ public V setValue(V value) { Iterator> entryIterator() { return new AbstractIndexedListIterator>(size()) { @Override - protected Entry get(final int index) { + protected Entry get(int index) { return getEntry(index); } }; @@ -248,12 +257,12 @@ protected Entry get(final int index) { // TODO(lowasser): consider an optimized values() implementation @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return keyIndex.containsKey(key); } @Override - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { Integer index = keyIndex.get(key); if (index == null) { return null; @@ -263,7 +272,7 @@ public V get(@NullableDecl Object key) { } @Override - public V put(K key, V value) { + public @Nullable V put(K key, @ParametricNullness V value) { Integer index = keyIndex.get(key); if (index == null) { throw new IllegalArgumentException( @@ -273,7 +282,7 @@ public V put(K key, V value) { } @Override - public V remove(Object key) { + public @Nullable V remove(@Nullable Object key) { throw new UnsupportedOperationException(); } @@ -311,7 +320,7 @@ public ImmutableList columnKeyList() { * or equal to the number of allowed row keys, or {@code columnIndex} is greater than or equal * to the number of allowed column keys */ - public V at(int rowIndex, int columnIndex) { + public @Nullable V at(int rowIndex, int columnIndex) { // In GWT array access never throws IndexOutOfBoundsException. checkElementIndex(rowIndex, rowList.size()); checkElementIndex(columnIndex, columnList.size()); @@ -332,7 +341,7 @@ public V at(int rowIndex, int columnIndex) { * to the number of allowed column keys */ @CanIgnoreReturnValue - public V set(int rowIndex, int columnIndex, @NullableDecl V value) { + public @Nullable V set(int rowIndex, int columnIndex, @Nullable V value) { // In GWT array access never throws IndexOutOfBoundsException. checkElementIndex(rowIndex, rowList.size()); checkElementIndex(columnIndex, columnList.size()); @@ -351,11 +360,12 @@ public V set(int rowIndex, int columnIndex, @NullableDecl V value) { * @param valueClass class of values stored in the returned array */ @GwtIncompatible // reflection - public V[][] toArray(Class valueClass) { + public @Nullable V[][] toArray(Class valueClass) { @SuppressWarnings("unchecked") // TODO: safe? - V[][] copy = (V[][]) Array.newInstance(valueClass, rowList.size(), columnList.size()); + @Nullable V[][] copy = + (@Nullable V[][]) Array.newInstance(valueClass, rowList.size(), columnList.size()); for (int i = 0; i < rowList.size(); i++) { - System.arraycopy(array[i], 0, copy[i], 0, array[i].length); + arraycopy(array[i], 0, copy[i], 0, array[i].length); } return copy; } @@ -375,7 +385,7 @@ public void clear() { /** Associates the value {@code null} with every pair of allowed row and column keys. */ public void eraseAll() { - for (V[] row : array) { + for (@Nullable V[] row : array) { Arrays.fill(row, null); } } @@ -385,7 +395,7 @@ public void eraseAll() { * constructed. */ @Override - public boolean contains(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { return containsRow(rowKey) && containsColumn(columnKey); } @@ -394,7 +404,7 @@ public boolean contains(@NullableDecl Object rowKey, @NullableDecl Object column * table was constructed. */ @Override - public boolean containsColumn(@NullableDecl Object columnKey) { + public boolean containsColumn(@Nullable Object columnKey) { return columnKeyToIndex.containsKey(columnKey); } @@ -403,15 +413,15 @@ public boolean containsColumn(@NullableDecl Object columnKey) { * constructed. */ @Override - public boolean containsRow(@NullableDecl Object rowKey) { + public boolean containsRow(@Nullable Object rowKey) { return rowKeyToIndex.containsKey(rowKey); } @Override - public boolean containsValue(@NullableDecl Object value) { - for (V[] row : array) { + public boolean containsValue(@Nullable Object value) { + for (@Nullable V[] row : array) { for (V element : row) { - if (Objects.equal(value, element)) { + if (Objects.equals(value, element)) { return true; } } @@ -420,7 +430,7 @@ public boolean containsValue(@NullableDecl Object value) { } @Override - public V get(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V get(@Nullable Object rowKey, @Nullable Object columnKey) { Integer rowIndex = rowKeyToIndex.get(rowKey); Integer columnIndex = columnKeyToIndex.get(columnKey); return (rowIndex == null || columnIndex == null) ? null : at(rowIndex, columnIndex); @@ -442,7 +452,7 @@ public boolean isEmpty() { */ @CanIgnoreReturnValue @Override - public V put(R rowKey, C columnKey, @NullableDecl V value) { + public @Nullable V put(R rowKey, C columnKey, @Nullable V value) { checkNotNull(rowKey); checkNotNull(columnKey); Integer rowIndex = rowKeyToIndex.get(rowKey); @@ -468,7 +478,7 @@ public V put(R rowKey, C columnKey, @NullableDecl V value) { * in {@link #rowKeySet()} or {@link #columnKeySet()} */ @Override - public void putAll(Table table) { + public void putAll(Table table) { super.putAll(table); } @@ -482,7 +492,7 @@ public void putAll(Table table) { @CanIgnoreReturnValue @Override @Deprecated - public V remove(Object rowKey, Object columnKey) { + public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { throw new UnsupportedOperationException(); } @@ -500,7 +510,7 @@ public V remove(Object rowKey, Object columnKey) { * for the keys */ @CanIgnoreReturnValue - public V erase(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V erase(@Nullable Object rowKey, @Nullable Object columnKey) { Integer rowIndex = rowKeyToIndex.get(rowKey); Integer columnIndex = columnKeyToIndex.get(columnKey); if (rowIndex == null || columnIndex == null) { @@ -528,22 +538,22 @@ public int size() { * @return set of table cells consisting of row key / column key / value triplets */ @Override - public Set> cellSet() { + public Set> cellSet() { return super.cellSet(); } @Override - Iterator> cellIterator() { - return new AbstractIndexedListIterator>(size()) { + Iterator> cellIterator() { + return new AbstractIndexedListIterator>(size()) { @Override - protected Cell get(final int index) { + protected Cell get(int index) { return getCell(index); } }; } - private Cell getCell(final int index) { - return new Tables.AbstractCell() { + private Cell getCell(int index) { + return new Tables.AbstractCell() { final int rowIndex = index / columnList.size(); final int columnIndex = index % columnList.size(); @@ -558,13 +568,13 @@ public C getColumnKey() { } @Override - public V getValue() { + public @Nullable V getValue() { return at(rowIndex, columnIndex); } }; } - private V getValue(int index) { + private @Nullable V getValue(int index) { int rowIndex = index / columnList.size(); int columnIndex = index % columnList.size(); return at(rowIndex, columnIndex); @@ -582,13 +592,17 @@ private V getValue(int index) { * @return the corresponding map from row keys to values */ @Override - public Map column(C columnKey) { + public Map column(C columnKey) { checkNotNull(columnKey); Integer columnIndex = columnKeyToIndex.get(columnKey); - return (columnIndex == null) ? ImmutableMap.of() : new Column(columnIndex); + if (columnIndex == null) { + return emptyMap(); + } else { + return new Column(columnIndex); + } } - private class Column extends ArrayMap { + private final class Column extends ArrayMap { final int columnIndex; Column(int columnIndex) { @@ -602,12 +616,12 @@ String getKeyRole() { } @Override - V getValue(int index) { + @Nullable V getValue(int index) { return at(index, columnIndex); } @Override - V setValue(int index, V newValue) { + @Nullable V setValue(int index, @Nullable V newValue) { return set(index, columnIndex, newValue); } } @@ -623,16 +637,16 @@ public ImmutableSet columnKeySet() { return columnKeyToIndex.keySet(); } - @NullableDecl private transient ColumnMap columnMap; + @LazyInit private transient @Nullable ColumnMap columnMap; @Override - public Map> columnMap() { + public Map> columnMap() { ColumnMap map = columnMap; return (map == null) ? columnMap = new ColumnMap() : map; } @WeakOuter - private class ColumnMap extends ArrayMap> { + private final class ColumnMap extends ArrayMap> { private ColumnMap() { super(columnKeyToIndex); } @@ -643,17 +657,17 @@ String getKeyRole() { } @Override - Map getValue(int index) { + Map getValue(int index) { return new Column(index); } @Override - Map setValue(int index, Map newValue) { + Map setValue(int index, Map newValue) { throw new UnsupportedOperationException(); } @Override - public Map put(C key, Map value) { + public @Nullable Map put(C key, Map value) { throw new UnsupportedOperationException(); } } @@ -670,13 +684,17 @@ public Map put(C key, Map value) { * @return the corresponding map from column keys to values */ @Override - public Map row(R rowKey) { + public Map row(R rowKey) { checkNotNull(rowKey); Integer rowIndex = rowKeyToIndex.get(rowKey); - return (rowIndex == null) ? ImmutableMap.of() : new Row(rowIndex); + if (rowIndex == null) { + return emptyMap(); + } else { + return new Row(rowIndex); + } } - private class Row extends ArrayMap { + private final class Row extends ArrayMap { final int rowIndex; Row(int rowIndex) { @@ -690,12 +708,12 @@ String getKeyRole() { } @Override - V getValue(int index) { + @Nullable V getValue(int index) { return at(rowIndex, index); } @Override - V setValue(int index, V newValue) { + @Nullable V setValue(int index, @Nullable V newValue) { return set(rowIndex, index, newValue); } } @@ -711,16 +729,16 @@ public ImmutableSet rowKeySet() { return rowKeyToIndex.keySet(); } - @NullableDecl private transient RowMap rowMap; + @LazyInit private transient @Nullable RowMap rowMap; @Override - public Map> rowMap() { + public Map> rowMap() { RowMap map = rowMap; return (map == null) ? rowMap = new RowMap() : map; } @WeakOuter - private class RowMap extends ArrayMap> { + private final class RowMap extends ArrayMap> { private RowMap() { super(rowKeyToIndex); } @@ -731,17 +749,17 @@ String getKeyRole() { } @Override - Map getValue(int index) { + Map getValue(int index) { return new Row(index); } @Override - Map setValue(int index, Map newValue) { + Map setValue(int index, Map newValue) { throw new UnsupportedOperationException(); } @Override - public Map put(R key, Map value) { + public @Nullable Map put(R key, Map value) { throw new UnsupportedOperationException(); } } @@ -756,19 +774,19 @@ public Map put(R key, Map value) { * @return collection of values */ @Override - public Collection values() { + public Collection<@Nullable V> values() { return super.values(); } @Override - Iterator valuesIterator() { - return new AbstractIndexedListIterator(size()) { + Iterator<@Nullable V> valuesIterator() { + return new AbstractIndexedListIterator<@Nullable V>(size()) { @Override - protected V get(int index) { + protected @Nullable V get(int index) { return getValue(index); } }; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/BiMap.java b/android/guava/src/com/google/common/collect/BiMap.java index 0fd75c0a1e5e..3b312f460031 100644 --- a/android/guava/src/com/google/common/collect/BiMap.java +++ b/android/guava/src/com/google/common/collect/BiMap.java @@ -20,21 +20,30 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as * that of its keys. This constraint enables bimaps to support an "inverse view", which is another * bimap containing the same entries as this bimap but with reversed keys and values. * + *

    Implementations

    + * + *
      + *
    • {@link ImmutableBiMap} + *
    • {@link HashBiMap} + *
    • {@link EnumBiMap} + *
    • {@link EnumHashBiMap} + *
    + * *

    See the Guava User Guide article on {@code BiMap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap">{@code BiMap}. * * @author Kevin Bourrillion * @since 2.0 */ @GwtCompatible -public interface BiMap extends Map { +public interface BiMap extends Map { // Modification Operations /** @@ -46,8 +55,7 @@ public interface BiMap extends Map { */ @CanIgnoreReturnValue @Override - @NullableDecl - V put(@NullableDecl K key, @NullableDecl V value); + @Nullable V put(@ParametricNullness K key, @ParametricNullness V value); /** * An alternate form of {@code put} that silently removes any existing entry with the value {@code @@ -62,12 +70,13 @@ public interface BiMap extends Map { * * @param key the key with which the specified value is to be associated * @param value the value to be associated with the specified key - * @return the value which was previously associated with the key, which may be {@code null}, or - * {@code null} if there was no previous entry + * @return the value that was previously associated with the key, or {@code null} if there was no + * previous entry. (If the bimap contains null values, then {@code forcePut}, like {@code + * put}, returns {@code null} both if the key is absent and if it is present with a null + * value.) */ @CanIgnoreReturnValue - @NullableDecl - V forcePut(@NullableDecl K key, @NullableDecl V value); + @Nullable V forcePut(@ParametricNullness K key, @ParametricNullness V value); // Bulk Operations @@ -99,8 +108,8 @@ public interface BiMap extends Map { * associated key. The two bimaps are backed by the same data; any changes to one will appear in * the other. * - *

    Note:There is no guaranteed correspondence between the iteration order of a bimap and - * that of its inverse. + *

    Note: There is no guaranteed correspondence between the iteration order of a bimap + * and that of its inverse. * * @return the inverse view of this bimap */ diff --git a/android/guava/src/com/google/common/collect/BoundType.java b/android/guava/src/com/google/common/collect/BoundType.java index ce038026a078..6f24a6ad62ba 100644 --- a/android/guava/src/com/google/common/collect/BoundType.java +++ b/android/guava/src/com/google/common/collect/BoundType.java @@ -39,8 +39,4 @@ public enum BoundType { static BoundType forBoolean(boolean inclusive) { return inclusive ? CLOSED : OPEN; } - - BoundType flip() { - return forBoolean(!inclusive); - } } diff --git a/android/guava/src/com/google/common/collect/ByFunctionOrdering.java b/android/guava/src/com/google/common/collect/ByFunctionOrdering.java index 9e8671b12583..0d597e7b0191 100644 --- a/android/guava/src/com/google/common/collect/ByFunctionOrdering.java +++ b/android/guava/src/com/google/common/collect/ByFunctionOrdering.java @@ -19,17 +19,20 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; -import com.google.common.base.Objects; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * An ordering that orders elements by applying an order to the result of a function on those * elements. */ -@GwtCompatible(serializable = true) -final class ByFunctionOrdering extends Ordering implements Serializable { +@GwtCompatible +final class ByFunctionOrdering + extends Ordering implements Serializable { final Function function; final Ordering ordering; @@ -39,12 +42,12 @@ final class ByFunctionOrdering extends Ordering implements Serializable } @Override - public int compare(F left, F right) { + public int compare(@ParametricNullness F left, @ParametricNullness F right) { return ordering.compare(function.apply(left), function.apply(right)); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -57,7 +60,7 @@ public boolean equals(@NullableDecl Object object) { @Override public int hashCode() { - return Objects.hashCode(function, ordering); + return Objects.hash(function, ordering); } @Override @@ -65,5 +68,5 @@ public String toString() { return ordering + ".onResultOf(" + function + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/CartesianList.java b/android/guava/src/com/google/common/collect/CartesianList.java index 63c7f1a3efe2..7c5d2df446dd 100644 --- a/android/guava/src/com/google/common/collect/CartesianList.java +++ b/android/guava/src/com/google/common/collect/CartesianList.java @@ -17,12 +17,13 @@ import static com.google.common.base.Preconditions.checkElementIndex; import com.google.common.annotations.GwtCompatible; -import com.google.common.math.IntMath; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.AbstractList; import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link Lists#cartesianProduct(List)}. @@ -44,7 +45,7 @@ static List> create(List> lists) { } axesBuilder.add(copy); } - return new CartesianList(axesBuilder.build()); + return new CartesianList<>(axesBuilder.build()); } CartesianList(ImmutableList> axes) { @@ -53,7 +54,7 @@ static List> create(List> lists) { axesSizeProduct[axes.size()] = 1; try { for (int i = axes.size() - 1; i >= 0; i--) { - axesSizeProduct[i] = IntMath.checkedMultiply(axesSizeProduct[i + 1], axes.get(i).size()); + axesSizeProduct[i] = Math.multiplyExact(axesSizeProduct[i + 1], axes.get(i).size()); } } catch (ArithmeticException e) { throw new IllegalArgumentException( @@ -67,7 +68,7 @@ private int getAxisIndexForProductIndex(int index, int axis) { } @Override - public int indexOf(Object o) { + public int indexOf(@Nullable Object o) { if (!(o instanceof List)) { return -1; } @@ -89,7 +90,7 @@ public int indexOf(Object o) { } @Override - public int lastIndexOf(Object o) { + public int lastIndexOf(@Nullable Object o) { if (!(o instanceof List)) { return -1; } @@ -111,7 +112,7 @@ public int lastIndexOf(Object o) { } @Override - public ImmutableList get(final int index) { + public ImmutableList get(int index) { checkElementIndex(index, size()); return new ImmutableList() { @@ -131,6 +132,15 @@ public E get(int axis) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @J2ktIncompatible // serialization + @Override + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; } @@ -140,7 +150,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { if (!(object instanceof List)) { return false; } diff --git a/android/guava/src/com/google/common/collect/ClassToInstanceMap.java b/android/guava/src/com/google/common/collect/ClassToInstanceMap.java index 8d454c0c6869..12c8ee32a346 100644 --- a/android/guava/src/com/google/common/collect/ClassToInstanceMap.java +++ b/android/guava/src/com/google/common/collect/ClassToInstanceMap.java @@ -20,7 +20,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotMock; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A map, each entry of which maps a Java raw type to an @@ -30,26 +31,34 @@ *

    Like any other {@code Map}, this map may contain entries for primitive types, * and a primitive type and its corresponding wrapper type may map to different values. * - *

    See the Guava User Guide article on {@code - * ClassToInstanceMap}. + *

    Implementations

    + * + *
      + *
    • {@link ImmutableClassToInstanceMap} + *
    • {@link MutableClassToInstanceMap} + *
    * *

    To map a generic type to an instance of that type, use {@link * com.google.common.reflect.TypeToInstanceMap} instead. * - * @param the common supertype that all entries must share; often this is simply {@link Object} - * @author Kevin Bourrillion + *

    See the Guava User Guide article on {@code + * ClassToInstanceMap}. + * + * @param the common supertype that all values will share. When in doubt, just use {@link + * Object}, or use {@code @Nullable Object} to allow null values. * @since 2.0 */ @DoNotMock("Use ImmutableClassToInstanceMap or MutableClassToInstanceMap") @GwtCompatible -public interface ClassToInstanceMap extends Map, B> { +public interface ClassToInstanceMap + extends Map, B> { /** * Returns the value the specified class is mapped to, or {@code null} if no entry for this class * is present. This will only return a value that was bound to this specific class, not a value * that may have been bound to a subtype. */ - T getInstance(Class type); + @Nullable T getInstance(Class type); /** * Maps the specified class to the specified value. Does not associate this value with any @@ -59,5 +68,5 @@ public interface ClassToInstanceMap extends Map, B> { * null} if there was no previous entry. */ @CanIgnoreReturnValue - T putInstance(Class type, @NullableDecl T value); + @Nullable T putInstance(Class<@NonNull T> type, @ParametricNullness T value); } diff --git a/android/guava/src/com/google/common/collect/CollectCollectors.java b/android/guava/src/com/google/common/collect/CollectCollectors.java new file mode 100644 index 000000000000..c8527ab46f23 --- /dev/null +++ b/android/guava/src/com/google/common/collect/CollectCollectors.java @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Collections.singletonMap; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toMap; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import java.util.Collection; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.TreeMap; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + +/** Collectors utilities for {@code common.collect} internals. */ +@GwtCompatible +@IgnoreJRERequirement // used only from APIs with Java 8 types in them +final class CollectCollectors { + + private static final Collector> TO_IMMUTABLE_LIST = + Collector.of( + ImmutableList::builder, + ImmutableList.Builder::add, + ImmutableList.Builder::combine, + ImmutableList.Builder::build); + + private static final Collector> TO_IMMUTABLE_SET = + Collector.of( + ImmutableSet::builder, + ImmutableSet.Builder::add, + ImmutableSet.Builder::combine, + ImmutableSet.Builder::build); + + @GwtIncompatible + private static final Collector>, ?, ImmutableRangeSet>> + TO_IMMUTABLE_RANGE_SET = + Collector.of( + ImmutableRangeSet::builder, + ImmutableRangeSet.Builder::add, + ImmutableRangeSet.Builder::combine, + ImmutableRangeSet.Builder::build); + + // Lists + + @SuppressWarnings({"rawtypes", "unchecked"}) + static Collector> toImmutableList() { + return (Collector) TO_IMMUTABLE_LIST; + } + + // Sets + + @SuppressWarnings({"rawtypes", "unchecked"}) + static Collector> toImmutableSet() { + return (Collector) TO_IMMUTABLE_SET; + } + + static Collector> toImmutableSortedSet( + Comparator comparator) { + checkNotNull(comparator); + return Collector.of( + () -> new ImmutableSortedSet.Builder(comparator), + ImmutableSortedSet.Builder::add, + ImmutableSortedSet.Builder::combine, + ImmutableSortedSet.Builder::build); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + static > Collector> toImmutableEnumSet() { + return (Collector) EnumSetAccumulator.TO_IMMUTABLE_ENUM_SET; + } + + private static > + Collector, ImmutableSet> toImmutableEnumSetGeneric() { + return Collector.of( + EnumSetAccumulator::new, + EnumSetAccumulator::add, + EnumSetAccumulator::combine, + EnumSetAccumulator::toImmutableSet, + Collector.Characteristics.UNORDERED); + } + + @IgnoreJRERequirement // see enclosing class (whose annotation Animal Sniffer ignores here...) + private static final class EnumSetAccumulator> { + @SuppressWarnings({"rawtypes", "unchecked"}) + static final Collector, ?, ImmutableSet>> TO_IMMUTABLE_ENUM_SET = + (Collector) toImmutableEnumSetGeneric(); + + private @Nullable EnumSet set; + + void add(E e) { + if (set == null) { + set = EnumSet.of(e); + } else { + set.add(e); + } + } + + EnumSetAccumulator combine(EnumSetAccumulator other) { + if (this.set == null) { + return other; + } else if (other.set == null) { + return this; + } else { + this.set.addAll(other.set); + return this; + } + } + + ImmutableSet toImmutableSet() { + if (set == null) { + return ImmutableSet.of(); + } + ImmutableSet ret = ImmutableEnumSet.asImmutable(set); + set = null; // subsequent manual manipulation of the accumulator mustn't affect ret + return ret; + } + } + + @GwtIncompatible + @SuppressWarnings({"rawtypes", "unchecked"}) + static > + Collector, ?, ImmutableRangeSet> toImmutableRangeSet() { + return (Collector) TO_IMMUTABLE_RANGE_SET; + } + + // Multisets + + static Collector> toImmutableMultiset( + Function elementFunction, ToIntFunction countFunction) { + checkNotNull(elementFunction); + checkNotNull(countFunction); + return Collector.of( + LinkedHashMultiset::create, + (multiset, t) -> + multiset.add(checkNotNull(elementFunction.apply(t)), countFunction.applyAsInt(t)), + (multiset1, multiset2) -> { + multiset1.addAll(multiset2); + return multiset1; + }, + (Multiset multiset) -> ImmutableMultiset.copyFromEntries(multiset.entrySet())); + } + + static > + Collector toMultiset( + Function elementFunction, + ToIntFunction countFunction, + Supplier multisetSupplier) { + checkNotNull(elementFunction); + checkNotNull(countFunction); + checkNotNull(multisetSupplier); + return Collector.of( + multisetSupplier, + (ms, t) -> ms.add(elementFunction.apply(t), countFunction.applyAsInt(t)), + (ms1, ms2) -> { + ms1.addAll(ms2); + return ms1; + }); + } + + // Maps + + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + ImmutableMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableMap.Builder::combine, + ImmutableMap.Builder::buildOrThrow); + } + + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + return collectingAndThen( + toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), ImmutableMap::copyOf); + } + + static + Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction) { + checkNotNull(comparator); + checkNotNull(keyFunction); + checkNotNull(valueFunction); + /* + * We will always fail if there are duplicate keys, and the keys are always sorted by + * the Comparator, so the entries can come in an arbitrary order -- so we report UNORDERED. + */ + return Collector.of( + () -> new ImmutableSortedMap.Builder(comparator), + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableSortedMap.Builder::combine, + ImmutableSortedMap.Builder::buildOrThrow, + Collector.Characteristics.UNORDERED); + } + + static + Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(comparator); + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + return collectingAndThen( + toMap(keyFunction, valueFunction, mergeFunction, () -> new TreeMap(comparator)), + ImmutableSortedMap::copyOfSorted); + } + + static Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + ImmutableBiMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableBiMap.Builder::combine, + ImmutableBiMap.Builder::buildOrThrow, + new Collector.Characteristics[0]); + } + + static , V> + Collector> toImmutableEnumMap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + () -> + new EnumMapAccumulator( + (v1, v2) -> { + throw new IllegalArgumentException("Multiple values for key: " + v1 + ", " + v2); + }), + (accum, t) -> { + /* + * We assign these to variables before calling checkNotNull to work around a bug in our + * nullness checker. + */ + K key = keyFunction.apply(t); + V newValue = valueFunction.apply(t); + accum.put( + checkNotNull(key, "Null key for input %s", t), + checkNotNull(newValue, "Null value for input %s", t)); + }, + EnumMapAccumulator::combine, + EnumMapAccumulator::toImmutableMap, + Collector.Characteristics.UNORDERED); + } + + static , V> + Collector> toImmutableEnumMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + // not UNORDERED because we don't know if mergeFunction is commutative + return Collector.of( + () -> new EnumMapAccumulator(mergeFunction), + (accum, t) -> { + /* + * We assign these to variables before calling checkNotNull to work around a bug in our + * nullness checker. + */ + K key = keyFunction.apply(t); + V newValue = valueFunction.apply(t); + accum.put( + checkNotNull(key, "Null key for input %s", t), + checkNotNull(newValue, "Null value for input %s", t)); + }, + EnumMapAccumulator::combine, + EnumMapAccumulator::toImmutableMap); + } + + @IgnoreJRERequirement // see enclosing class (whose annotation Animal Sniffer ignores here...) + private static final class EnumMapAccumulator, V> { + private final BinaryOperator mergeFunction; + private @Nullable EnumMap map = null; + + EnumMapAccumulator(BinaryOperator mergeFunction) { + this.mergeFunction = mergeFunction; + } + + void put(K key, V value) { + if (map == null) { + map = new EnumMap<>(singletonMap(key, value)); + } else { + map.merge(key, value, mergeFunction); + } + } + + EnumMapAccumulator combine(EnumMapAccumulator other) { + if (this.map == null) { + return other; + } else if (other.map == null) { + return this; + } else { + other.map.forEach(this::put); + return this; + } + } + + ImmutableMap toImmutableMap() { + return (map == null) ? ImmutableMap.of() : ImmutableEnumMap.asImmutable(map); + } + } + + @GwtIncompatible + static , V> + Collector> toImmutableRangeMap( + Function> keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + ImmutableRangeMap::builder, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableRangeMap.Builder::combine, + ImmutableRangeMap.Builder::build); + } + + // Multimaps + + static + Collector> toImmutableListMultimap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction, "keyFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + ImmutableListMultimap::builder, + (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), + ImmutableListMultimap.Builder::combine, + ImmutableListMultimap.Builder::build); + } + + static + Collector> flatteningToImmutableListMultimap( + Function keyFunction, + Function> valuesFunction) { + checkNotNull(keyFunction); + checkNotNull(valuesFunction); + return collectingAndThen( + flatteningToMultimap( + input -> checkNotNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull), + MultimapBuilder.linkedHashKeys().arrayListValues()::build), + ImmutableListMultimap::copyOf); + } + + static + Collector> toImmutableSetMultimap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction, "keyFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + ImmutableSetMultimap::builder, + (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), + ImmutableSetMultimap.Builder::combine, + ImmutableSetMultimap.Builder::build); + } + + static + Collector> flatteningToImmutableSetMultimap( + Function keyFunction, + Function> valuesFunction) { + checkNotNull(keyFunction); + checkNotNull(valuesFunction); + return collectingAndThen( + flatteningToMultimap( + input -> checkNotNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull), + MultimapBuilder.linkedHashKeys().linkedHashSetValues()::build), + ImmutableSetMultimap::copyOf); + } + + static < + T extends @Nullable Object, + K extends @Nullable Object, + V extends @Nullable Object, + M extends Multimap> + Collector toMultimap( + Function keyFunction, + Function valueFunction, + Supplier multimapSupplier) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(multimapSupplier); + return Collector.of( + multimapSupplier, + (multimap, input) -> multimap.put(keyFunction.apply(input), valueFunction.apply(input)), + (multimap1, multimap2) -> { + multimap1.putAll(multimap2); + return multimap1; + }); + } + + static < + T extends @Nullable Object, + K extends @Nullable Object, + V extends @Nullable Object, + M extends Multimap> + Collector flatteningToMultimap( + Function keyFunction, + Function> valueFunction, + Supplier multimapSupplier) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(multimapSupplier); + return Collector.of( + multimapSupplier, + (multimap, input) -> { + K key = keyFunction.apply(input); + Collection valuesForKey = multimap.get(key); + valueFunction.apply(input).forEachOrdered(valuesForKey::add); + }, + (multimap1, multimap2) -> { + multimap1.putAll(multimap2); + return multimap1; + }); + } + + private CollectCollectors() {} +} diff --git a/android/guava/src/com/google/common/collect/CollectPreconditions.java b/android/guava/src/com/google/common/collect/CollectPreconditions.java index 98b30c6d5e7b..0595e718e335 100644 --- a/android/guava/src/com/google/common/collect/CollectPreconditions.java +++ b/android/guava/src/com/google/common/collect/CollectPreconditions.java @@ -20,12 +20,13 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.jspecify.annotations.Nullable; /** Precondition checks useful in collection implementations. */ @GwtCompatible final class CollectPreconditions { - static void checkEntryNotNull(Object key, Object value) { + static void checkEntryNotNull(@Nullable Object key, @Nullable Object value) { if (key == null) { throw new NullPointerException("null key in entry: null=" + value); } else if (value == null) { @@ -62,4 +63,6 @@ static void checkPositive(int value, String name) { static void checkRemove(boolean canRemove) { checkState(canRemove, "no calls to next() since the last call to remove()"); } + + private CollectPreconditions() {} } diff --git a/android/guava/src/com/google/common/collect/CollectSpliterators.java b/android/guava/src/com/google/common/collect/CollectSpliterators.java new file mode 100644 index 000000000000..cd5890dbd4fd --- /dev/null +++ b/android/guava/src/com/google/common/collect/CollectSpliterators.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.lang.Math.max; + +import com.google.common.annotations.GwtCompatible; +import com.google.j2objc.annotations.Weak; +import java.util.Comparator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.LongConsumer; +import java.util.function.Predicate; +import java.util.stream.IntStream; +import org.jspecify.annotations.Nullable; + +/** Spliterator utilities for {@code common.collect} internals. */ +@GwtCompatible +@IgnoreJRERequirement // used only from APIs that work with Stream +final class CollectSpliterators { + private CollectSpliterators() {} + + static Spliterator indexed( + int size, int extraCharacteristics, IntFunction function) { + return indexed(size, extraCharacteristics, function, null); + } + + static Spliterator indexed( + int size, + int extraCharacteristics, + IntFunction function, + @Nullable Comparator comparator) { + if (comparator != null) { + checkArgument((extraCharacteristics & Spliterator.SORTED) != 0); + } + /* + * @IgnoreJRERequirement should be redundant with the one on Streams itself, but it's necessary + * as of Animal Sniffer 1.24. Maybe Animal Sniffer processes this nested class before it + * processes Streams and thus hasn't had a chance to see Streams's annotation? + */ + @IgnoreJRERequirement + final class WithCharacteristics implements Spliterator { + private final Spliterator.OfInt delegate; + + WithCharacteristics(Spliterator.OfInt delegate) { + this.delegate = delegate; + } + + @Override + public boolean tryAdvance(Consumer action) { + return delegate.tryAdvance((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public void forEachRemaining(Consumer action) { + delegate.forEachRemaining((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public @Nullable Spliterator trySplit() { + Spliterator.OfInt split = delegate.trySplit(); + return (split == null) ? null : new WithCharacteristics(split); + } + + @Override + public long estimateSize() { + return delegate.estimateSize(); + } + + @Override + public int characteristics() { + return Spliterator.ORDERED + | Spliterator.SIZED + | Spliterator.SUBSIZED + | extraCharacteristics; + } + + @Override + public @Nullable Comparator getComparator() { + if (hasCharacteristics(Spliterator.SORTED)) { + return comparator; + } else { + throw new IllegalStateException(); + } + } + } + return new WithCharacteristics(IntStream.range(0, size).spliterator()); + } + + /** + * Returns a {@code Spliterator} over the elements of {@code fromSpliterator} mapped by {@code + * function}. + */ + static + Spliterator map( + Spliterator fromSpliterator, + Function function) { + checkNotNull(fromSpliterator); + checkNotNull(function); + return new Spliterator() { + + @Override + public boolean tryAdvance(Consumer action) { + return fromSpliterator.tryAdvance( + fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public void forEachRemaining(Consumer action) { + fromSpliterator.forEachRemaining(fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public @Nullable Spliterator trySplit() { + Spliterator fromSplit = fromSpliterator.trySplit(); + return (fromSplit != null) ? map(fromSplit, function) : null; + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & ~(Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SORTED); + } + }; + } + + /** Returns a {@code Spliterator} filtered by the specified predicate. */ + static Spliterator filter( + Spliterator fromSpliterator, Predicate predicate) { + checkNotNull(fromSpliterator); + checkNotNull(predicate); + @IgnoreJRERequirement // see earlier comment about redundancy + final class Splitr implements Spliterator, Consumer { + @Nullable T holder = null; + + @Override + public void accept(@ParametricNullness T t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + while (fromSpliterator.tryAdvance(this)) { + try { + // The cast is safe because tryAdvance puts a T into `holder`. + T next = uncheckedCastNullableTToT(holder); + if (predicate.test(next)) { + action.accept(next); + return true; + } + } finally { + holder = null; + } + } + return false; + } + + @Override + public @Nullable Spliterator trySplit() { + Spliterator fromSplit = fromSpliterator.trySplit(); + return (fromSplit == null) ? null : filter(fromSplit, predicate); + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize() / 2; + } + + @Override + public @Nullable Comparator getComparator() { + return fromSpliterator.getComparator(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & (Spliterator.DISTINCT + | Spliterator.NONNULL + | Spliterator.ORDERED + | Spliterator.SORTED); + } + } + return new Splitr(); + } + + /** + * Returns a {@code Spliterator} that iterates over the elements of the spliterators generated by + * applying {@code function} to the elements of {@code fromSpliterator}. + */ + static + Spliterator flatMap( + Spliterator fromSpliterator, + Function> function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfObject<>( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfInt} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfInt flatMapToInt( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfInt<>( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfLong} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfLong flatMapToLong( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfLong<>( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfDouble} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfDouble flatMapToDouble( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfDouble<>( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Implements the {@link Stream#flatMap} operation on spliterators. + * + * @param the element type of the input spliterator + * @param the element type of the output spliterators + * @param the type of the output spliterators + */ + @IgnoreJRERequirement // see earlier comment about redundancy + abstract static class FlatMapSpliterator< + InElementT extends @Nullable Object, + OutElementT extends @Nullable Object, + OutSpliteratorT extends Spliterator> + implements Spliterator { + /** Factory for constructing {@link FlatMapSpliterator} instances. */ + @IgnoreJRERequirement // should be redundant with the annotations on *both* enclosing classes + interface Factory> { + OutSpliteratorT newFlatMapSpliterator( + @Nullable OutSpliteratorT prefix, + Spliterator fromSplit, + Function function, + int splitCharacteristics, + long estSplitSize); + } + + @Weak @Nullable OutSpliteratorT prefix; + final Spliterator from; + final Function function; + final Factory factory; + int characteristics; + long estimatedSize; + + FlatMapSpliterator( + @Nullable OutSpliteratorT prefix, + Spliterator from, + Function function, + Factory factory, + int characteristics, + long estimatedSize) { + this.prefix = prefix; + this.from = from; + this.function = function; + this.factory = factory; + this.characteristics = characteristics; + this.estimatedSize = estimatedSize; + } + + /* + * The tryAdvance and forEachRemaining in FlatMapSpliteratorOfPrimitive are overloads of these + * methods, not overrides. They are annotated @Override because they implement methods from + * Spliterator.OfPrimitive (and override default implementations from Spliterator.OfPrimitive or + * a subtype like Spliterator.OfInt). + */ + + @Override + public /*non-final for J2KT*/ boolean tryAdvance(Consumer action) { + while (true) { + if (prefix != null && prefix.tryAdvance(action)) { + if (estimatedSize != Long.MAX_VALUE) { + estimatedSize--; + } + return true; + } else { + prefix = null; + } + if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) { + return false; + } + } + } + + @Override + public /*non-final for J2KT*/ void forEachRemaining(Consumer action) { + if (prefix != null) { + prefix.forEachRemaining(action); + prefix = null; + } + from.forEachRemaining( + fromElement -> { + Spliterator elements = function.apply(fromElement); + if (elements != null) { + elements.forEachRemaining(action); + } + }); + estimatedSize = 0; + } + + @Override + public final @Nullable OutSpliteratorT trySplit() { + Spliterator fromSplit = from.trySplit(); + if (fromSplit != null) { + int splitCharacteristics = characteristics & ~Spliterator.SIZED; + long estSplitSize = estimateSize(); + if (estSplitSize < Long.MAX_VALUE) { + estSplitSize /= 2; + this.estimatedSize -= estSplitSize; + this.characteristics = splitCharacteristics; + } + OutSpliteratorT result = + factory.newFlatMapSpliterator( + this.prefix, fromSplit, function, splitCharacteristics, estSplitSize); + this.prefix = null; + return result; + } else if (prefix != null) { + OutSpliteratorT result = prefix; + this.prefix = null; + return result; + } else { + return null; + } + } + + @Override + public final long estimateSize() { + if (prefix != null) { + estimatedSize = max(estimatedSize, prefix.estimateSize()); + } + return max(estimatedSize, 0); + } + + @Override + public final int characteristics() { + return characteristics; + } + } + + /** + * Implementation of {@link Stream#flatMap} with an object spliterator output type. + * + *

    To avoid having this type, we could use {@code FlatMapSpliterator} directly. The main + * advantages to having the type are the ability to use its constructor reference below and the + * parallelism with the primitive version. In short, it makes its caller ({@code flatMap}) + * simpler. + * + * @param the element type of the input spliterator + * @param the element type of the output spliterators + */ + @IgnoreJRERequirement // see earlier comment about redundancy + static final class FlatMapSpliteratorOfObject< + InElementT extends @Nullable Object, OutElementT extends @Nullable Object> + extends FlatMapSpliterator> { + FlatMapSpliteratorOfObject( + @Nullable Spliterator prefix, + Spliterator from, + Function> function, + int characteristics, + long estimatedSize) { + super( + prefix, from, function, FlatMapSpliteratorOfObject::new, characteristics, estimatedSize); + } + } + + /** + * Implementation of {@link Stream#flatMap} with a primitive spliterator output type. + * + * @param the element type of the input spliterator + * @param the (boxed) element type of the output spliterators + * @param the specialized consumer type for the primitive output type + * @param the primitive spliterator type associated with {@code OutElementT} + */ + @IgnoreJRERequirement // see earlier comment about redundancy + abstract static class FlatMapSpliteratorOfPrimitive< + InElementT extends @Nullable Object, + OutElementT extends @Nullable Object, + OutConsumerT, + OutSpliteratorT extends + Spliterator.OfPrimitive> + extends FlatMapSpliterator + implements Spliterator.OfPrimitive { + + FlatMapSpliteratorOfPrimitive( + @Nullable OutSpliteratorT prefix, + Spliterator from, + Function function, + Factory factory, + int characteristics, + long estimatedSize) { + super(prefix, from, function, factory, characteristics, estimatedSize); + } + + @Override + public final boolean tryAdvance(OutConsumerT action) { + while (true) { + if (prefix != null && prefix.tryAdvance(action)) { + if (estimatedSize != Long.MAX_VALUE) { + estimatedSize--; + } + return true; + } else { + prefix = null; + } + if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) { + return false; + } + } + } + + @Override + public final void forEachRemaining(OutConsumerT action) { + if (prefix != null) { + prefix.forEachRemaining(action); + prefix = null; + } + from.forEachRemaining( + fromElement -> { + OutSpliteratorT elements = function.apply(fromElement); + if (elements != null) { + elements.forEachRemaining(action); + } + }); + estimatedSize = 0; + } + } + + /** Implementation of {@link #flatMapToInt}. */ + @IgnoreJRERequirement // see earlier comment about redundancy + static final class FlatMapSpliteratorOfInt + extends FlatMapSpliteratorOfPrimitive + implements Spliterator.OfInt { + FlatMapSpliteratorOfInt( + Spliterator.@Nullable OfInt prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super(prefix, from, function, FlatMapSpliteratorOfInt::new, characteristics, estimatedSize); + } + } + + /** Implementation of {@link #flatMapToLong}. */ + @IgnoreJRERequirement // see earlier comment about redundancy + static final class FlatMapSpliteratorOfLong + extends FlatMapSpliteratorOfPrimitive + implements Spliterator.OfLong { + FlatMapSpliteratorOfLong( + Spliterator.@Nullable OfLong prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super(prefix, from, function, FlatMapSpliteratorOfLong::new, characteristics, estimatedSize); + } + } + + /** Implementation of {@link #flatMapToDouble}. */ + @IgnoreJRERequirement // see earlier comment about redundancy + static final class FlatMapSpliteratorOfDouble + extends FlatMapSpliteratorOfPrimitive< + InElementT, Double, DoubleConsumer, Spliterator.OfDouble> + implements Spliterator.OfDouble { + FlatMapSpliteratorOfDouble( + Spliterator.@Nullable OfDouble prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super( + prefix, from, function, FlatMapSpliteratorOfDouble::new, characteristics, estimatedSize); + } + } +} diff --git a/android/guava/src/com/google/common/collect/Collections2.java b/android/guava/src/com/google/common/collect/Collections2.java index 56b7a5b1d5cd..ca0e34ea6c3b 100644 --- a/android/guava/src/com/google/common/collect/Collections2.java +++ b/android/guava/src/com/google/common/collect/Collections2.java @@ -19,8 +19,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import com.google.common.base.Predicate; @@ -35,15 +36,15 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Provides static methods for working with {@code Collection} instances. * - *

    Java 8 users: several common uses for this class are now more comprehensively addressed - * by the new {@link java.util.stream.Stream} library. Read the method documentation below for - * comparisons. These methods are not being deprecated, but we gently encourage you to migrate to - * streams. + *

    Java 8+ users: several common uses for this class are now more comprehensively + * addressed by the new {@link java.util.stream.Stream} library. Read the method documentation below + * for comparisons. These methods are not being deprecated, but we gently encourage you to migrate + * to streams. * * @author Chris Povirk * @author Mike Bostock @@ -79,23 +80,22 @@ private Collections2() {} * *

    {@code Stream} equivalent: {@link java.util.stream.Stream#filter Stream.filter}. */ - // TODO(kevinb): how can we omit that Iterables link when building gwt - // javadoc? - public static Collection filter(Collection unfiltered, Predicate predicate) { + public static Collection filter( + Collection unfiltered, Predicate predicate) { if (unfiltered instanceof FilteredCollection) { // Support clear(), removeAll(), and retainAll() when filtering a filtered // collection. return ((FilteredCollection) unfiltered).createCombined(predicate); } - return new FilteredCollection(checkNotNull(unfiltered), checkNotNull(predicate)); + return new FilteredCollection<>(checkNotNull(unfiltered), checkNotNull(predicate)); } /** * Delegates to {@link Collection#contains}. Returns {@code false} if the {@code contains} method * throws a {@code ClassCastException} or {@code NullPointerException}. */ - static boolean safeContains(Collection collection, @NullableDecl Object object) { + static boolean safeContains(Collection collection, @Nullable Object object) { checkNotNull(collection); try { return collection.contains(object); @@ -108,7 +108,7 @@ static boolean safeContains(Collection collection, @NullableDecl Object objec * Delegates to {@link Collection#remove}. Returns {@code false} if the {@code remove} method * throws a {@code ClassCastException} or {@code NullPointerException}. */ - static boolean safeRemove(Collection collection, @NullableDecl Object object) { + static boolean safeRemove(Collection collection, @Nullable Object object) { checkNotNull(collection); try { return collection.remove(object); @@ -117,7 +117,7 @@ static boolean safeRemove(Collection collection, @NullableDecl Object object) } } - static class FilteredCollection extends AbstractCollection { + static class FilteredCollection extends AbstractCollection { final Collection unfiltered; final Predicate predicate; @@ -127,12 +127,11 @@ static class FilteredCollection extends AbstractCollection { } FilteredCollection createCombined(Predicate newPredicate) { - return new FilteredCollection(unfiltered, Predicates.and(predicate, newPredicate)); - // . above needed to compile in JDK 5 + return new FilteredCollection<>(unfiltered, Predicates.and(predicate, newPredicate)); } @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { checkArgument(predicate.apply(element)); return unfiltered.add(element); } @@ -151,7 +150,7 @@ public void clear() { } @Override - public boolean contains(@NullableDecl Object element) { + public boolean contains(@Nullable Object element) { if (safeContains(unfiltered, element)) { @SuppressWarnings("unchecked") // element is in unfiltered, so it must be an E E e = (E) element; @@ -176,12 +175,12 @@ public Iterator iterator() { } @Override - public boolean remove(Object element) { + public boolean remove(@Nullable Object element) { return contains(element) && unfiltered.remove(element); } @Override - public boolean removeAll(final Collection collection) { + public boolean removeAll(Collection collection) { boolean changed = false; Iterator itr = unfiltered.iterator(); while (itr.hasNext()) { @@ -195,7 +194,7 @@ public boolean removeAll(final Collection collection) { } @Override - public boolean retainAll(final Collection collection) { + public boolean retainAll(Collection collection) { boolean changed = false; Iterator itr = unfiltered.iterator(); while (itr.hasNext()) { @@ -220,13 +219,14 @@ public int size() { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { // creating an ArrayList so filtering happens once return Lists.newArrayList(iterator()).toArray(); } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return Lists.newArrayList(iterator()).toArray(array); } } @@ -250,12 +250,14 @@ public T[] toArray(T[] array) { * *

    {@code Stream} equivalent: {@link java.util.stream.Stream#map Stream.map}. */ - public static Collection transform( + public static Collection transform( Collection fromCollection, Function function) { return new TransformedCollection<>(fromCollection, function); } - static class TransformedCollection extends AbstractCollection { + private static final class TransformedCollection< + F extends @Nullable Object, T extends @Nullable Object> + extends AbstractCollection { final Collection fromCollection; final Function function; @@ -306,7 +308,7 @@ static boolean containsAllImpl(Collection self, Collection c) { } /** An implementation of {@link Collection#toString()}. */ - static String toStringImpl(final Collection collection) { + static String toStringImpl(Collection collection) { StringBuilder sb = newStringBuilderForCollection(collection.size()).append('['); boolean first = true; for (Object o : collection) { @@ -326,7 +328,7 @@ static String toStringImpl(final Collection collection) { /** Returns best-effort-sized StringBuilder based on the given collection size. */ static StringBuilder newStringBuilderForCollection(int size) { checkNonnegative(size, "size"); - return new StringBuilder((int) Math.min(size * 8L, Ints.MAX_POWER_OF_TWO)); + return new StringBuilder((int) min(size * 8L, Ints.MAX_POWER_OF_TWO)); } /** @@ -351,7 +353,6 @@ static StringBuilder newStringBuilderForCollection(int size) { * @throws NullPointerException if the specified iterable is null or has any null elements. * @since 12.0 */ - @Beta public static > Collection> orderedPermutations( Iterable elements) { return orderedPermutations(elements, Ordering.natural()); @@ -363,7 +364,7 @@ public static > Collection> orderedPermu * *

    Examples: * - *

    {@code
    +   * {@snippet :
        * for (List perm : orderedPermutations(asList("b", "c", "a"))) {
        *   println(perm);
        * }
    @@ -383,7 +384,7 @@ public static > Collection> orderedPermu
        * // -> [2, 1, 1, 2]
        * // -> [2, 1, 2, 1]
        * // -> [2, 2, 1, 1]
    -   * }
    + * } * *

    Notes: This is an implementation of the algorithm for Lexicographical Permutations * Generation, described in Knuth's "The Art of Computer Programming", Volume 4, Chapter 7, @@ -403,7 +404,6 @@ public static > Collection> orderedPermu * the specified comparator is null. * @since 12.0 */ - @Beta public static Collection> orderedPermutations( Iterable elements, Comparator comparator) { return new OrderedPermutationCollection(elements, comparator); @@ -466,7 +466,7 @@ public Iterator> iterator() { } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { if (obj instanceof List) { List list = (List) obj; return isPermutation(inputList, list); @@ -481,16 +481,16 @@ public String toString() { } private static final class OrderedPermutationIterator extends AbstractIterator> { - @NullableDecl List nextPermutation; + @Nullable List nextPermutation; final Comparator comparator; OrderedPermutationIterator(List list, Comparator comparator) { - this.nextPermutation = Lists.newArrayList(list); + this.nextPermutation = new ArrayList<>(list); this.comparator = comparator; } @Override - protected List computeNext() { + protected @Nullable List computeNext() { if (nextPermutation == null) { return endOfData(); } @@ -505,6 +505,11 @@ void calculateNextPermutation() { nextPermutation = null; return; } + /* + * requireNonNull is safe because we don't clear nextPermutation until we're done calling this + * method. + */ + requireNonNull(nextPermutation); int l = findNextL(j); Collections.swap(nextPermutation, j, l); @@ -513,6 +518,11 @@ void calculateNextPermutation() { } int findNextJ() { + /* + * requireNonNull is safe because we don't clear nextPermutation until we're done calling this + * method. + */ + requireNonNull(nextPermutation); for (int k = nextPermutation.size() - 2; k >= 0; k--) { if (comparator.compare(nextPermutation.get(k), nextPermutation.get(k + 1)) < 0) { return k; @@ -522,6 +532,11 @@ int findNextJ() { } int findNextL(int j) { + /* + * requireNonNull is safe because we don't clear nextPermutation until we're done calling this + * method. + */ + requireNonNull(nextPermutation); E ak = nextPermutation.get(j); for (int l = nextPermutation.size() - 1; l > j; l--) { if (comparator.compare(ak, nextPermutation.get(l)) < 0) { @@ -549,7 +564,6 @@ int findNextL(int j) { * @throws NullPointerException if the specified collection is null or has any null elements. * @since 12.0 */ - @Beta public static Collection> permutations(Collection elements) { return new PermutationCollection(ImmutableList.copyOf(elements)); } @@ -577,7 +591,7 @@ public Iterator> iterator() { } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { if (obj instanceof List) { List list = (List) obj; return isPermutation(inputList, list); @@ -591,14 +605,14 @@ public String toString() { } } - private static class PermutationIterator extends AbstractIterator> { + private static final class PermutationIterator extends AbstractIterator> { final List list; final int[] c; final int[] o; int j; PermutationIterator(List list) { - this.list = new ArrayList(list); + this.list = new ArrayList<>(list); int n = list.size(); c = new int[n]; o = new int[n]; @@ -608,7 +622,7 @@ private static class PermutationIterator extends AbstractIterator> { } @Override - protected List computeNext() { + protected @Nullable List computeNext() { if (j <= 0) { return endOfData(); } @@ -672,7 +686,8 @@ private static boolean isPermutation(List first, List second) { return true; } - private static ObjectCountHashMap counts(Collection collection) { + private static ObjectCountHashMap counts( + Collection collection) { ObjectCountHashMap map = new ObjectCountHashMap<>(); for (E e : collection) { map.put(e, map.get(e) + 1); diff --git a/android/guava/src/com/google/common/collect/CompactHashMap.java b/android/guava/src/com/google/common/collect/CompactHashMap.java index 1fa4f96f0762..f05dfcfd327f 100644 --- a/android/guava/src/com/google/common/collect/CompactHashMap.java +++ b/android/guava/src/com/google/common/collect/CompactHashMap.java @@ -19,13 +19,19 @@ import static com.google.common.collect.CollectPreconditions.checkRemove; import static com.google.common.collect.CompactHashing.UNSET; import static com.google.common.collect.Hashing.smearedHash; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static com.google.common.collect.NullnessCasts.unsafeNull; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.WeakOuter; import java.io.IOException; import java.io.InvalidObjectException; @@ -42,8 +48,9 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * CompactHashMap is an implementation of a Map. All optional operations (put and remove) are @@ -72,7 +79,8 @@ * @author Jon Noack */ @GwtIncompatible // not worth using in GWT for now -class CompactHashMap extends AbstractMap implements Serializable { +class CompactHashMap + extends AbstractMap implements Serializable { /* * TODO: Make this a drop-in replacement for j.u. versions, actually drop them in, and test the * world. Figure out what sort of space-time tradeoff we're actually going to get here with the @@ -82,7 +90,8 @@ class CompactHashMap extends AbstractMap implements Serializable { */ /** Creates an empty {@code CompactHashMap} instance. */ - public static CompactHashMap create() { + public static + CompactHashMap create() { return new CompactHashMap<>(); } @@ -95,7 +104,8 @@ public static CompactHashMap create() { * elements without resizing * @throws IllegalArgumentException if {@code expectedSize} is negative */ - public static CompactHashMap createWithExpectedSize(int expectedSize) { + public static + CompactHashMap createWithExpectedSize(int expectedSize) { return new CompactHashMap<>(expectedSize); } @@ -115,6 +125,46 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz */ private static final int MAX_HASH_BUCKET_LENGTH = 9; + // The way the `table`, `entries`, `keys`, and `values` arrays work together is as follows. + // + // The `table` array always has a size that is a power of 2. The hashcode of a key in the map + // is masked in order to correspond to the current table size. For example, if the table size + // is 128 then the mask is 127 == 0x7f, keeping the bottom 7 bits of the hash value. + // If a key hashes to 0x89abcdef the mask reduces it to 0x89abcdef & 0x7f == 0x6f. We'll call this + // the "short hash". + // + // The `keys`, `values`, and `entries` arrays always have the same size as each other. They can be + // seen as fields of an imaginary `Entry` object like this: + // + // class Entry { + // int hash; + // Entry next; + // K key; + // V value; + // } + // + // The imaginary `hash` and `next` values are combined into a single `int` value in the `entries` + // array. The top bits of this value are the remaining bits of the hash value that were not used + // in the short hash. We saw that a mask of 0x7f would keep the 7-bit value 0x6f from a full + // hashcode of 0x89abcdef. The imaginary `hash` value would then be the remaining top 25 bits, + // 0x89abcd80. To this is added (or'd) the `next` value, which is an index within `entries` + // (and therefore within `keys` and `values`) of another entry that has the same short hash + // value. In our example, it would be another entry for a key whose short hash is also 0x6f. + // + // Essentially, then, `table[h]` gives us the start of a linked list in `entries`, where every + // element of the list has the short hash value h. + // + // A wrinkle here is that the value 0 (called UNSET in the code) is used as the equivalent of a + // null pointer. If `table[h] == 0` that means there are no keys in the map whose short hash is h. + // If the `next` bits in `entries[i]` are 0 that means there are no further entries for the given + // short hash. But 0 is also a valid index in `entries`, so we add 1 to these indices before + // putting them in `table` or in `next` bits, and subtract 1 again when we need an index value. + // + // The elements of `keys`, `values`, and `entries` are added sequentially, so that elements 0 to + // `size() - 1` are used and remaining elements are not. This makes iteration straightforward. + // Removing an entry generally involves moving the last element of each array to where the removed + // entry was, and adjusting index links accordingly. + /** * The hashtable object. This can be either: * @@ -134,7 +184,7 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz *

  • null, if no entries have yet been added to the map * */ - @NullableDecl private transient Object table; + private transient @Nullable Object table; /** * Contains the logical entries, in the range of [0, size()). The high bits of each int are the @@ -144,32 +194,38 @@ public static CompactHashMap createWithExpectedSize(int expectedSiz * *
        * hash  = aaaaaaaa
    -   * mask  = 0000ffff
    -   * next  = 0000bbbb
    -   * entry = aaaabbbb
    +   * mask  = 00000fff
    +   * next  = 00000bbb
    +   * entry = aaaaabbb
        * 
    * *

    The pointers in [size(), entries.length) are all "null" (UNSET). */ - @VisibleForTesting @NullableDecl transient int[] entries; + @VisibleForTesting transient int @Nullable [] entries; /** * The keys of the entries in the map, in the range of [0, size()). The keys in [size(), * keys.length) are all {@code null}. */ - @VisibleForTesting @NullableDecl transient Object[] keys; + @VisibleForTesting transient @Nullable Object @Nullable [] keys; /** * The values of the entries in the map, in the range of [0, size()). The values in [size(), * values.length) are all {@code null}. */ - @VisibleForTesting @NullableDecl transient Object[] values; + @VisibleForTesting transient @Nullable Object @Nullable [] values; /** * Keeps track of metadata like the number of hash table bits and modifications of this data * structure (to make it possible to throw ConcurrentModificationException in the iterator). Note * that we choose not to make this volatile, so we do less of a "best effort" to track such * errors, for better performance. + * + *

    For a new instance, where the arrays above have not yet been allocated, the value of {@code + * metadata} is the size that the arrays should be allocated with. Once the arrays have been + * allocated, the value of {@code metadata} combines the number of bits in the "short hash", in + * its bottom {@value CompactHashing#HASH_TABLE_BITS_MAX_BITS} bits, with a modification count in + * the remaining bits that is used to detect concurrent modification during iteration. */ private transient int metadata; @@ -199,7 +255,6 @@ void init(int expectedSize) { } /** Returns whether arrays need to be allocated. */ - @VisibleForTesting boolean needsAllocArrays() { return table == null; } @@ -223,8 +278,7 @@ int allocArrays() { @SuppressWarnings("unchecked") @VisibleForTesting - @NullableDecl - Map delegateOrNull() { + @Nullable Map delegateOrNull() { if (table instanceof Map) { return (Map) table; } @@ -235,13 +289,11 @@ Map createHashFloodingResistantDelegate(int tableSize) { return new LinkedHashMap<>(tableSize, 1.0f); } - @SuppressWarnings("unchecked") - @VisibleForTesting @CanIgnoreReturnValue Map convertToHashFloodingResistantImplementation() { Map newDelegate = createHashFloodingResistantDelegate(hashTableMask() + 1); for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { - newDelegate.put((K) keys[i], (V) values[i]); + newDelegate.put(key(i), value(i)); } this.table = newDelegate; this.entries = null; @@ -277,31 +329,30 @@ void accessEntry(int index) { @CanIgnoreReturnValue @Override - @NullableDecl - public V put(@NullableDecl K key, @NullableDecl V value) { + public @Nullable V put(@ParametricNullness K key, @ParametricNullness V value) { if (needsAllocArrays()) { allocArrays(); } - @NullableDecl Map delegate = delegateOrNull(); + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.put(key, value); } - int[] entries = this.entries; - Object[] keys = this.keys; - Object[] values = this.values; + int[] entries = requireEntries(); + @Nullable Object[] keys = requireKeys(); + @Nullable Object[] values = requireValues(); int newEntryIndex = this.size; // current size, and pointer to the entry to be appended int newSize = newEntryIndex + 1; int hash = smearedHash(key); int mask = hashTableMask(); int tableIndex = hash & mask; - int next = CompactHashing.tableGet(table, tableIndex); + int next = CompactHashing.tableGet(requireTable(), tableIndex); if (next == UNSET) { // uninitialized bucket if (newSize > mask) { // Resize and add new entry mask = resizeTable(mask, CompactHashing.newCapacity(mask), hash, newEntryIndex); } else { - CompactHashing.tableSet(table, tableIndex, newEntryIndex + 1); + CompactHashing.tableSet(requireTable(), tableIndex, newEntryIndex + 1); } } else { int entryIndex; @@ -312,9 +363,8 @@ public V put(@NullableDecl K key, @NullableDecl V value) { entryIndex = next - 1; entry = entries[entryIndex]; if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix - && Objects.equal(key, keys[entryIndex])) { + && Objects.equals(key, keys[entryIndex])) { @SuppressWarnings("unchecked") // known to be a V - @NullableDecl V oldValue = (V) values[entryIndex]; values[entryIndex] = value; @@ -346,19 +396,19 @@ public V put(@NullableDecl K key, @NullableDecl V value) { /** * Creates a fresh entry with the specified object at the specified position in the entry arrays. */ - void insertEntry(int entryIndex, @NullableDecl K key, @NullableDecl V value, int hash, int mask) { - this.entries[entryIndex] = CompactHashing.maskCombine(hash, UNSET, mask); - this.keys[entryIndex] = key; - this.values[entryIndex] = value; + void insertEntry( + int entryIndex, @ParametricNullness K key, @ParametricNullness V value, int hash, int mask) { + this.setEntry(entryIndex, CompactHashing.maskCombine(hash, UNSET, mask)); + this.setKey(entryIndex, key); + this.setValue(entryIndex, value); } /** Resizes the entries storage if necessary. */ private void resizeMeMaybe(int newSize) { - int entriesSize = entries.length; + int entriesSize = requireEntries().length; if (newSize > entriesSize) { // 1.5x but round up to nearest odd (this is optimal for memory consumption on Android) - int newCapacity = - Math.min(CompactHashing.MAX_SIZE, (entriesSize + Math.max(1, entriesSize >>> 1)) | 1); + int newCapacity = min(CompactHashing.MAX_SIZE, (entriesSize + max(1, entriesSize >>> 1)) | 1); if (newCapacity != entriesSize) { resizeEntries(newCapacity); } @@ -370,13 +420,13 @@ private void resizeMeMaybe(int newSize) { * the current capacity. */ void resizeEntries(int newCapacity) { - this.entries = Arrays.copyOf(entries, newCapacity); - this.keys = Arrays.copyOf(keys, newCapacity); - this.values = Arrays.copyOf(values, newCapacity); + this.entries = Arrays.copyOf(requireEntries(), newCapacity); + this.keys = Arrays.copyOf(requireKeys(), newCapacity); + this.values = Arrays.copyOf(requireValues(), newCapacity); } @CanIgnoreReturnValue - private int resizeTable(int mask, int newCapacity, int targetHash, int targetEntryIndex) { + private int resizeTable(int oldMask, int newCapacity, int targetHash, int targetEntryIndex) { Object newTable = CompactHashing.createTable(newCapacity); int newMask = newCapacity - 1; @@ -385,25 +435,35 @@ private int resizeTable(int mask, int newCapacity, int targetHash, int targetEnt CompactHashing.tableSet(newTable, targetHash & newMask, targetEntryIndex + 1); } - Object table = this.table; - int[] entries = this.entries; - - // Loop over current hashtable - for (int tableIndex = 0; tableIndex <= mask; tableIndex++) { - int next = CompactHashing.tableGet(table, tableIndex); - while (next != UNSET) { - int entryIndex = next - 1; - int entry = entries[entryIndex]; - - // Rebuild hash using entry hashPrefix and tableIndex ("hashSuffix") - int hash = CompactHashing.getHashPrefix(entry, mask) | tableIndex; + Object oldTable = requireTable(); + int[] entries = requireEntries(); + + // Loop over `oldTable` to construct its replacement, ``newTable`. The entries do not move, so + // the `keys` and `values` arrays do not need to change. But because the "short hash" now has a + // different number of bits, we must rewrite each element of `entries` so that its contribution + // to the full hashcode reflects the change, and so that its `next` link corresponds to the new + // linked list of entries with the new short hash. + for (int oldTableIndex = 0; oldTableIndex <= oldMask; oldTableIndex++) { + int oldNext = CompactHashing.tableGet(oldTable, oldTableIndex); + // Each element of `oldTable` is the head of a (possibly empty) linked list of elements in + // `entries`. The `oldNext` loop is going to traverse that linked list. + // We need to rewrite the `next` link of each of the elements so that it is in the appropriate + // linked list starting from `newTable`. In general, each element from the old linked list + // belongs to a different linked list from `newTable`. We insert each element in turn at the + // head of its appropriate `newTable` linked list. + while (oldNext != UNSET) { + int entryIndex = oldNext - 1; + int oldEntry = entries[entryIndex]; + + // Rebuild the full 32-bit hash using entry hashPrefix and oldTableIndex ("hashSuffix"). + int hash = CompactHashing.getHashPrefix(oldEntry, oldMask) | oldTableIndex; int newTableIndex = hash & newMask; int newNext = CompactHashing.tableGet(newTable, newTableIndex); - CompactHashing.tableSet(newTable, newTableIndex, next); + CompactHashing.tableSet(newTable, newTableIndex, oldNext); entries[entryIndex] = CompactHashing.maskCombine(hash, newNext, newMask); - next = CompactHashing.getNext(entry, mask); + oldNext = CompactHashing.getNext(oldEntry, oldMask); } } @@ -412,22 +472,22 @@ private int resizeTable(int mask, int newCapacity, int targetHash, int targetEnt return newMask; } - private int indexOf(@NullableDecl Object key) { + private int indexOf(@Nullable Object key) { if (needsAllocArrays()) { return -1; } int hash = smearedHash(key); int mask = hashTableMask(); - int next = CompactHashing.tableGet(table, hash & mask); + int next = CompactHashing.tableGet(requireTable(), hash & mask); if (next == UNSET) { return -1; } int hashPrefix = CompactHashing.getHashPrefix(hash, mask); do { int entryIndex = next - 1; - int entry = entries[entryIndex]; + int entry = entry(entryIndex); if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix - && Objects.equal(key, keys[entryIndex])) { + && Objects.equals(key, key(entryIndex))) { return entryIndex; } next = CompactHashing.getNext(entry, mask); @@ -436,15 +496,14 @@ private int indexOf(@NullableDecl Object key) { } @Override - public boolean containsKey(@NullableDecl Object key) { - @NullableDecl Map delegate = delegateOrNull(); + public boolean containsKey(@Nullable Object key) { + Map delegate = delegateOrNull(); return (delegate != null) ? delegate.containsKey(key) : indexOf(key) != -1; } - @SuppressWarnings("unchecked") // known to be a V @Override - public V get(@NullableDecl Object key) { - @NullableDecl Map delegate = delegateOrNull(); + public @Nullable V get(@Nullable Object key) { + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.get(key); } @@ -453,15 +512,14 @@ public V get(@NullableDecl Object key) { return null; } accessEntry(index); - return (V) values[index]; + return value(index); } @CanIgnoreReturnValue @SuppressWarnings("unchecked") // known to be a V @Override - @NullableDecl - public V remove(@NullableDecl Object key) { - @NullableDecl Map delegate = delegateOrNull(); + public @Nullable V remove(@Nullable Object key) { + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.remove(key); } @@ -469,20 +527,25 @@ public V remove(@NullableDecl Object key) { return (oldValue == NOT_FOUND) ? null : (V) oldValue; } - @NullableDecl - private Object removeHelper(@NullableDecl Object key) { + private @Nullable Object removeHelper(@Nullable Object key) { if (needsAllocArrays()) { return NOT_FOUND; } int mask = hashTableMask(); int index = CompactHashing.remove( - key, /* value= */ null, mask, table, entries, keys, /* values= */ null); + key, + /* value= */ null, + mask, + requireTable(), + requireEntries(), + requireKeys(), + /* values= */ null); if (index == -1) { return NOT_FOUND; } - @NullableDecl Object oldValue = values[index]; + Object oldValue = value(index); moveLastEntry(index, mask); size--; @@ -495,10 +558,14 @@ private Object removeHelper(@NullableDecl Object key) { * Moves the last entry in the entry array into {@code dstIndex}, and nulls out its old position. */ void moveLastEntry(int dstIndex, int mask) { + Object table = requireTable(); + int[] entries = requireEntries(); + @Nullable Object[] keys = requireKeys(); + @Nullable Object[] values = requireValues(); int srcIndex = size() - 1; if (dstIndex < srcIndex) { // move last entry to deleted spot - @NullableDecl Object key = keys[srcIndex]; + Object key = keys[srcIndex]; keys[dstIndex] = key; values[dstIndex] = values[srcIndex]; keys[srcIndex] = null; @@ -551,7 +618,7 @@ int adjustAfterRemove(int indexBeforeRemove, @SuppressWarnings("unused") int ind return indexBeforeRemove - 1; } - private abstract class Itr implements Iterator { + private abstract class Itr implements Iterator { int expectedMetadata = metadata; int currentIndex = firstEntryIndex(); int indexToRemove = -1; @@ -561,9 +628,11 @@ public boolean hasNext() { return currentIndex >= 0; } + @ParametricNullness abstract T getOutput(int entry); @Override + @ParametricNullness public T next() { checkForConcurrentModification(); if (!hasNext()) { @@ -580,7 +649,7 @@ public void remove() { checkForConcurrentModification(); checkRemove(indexToRemove >= 0); incrementExpectedModCount(); - CompactHashMap.this.remove(keys[indexToRemove]); + CompactHashMap.this.remove(key(indexToRemove)); currentIndex = adjustAfterRemove(currentIndex, indexToRemove); indexToRemove = -1; } @@ -596,7 +665,7 @@ private void checkForConcurrentModification() { } } - @NullableDecl private transient Set keySetView; + @LazyInit private transient @Nullable Set keySetView; @Override public Set keySet() { @@ -608,20 +677,20 @@ Set createKeySet() { } @WeakOuter - class KeySetView extends AbstractSet { + private final class KeySetView extends AbstractSet { @Override public int size() { return CompactHashMap.this.size(); } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { return CompactHashMap.this.containsKey(o); } @Override - public boolean remove(@NullableDecl Object o) { - @NullableDecl Map delegate = delegateOrNull(); + public boolean remove(@Nullable Object o) { + Map delegate = delegateOrNull(); return (delegate != null) ? delegate.keySet().remove(o) : CompactHashMap.this.removeHelper(o) != NOT_FOUND; @@ -639,20 +708,20 @@ public void clear() { } Iterator keySetIterator() { - @NullableDecl Map delegate = delegateOrNull(); + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.keySet().iterator(); } return new Itr() { - @SuppressWarnings("unchecked") // known to be a K @Override + @ParametricNullness K getOutput(int entry) { - return (K) keys[entry]; + return key(entry); } }; } - @NullableDecl private transient Set> entrySetView; + @LazyInit private transient @Nullable Set> entrySetView; @Override public Set> entrySet() { @@ -664,8 +733,7 @@ Set> createEntrySet() { } @WeakOuter - class EntrySetView extends AbstractSet> { - + private final class EntrySetView extends AbstractSet> { @Override public int size() { return CompactHashMap.this.size(); @@ -682,21 +750,21 @@ public Iterator> iterator() { } @Override - public boolean contains(@NullableDecl Object o) { - @NullableDecl Map delegate = delegateOrNull(); + public boolean contains(@Nullable Object o) { + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.entrySet().contains(o); } else if (o instanceof Entry) { Entry entry = (Entry) o; int index = indexOf(entry.getKey()); - return index != -1 && Objects.equal(values[index], entry.getValue()); + return index != -1 && Objects.equals(value(index), entry.getValue()); } return false; } @Override - public boolean remove(@NullableDecl Object o) { - @NullableDecl Map delegate = delegateOrNull(); + public boolean remove(@Nullable Object o) { + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.entrySet().remove(o); } else if (o instanceof Entry) { @@ -707,7 +775,13 @@ public boolean remove(@NullableDecl Object o) { int mask = hashTableMask(); int index = CompactHashing.remove( - entry.getKey(), entry.getValue(), mask, table, entries, keys, values); + entry.getKey(), + entry.getValue(), + mask, + requireTable(), + requireEntries(), + requireKeys(), + requireValues()); if (index == -1) { return false; } @@ -723,7 +797,7 @@ public boolean remove(@NullableDecl Object o) { } Iterator> entrySetIterator() { - @NullableDecl Map delegate = delegateOrNull(); + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.entrySet().iterator(); } @@ -736,18 +810,17 @@ Entry getOutput(int entry) { } final class MapEntry extends AbstractMapEntry { - @NullableDecl private final K key; + @ParametricNullness private final K key; private int lastKnownIndex; - @SuppressWarnings("unchecked") // known to be a K MapEntry(int index) { - this.key = (K) keys[index]; + this.key = key(index); this.lastKnownIndex = index; } - @NullableDecl @Override + @ParametricNullness public K getKey() { return key; } @@ -755,37 +828,48 @@ public K getKey() { private void updateLastKnownIndex() { if (lastKnownIndex == -1 || lastKnownIndex >= size() - || !Objects.equal(key, keys[lastKnownIndex])) { + || !Objects.equals(key, key(lastKnownIndex))) { lastKnownIndex = indexOf(key); } } - @SuppressWarnings("unchecked") // known to be a V @Override - @NullableDecl + @ParametricNullness public V getValue() { - @NullableDecl Map delegate = delegateOrNull(); + Map delegate = delegateOrNull(); if (delegate != null) { - return delegate.get(key); + /* + * The cast is safe because the entry is present in the map. Or, if it has been removed by a + * concurrent modification, behavior is undefined. + */ + return uncheckedCastNullableTToT(delegate.get(key)); } updateLastKnownIndex(); - return (lastKnownIndex == -1) ? null : (V) values[lastKnownIndex]; + /* + * If the entry has been removed from the map, we return null, even though that might not be a + * valid value. That's the best we can do, short of holding a reference to the most recently + * seen value. And while we *could* do that, we aren't required to: Map.Entry explicitly says + * that behavior is undefined when the backing map is modified through another API. (It even + * permits us to throw IllegalStateException. Maybe we should have done that, but we probably + * shouldn't change now for fear of breaking people.) + */ + return (lastKnownIndex == -1) ? unsafeNull() : value(lastKnownIndex); } - @SuppressWarnings("unchecked") // known to be a V @Override - public V setValue(V value) { - @NullableDecl Map delegate = delegateOrNull(); + @ParametricNullness + public V setValue(@ParametricNullness V value) { + Map delegate = delegateOrNull(); if (delegate != null) { - return delegate.put(key, value); + return uncheckedCastNullableTToT(delegate.put(key, value)); // See discussion in getValue(). } updateLastKnownIndex(); if (lastKnownIndex == -1) { put(key, value); - return null; + return unsafeNull(); // See discussion in getValue(). } else { - V old = (V) values[lastKnownIndex]; - values[lastKnownIndex] = value; + V old = value(lastKnownIndex); + CompactHashMap.this.setValue(lastKnownIndex, value); return old; } } @@ -793,7 +877,7 @@ public V setValue(V value) { @Override public int size() { - @NullableDecl Map delegate = delegateOrNull(); + Map delegate = delegateOrNull(); return (delegate != null) ? delegate.size() : size; } @@ -803,20 +887,20 @@ public boolean isEmpty() { } @Override - public boolean containsValue(@NullableDecl Object value) { - @NullableDecl Map delegate = delegateOrNull(); + public boolean containsValue(@Nullable Object value) { + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.containsValue(value); } for (int i = 0; i < size; i++) { - if (Objects.equal(value, values[i])) { + if (Objects.equals(value, value(i))) { return true; } } return false; } - @NullableDecl private transient Collection valuesView; + @LazyInit private transient @Nullable Collection valuesView; @Override public Collection values() { @@ -828,7 +912,7 @@ Collection createValues() { } @WeakOuter - class ValuesView extends AbstractCollection { + private final class ValuesView extends AbstractCollection { @Override public int size() { return CompactHashMap.this.size(); @@ -846,15 +930,15 @@ public Iterator iterator() { } Iterator valuesIterator() { - @NullableDecl Map delegate = delegateOrNull(); + Map delegate = delegateOrNull(); if (delegate != null) { return delegate.values().iterator(); } return new Itr() { - @SuppressWarnings("unchecked") // known to be a V @Override + @ParametricNullness V getOutput(int entry) { - return (V) values[entry]; + return value(entry); } }; } @@ -867,7 +951,7 @@ public void trimToSize() { if (needsAllocArrays()) { return; } - @NullableDecl Map delegate = delegateOrNull(); + Map delegate = delegateOrNull(); if (delegate != null) { Map newDelegate = createHashFloodingResistantDelegate(size()); newDelegate.putAll(delegate); @@ -875,7 +959,7 @@ public void trimToSize() { return; } int size = this.size; - if (size < entries.length) { + if (size < requireEntries().length) { resizeEntries(size); } int minimumTableSize = CompactHashing.tableSize(size); @@ -891,7 +975,7 @@ public void clear() { return; } incrementModCount(); - @NullableDecl Map delegate = delegateOrNull(); + Map delegate = delegateOrNull(); if (delegate != null) { metadata = Ints.constrainToRange(size(), CompactHashing.DEFAULT_SIZE, CompactHashing.MAX_SIZE); @@ -899,14 +983,15 @@ public void clear() { table = null; size = 0; } else { - Arrays.fill(keys, 0, size, null); - Arrays.fill(values, 0, size, null); - CompactHashing.tableClear(table); - Arrays.fill(entries, 0, size, 0); + Arrays.fill(requireKeys(), 0, size, null); + Arrays.fill(requireValues(), 0, size, null); + CompactHashing.tableClear(requireTable()); + Arrays.fill(requireEntries(), 0, size, 0); this.size = 0; } } + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(size()); @@ -919,6 +1004,7 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @SuppressWarnings("unchecked") + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int elementCount = stream.readInt(); @@ -932,4 +1018,66 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo put(key, value); } } + + /* + * The following methods are safe to call as long as both of the following hold: + * + * - allocArrays() has been called. Callers can confirm this by checking needsAllocArrays(). + * + * - The map has not switched to delegating to a java.util implementation to mitigate hash + * flooding. Callers can confirm this by null-checking delegateOrNull(). + * + * In an ideal world, we would document why we know those things are true every time we call these + * methods. But that is a bit too painful.... + */ + + private Object requireTable() { + return requireNonNull(table); + } + + private int[] requireEntries() { + return requireNonNull(entries); + } + + private @Nullable Object[] requireKeys() { + return requireNonNull(keys); + } + + private @Nullable Object[] requireValues() { + return requireNonNull(values); + } + + /* + * The following methods are safe to call as long as the conditions in the *previous* comment are + * met *and* the index is less than size(). + * + * (The above explains when these methods are safe from a `nullness` perspective. From an + * `unchecked` perspective, they're safe because we put only K/V elements into each array.) + */ + + @SuppressWarnings("unchecked") + private K key(int i) { + return (K) requireKeys()[i]; + } + + @SuppressWarnings("unchecked") + private V value(int i) { + return (V) requireValues()[i]; + } + + private int entry(int i) { + return requireEntries()[i]; + } + + private void setKey(int i, K key) { + requireKeys()[i] = key; + } + + private void setValue(int i, V value) { + requireValues()[i] = value; + } + + private void setEntry(int i, int value) { + requireEntries()[i] = value; + } } diff --git a/android/guava/src/com/google/common/collect/CompactHashSet.java b/android/guava/src/com/google/common/collect/CompactHashSet.java index d4c585cf5140..49d0f37a00b5 100644 --- a/android/guava/src/com/google/common/collect/CompactHashSet.java +++ b/android/guava/src/com/google/common/collect/CompactHashSet.java @@ -19,10 +19,13 @@ import static com.google.common.collect.CollectPreconditions.checkRemove; import static com.google.common.collect.CompactHashing.UNSET; import static com.google.common.collect.Hashing.smearedHash; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -39,8 +42,9 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * CompactHashSet is an implementation of a Set. All optional operations (adding and removing) are @@ -70,11 +74,11 @@ * @author Jon Noack */ @GwtIncompatible // not worth using in GWT for now -class CompactHashSet extends AbstractSet implements Serializable { +class CompactHashSet extends AbstractSet implements Serializable { // TODO(user): cache all field accesses in local vars /** Creates an empty {@code CompactHashSet} instance. */ - public static CompactHashSet create() { + public static CompactHashSet create() { return new CompactHashSet<>(); } @@ -85,7 +89,8 @@ public static CompactHashSet create() { * @param collection the elements that the set should contain * @return a new {@code CompactHashSet} containing those elements (minus duplicates) */ - public static CompactHashSet create(Collection collection) { + public static CompactHashSet create( + Collection collection) { CompactHashSet set = createWithExpectedSize(collection.size()); set.addAll(collection); return set; @@ -99,7 +104,7 @@ public static CompactHashSet create(Collection collection) { * @return a new {@code CompactHashSet} containing those elements (minus duplicates) */ @SafeVarargs - public static CompactHashSet create(E... elements) { + public static CompactHashSet create(E... elements) { CompactHashSet set = createWithExpectedSize(elements.length); Collections.addAll(set, elements); return set; @@ -114,7 +119,8 @@ public static CompactHashSet create(E... elements) { * elements without resizing * @throws IllegalArgumentException if {@code expectedSize} is negative */ - public static CompactHashSet createWithExpectedSize(int expectedSize) { + public static CompactHashSet createWithExpectedSize( + int expectedSize) { return new CompactHashSet<>(expectedSize); } @@ -132,6 +138,10 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { */ private static final int MAX_HASH_BUCKET_LENGTH = 9; + // See CompactHashMap for a detailed description of how the following fields work. That + // description talks about `keys`, `values`, and `entries`; here the `keys` and `values` arrays + // are replaced by a single `elements` array but everything else works similarly. + /** * The hashtable object. This can be either: * @@ -151,7 +161,7 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { *

  • null, if no entries have yet been added to the map * */ - @NullableDecl private transient Object table; + private transient @Nullable Object table; /** * Contains the logical entries, in the range of [0, size()). The high bits of each int are the @@ -161,20 +171,20 @@ public static CompactHashSet createWithExpectedSize(int expectedSize) { * *
        * hash  = aaaaaaaa
    -   * mask  = 0000ffff
    -   * next  = 0000bbbb
    -   * entry = aaaabbbb
    +   * mask  = 00000fff
    +   * next  = 00000bbb
    +   * entry = aaaaabbb
        * 
    * *

    The pointers in [size(), entries.length) are all "null" (UNSET). */ - @NullableDecl private transient int[] entries; + private transient int @Nullable [] entries; /** * The elements contained in the set, in the range of [0, size()). The elements in [size(), * elements.length) are all {@code null}. */ - @VisibleForTesting @NullableDecl transient Object[] elements; + @VisibleForTesting transient @Nullable Object @Nullable [] elements; /** * Keeps track of metadata like the number of hash table bits and modifications of this data @@ -210,7 +220,6 @@ void init(int expectedSize) { } /** Returns whether arrays need to be allocated. */ - @VisibleForTesting boolean needsAllocArrays() { return table == null; } @@ -233,8 +242,7 @@ int allocArrays() { @SuppressWarnings("unchecked") @VisibleForTesting - @NullableDecl - Set delegateOrNull() { + @Nullable Set delegateOrNull() { if (table instanceof Set) { return (Set) table; } @@ -245,13 +253,11 @@ private Set createHashFloodingResistantDelegate(int tableSize) { return new LinkedHashSet<>(tableSize, 1.0f); } - @SuppressWarnings("unchecked") - @VisibleForTesting @CanIgnoreReturnValue Set convertToHashFloodingResistantImplementation() { Set newDelegate = createHashFloodingResistantDelegate(hashTableMask() + 1); for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { - newDelegate.add((E) elements[i]); + newDelegate.add(element(i)); } this.table = newDelegate; this.entries = null; @@ -283,29 +289,29 @@ void incrementModCount() { @CanIgnoreReturnValue @Override - public boolean add(@NullableDecl E object) { + public boolean add(@ParametricNullness E object) { if (needsAllocArrays()) { allocArrays(); } - @NullableDecl Set delegate = delegateOrNull(); + Set delegate = delegateOrNull(); if (delegate != null) { return delegate.add(object); } - int[] entries = this.entries; - Object[] elements = this.elements; + int[] entries = requireEntries(); + @Nullable Object[] elements = requireElements(); int newEntryIndex = this.size; // current size, and pointer to the entry to be appended int newSize = newEntryIndex + 1; int hash = smearedHash(object); int mask = hashTableMask(); int tableIndex = hash & mask; - int next = CompactHashing.tableGet(table, tableIndex); + int next = CompactHashing.tableGet(requireTable(), tableIndex); if (next == UNSET) { // uninitialized bucket if (newSize > mask) { // Resize and add new entry mask = resizeTable(mask, CompactHashing.newCapacity(mask), hash, newEntryIndex); } else { - CompactHashing.tableSet(table, tableIndex, newEntryIndex + 1); + CompactHashing.tableSet(requireTable(), tableIndex, newEntryIndex + 1); } } else { int entryIndex; @@ -316,7 +322,7 @@ public boolean add(@NullableDecl E object) { entryIndex = next - 1; entry = entries[entryIndex]; if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix - && Objects.equal(object, elements[entryIndex])) { + && Objects.equals(object, elements[entryIndex])) { return false; } next = CompactHashing.getNext(entry, mask); @@ -344,18 +350,17 @@ public boolean add(@NullableDecl E object) { /** * Creates a fresh entry with the specified object at the specified position in the entry arrays. */ - void insertEntry(int entryIndex, @NullableDecl E object, int hash, int mask) { - this.entries[entryIndex] = CompactHashing.maskCombine(hash, UNSET, mask); - this.elements[entryIndex] = object; + void insertEntry(int entryIndex, @ParametricNullness E object, int hash, int mask) { + setEntry(entryIndex, CompactHashing.maskCombine(hash, UNSET, mask)); + setElement(entryIndex, object); } /** Resizes the entries storage if necessary. */ private void resizeMeMaybe(int newSize) { - int entriesSize = entries.length; + int entriesSize = requireEntries().length; if (newSize > entriesSize) { // 1.5x but round up to nearest odd (this is optimal for memory consumption on Android) - int newCapacity = - Math.min(CompactHashing.MAX_SIZE, (entriesSize + Math.max(1, entriesSize >>> 1)) | 1); + int newCapacity = min(CompactHashing.MAX_SIZE, (entriesSize + max(1, entriesSize >>> 1)) | 1); if (newCapacity != entriesSize) { resizeEntries(newCapacity); } @@ -367,12 +372,12 @@ private void resizeMeMaybe(int newSize) { * the current capacity. */ void resizeEntries(int newCapacity) { - this.entries = Arrays.copyOf(entries, newCapacity); - this.elements = Arrays.copyOf(elements, newCapacity); + this.entries = Arrays.copyOf(requireEntries(), newCapacity); + this.elements = Arrays.copyOf(requireElements(), newCapacity); } @CanIgnoreReturnValue - private int resizeTable(int mask, int newCapacity, int targetHash, int targetEntryIndex) { + private int resizeTable(int oldMask, int newCapacity, int targetHash, int targetEntryIndex) { Object newTable = CompactHashing.createTable(newCapacity); int newMask = newCapacity - 1; @@ -381,25 +386,25 @@ private int resizeTable(int mask, int newCapacity, int targetHash, int targetEnt CompactHashing.tableSet(newTable, targetHash & newMask, targetEntryIndex + 1); } - Object table = this.table; - int[] entries = this.entries; + Object oldTable = requireTable(); + int[] entries = requireEntries(); // Loop over current hashtable - for (int tableIndex = 0; tableIndex <= mask; tableIndex++) { - int next = CompactHashing.tableGet(table, tableIndex); - while (next != UNSET) { - int entryIndex = next - 1; - int entry = entries[entryIndex]; + for (int oldTableIndex = 0; oldTableIndex <= oldMask; oldTableIndex++) { + int oldNext = CompactHashing.tableGet(oldTable, oldTableIndex); + while (oldNext != UNSET) { + int entryIndex = oldNext - 1; + int oldEntry = entries[entryIndex]; // Rebuild hash using entry hashPrefix and tableIndex ("hashSuffix") - int hash = CompactHashing.getHashPrefix(entry, mask) | tableIndex; + int hash = CompactHashing.getHashPrefix(oldEntry, oldMask) | oldTableIndex; int newTableIndex = hash & newMask; int newNext = CompactHashing.tableGet(newTable, newTableIndex); - CompactHashing.tableSet(newTable, newTableIndex, next); + CompactHashing.tableSet(newTable, newTableIndex, oldNext); entries[entryIndex] = CompactHashing.maskCombine(hash, newNext, newMask); - next = CompactHashing.getNext(entry, mask); + oldNext = CompactHashing.getNext(oldEntry, oldMask); } } @@ -409,26 +414,26 @@ private int resizeTable(int mask, int newCapacity, int targetHash, int targetEnt } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { if (needsAllocArrays()) { return false; } - @NullableDecl Set delegate = delegateOrNull(); + Set delegate = delegateOrNull(); if (delegate != null) { return delegate.contains(object); } int hash = smearedHash(object); int mask = hashTableMask(); - int next = CompactHashing.tableGet(table, hash & mask); + int next = CompactHashing.tableGet(requireTable(), hash & mask); if (next == UNSET) { return false; } int hashPrefix = CompactHashing.getHashPrefix(hash, mask); do { int entryIndex = next - 1; - int entry = entries[entryIndex]; + int entry = entry(entryIndex); if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix - && Objects.equal(object, elements[entryIndex])) { + && Objects.equals(object, element(entryIndex))) { return true; } next = CompactHashing.getNext(entry, mask); @@ -438,18 +443,24 @@ public boolean contains(@NullableDecl Object object) { @CanIgnoreReturnValue @Override - public boolean remove(@NullableDecl Object object) { + public boolean remove(@Nullable Object object) { if (needsAllocArrays()) { return false; } - @NullableDecl Set delegate = delegateOrNull(); + Set delegate = delegateOrNull(); if (delegate != null) { return delegate.remove(object); } int mask = hashTableMask(); int index = CompactHashing.remove( - object, /* value= */ null, mask, table, entries, elements, /* values= */ null); + object, + /* value= */ null, + mask, + requireTable(), + requireEntries(), + requireElements(), + /* values= */ null); if (index == -1) { return false; } @@ -465,10 +476,13 @@ public boolean remove(@NullableDecl Object object) { * Moves the last entry in the entry array into {@code dstIndex}, and nulls out its old position. */ void moveLastEntry(int dstIndex, int mask) { + Object table = requireTable(); + int[] entries = requireEntries(); + @Nullable Object[] elements = requireElements(); int srcIndex = size() - 1; if (dstIndex < srcIndex) { // move last entry to deleted spot - @NullableDecl Object object = elements[srcIndex]; + Object object = elements[srcIndex]; elements[dstIndex] = object; elements[srcIndex] = null; @@ -520,7 +534,7 @@ int adjustAfterRemove(int indexBeforeRemove, @SuppressWarnings("unused") int ind @Override public Iterator iterator() { - @NullableDecl Set delegate = delegateOrNull(); + Set delegate = delegateOrNull(); if (delegate != null) { return delegate.iterator(); } @@ -534,15 +548,15 @@ public boolean hasNext() { return currentIndex >= 0; } - @SuppressWarnings("unchecked") // known to be Es @Override + @ParametricNullness public E next() { checkForConcurrentModification(); if (!hasNext()) { throw new NoSuchElementException(); } indexToRemove = currentIndex; - E result = (E) elements[currentIndex]; + E result = element(currentIndex); currentIndex = getSuccessor(currentIndex); return result; } @@ -552,7 +566,7 @@ public void remove() { checkForConcurrentModification(); checkRemove(indexToRemove >= 0); incrementExpectedModCount(); - CompactHashSet.this.remove(elements[indexToRemove]); + CompactHashSet.this.remove(element(indexToRemove)); currentIndex = adjustAfterRemove(currentIndex, indexToRemove); indexToRemove = -1; } @@ -571,7 +585,7 @@ private void checkForConcurrentModification() { @Override public int size() { - @NullableDecl Set delegate = delegateOrNull(); + Set delegate = delegateOrNull(); return (delegate != null) ? delegate.size() : size; } @@ -581,27 +595,28 @@ public boolean isEmpty() { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { if (needsAllocArrays()) { return new Object[0]; } - @NullableDecl Set delegate = delegateOrNull(); - return (delegate != null) ? delegate.toArray() : Arrays.copyOf(elements, size); + Set delegate = delegateOrNull(); + return (delegate != null) ? delegate.toArray() : Arrays.copyOf(requireElements(), size); } @CanIgnoreReturnValue @Override - public T[] toArray(T[] a) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] a) { if (needsAllocArrays()) { if (a.length > 0) { a[0] = null; } return a; } - @NullableDecl Set delegate = delegateOrNull(); + Set delegate = delegateOrNull(); return (delegate != null) ? delegate.toArray(a) - : ObjectArrays.toArrayImpl(elements, 0, size, a); + : ObjectArrays.toArrayImpl(requireElements(), 0, size, a); } /** @@ -612,7 +627,7 @@ public void trimToSize() { if (needsAllocArrays()) { return; } - @NullableDecl Set delegate = delegateOrNull(); + Set delegate = delegateOrNull(); if (delegate != null) { Set newDelegate = createHashFloodingResistantDelegate(size()); newDelegate.addAll(delegate); @@ -620,7 +635,7 @@ public void trimToSize() { return; } int size = this.size; - if (size < entries.length) { + if (size < requireEntries().length) { resizeEntries(size); } int minimumTableSize = CompactHashing.tableSize(size); @@ -636,7 +651,7 @@ public void clear() { return; } incrementModCount(); - @NullableDecl Set delegate = delegateOrNull(); + Set delegate = delegateOrNull(); if (delegate != null) { metadata = Ints.constrainToRange(size(), CompactHashing.DEFAULT_SIZE, CompactHashing.MAX_SIZE); @@ -644,13 +659,14 @@ public void clear() { table = null; size = 0; } else { - Arrays.fill(elements, 0, size, null); - CompactHashing.tableClear(table); - Arrays.fill(entries, 0, size, 0); + Arrays.fill(requireElements(), 0, size, null); + CompactHashing.tableClear(requireTable()); + Arrays.fill(requireEntries(), 0, size, 0); this.size = 0; } } + @J2ktIncompatible private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(size()); @@ -660,6 +676,7 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } @SuppressWarnings("unchecked") + @J2ktIncompatible private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int elementCount = stream.readInt(); @@ -672,4 +689,38 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo add(element); } } + + /* + * For discussion of the safety of the following methods, see the comments near the end of + * CompactHashMap. + */ + + private Object requireTable() { + return requireNonNull(table); + } + + private int[] requireEntries() { + return requireNonNull(entries); + } + + private @Nullable Object[] requireElements() { + return requireNonNull(elements); + } + + @SuppressWarnings("unchecked") + private E element(int i) { + return (E) requireElements()[i]; + } + + private int entry(int i) { + return requireEntries()[i]; + } + + private void setElement(int i, E value) { + requireElements()[i] = value; + } + + private void setEntry(int i, int value) { + requireEntries()[i] = value; + } } diff --git a/android/guava/src/com/google/common/collect/CompactHashing.java b/android/guava/src/com/google/common/collect/CompactHashing.java index 7e83a05faa41..55dc414c868a 100644 --- a/android/guava/src/com/google/common/collect/CompactHashing.java +++ b/android/guava/src/com/google/common/collect/CompactHashing.java @@ -16,11 +16,13 @@ package com.google.common.collect; +import static java.lang.Math.max; + import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; import com.google.common.primitives.Ints; import java.util.Arrays; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * Helper classes and static methods for implementing compact hash-based collections. @@ -38,7 +40,7 @@ private CompactHashing() {} private static final int HASH_TABLE_BITS_MAX_BITS = 5; /** Use high bits of metadata for modification count. */ - static final int MODIFICATION_COUNT_INCREMENT = (1 << HASH_TABLE_BITS_MAX_BITS); + static final int MODIFICATION_COUNT_INCREMENT = 1 << HASH_TABLE_BITS_MAX_BITS; /** Bitmask that selects the low bits of metadata to get hashTableBits. */ static final int HASH_TABLE_BITS_MASK = (1 << HASH_TABLE_BITS_MAX_BITS) - 1; @@ -67,7 +69,7 @@ private CompactHashing() {} */ static int tableSize(int expectedSize) { // We use entries next == 0 to indicate UNSET, so actual capacity is 1 less than requested. - return Math.max(MIN_HASH_TABLE_SIZE, Hashing.closedTableSize(expectedSize + 1, 1.0f)); + return max(MIN_HASH_TABLE_SIZE, Hashing.closedTableSize(expectedSize + 1, 1.0)); } /** Creates and returns a properly-sized array with the given number of buckets. */ @@ -96,6 +98,11 @@ static void tableClear(Object table) { } } + /** + * Returns {@code table[index]}, where {@code table} is actually a {@code byte[]}, {@code + * short[]}, or {@code int[]}. When it is a {@code byte[]} or {@code short[]}, the returned value + * is unsigned, so the range of possible returned values is 0–255 or 0–65535, respectively. + */ static int tableGet(Object table, int index) { if (table instanceof byte[]) { return ((byte[]) table)[index] & BYTE_MASK; // unsigned read @@ -106,6 +113,13 @@ static int tableGet(Object table, int index) { } } + /** + * Sets {@code table[index]} to {@code entry}, where {@code table} is actually a {@code byte[]}, + * {@code short[]}, or {@code int[]}. The value of {@code entry} should fit in the size of the + * assigned array element, when seen as an unsigned value. So if {@code table} is a {@code byte[]} + * then we should have {@code 0 ≤ entry ≤ 255}, and if {@code table} is a {@code short[]} then we + * should have {@code 0 ≤ entry ≤ 65535}. It is the caller's responsibility to ensure this. + */ static void tableSet(Object table, int index, int entry) { if (table instanceof byte[]) { ((byte[]) table)[index] = (byte) entry; // unsigned write @@ -143,13 +157,13 @@ static int maskCombine(int prefix, int suffix, int mask) { } static int remove( - @NullableDecl Object key, - @NullableDecl Object value, + @Nullable Object key, + @Nullable Object value, int mask, Object table, int[] entries, - Object[] keys, - @NullableDecl Object[] values) { + @Nullable Object[] keys, + @Nullable Object @Nullable [] values) { int hash = Hashing.smearedHash(key); int tableIndex = hash & mask; int next = tableGet(table, tableIndex); @@ -162,8 +176,8 @@ static int remove( int entryIndex = next - 1; int entry = entries[entryIndex]; if (getHashPrefix(entry, mask) == hashPrefix - && Objects.equal(key, keys[entryIndex]) - && (values == null || Objects.equal(value, values[entryIndex]))) { + && Objects.equals(key, keys[entryIndex]) + && (values == null || Objects.equals(value, values[entryIndex]))) { int newNext = getNext(entry, mask); if (lastEntryIndex == -1) { // we need to update the root link from table[] diff --git a/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java b/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java index 9135524677b3..0ca015475aa1 100644 --- a/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java +++ b/android/guava/src/com/google/common/collect/CompactLinkedHashMap.java @@ -16,13 +16,16 @@ package com.google.common.collect; +import static java.util.Objects.requireNonNull; + import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * CompactLinkedHashMap is an implementation of a Map with insertion or LRU iteration order, @@ -44,12 +47,15 @@ * * @author Louis Wasserman */ +@J2ktIncompatible // no support for access-order mode in LinkedHashMap delegate @GwtIncompatible // not worth using in GWT for now -class CompactLinkedHashMap extends CompactHashMap { +final class CompactLinkedHashMap + extends CompactHashMap { // TODO(lowasser): implement removeEldestEntry so this can be used as a drop-in replacement /** Creates an empty {@code CompactLinkedHashMap} instance. */ - public static CompactLinkedHashMap create() { + public static + CompactLinkedHashMap create() { return new CompactLinkedHashMap<>(); } @@ -62,7 +68,8 @@ public static CompactLinkedHashMap create() { * expectedSize} elements without resizing * @throws IllegalArgumentException if {@code expectedSize} is negative */ - public static CompactLinkedHashMap createWithExpectedSize(int expectedSize) { + public static + CompactLinkedHashMap createWithExpectedSize(int expectedSize) { return new CompactLinkedHashMap<>(expectedSize); } @@ -77,7 +84,7 @@ public static CompactLinkedHashMap createWithExpectedSize(int expec *

    A node with "prev" pointer equal to {@code ENDPOINT} is the first node in the linked list, * and a node with "next" pointer equal to {@code ENDPOINT} is the last node. */ - @VisibleForTesting @NullableDecl transient long[] links; + @VisibleForTesting transient long @Nullable [] links; /** Pointer to the first node in the linked list, or {@code ENDPOINT} if there are no entries. */ private transient int firstEntry; @@ -116,7 +123,7 @@ int allocArrays() { @Override Map createHashFloodingResistantDelegate(int tableSize) { - return new LinkedHashMap(tableSize, 1.0f, accessOrder); + return new LinkedHashMap<>(tableSize, 1.0f, accessOrder); } @Override @@ -127,23 +134,29 @@ Map convertToHashFloodingResistantImplementation() { return result; } + /* + * For discussion of the safety of the following methods for operating on predecessors and + * successors, see the comments near the end of CompactHashMap, noting that the methods here call + * link(), which is defined at the end of this file. + */ + private int getPredecessor(int entry) { - return ((int) (links[entry] >>> 32)) - 1; + return ((int) (link(entry) >>> 32)) - 1; } @Override int getSuccessor(int entry) { - return ((int) links[entry]) - 1; + return ((int) link(entry)) - 1; } private void setSuccessor(int entry, int succ) { - long succMask = (~0L) >>> 32; - links[entry] = (links[entry] & ~succMask) | ((succ + 1) & succMask); + long succMask = ~0L >>> 32; + setLink(entry, (link(entry) & ~succMask) | ((succ + 1) & succMask)); } private void setPredecessor(int entry, int pred) { long predMask = ~0L << 32; - links[entry] = (links[entry] & ~predMask) | ((long) (pred + 1) << 32); + setLink(entry, (link(entry) & ~predMask) | ((long) (pred + 1) << 32)); } private void setSucceeds(int pred, int succ) { @@ -161,7 +174,8 @@ private void setSucceeds(int pred, int succ) { } @Override - void insertEntry(int entryIndex, @NullableDecl K key, @NullableDecl V value, int hash, int mask) { + void insertEntry( + int entryIndex, @ParametricNullness K key, @ParametricNullness V value, int hash, int mask) { super.insertEntry(entryIndex, key, value, hash, mask); setSucceeds(lastEntry, entryIndex); setSucceeds(entryIndex, ENDPOINT); @@ -189,13 +203,13 @@ void moveLastEntry(int dstIndex, int mask) { setSucceeds(getPredecessor(srcIndex), dstIndex); setSucceeds(dstIndex, getSuccessor(srcIndex)); } - links[srcIndex] = 0; + setLink(srcIndex, 0); } @Override void resizeEntries(int newCapacity) { super.resizeEntries(newCapacity); - links = Arrays.copyOf(links, newCapacity); + links = Arrays.copyOf(requireLinks(), newCapacity); } @Override @@ -220,4 +234,27 @@ public void clear() { } super.clear(); } + + /* + * For discussion of the safety of the following methods, see the comments near the end of + * CompactHashMap. + */ + + private long[] requireLinks() { + return requireNonNull(links); + } + + private long link(int i) { + return requireLinks()[i]; + } + + private void setLink(int i, long value) { + requireLinks()[i] = value; + } + + /* + * We don't define getPredecessor+getSuccessor and setPredecessor+setSuccessor here because + * they're defined above -- including logic to add and subtract 1 to map between the values stored + * in the predecessor/successor arrays and the indexes in the elements array that they identify. + */ } diff --git a/android/guava/src/com/google/common/collect/CompactLinkedHashSet.java b/android/guava/src/com/google/common/collect/CompactLinkedHashSet.java index 4f9aa4a0c137..049067372261 100644 --- a/android/guava/src/com/google/common/collect/CompactLinkedHashSet.java +++ b/android/guava/src/com/google/common/collect/CompactLinkedHashSet.java @@ -16,13 +16,15 @@ package com.google.common.collect; +import static java.util.Objects.requireNonNull; + import com.google.common.annotations.GwtIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * CompactLinkedHashSet is an implementation of a Set, which a predictable iteration order that @@ -46,10 +48,10 @@ * @author Louis Wasserman */ @GwtIncompatible // not worth using in GWT for now -class CompactLinkedHashSet extends CompactHashSet { +final class CompactLinkedHashSet extends CompactHashSet { /** Creates an empty {@code CompactLinkedHashSet} instance. */ - public static CompactLinkedHashSet create() { + public static CompactLinkedHashSet create() { return new CompactLinkedHashSet<>(); } @@ -60,7 +62,8 @@ public static CompactLinkedHashSet create() { * @param collection the elements that the set should contain * @return a new {@code CompactLinkedHashSet} containing those elements (minus duplicates) */ - public static CompactLinkedHashSet create(Collection collection) { + public static CompactLinkedHashSet create( + Collection collection) { CompactLinkedHashSet set = createWithExpectedSize(collection.size()); set.addAll(collection); return set; @@ -74,7 +77,7 @@ public static CompactLinkedHashSet create(Collection collect * @return a new {@code CompactLinkedHashSet} containing those elements (minus duplicates) */ @SafeVarargs - public static CompactLinkedHashSet create(E... elements) { + public static CompactLinkedHashSet create(E... elements) { CompactLinkedHashSet set = createWithExpectedSize(elements.length); Collections.addAll(set, elements); return set; @@ -89,7 +92,8 @@ public static CompactLinkedHashSet create(E... elements) { * expectedSize} elements without resizing * @throws IllegalArgumentException if {@code expectedSize} is negative */ - public static CompactLinkedHashSet createWithExpectedSize(int expectedSize) { + public static CompactLinkedHashSet createWithExpectedSize( + int expectedSize) { return new CompactLinkedHashSet<>(expectedSize); } @@ -103,13 +107,13 @@ public static CompactLinkedHashSet createWithExpectedSize(int expectedSiz * Pointer to the predecessor of an entry in insertion order. ENDPOINT indicates a node is the * first node in insertion order; all values at indices ≥ {@link #size()} are UNSET. */ - @NullableDecl private transient int[] predecessor; + private transient int @Nullable [] predecessor; /** * Pointer to the successor of an entry in insertion order. ENDPOINT indicates a node is the last * node in insertion order; all values at indices ≥ {@link #size()} are UNSET. */ - @NullableDecl private transient int[] successor; + private transient int @Nullable [] successor; /** Pointer to the first node in the linked list, or {@code ENDPOINT} if there are no entries. */ private transient int firstEntry; @@ -117,9 +121,7 @@ public static CompactLinkedHashSet createWithExpectedSize(int expectedSiz /** Pointer to the last node in the linked list, or {@code ENDPOINT} if there are no entries. */ private transient int lastEntry; - CompactLinkedHashSet() { - super(); - } + CompactLinkedHashSet() {} CompactLinkedHashSet(int expectedSize) { super(expectedSize); @@ -149,21 +151,27 @@ Set convertToHashFloodingResistantImplementation() { return result; } + /* + * For discussion of the safety of the following methods for operating on predecessors and + * successors, see the comments near the end of CompactHashMap, noting that the methods here call + * requirePredecessors() and requireSuccessors(), which are defined at the end of this file. + */ + private int getPredecessor(int entry) { - return predecessor[entry] - 1; + return requirePredecessors()[entry] - 1; } @Override int getSuccessor(int entry) { - return successor[entry] - 1; + return requireSuccessors()[entry] - 1; } private void setSuccessor(int entry, int succ) { - successor[entry] = succ + 1; + requireSuccessors()[entry] = succ + 1; } private void setPredecessor(int entry, int pred) { - predecessor[entry] = pred + 1; + requirePredecessors()[entry] = pred + 1; } private void setSucceeds(int pred, int succ) { @@ -181,7 +189,7 @@ private void setSucceeds(int pred, int succ) { } @Override - void insertEntry(int entryIndex, @NullableDecl E object, int hash, int mask) { + void insertEntry(int entryIndex, @ParametricNullness E object, int hash, int mask) { super.insertEntry(entryIndex, object, hash, mask); setSucceeds(lastEntry, entryIndex); setSucceeds(entryIndex, ENDPOINT); @@ -197,15 +205,15 @@ void moveLastEntry(int dstIndex, int mask) { setSucceeds(getPredecessor(srcIndex), dstIndex); setSucceeds(dstIndex, getSuccessor(srcIndex)); } - predecessor[srcIndex] = 0; - successor[srcIndex] = 0; + requirePredecessors()[srcIndex] = 0; + requireSuccessors()[srcIndex] = 0; } @Override void resizeEntries(int newCapacity) { super.resizeEntries(newCapacity); - predecessor = Arrays.copyOf(predecessor, newCapacity); - successor = Arrays.copyOf(successor, newCapacity); + predecessor = Arrays.copyOf(requirePredecessors(), newCapacity); + successor = Arrays.copyOf(requireSuccessors(), newCapacity); } @Override @@ -219,12 +227,13 @@ int adjustAfterRemove(int indexBeforeRemove, int indexRemoved) { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { return ObjectArrays.toArrayImpl(this); } @Override - public T[] toArray(T[] a) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] a) { return ObjectArrays.toArrayImpl(this, a); } @@ -235,10 +244,30 @@ public void clear() { } this.firstEntry = ENDPOINT; this.lastEntry = ENDPOINT; - if (predecessor != null) { + // Either both arrays are null or neither is, but we check both to satisfy the nullness checker. + if (predecessor != null && successor != null) { Arrays.fill(predecessor, 0, size(), 0); Arrays.fill(successor, 0, size(), 0); } super.clear(); } + + /* + * For discussion of the safety of the following methods, see the comments near the end of + * CompactHashMap. + */ + + private int[] requirePredecessors() { + return requireNonNull(predecessor); + } + + private int[] requireSuccessors() { + return requireNonNull(successor); + } + + /* + * We don't define getPredecessor+getSuccessor and setPredecessor+setSuccessor here because + * they're defined above -- including logic to add and subtract 1 to map between the values stored + * in the predecessor/successor arrays and the indexes in the elements array that they identify. + */ } diff --git a/android/guava/src/com/google/common/collect/ComparatorOrdering.java b/android/guava/src/com/google/common/collect/ComparatorOrdering.java index a40892003e64..291ddb2b4786 100644 --- a/android/guava/src/com/google/common/collect/ComparatorOrdering.java +++ b/android/guava/src/com/google/common/collect/ComparatorOrdering.java @@ -19,13 +19,16 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.Comparator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** An ordering for a pre-existing comparator. */ -@GwtCompatible(serializable = true) -final class ComparatorOrdering extends Ordering implements Serializable { +@GwtCompatible +final class ComparatorOrdering extends Ordering + implements Serializable { final Comparator comparator; ComparatorOrdering(Comparator comparator) { @@ -33,12 +36,12 @@ final class ComparatorOrdering extends Ordering implements Serializable { } @Override - public int compare(T a, T b) { + public int compare(@ParametricNullness T a, @ParametricNullness T b) { return comparator.compare(a, b); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -59,5 +62,5 @@ public String toString() { return comparator.toString(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/Comparators.java b/android/guava/src/com/google/common/collect/Comparators.java index 555e5b80bfaa..c5ad76921e9b 100644 --- a/android/guava/src/com/google/common/collect/Comparators.java +++ b/android/guava/src/com/google/common/collect/Comparators.java @@ -17,16 +17,19 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import java.util.Comparator; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * Provides static methods for working with {@link Comparator} instances. For many other helpful - * comparator utilities, see either {@code Comparator} itself (for Java 8 or later), or {@code + * comparator utilities, see either {@code Comparator} itself (for Java 8+), or {@code * com.google.common.collect.Ordering} (otherwise). * *

    Relationship to {@code Ordering}

    @@ -39,7 +42,6 @@ * @since 21.0 * @author Louis Wasserman */ -@Beta @GwtCompatible public final class Comparators { private Comparators() {} @@ -58,7 +60,8 @@ private Comparators() {} // Note: 90% of the time we don't add type parameters or wildcards that serve only to "tweak" the // desired return type. However, *nested* generics introduce a special class of problems that we // think tip it over into being worthwhile. - public static Comparator> lexicographical(Comparator comparator) { + public static Comparator> lexicographical( + Comparator comparator) { return new LexicographicalOrdering(checkNotNull(comparator)); } @@ -67,7 +70,8 @@ public static Comparator> lexicographical(Comparato * equal to the element that preceded it, according to the specified comparator. Note that this is * always true when the iterable has fewer than two elements. */ - public static boolean isInOrder(Iterable iterable, Comparator comparator) { + public static boolean isInOrder( + Iterable iterable, Comparator comparator) { checkNotNull(comparator); Iterator it = iterable.iterator(); if (it.hasNext()) { @@ -88,7 +92,7 @@ public static boolean isInOrder(Iterable iterable, Comparator boolean isInStrictOrder( + public static boolean isInStrictOrder( Iterable iterable, Comparator comparator) { checkNotNull(comparator); Iterator it = iterable.iterator(); @@ -105,6 +109,103 @@ public static boolean isInStrictOrder( return true; } + /** + * Returns a {@code Collector} that returns the {@code k} smallest (relative to the specified + * {@code Comparator}) input elements, in ascending order, as an unmodifiable {@code List}. Ties + * are broken arbitrarily. + * + *

    For example: + * + * {@snippet : + * Stream.of("foo", "quux", "banana", "elephant") + * .collect(least(2, comparingInt(String::length))) + * // returns {"foo", "quux"} + * } + * + *

    This {@code Collector} uses O(k) memory and takes expected time O(n) (worst-case O(n log + * k)), as opposed to e.g. {@code Stream.sorted(comparator).limit(k)}, which currently takes O(n + * log n) time and O(n) space. + * + * @throws IllegalArgumentException if {@code k < 0} + * @since 33.2.0 (available since 22.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> least( + int k, Comparator comparator) { + checkNonnegative(k, "k"); + checkNotNull(comparator); + return Collector.of( + () -> TopKSelector.least(k, comparator), + TopKSelector::offer, + TopKSelector::combine, + TopKSelector::topK, + Collector.Characteristics.UNORDERED); + } + + /** + * Returns a {@code Collector} that returns the {@code k} greatest (relative to the specified + * {@code Comparator}) input elements, in descending order, as an unmodifiable {@code List}. Ties + * are broken arbitrarily. + * + *

    For example: + * + * {@snippet : + * Stream.of("foo", "quux", "banana", "elephant") + * .collect(greatest(2, comparingInt(String::length))) + * // returns {"elephant", "banana"} + * } + * + *

    This {@code Collector} uses O(k) memory and takes expected time O(n) (worst-case O(n log + * k)), as opposed to e.g. {@code Stream.sorted(comparator.reversed()).limit(k)}, which currently + * takes O(n log n) time and O(n) space. + * + * @throws IllegalArgumentException if {@code k < 0} + * @since 33.2.0 (available since 22.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> greatest( + int k, Comparator comparator) { + return least(k, comparator.reversed()); + } + + /** + * Returns a comparator of {@link Optional} values which treats {@link Optional#empty} as less + * than all other values, and orders the rest using {@code valueComparator} on the contained + * value. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using Optional. + public static Comparator> emptiesFirst(Comparator valueComparator) { + checkNotNull(valueComparator); + return Comparator., @Nullable T>comparing( + o -> orElseNull(o), Comparator.nullsFirst(valueComparator)); + } + + /** + * Returns a comparator of {@link Optional} values which treats {@link Optional#empty} as greater + * than all other values, and orders the rest using {@code valueComparator} on the contained + * value. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using Optional. + public static Comparator> emptiesLast(Comparator valueComparator) { + checkNotNull(valueComparator); + return Comparator., @Nullable T>comparing( + o -> orElseNull(o), Comparator.nullsLast(valueComparator)); + } + + @IgnoreJRERequirement // helper for emptiesFirst+emptiesLast + /* + * If we make these calls inline inside the lambda inside emptiesFirst()/emptiesLast(), we get an + * Animal Sniffer error, despite the @IgnoreJRERequirement annotation there. For details, see + * ImmutableSortedMultiset. + */ + private static @Nullable T orElseNull(Optional optional) { + return optional.orElse(null); + } + /** * Returns the minimum of the two values. If the values compare as 0, the first is returned. * @@ -118,7 +219,6 @@ public static boolean isInStrictOrder( * @throws ClassCastException if the parameters are not mutually comparable. * @since 30.0 */ - @Beta public static > T min(T a, T b) { return (a.compareTo(b) <= 0) ? a : b; } @@ -138,8 +238,9 @@ public static > T min(T a, T b) { * comparator. * @since 30.0 */ - @Beta - public static T min(@NullableDecl T a, @NullableDecl T b, Comparator comparator) { + @ParametricNullness + public static T min( + @ParametricNullness T a, @ParametricNullness T b, Comparator comparator) { return (comparator.compare(a, b) <= 0) ? a : b; } @@ -156,7 +257,6 @@ public static T min(@NullableDecl T a, @NullableDecl T b, Comparator comp * @throws ClassCastException if the parameters are not mutually comparable. * @since 30.0 */ - @Beta public static > T max(T a, T b) { return (a.compareTo(b) >= 0) ? a : b; } @@ -176,8 +276,9 @@ public static > T max(T a, T b) { * comparator. * @since 30.0 */ - @Beta - public static T max(@NullableDecl T a, @NullableDecl T b, Comparator comparator) { + @ParametricNullness + public static T max( + @ParametricNullness T a, @ParametricNullness T b, Comparator comparator) { return (comparator.compare(a, b) >= 0) ? a : b; } } diff --git a/android/guava/src/com/google/common/collect/ComparisonChain.java b/android/guava/src/com/google/common/collect/ComparisonChain.java index 2f748961949a..9bb2cd7c0fc2 100644 --- a/android/guava/src/com/google/common/collect/ComparisonChain.java +++ b/android/guava/src/com/google/common/collect/ComparisonChain.java @@ -17,16 +17,14 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import com.google.common.primitives.Booleans; -import com.google.common.primitives.Ints; -import com.google.common.primitives.Longs; +import com.google.errorprone.annotations.InlineMe; import java.util.Comparator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A utility for performing a chained comparison statement. For example: * - *

    {@code
    + * {@snippet :
      * public int compareTo(Foo that) {
      *   return ComparisonChain.start()
      *       .compare(this.aString, that.aString)
    @@ -34,7 +32,7 @@
      *       .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
      *       .result();
      * }
    - * }
    + * } * *

    The value of this expression will have the same sign as the first nonzero comparison * result in the chain, or will be zero if every comparison result was zero. @@ -49,9 +47,40 @@ * the presence of expensive {@code compareTo} and {@code compare} implementations. * *

    See the Guava User Guide article on {@code + * "https://github.com/google/guava/wiki/CommonObjectUtilitiesExplained#comparecompareto">{@code * ComparisonChain}. * + *

    Java 8+ equivalents

    + * + * If you are using Java version 8 or greater, you should generally use the static methods in {@link + * Comparator} instead of {@code ComparisonChain}. The example above can be implemented like this: + * + * {@snippet : + * import static java.util.Comparator.comparing; + * import static java.util.Comparator.nullsLast; + * import static java.util.Comparator.naturalOrder; + * + * ... + * private static final Comparator COMPARATOR = + * comparing((Foo foo) -> foo.aString) + * .thenComparing(foo -> foo.anInt) + * .thenComparing(foo -> foo.anEnum, nullsLast(naturalOrder())); + * + * @Override + * public int compareTo(Foo that) { + * return COMPARATOR.compare(this, that); + * } + * } + * + *

    With method references it is more succinct: {@code comparing(Foo::aString)} for example. + * + *

    Using {@link Comparator} avoids certain types of bugs, for example when you meant to write + * {@code .compare(a.foo, b.foo)} but you actually wrote {@code .compare(a.foo, a.foo)} or {@code + * .compare(a.foo, b.bar)}. {@code ComparisonChain} also has a potential performance problem that + * {@code Comparator} doesn't: it evaluates all the parameters of all the {@code .compare} calls, + * even when the result of the comparison is already known from previous {@code .compare} calls. + * That can be expensive. + * * @author Mark Davis * @author Kevin Bourrillion * @since 2.0 @@ -67,26 +96,26 @@ public static ComparisonChain start() { private static final ComparisonChain ACTIVE = new ComparisonChain() { - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") // unsafe; see discussion on supertype @Override - public ComparisonChain compare(Comparable left, Comparable right) { - return classify(left.compareTo(right)); + public ComparisonChain compare(Comparable left, Comparable right) { + return classify(((Comparable) left).compareTo(right)); } @Override - public ComparisonChain compare( - @NullableDecl T left, @NullableDecl T right, Comparator comparator) { + public ComparisonChain compare( + @ParametricNullness T left, @ParametricNullness T right, Comparator comparator) { return classify(comparator.compare(left, right)); } @Override public ComparisonChain compare(int left, int right) { - return classify(Ints.compare(left, right)); + return classify(Integer.compare(left, right)); } @Override public ComparisonChain compare(long left, long right) { - return classify(Longs.compare(left, right)); + return classify(Long.compare(left, right)); } @Override @@ -101,12 +130,12 @@ public ComparisonChain compare(double left, double right) { @Override public ComparisonChain compareTrueFirst(boolean left, boolean right) { - return classify(Booleans.compare(right, left)); // reversed + return classify(Boolean.compare(right, left)); // reversed } @Override public ComparisonChain compareFalseFirst(boolean left, boolean right) { - return classify(Booleans.compare(left, right)); + return classify(Boolean.compare(left, right)); } ComparisonChain classify(int result) { @@ -131,13 +160,13 @@ private static final class InactiveComparisonChain extends ComparisonChain { } @Override - public ComparisonChain compare(@NullableDecl Comparable left, @NullableDecl Comparable right) { + public ComparisonChain compare(Comparable left, Comparable right) { return this; } @Override - public ComparisonChain compare( - @NullableDecl T left, @NullableDecl T right, @NullableDecl Comparator comparator) { + public ComparisonChain compare( + @ParametricNullness T left, @ParametricNullness T right, Comparator comparator) { return this; } @@ -180,6 +209,18 @@ public int result() { /** * Compares two comparable objects as specified by {@link Comparable#compareTo}, if the * result of this comparison chain has not already been determined. + * + *

    This method is declared to accept any 2 {@code Comparable} objects, even if they are not mutually + * comparable. If you pass objects that are not mutually comparable, this method may throw an + * exception. (The reason for this decision is lost to time, but the reason might be that + * we wanted to support legacy classes that implement the raw type {@code Comparable} (instead of + * implementing {@code Comparable}) without producing warnings. If so, we would prefer today + * to produce warnings in that case, and we may change this method to do so in the future. Support + * for raw {@code Comparable} types in Guava in general is tracked as #989.) + * + * @throws ClassCastException if the parameters are not mutually comparable */ public abstract ComparisonChain compare(Comparable left, Comparable right); @@ -187,17 +228,17 @@ public int result() { * Compares two objects using a comparator, if the result of this comparison chain has not * already been determined. */ - public abstract ComparisonChain compare( - @NullableDecl T left, @NullableDecl T right, Comparator comparator); + public abstract ComparisonChain compare( + @ParametricNullness T left, @ParametricNullness T right, Comparator comparator); /** - * Compares two {@code int} values as specified by {@link Ints#compare}, if the result of - * this comparison chain has not already been determined. + * Compares two {@code int} values as specified by {@link Integer#compare}, if the result + * of this comparison chain has not already been determined. */ public abstract ComparisonChain compare(int left, int right); /** - * Compares two {@code long} values as specified by {@link Longs#compare}, if the result of + * Compares two {@code long} values as specified by {@link Long#compare}, if the result of * this comparison chain has not already been determined. */ public abstract ComparisonChain compare(long left, long right); @@ -221,6 +262,7 @@ public abstract ComparisonChain compare( * negated or reversed, undo the negation or reversal and use {@link #compareTrueFirst}. * @since 19.0 */ + @InlineMe(replacement = "this.compareFalseFirst(left, right)") @Deprecated public final ComparisonChain compare(Boolean left, Boolean right) { return compareFalseFirst(left, right); diff --git a/android/guava/src/com/google/common/collect/CompoundOrdering.java b/android/guava/src/com/google/common/collect/CompoundOrdering.java index e803acb4fbb7..e2e6e8b408e4 100644 --- a/android/guava/src/com/google/common/collect/CompoundOrdering.java +++ b/android/guava/src/com/google/common/collect/CompoundOrdering.java @@ -17,25 +17,32 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.Arrays; import java.util.Comparator; +import org.jspecify.annotations.Nullable; /** An ordering that tries several comparators in order. */ -@GwtCompatible(serializable = true) -final class CompoundOrdering extends Ordering implements Serializable { +@GwtCompatible +final class CompoundOrdering extends Ordering + implements Serializable { final Comparator[] comparators; + @SuppressWarnings("unchecked") // Generic array creation CompoundOrdering(Comparator primary, Comparator secondary) { - this.comparators = (Comparator[]) new Comparator[] {primary, secondary}; + this.comparators = (Comparator[]) new Comparator[] {primary, secondary}; } + @SuppressWarnings("unchecked") // Generic array creation CompoundOrdering(Iterable> comparators) { - this.comparators = Iterables.toArray(comparators, new Comparator[0]); + this.comparators = + Iterables.toArray(comparators, (Comparator[]) new Comparator[0]); } @Override - public int compare(T left, T right) { + public int compare(@ParametricNullness T left, @ParametricNullness T right) { for (int i = 0; i < comparators.length; i++) { int result = comparators[i].compare(left, right); if (result != 0) { @@ -46,7 +53,7 @@ public int compare(T left, T right) { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -67,5 +74,5 @@ public String toString() { return "Ordering.compound(" + Arrays.toString(comparators) + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ComputationException.java b/android/guava/src/com/google/common/collect/ComputationException.java index dc569da3544b..d5f6bcd26a82 100644 --- a/android/guava/src/com/google/common/collect/ComputationException.java +++ b/android/guava/src/com/google/common/collect/ComputationException.java @@ -17,7 +17,9 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * Wraps an exception that occurred during a computation. @@ -36,9 +38,9 @@ @GwtCompatible public class ComputationException extends RuntimeException { /** Creates a new instance with the given cause. */ - public ComputationException(@NullableDecl Throwable cause) { + public ComputationException(@Nullable Throwable cause) { super(cause); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ConcurrentHashMultiset.java b/android/guava/src/com/google/common/collect/ConcurrentHashMultiset.java index 8bdfca32d0d4..6242c605e799 100644 --- a/android/guava/src/com/google/common/collect/ConcurrentHashMultiset.java +++ b/android/guava/src/com/google/common/collect/ConcurrentHashMultiset.java @@ -18,14 +18,17 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.CollectPreconditions.checkNonnegative; -import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.Lists.newArrayListWithExpectedSize; +import static com.google.common.collect.Maps.safeGet; +import static java.lang.Math.max; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Serialization.FieldSetter; -import com.google.common.math.IntMath; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.j2objc.annotations.WeakOuter; @@ -41,20 +44,20 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A multiset that supports concurrent modifications and that provides atomic versions of most * {@code Multiset} operations (exceptions where noted). Null elements are not supported. * *

    See the Guava User Guide article on {@code - * Multiset}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}. * * @author Cliff L. Biffle * @author mike nonemacher * @since 2.0 */ +@J2ktIncompatible @GwtIncompatible public final class ConcurrentHashMultiset extends AbstractMultiset implements Serializable { @@ -73,8 +76,8 @@ public final class ConcurrentHashMultiset extends AbstractMultiset impleme // This constant allows the deserialization code to set a final field. This holder class // makes sure it is not initialized unless an instance is deserialized. - private static class FieldSettersHolder { - static final FieldSetter COUNT_MAP_FIELD_SETTER = + private static final class FieldSettersHolder { + static final FieldSetter> COUNT_MAP_FIELD_SETTER = Serialization.getFieldSetter(ConcurrentHashMultiset.class, "countMap"); } @@ -86,7 +89,7 @@ public static ConcurrentHashMultiset create() { // TODO(schmoe): provide a way to use this class with other (possibly arbitrary) // ConcurrentMap implementors. One possibility is to extract most of this class into // an AbstractConcurrentMapMultiset. - return new ConcurrentHashMultiset(new ConcurrentHashMap()); + return new ConcurrentHashMultiset<>(new ConcurrentHashMap()); } /** @@ -117,9 +120,8 @@ public static ConcurrentHashMultiset create(Iterable element * @throws IllegalArgumentException if {@code countMap} is not empty * @since 20.0 */ - @Beta public static ConcurrentHashMultiset create(ConcurrentMap countMap) { - return new ConcurrentHashMultiset(countMap); + return new ConcurrentHashMultiset<>(countMap); } @VisibleForTesting @@ -137,8 +139,8 @@ public static ConcurrentHashMultiset create(ConcurrentMap T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return snapshot().toArray(array); } @@ -177,7 +180,7 @@ public T[] toArray(T[] array) { * either of these would recurse back to us again! */ private List snapshot() { - List list = Lists.newArrayListWithExpectedSize(size()); + List list = newArrayListWithExpectedSize(size()); for (Multiset.Entry entry : entrySet()) { E element = entry.getElement(); for (int i = entry.getCount(); i > 0; i--) { @@ -205,10 +208,10 @@ public int add(E element, int occurrences) { if (occurrences == 0) { return count(element); } - CollectPreconditions.checkPositive(occurrences, "occurences"); + CollectPreconditions.checkPositive(occurrences, "occurrences"); while (true) { - AtomicInteger existingCounter = Maps.safeGet(countMap, element); + AtomicInteger existingCounter = safeGet(countMap, element); if (existingCounter == null) { existingCounter = countMap.putIfAbsent(element, new AtomicInteger(occurrences)); if (existingCounter == null) { @@ -221,7 +224,7 @@ public int add(E element, int occurrences) { int oldValue = existingCounter.get(); if (oldValue != 0) { try { - int newValue = IntMath.checkedAdd(oldValue, occurrences); + int newValue = Math.addExact(oldValue, occurrences); if (existingCounter.compareAndSet(oldValue, newValue)) { // newValue can't == 0, so no need to check & remove return oldValue; @@ -261,26 +264,26 @@ public int add(E element, int occurrences) { * if occurrences == 0. This satisfies both NullPointerTester and * CollectionRemoveTester.testRemove_nullAllowed, but it's not clear that it's * a good policy, especially because, in order for the test to pass, the - * parameter must be misleadingly annotated as @NullableDecl. I suspect that - * we'll want to remove @NullableDecl, add an eager checkNotNull, and loosen up + * parameter must be misleadingly annotated as @Nullable. I suspect that + * we'll want to remove @Nullable, add an eager checkNotNull, and loosen up * testRemove_nullAllowed. */ @CanIgnoreReturnValue @Override - public int remove(@NullableDecl Object element, int occurrences) { + public int remove(@Nullable Object element, int occurrences) { if (occurrences == 0) { return count(element); } - CollectPreconditions.checkPositive(occurrences, "occurences"); + CollectPreconditions.checkPositive(occurrences, "occurrences"); - AtomicInteger existingCounter = Maps.safeGet(countMap, element); + AtomicInteger existingCounter = safeGet(countMap, element); if (existingCounter == null) { return 0; } while (true) { int oldValue = existingCounter.get(); if (oldValue != 0) { - int newValue = Math.max(0, oldValue - occurrences); + int newValue = max(0, oldValue - occurrences); if (existingCounter.compareAndSet(oldValue, newValue)) { if (newValue == 0) { // Just CASed to 0; remove the entry to clean up the map. If the removal fails, @@ -308,13 +311,13 @@ public int remove(@NullableDecl Object element, int occurrences) { * @throws IllegalArgumentException if {@code occurrences} is negative */ @CanIgnoreReturnValue - public boolean removeExactly(@NullableDecl Object element, int occurrences) { + public boolean removeExactly(@Nullable Object element, int occurrences) { if (occurrences == 0) { return true; } - CollectPreconditions.checkPositive(occurrences, "occurences"); + CollectPreconditions.checkPositive(occurrences, "occurrences"); - AtomicInteger existingCounter = Maps.safeGet(countMap, element); + AtomicInteger existingCounter = safeGet(countMap, element); if (existingCounter == null) { return false; } @@ -348,7 +351,7 @@ public int setCount(E element, int count) { checkNotNull(element); checkNonnegative(count, "count"); while (true) { - AtomicInteger existingCounter = Maps.safeGet(countMap, element); + AtomicInteger existingCounter = safeGet(countMap, element); if (existingCounter == null) { if (count == 0) { return 0; @@ -405,7 +408,7 @@ public boolean setCount(E element, int expectedOldCount, int newCount) { checkNonnegative(expectedOldCount, "oldCount"); checkNonnegative(newCount, "newCount"); - AtomicInteger existingCounter = Maps.safeGet(countMap, element); + AtomicInteger existingCounter = safeGet(countMap, element); if (existingCounter == null) { if (expectedOldCount != 0) { return false; @@ -446,7 +449,7 @@ public boolean setCount(E element, int expectedOldCount, int newCount) { @Override Set createElementSet() { - final Set delegate = countMap.keySet(); + Set delegate = countMap.keySet(); return new ForwardingSet() { @Override protected Set delegate() { @@ -454,7 +457,7 @@ protected Set delegate() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return object != null && Collections2.safeContains(delegate, object); } @@ -464,7 +467,7 @@ public boolean containsAll(Collection collection) { } @Override - public boolean remove(Object object) { + public boolean remove(@Nullable Object object) { return object != null && Collections2.safeRemove(delegate, object); } @@ -480,7 +483,9 @@ Iterator elementIterator() { throw new AssertionError("should never be called"); } - /** @deprecated Internal method, use {@link #entrySet()}. */ + /** + * @deprecated Internal method, use {@link #entrySet()}. + */ @Deprecated @Override public Set> createEntrySet() { @@ -501,13 +506,13 @@ public boolean isEmpty() { Iterator> entryIterator() { // AbstractIterator makes this fairly clean, but it doesn't support remove(). To support // remove(), we create an AbstractIterator, and then use ForwardingIterator to delegate to it. - final Iterator> readOnlyIterator = + Iterator> readOnlyIterator = new AbstractIterator>() { private final Iterator> mapEntries = countMap.entrySet().iterator(); @Override - protected Entry computeNext() { + protected @Nullable Entry computeNext() { while (true) { if (!mapEntries.hasNext()) { return endOfData(); @@ -522,7 +527,7 @@ protected Entry computeNext() { }; return new ForwardingIterator>() { - @NullableDecl private Entry last; + private @Nullable Entry last; @Override protected Iterator> delegate() { @@ -537,7 +542,7 @@ public Entry next() { @Override public void remove() { - checkRemove(last != null); + checkState(last != null, "no calls to next() since the last call to remove()"); ConcurrentHashMultiset.this.setCount(last.getElement(), 0); last = null; } @@ -555,7 +560,7 @@ public void clear() { } @WeakOuter - private class EntrySet extends AbstractMultiset.EntrySet { + private final class EntrySet extends AbstractMultiset.EntrySet { @Override ConcurrentHashMultiset multiset() { return ConcurrentHashMultiset.this; @@ -572,29 +577,33 @@ public Object[] toArray() { } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return snapshot().toArray(array); } private List> snapshot() { - List> list = Lists.newArrayListWithExpectedSize(size()); + List> list = newArrayListWithExpectedSize(size()); // Not Iterables.addAll(list, this), because that'll forward right back here. Iterators.addAll(list, iterator()); return list; } } - /** @serialData the ConcurrentMap of elements and their counts. */ + /** + * @serialData the ConcurrentMap of elements and their counts. + */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(countMap); } + @J2ktIncompatible // serialization private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); @SuppressWarnings("unchecked") // reading data stored by writeObject ConcurrentMap deserializedCountMap = - (ConcurrentMap) stream.readObject(); + (ConcurrentMap) requireNonNull(stream.readObject()); FieldSettersHolder.COUNT_MAP_FIELD_SETTER.set(this, deserializedCountMap); } diff --git a/android/guava/src/com/google/common/collect/ConsumingQueueIterator.java b/android/guava/src/com/google/common/collect/ConsumingQueueIterator.java index 2f288f041f4e..847229e6a299 100644 --- a/android/guava/src/com/google/common/collect/ConsumingQueueIterator.java +++ b/android/guava/src/com/google/common/collect/ConsumingQueueIterator.java @@ -17,29 +17,27 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; -import java.util.ArrayDeque; -import java.util.Collections; import java.util.Queue; +import org.jspecify.annotations.Nullable; /** * An Iterator implementation which draws elements from a queue, removing them from the queue as it - * iterates. + * iterates. This class is not thread safe. */ @GwtCompatible -class ConsumingQueueIterator extends AbstractIterator { +final class ConsumingQueueIterator extends AbstractIterator { private final Queue queue; - ConsumingQueueIterator(T... elements) { - this.queue = new ArrayDeque(elements.length); - Collections.addAll(queue, elements); - } - ConsumingQueueIterator(Queue queue) { this.queue = checkNotNull(queue); } @Override - public T computeNext() { - return queue.isEmpty() ? endOfData() : queue.remove(); + protected @Nullable T computeNext() { + // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker. + if (queue.isEmpty()) { + return endOfData(); + } + return queue.remove(); } } diff --git a/android/guava/src/com/google/common/collect/ContiguousSet.java b/android/guava/src/com/google/common/collect/ContiguousSet.java index 9002b8e47684..78d96a1830e9 100644 --- a/android/guava/src/com/google/common/collect/ContiguousSet.java +++ b/android/guava/src/com/google/common/collect/ContiguousSet.java @@ -16,10 +16,11 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.DoNotCall; import java.util.Collections; import java.util.NoSuchElementException; @@ -28,16 +29,16 @@ /** * A sorted set of contiguous values in a given {@link DiscreteDomain}. Example: * - *

    {@code
    + * {@snippet :
      * ContiguousSet.create(Range.closed(5, 42), DiscreteDomain.integers())
    - * }
    + * } * *

    Note that because bounded ranges over {@code int} and {@code long} values are so common, this * particular example can be written as just: * - *

    {@code
    + * {@snippet :
      * ContiguousSet.closed(5, 42)
    - * }
    + * } * *

    Warning: Be extremely careful what you do with conceptually large instances (such as * {@code ContiguousSet.create(Range.greaterThan(0), DiscreteDomain.integers()}). Certain operations @@ -47,7 +48,7 @@ * @author Gregory Kick * @since 10.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible @SuppressWarnings("rawtypes") // allow ungenerified Comparable types public abstract class ContiguousSet extends ImmutableSortedSet { /** @@ -74,13 +75,19 @@ public static ContiguousSet create( throw new IllegalArgumentException(e); } - // Per class spec, we are allowed to throw CCE if necessary - boolean empty = - effectiveRange.isEmpty() - || Range.compareOrThrow( - range.lowerBound.leastValueAbove(domain), - range.upperBound.greatestValueBelow(domain)) - > 0; + boolean empty; + if (effectiveRange.isEmpty()) { + empty = true; + } else { + /* + * requireNonNull is safe because the effectiveRange operations above would have thrown or + * effectiveRange.isEmpty() would have returned true. + */ + C afterLower = requireNonNull(range.lowerBound.leastValueAbove(domain)); + C beforeUpper = requireNonNull(range.upperBound.greatestValueBelow(domain)); + // Per class spec, we are allowed to throw CCE if necessary + empty = Range.compareOrThrow(afterLower, beforeUpper) > 0; + } return empty ? new EmptyContiguousSet(domain) @@ -95,7 +102,6 @@ public static ContiguousSet create( * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} * @since 23.0 */ - @Beta public static ContiguousSet closed(int lower, int upper) { return create(Range.closed(lower, upper), DiscreteDomain.integers()); } @@ -108,7 +114,6 @@ public static ContiguousSet closed(int lower, int upper) { * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} * @since 23.0 */ - @Beta public static ContiguousSet closed(long lower, long upper) { return create(Range.closed(lower, upper), DiscreteDomain.longs()); } @@ -121,7 +126,6 @@ public static ContiguousSet closed(long lower, long upper) { * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} * @since 23.0 */ - @Beta public static ContiguousSet closedOpen(int lower, int upper) { return create(Range.closedOpen(lower, upper), DiscreteDomain.integers()); } @@ -134,7 +138,6 @@ public static ContiguousSet closedOpen(int lower, int upper) { * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} * @since 23.0 */ - @Beta public static ContiguousSet closedOpen(long lower, long upper) { return create(Range.closedOpen(lower, upper), DiscreteDomain.longs()); } @@ -151,7 +154,9 @@ public ContiguousSet headSet(C toElement) { return headSetImpl(checkNotNull(toElement), false); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @GwtIncompatible // NavigableSet @Override public ContiguousSet headSet(C toElement, boolean inclusive) { @@ -166,7 +171,9 @@ public ContiguousSet subSet(C fromElement, C toElement) { return subSetImpl(fromElement, true, toElement, false); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @GwtIncompatible // NavigableSet @Override public ContiguousSet subSet( @@ -182,7 +189,9 @@ public ContiguousSet tailSet(C fromElement) { return tailSetImpl(checkNotNull(fromElement), true); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @GwtIncompatible // NavigableSet @Override public ContiguousSet tailSet(C fromElement, boolean inclusive) { @@ -234,10 +243,10 @@ abstract ContiguousSet subSetImpl( @Override @GwtIncompatible // NavigableSet ImmutableSortedSet createDescendingSet() { - return new DescendingImmutableSortedSet(this); + return new DescendingImmutableSortedSet<>(this); } - /** Returns a short-hand representation of the contents such as {@code "[1..100]"}. */ + /** Returns a shorthand representation of the contents such as {@code "[1..100]"}. */ @Override public String toString() { return range().toString(); @@ -256,4 +265,13 @@ public String toString() { public static ImmutableSortedSet.Builder builder() { throw new UnsupportedOperationException(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @J2ktIncompatible // serialization + @Override + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/Count.java b/android/guava/src/com/google/common/collect/Count.java index 9a0ea41d9cba..fa01412d9c54 100644 --- a/android/guava/src/com/google/common/collect/Count.java +++ b/android/guava/src/com/google/common/collect/Count.java @@ -16,7 +16,7 @@ import com.google.common.annotations.GwtCompatible; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A mutable value of type {@code int}, for multisets to use in tracking counts of values. @@ -59,7 +59,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { return obj instanceof Count && ((Count) obj).value == value; } diff --git a/android/guava/src/com/google/common/collect/Cut.java b/android/guava/src/com/google/common/collect/Cut.java index b792d84e0403..7725a8a3147e 100644 --- a/android/guava/src/com/google/common/collect/Cut.java +++ b/android/guava/src/com/google/common/collect/Cut.java @@ -17,10 +17,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; -import com.google.common.primitives.Booleans; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation detail for the internal structure of {@link Range} instances. Represents a unique @@ -31,11 +32,12 @@ * * @author Kevin Bourrillion */ +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 @GwtCompatible abstract class Cut implements Comparable>, Serializable { - @NullableDecl final C endpoint; + final C endpoint; - Cut(@NullableDecl C endpoint) { + Cut(C endpoint) { this.endpoint = endpoint; } @@ -53,9 +55,9 @@ abstract class Cut implements Comparable>, Serializ abstract void describeAsUpperBound(StringBuilder sb); - abstract C leastValueAbove(DiscreteDomain domain); + abstract @Nullable C leastValueAbove(DiscreteDomain domain); - abstract C greatestValueBelow(DiscreteDomain domain); + abstract @Nullable C greatestValueBelow(DiscreteDomain domain); /* * The canonical form is a BelowValue cut whenever possible, otherwise ABOVE_ALL, or @@ -79,7 +81,7 @@ public int compareTo(Cut that) { return result; } // same value. below comes before above - return Booleans.compare(this instanceof AboveValue, that instanceof AboveValue); + return Boolean.compare(this instanceof AboveValue, that instanceof AboveValue); } C endpoint() { @@ -88,14 +90,15 @@ C endpoint() { @SuppressWarnings("unchecked") // catching CCE @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof Cut) { // It might not really be a Cut, but we'll catch a CCE if it's not Cut that = (Cut) obj; try { int compareResult = compareTo(that); return compareResult == 0; - } catch (ClassCastException ignored) { + } catch (ClassCastException wastNotComparableToOurType) { + return false; } } return false; @@ -114,13 +117,19 @@ static Cut belowAll() { return (Cut) BelowAll.INSTANCE; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; private static final class BelowAll extends Cut> { private static final BelowAll INSTANCE = new BelowAll(); private BelowAll() { - super(null); + /* + * No code ever sees this bogus value for `endpoint`: This class overrides both methods that + * use the `endpoint` field, compareTo() and endpoint(). Additionally, the main implementation + * of Cut.compareTo checks for belowAll before reading accessing `endpoint` on another Cut + * instance. + */ + super(""); } @Override @@ -178,7 +187,7 @@ Comparable greatestValueBelow(DiscreteDomain> domain) { @Override Cut> canonical(DiscreteDomain> domain) { try { - return Cut.>belowValue(domain.minValue()); + return Cut.belowValue(domain.minValue()); } catch (NoSuchElementException e) { return this; } @@ -203,7 +212,7 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /* @@ -219,7 +228,8 @@ private static final class AboveAll extends Cut> { private static final AboveAll INSTANCE = new AboveAll(); private AboveAll() { - super(null); + // For discussion of "", see BelowAll. + super(""); } @Override @@ -293,11 +303,11 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } static Cut belowValue(C endpoint) { - return new BelowValue(endpoint); + return new BelowValue<>(endpoint); } private static final class BelowValue extends Cut { @@ -326,24 +336,22 @@ Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain) { case CLOSED: return this; case OPEN: - @NullableDecl C previous = domain.previous(endpoint); - return (previous == null) ? Cut.belowAll() : new AboveValue(previous); - default: - throw new AssertionError(); + C previous = domain.previous(endpoint); + return (previous == null) ? Cut.belowAll() : new AboveValue<>(previous); } + throw new AssertionError(); } @Override Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain) { switch (boundType) { case CLOSED: - @NullableDecl C previous = domain.previous(endpoint); - return (previous == null) ? Cut.aboveAll() : new AboveValue(previous); + C previous = domain.previous(endpoint); + return (previous == null) ? Cut.aboveAll() : new AboveValue<>(previous); case OPEN: return this; - default: - throw new AssertionError(); } + throw new AssertionError(); } @Override @@ -362,7 +370,7 @@ C leastValueAbove(DiscreteDomain domain) { } @Override - C greatestValueBelow(DiscreteDomain domain) { + @Nullable C greatestValueBelow(DiscreteDomain domain) { return domain.previous(endpoint); } @@ -376,11 +384,11 @@ public String toString() { return "\\" + endpoint + "/"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } static Cut aboveValue(C endpoint) { - return new AboveValue(endpoint); + return new AboveValue<>(endpoint); } private static final class AboveValue extends Cut { @@ -409,24 +417,22 @@ Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain) { case OPEN: return this; case CLOSED: - @NullableDecl C next = domain.next(endpoint); - return (next == null) ? Cut.belowAll() : belowValue(next); - default: - throw new AssertionError(); + C next = domain.next(endpoint); + return (next == null) ? Cut.belowAll() : belowValue(next); } + throw new AssertionError(); } @Override Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain) { switch (boundType) { case OPEN: - @NullableDecl C next = domain.next(endpoint); - return (next == null) ? Cut.aboveAll() : belowValue(next); + C next = domain.next(endpoint); + return (next == null) ? Cut.aboveAll() : belowValue(next); case CLOSED: return this; - default: - throw new AssertionError(); } + throw new AssertionError(); } @Override @@ -440,7 +446,7 @@ void describeAsUpperBound(StringBuilder sb) { } @Override - C leastValueAbove(DiscreteDomain domain) { + @Nullable C leastValueAbove(DiscreteDomain domain) { return domain.next(endpoint); } @@ -452,7 +458,7 @@ C greatestValueBelow(DiscreteDomain domain) { @Override Cut canonical(DiscreteDomain domain) { C next = leastValueAbove(domain); - return (next != null) ? belowValue(next) : Cut.aboveAll(); + return (next != null) ? belowValue(next) : Cut.aboveAll(); } @Override @@ -465,6 +471,6 @@ public String toString() { return "/" + endpoint + "\\"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/collect/DenseImmutableTable.java b/android/guava/src/com/google/common/collect/DenseImmutableTable.java index 4e28c899ae4a..17307ef14030 100644 --- a/android/guava/src/com/google/common/collect/DenseImmutableTable.java +++ b/android/guava/src/com/google/common/collect/DenseImmutableTable.java @@ -14,12 +14,17 @@ package com.google.common.collect; +import static com.google.common.collect.Maps.immutableEntry; +import static java.util.Objects.requireNonNull; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMap.IteratorBasedImmutableMap; import com.google.errorprone.annotations.Immutable; import com.google.j2objc.annotations.WeakOuter; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** A {@code RegularImmutableTable} optimized for dense data. */ @GwtCompatible @@ -37,7 +42,7 @@ final class DenseImmutableTable extends RegularImmutableTable private final int[] columnCounts; @SuppressWarnings("Immutable") // We don't modify this after construction. - private final V[][] values; + private final @Nullable V[][] values; // For each cell in iteration order, the index of that cell's row key in the row key list. @SuppressWarnings("Immutable") // We don't modify this after construction. @@ -52,7 +57,7 @@ final class DenseImmutableTable extends RegularImmutableTable ImmutableSet rowSpace, ImmutableSet columnSpace) { @SuppressWarnings("unchecked") - V[][] array = (V[][]) new Object[rowSpace.size()][columnSpace.size()]; + @Nullable V[][] array = (@Nullable V[][]) new Object[rowSpace.size()][columnSpace.size()]; this.values = array; this.rowKeyToIndex = Maps.indexMap(rowSpace); this.columnKeyToIndex = Maps.indexMap(columnSpace); @@ -64,8 +69,9 @@ final class DenseImmutableTable extends RegularImmutableTable Cell cell = cellList.get(i); R rowKey = cell.getRowKey(); C columnKey = cell.getColumnKey(); - int rowIndex = rowKeyToIndex.get(rowKey); - int columnIndex = columnKeyToIndex.get(columnKey); + // The requireNonNull calls are safe because we construct the indexes with indexMap. + int rowIndex = requireNonNull(rowKeyToIndex.get(rowKey)); + int columnIndex = requireNonNull(columnKeyToIndex.get(columnKey)); V existingValue = values[rowIndex][columnIndex]; checkNoDuplicate(rowKey, columnKey, existingValue, cell.getValue()); values[rowIndex][columnIndex] = cell.getValue(); @@ -99,8 +105,7 @@ K getKey(int index) { return keyToIndex().keySet().asList().get(index); } - @NullableDecl - abstract V getValue(int keyIndex); + abstract @Nullable V getValue(int keyIndex); @Override ImmutableSet createKeySet() { @@ -113,7 +118,7 @@ public int size() { } @Override - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { Integer keyIndex = keyToIndex().get(key); return (keyIndex == null) ? null : getValue(keyIndex); } @@ -125,17 +130,26 @@ UnmodifiableIterator> entryIterator() { private final int maxIndex = keyToIndex().size(); @Override - protected Entry computeNext() { + protected @Nullable Entry computeNext() { for (index++; index < maxIndex; index++) { V value = getValue(index); if (value != null) { - return Maps.immutableEntry(getKey(index), value); + return immutableEntry(getKey(index), value); } } return endOfData(); } }; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @J2ktIncompatible // serialization + @Override + @GwtIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } private final class Row extends ImmutableArrayMap { @@ -152,7 +166,7 @@ ImmutableMap keyToIndex() { } @Override - V getValue(int keyIndex) { + @Nullable V getValue(int keyIndex) { return values[rowIndex][keyIndex]; } @@ -160,6 +174,15 @@ V getValue(int keyIndex) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } private final class Column extends ImmutableArrayMap { @@ -176,7 +199,7 @@ ImmutableMap keyToIndex() { } @Override - V getValue(int keyIndex) { + @Nullable V getValue(int keyIndex) { return values[keyIndex][columnIndex]; } @@ -184,6 +207,15 @@ V getValue(int keyIndex) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } @WeakOuter @@ -206,6 +238,15 @@ ImmutableMap getValue(int keyIndex) { boolean isPartialView() { return false; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } @WeakOuter @@ -228,24 +269,31 @@ ImmutableMap getValue(int keyIndex) { boolean isPartialView() { return false; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } @Override public ImmutableMap> columnMap() { // Casts without copying. - ImmutableMap> columnMap = this.columnMap; - return ImmutableMap.>copyOf(columnMap); + return ImmutableMap.copyOf(columnMap); } @Override public ImmutableMap> rowMap() { // Casts without copying. - ImmutableMap> rowMap = this.rowMap; - return ImmutableMap.>copyOf(rowMap); + return ImmutableMap.copyOf(rowMap); } @Override - public V get(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V get(@Nullable Object rowKey, @Nullable Object columnKey) { Integer rowIndex = rowKeyToIndex.get(rowKey); Integer columnIndex = columnKeyToIndex.get(columnKey); return ((rowIndex == null) || (columnIndex == null)) ? null : values[rowIndex][columnIndex]; @@ -262,17 +310,21 @@ Cell getCell(int index) { int columnIndex = cellColumnIndices[index]; R rowKey = rowKeySet().asList().get(rowIndex); C columnKey = columnKeySet().asList().get(columnIndex); - V value = values[rowIndex][columnIndex]; + // requireNonNull is safe because we use indexes that were populated by the constructor. + V value = requireNonNull(values[rowIndex][columnIndex]); return cellOf(rowKey, columnKey, value); } @Override V getValue(int index) { - return values[cellRowIndices[index]][cellColumnIndices[index]]; + // requireNonNull is safe because we use indexes that were populated by the constructor. + return requireNonNull(values[cellRowIndices[index]][cellColumnIndices[index]]); } @Override - SerializedForm createSerializedForm() { + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { return SerializedForm.create(this, cellRowIndices, cellColumnIndices); } } diff --git a/android/guava/src/com/google/common/collect/DescendingImmutableSortedMultiset.java b/android/guava/src/com/google/common/collect/DescendingImmutableSortedMultiset.java index 189acdb5fd26..a5fec75bce63 100644 --- a/android/guava/src/com/google/common/collect/DescendingImmutableSortedMultiset.java +++ b/android/guava/src/com/google/common/collect/DescendingImmutableSortedMultiset.java @@ -15,7 +15,8 @@ package com.google.common.collect; import com.google.common.annotations.GwtIncompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * A descending wrapper around an {@code ImmutableSortedMultiset} @@ -32,17 +33,17 @@ final class DescendingImmutableSortedMultiset extends ImmutableSortedMultiset } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { return forward.count(element); } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return forward.lastEntry(); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return forward.firstEntry(); } @@ -80,4 +81,12 @@ public ImmutableSortedMultiset tailMultiset(E lowerBound, BoundType boundType boolean isPartialView() { return forward.isPartialView(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/DescendingImmutableSortedSet.java b/android/guava/src/com/google/common/collect/DescendingImmutableSortedSet.java index 64e3e89acb07..4a13415c540b 100644 --- a/android/guava/src/com/google/common/collect/DescendingImmutableSortedSet.java +++ b/android/guava/src/com/google/common/collect/DescendingImmutableSortedSet.java @@ -17,7 +17,8 @@ package com.google.common.collect; import com.google.common.annotations.GwtIncompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * Skeletal implementation of {@link ImmutableSortedSet#descendingSet()}. @@ -34,7 +35,7 @@ final class DescendingImmutableSortedSet extends ImmutableSortedSet { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return forward.contains(object); } @@ -83,27 +84,27 @@ ImmutableSortedSet createDescendingSet() { } @Override - public E lower(E element) { + public @Nullable E lower(E element) { return forward.higher(element); } @Override - public E floor(E element) { + public @Nullable E floor(E element) { return forward.ceiling(element); } @Override - public E ceiling(E element) { + public @Nullable E ceiling(E element) { return forward.floor(element); } @Override - public E higher(E element) { + public @Nullable E higher(E element) { return forward.lower(element); } @Override - int indexOf(@NullableDecl Object target) { + int indexOf(@Nullable Object target) { int index = forward.indexOf(target); if (index == -1) { return index; @@ -116,4 +117,12 @@ int indexOf(@NullableDecl Object target) { boolean isPartialView() { return forward.isPartialView(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/DescendingMultiset.java b/android/guava/src/com/google/common/collect/DescendingMultiset.java index 72a88af8ad2b..93dda645f2f6 100644 --- a/android/guava/src/com/google/common/collect/DescendingMultiset.java +++ b/android/guava/src/com/google/common/collect/DescendingMultiset.java @@ -17,12 +17,13 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.WeakOuter; import java.util.Comparator; import java.util.Iterator; import java.util.NavigableSet; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A skeleton implementation of a descending multiset. Only needs {@code forwardMultiset()} and @@ -30,57 +31,62 @@ * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) -abstract class DescendingMultiset extends ForwardingMultiset implements SortedMultiset { +@GwtCompatible +abstract class DescendingMultiset extends ForwardingMultiset + implements SortedMultiset { abstract SortedMultiset forwardMultiset(); - @NullableDecl private transient Comparator comparator; + @LazyInit private transient @Nullable Comparator comparator; @Override public Comparator comparator() { Comparator result = comparator; if (result == null) { - return comparator = Ordering.from(forwardMultiset().comparator()).reverse(); + result = Ordering.from(forwardMultiset().comparator()).reverse(); + comparator = result; } return result; } - @NullableDecl private transient NavigableSet elementSet; + @LazyInit private transient @Nullable NavigableSet elementSet; @Override public NavigableSet elementSet() { NavigableSet result = elementSet; if (result == null) { - return elementSet = new SortedMultisets.NavigableElementSet(this); + return elementSet = new SortedMultisets.NavigableElementSet<>(this); } return result; } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return forwardMultiset().pollLastEntry(); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return forwardMultiset().pollFirstEntry(); } @Override - public SortedMultiset headMultiset(E toElement, BoundType boundType) { + public SortedMultiset headMultiset(@ParametricNullness E toElement, BoundType boundType) { return forwardMultiset().tailMultiset(toElement, boundType).descendingMultiset(); } @Override public SortedMultiset subMultiset( - E fromElement, BoundType fromBoundType, E toElement, BoundType toBoundType) { + @ParametricNullness E fromElement, + BoundType fromBoundType, + @ParametricNullness E toElement, + BoundType toBoundType) { return forwardMultiset() .subMultiset(toElement, toBoundType, fromElement, fromBoundType) .descendingMultiset(); } @Override - public SortedMultiset tailMultiset(E fromElement, BoundType boundType) { + public SortedMultiset tailMultiset(@ParametricNullness E fromElement, BoundType boundType) { return forwardMultiset().headMultiset(fromElement, boundType).descendingMultiset(); } @@ -95,18 +101,18 @@ public SortedMultiset descendingMultiset() { } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return forwardMultiset().lastEntry(); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return forwardMultiset().firstEntry(); } abstract Iterator> entryIterator(); - @NullableDecl private transient Set> entrySet; + @LazyInit private transient @Nullable Set> entrySet; @Override public Set> entrySet() { @@ -116,7 +122,7 @@ public Set> entrySet() { Set> createEntrySet() { @WeakOuter - class EntrySetImpl extends Multisets.EntrySet { + final class EntrySetImpl extends Multisets.EntrySet { @Override Multiset multiset() { return DescendingMultiset.this; @@ -141,12 +147,13 @@ public Iterator iterator() { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { return standardToArray(); } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return standardToArray(array); } diff --git a/android/guava/src/com/google/common/collect/DiscreteDomain.java b/android/guava/src/com/google/common/collect/DiscreteDomain.java index 3777a6d4bbc9..13e7be404726 100644 --- a/android/guava/src/com/google/common/collect/DiscreteDomain.java +++ b/android/guava/src/com/google/common/collect/DiscreteDomain.java @@ -20,11 +20,14 @@ import static com.google.common.collect.CollectPreconditions.checkNonnegative; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; import java.math.BigInteger; import java.util.NoSuchElementException; +import org.jspecify.annotations.Nullable; /** * A descriptor for a discrete {@code Comparable} domain such as all {@link Integer} @@ -36,18 +39,22 @@ * represent partial domains such as "prime integers" or "strings of length 5." * *

    See the Guava User Guide section on {@code + * "https://github.com/google/guava/wiki/RangesExplained#discrete-domains">{@code * DiscreteDomain}. * * @author Kevin Bourrillion * @since 10.0 */ +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 @GwtCompatible public abstract class DiscreteDomain { /** * Returns the discrete domain for values of type {@code Integer}. * + *

    This method always returns the same object. That object is serializable; deserializing it + * results in the same object too. + * * @since 14.0 (since 10.0 as {@code DiscreteDomains.integers()}) */ public static DiscreteDomain integers() { @@ -62,13 +69,13 @@ private static final class IntegerDomain extends DiscreteDomain impleme } @Override - public Integer next(Integer value) { + public @Nullable Integer next(Integer value) { int i = value; return (i == Integer.MAX_VALUE) ? null : i + 1; } @Override - public Integer previous(Integer value) { + public @Nullable Integer previous(Integer value) { int i = value; return (i == Integer.MIN_VALUE) ? null : i - 1; } @@ -103,12 +110,15 @@ public String toString() { return "DiscreteDomain.integers()"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns the discrete domain for values of type {@code Long}. * + *

    This method always returns the same object. That object is serializable; deserializing it + * results in the same object too. + * * @since 14.0 (since 10.0 as {@code DiscreteDomains.longs()}) */ public static DiscreteDomain longs() { @@ -123,13 +133,13 @@ private static final class LongDomain extends DiscreteDomain implements Se } @Override - public Long next(Long value) { + public @Nullable Long next(Long value) { long l = value; return (l == Long.MAX_VALUE) ? null : l + 1; } @Override - public Long previous(Long value) { + public @Nullable Long previous(Long value) { long l = value; return (l == Long.MIN_VALUE) ? null : l - 1; } @@ -175,12 +185,15 @@ public String toString() { return "DiscreteDomain.longs()"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns the discrete domain for values of type {@code BigInteger}. * + *

    This method always returns the same object. That object is serializable; deserializing it + * results in the same object too. + * * @since 15.0 */ public static DiscreteDomain bigIntegers() { @@ -228,7 +241,7 @@ public String toString() { return "DiscreteDomain.bigIntegers()"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } final boolean supportsFastOffset; @@ -248,11 +261,16 @@ private DiscreteDomain(boolean supportsFastOffset) { * #next} on {@code origin} {@code distance} times. */ C offset(C origin, long distance) { + C current = origin; checkNonnegative(distance, "distance"); for (long i = 0; i < distance; i++) { - origin = next(origin); + current = next(current); + if (current == null) { + throw new IllegalArgumentException( + "overflowed computing offset(" + origin + ", " + distance + ")"); + } } - return origin; + return current; } /** @@ -263,7 +281,7 @@ C offset(C origin, long distance) { * @return the least value greater than {@code value}, or {@code null} if {@code value} is {@code * maxValue()} */ - public abstract C next(C value); + public abstract @Nullable C next(C value); /** * Returns the unique greatest value of type {@code C} that is less than {@code value}, or {@code @@ -273,7 +291,7 @@ C offset(C origin, long distance) { * @return the greatest value less than {@code value}, or {@code null} if {@code value} is {@code * minValue()} */ - public abstract C previous(C value); + public abstract @Nullable C previous(C value); /** * Returns a signed value indicating how many nested invocations of {@link #next} (if positive) or diff --git a/android/guava/src/com/google/common/collect/EmptyContiguousSet.java b/android/guava/src/com/google/common/collect/EmptyContiguousSet.java index 8043ef7880da..1f0a7fc73652 100644 --- a/android/guava/src/com/google/common/collect/EmptyContiguousSet.java +++ b/android/guava/src/com/google/common/collect/EmptyContiguousSet.java @@ -13,19 +13,24 @@ */ package com.google.common.collect; +import static com.google.common.collect.Iterators.emptyIterator; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.NoSuchElementException; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An empty contiguous set. * * @author Gregory Kick */ -@GwtCompatible(emulated = true) +@GwtCompatible @SuppressWarnings("rawtypes") // allow ungenerified Comparable types final class EmptyContiguousSet extends ContiguousSet { EmptyContiguousSet(DiscreteDomain domain) { @@ -79,25 +84,25 @@ ContiguousSet tailSetImpl(C fromElement, boolean fromInclusive) { } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { return false; } @GwtIncompatible // not used by GWT emulation @Override - int indexOf(Object target) { + int indexOf(@Nullable Object target) { return -1; } @Override public UnmodifiableIterator iterator() { - return Iterators.emptyIterator(); + return emptyIterator(); } @GwtIncompatible // NavigableSet @Override public UnmodifiableIterator descendingIterator() { - return Iterators.emptyIterator(); + return emptyIterator(); } @Override @@ -121,7 +126,7 @@ public String toString() { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof Set) { Set that = (Set) object; return that.isEmpty(); @@ -140,7 +145,8 @@ public int hashCode() { return 0; } - @GwtIncompatible // serialization + @GwtIncompatible + @J2ktIncompatible private static final class SerializedForm implements Serializable { private final DiscreteDomain domain; @@ -149,21 +155,28 @@ private SerializedForm(DiscreteDomain domain) { } private Object readResolve() { - return new EmptyContiguousSet(domain); + return new EmptyContiguousSet<>(domain); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - @GwtIncompatible // serialization - @Override + @GwtIncompatible + @J2ktIncompatible + @Override Object writeReplace() { - return new SerializedForm(domain); + return new SerializedForm<>(domain); + } + + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); } @GwtIncompatible // NavigableSet @Override ImmutableSortedSet createDescendingSet() { - return ImmutableSortedSet.emptySet(Ordering.natural().reverse()); + return ImmutableSortedSet.emptySet(Ordering.natural().reverse()); } } diff --git a/android/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java b/android/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java index 9b167fb38772..2c112b37fb74 100644 --- a/android/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java +++ b/android/guava/src/com/google/common/collect/EmptyImmutableListMultimap.java @@ -17,23 +17,37 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.util.Collection; /** * Implementation of {@link ImmutableListMultimap} with no entries. * * @author Jared Levy */ -@GwtCompatible(serializable = true) -class EmptyImmutableListMultimap extends ImmutableListMultimap { +@GwtCompatible +final class EmptyImmutableListMultimap extends ImmutableListMultimap { static final EmptyImmutableListMultimap INSTANCE = new EmptyImmutableListMultimap(); private EmptyImmutableListMultimap() { - super(ImmutableMap.>of(), 0); + super(ImmutableMap.of(), 0); + } + + /* + * TODO(b/242884182): Figure out why this helps produce the same class file when we compile most + * of common.collect a second time with the results of the first compilation on the classpath. Or + * just back this out once we stop doing that (which we'll do after our internal GWT setup + * changes). + */ + @Override + public ImmutableMap> asMap() { + return super.asMap(); } private Object readResolve() { return INSTANCE; // preserve singleton property } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java b/android/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java index ec2ce2e192b8..dd772f835f3a 100644 --- a/android/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java +++ b/android/guava/src/com/google/common/collect/EmptyImmutableSetMultimap.java @@ -17,23 +17,37 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.util.Collection; /** * Implementation of {@link ImmutableListMultimap} with no entries. * * @author Mike Ward */ -@GwtCompatible(serializable = true) -class EmptyImmutableSetMultimap extends ImmutableSetMultimap { +@GwtCompatible +final class EmptyImmutableSetMultimap extends ImmutableSetMultimap { static final EmptyImmutableSetMultimap INSTANCE = new EmptyImmutableSetMultimap(); private EmptyImmutableSetMultimap() { - super(ImmutableMap.>of(), 0, null); + super(ImmutableMap.of(), 0, null); + } + + /* + * TODO(b/242884182): Figure out why this helps produce the same class file when we compile most + * of common.collect a second time with the results of the first compilation on the classpath. Or + * just back this out once we stop doing that (which we'll do after our internal GWT setup + * changes). + */ + @Override + public ImmutableMap> asMap() { + return super.asMap(); } private Object readResolve() { return INSTANCE; // preserve singleton property } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/EnumBiMap.java b/android/guava/src/com/google/common/collect/EnumBiMap.java index f72b8b96f706..06ba92ee3d06 100644 --- a/android/guava/src/com/google/common/collect/EnumBiMap.java +++ b/android/guava/src/com/google/common/collect/EnumBiMap.java @@ -18,9 +18,12 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Platform.getDeclaringClassOrObjectForJ2cl; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -32,15 +35,30 @@ * An {@code EnumBiMap} and its inverse are both serializable. * *

    See the Guava User Guide article on {@code BiMap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap">{@code BiMap}. * * @author Mike Bostock * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible +@J2ktIncompatible public final class EnumBiMap, V extends Enum> extends AbstractBiMap { - private transient Class keyType; - private transient Class valueType; + /* + * J2CL's EnumMap does not need the Class instance, so we can use Object.class instead. (Or we + * could use null, but that messes with our nullness checking, including under J2KT. We could + * probably work around it by changing how we annotate the J2CL EnumMap, but that's probably more + * trouble than just using Object.class.) + * + * Then we declare the getters for these fields as @GwtIncompatible so that no one can try to use + * them under J2CL—or, as an unfortunate side effect, under GWT. We do still give the fields + * themselves their proper values under GWT, since GWT's EnumMap does need the Class instance. + * + * Note that sometimes these fields *do* have correct values under J2CL: They will if the caller + * calls `create(Foo.class)`, rather than `create(map)`. That's fine; we just shouldn't rely on + * it. + */ + transient Class keyTypeOrObjectUnderJ2cl; + transient Class valueTypeOrObjectUnderJ2cl; /** * Returns a new, empty {@code EnumBiMap} using the specified key and value types. @@ -63,44 +81,48 @@ public static , V extends Enum> EnumBiMap create( * mappings */ public static , V extends Enum> EnumBiMap create(Map map) { - EnumBiMap bimap = create(inferKeyType(map), inferValueType(map)); + EnumBiMap bimap = + create(inferKeyTypeOrObjectUnderJ2cl(map), inferValueTypeOrObjectUnderJ2cl(map)); bimap.putAll(map); return bimap; } - private EnumBiMap(Class keyType, Class valueType) { - super(new EnumMap(keyType), new EnumMap(valueType)); - this.keyType = keyType; - this.valueType = valueType; + private EnumBiMap(Class keyTypeOrObjectUnderJ2cl, Class valueTypeOrObjectUnderJ2cl) { + super( + new EnumMap(keyTypeOrObjectUnderJ2cl), new EnumMap(valueTypeOrObjectUnderJ2cl)); + this.keyTypeOrObjectUnderJ2cl = keyTypeOrObjectUnderJ2cl; + this.valueTypeOrObjectUnderJ2cl = valueTypeOrObjectUnderJ2cl; } - static > Class inferKeyType(Map map) { + static > Class inferKeyTypeOrObjectUnderJ2cl(Map map) { if (map instanceof EnumBiMap) { - return ((EnumBiMap) map).keyType(); + return ((EnumBiMap) map).keyTypeOrObjectUnderJ2cl; } if (map instanceof EnumHashBiMap) { - return ((EnumHashBiMap) map).keyType(); + return ((EnumHashBiMap) map).keyTypeOrObjectUnderJ2cl; } checkArgument(!map.isEmpty()); - return map.keySet().iterator().next().getDeclaringClass(); + return getDeclaringClassOrObjectForJ2cl(map.keySet().iterator().next()); } - private static > Class inferValueType(Map map) { + private static > Class inferValueTypeOrObjectUnderJ2cl(Map map) { if (map instanceof EnumBiMap) { - return ((EnumBiMap) map).valueType; + return ((EnumBiMap) map).valueTypeOrObjectUnderJ2cl; } checkArgument(!map.isEmpty()); - return map.values().iterator().next().getDeclaringClass(); + return getDeclaringClassOrObjectForJ2cl(map.values().iterator().next()); } /** Returns the associated key type. */ + @GwtIncompatible public Class keyType() { - return keyType; + return keyTypeOrObjectUnderJ2cl; } /** Returns the associated value type. */ + @GwtIncompatible public Class valueType() { - return valueType; + return valueTypeOrObjectUnderJ2cl; } @Override @@ -120,8 +142,8 @@ V checkValue(V value) { @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); - stream.writeObject(keyType); - stream.writeObject(valueType); + stream.writeObject(keyTypeOrObjectUnderJ2cl); + stream.writeObject(valueTypeOrObjectUnderJ2cl); Serialization.writeMap(this, stream); } @@ -129,12 +151,12 @@ private void writeObject(ObjectOutputStream stream) throws IOException { @GwtIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - keyType = (Class) stream.readObject(); - valueType = (Class) stream.readObject(); - setDelegates(new EnumMap(keyType), new EnumMap(valueType)); + keyTypeOrObjectUnderJ2cl = (Class) requireNonNull(stream.readObject()); + valueTypeOrObjectUnderJ2cl = (Class) requireNonNull(stream.readObject()); + setDelegates( + new EnumMap<>(keyTypeOrObjectUnderJ2cl), new EnumMap<>(valueTypeOrObjectUnderJ2cl)); Serialization.populateMap(this, stream); } - @GwtIncompatible // not needed in emulated source. - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/EnumHashBiMap.java b/android/guava/src/com/google/common/collect/EnumHashBiMap.java index 0a7e52e12756..de3242e89afa 100644 --- a/android/guava/src/com/google/common/collect/EnumHashBiMap.java +++ b/android/guava/src/com/google/common/collect/EnumHashBiMap.java @@ -17,9 +17,11 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.io.ObjectInputStream; @@ -27,7 +29,7 @@ import java.util.EnumMap; import java.util.HashMap; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@code BiMap} backed by an {@code EnumMap} instance for keys-to-values, and a {@code HashMap} @@ -35,21 +37,24 @@ * EnumHashBiMap} and its inverse are both serializable. * *

    See the Guava User Guide article on {@code BiMap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap">{@code BiMap}. * * @author Mike Bostock * @since 2.0 */ -@GwtCompatible(emulated = true) -public final class EnumHashBiMap, V> extends AbstractBiMap { - private transient Class keyType; +@GwtCompatible +@J2ktIncompatible +public final class EnumHashBiMap, V extends @Nullable Object> + extends AbstractBiMap { + transient Class keyTypeOrObjectUnderJ2cl; /** * Returns a new, empty {@code EnumHashBiMap} using the specified key type. * * @param keyType the key type */ - public static , V> EnumHashBiMap create(Class keyType) { + public static , V extends @Nullable Object> EnumHashBiMap create( + Class keyType) { return new EnumHashBiMap<>(keyType); } @@ -63,17 +68,17 @@ public static , V> EnumHashBiMap create(Class keyType * @throws IllegalArgumentException if map is not an {@code EnumBiMap} or an {@code EnumHashBiMap} * instance and contains no mappings */ - public static , V> EnumHashBiMap create(Map map) { - EnumHashBiMap bimap = create(EnumBiMap.inferKeyType(map)); + public static , V extends @Nullable Object> EnumHashBiMap create( + Map map) { + EnumHashBiMap bimap = create(EnumBiMap.inferKeyTypeOrObjectUnderJ2cl(map)); bimap.putAll(map); return bimap; } private EnumHashBiMap(Class keyType) { - super( - new EnumMap(keyType), - Maps.newHashMapWithExpectedSize(keyType.getEnumConstants().length)); - this.keyType = keyType; + super(new EnumMap(keyType), new HashMap()); + // TODO: cpovirk - Pre-size the HashMap based on the number of enum values? + this.keyTypeOrObjectUnderJ2cl = keyType; } // Overriding these 3 methods to show that values may be null (but not keys) @@ -85,19 +90,24 @@ K checkKey(K key) { @CanIgnoreReturnValue @Override - public V put(K key, @NullableDecl V value) { + @SuppressWarnings("RedundantOverride") // b/192446478: RedundantOverride ignores some annotations. + // TODO(b/192446998): Remove this override after tools understand nullness better. + public @Nullable V put(K key, @ParametricNullness V value) { return super.put(key, value); } @CanIgnoreReturnValue @Override - public V forcePut(K key, @NullableDecl V value) { + @SuppressWarnings("RedundantOverride") // b/192446478: RedundantOverride ignores some annotations. + // TODO(b/192446998): Remove this override after tools understand nullness better. + public @Nullable V forcePut(K key, @ParametricNullness V value) { return super.forcePut(key, value); } /** Returns the associated key type. */ + @GwtIncompatible public Class keyType() { - return keyType; + return keyTypeOrObjectUnderJ2cl; } /** @@ -107,7 +117,7 @@ public Class keyType() { @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); - stream.writeObject(keyType); + stream.writeObject(keyTypeOrObjectUnderJ2cl); Serialization.writeMap(this, stream); } @@ -115,12 +125,15 @@ private void writeObject(ObjectOutputStream stream) throws IOException { @GwtIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - keyType = (Class) stream.readObject(); - setDelegates( - new EnumMap(keyType), new HashMap(keyType.getEnumConstants().length * 3 / 2)); + keyTypeOrObjectUnderJ2cl = (Class) requireNonNull(stream.readObject()); + /* + * TODO: cpovirk - Pre-size the HashMap based on the number of enum values? (But *not* based on + * the number of entries in the map, as that makes it easy for hostile inputs to trigger lots of + * allocation—not that any program should be deserializing hostile inputs to begin with!) + */ + setDelegates(new EnumMap<>(keyTypeOrObjectUnderJ2cl), new HashMap<>()); Serialization.populateMap(this, stream); } - @GwtIncompatible // only needed in emulated source. - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/EnumMultiset.java b/android/guava/src/com/google/common/collect/EnumMultiset.java index af0deef21bce..3c702ab1d829 100644 --- a/android/guava/src/com/google/common/collect/EnumMultiset.java +++ b/android/guava/src/com/google/common/collect/EnumMultiset.java @@ -18,9 +18,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; import static com.google.common.collect.CollectPreconditions.checkRemove; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; @@ -30,25 +32,26 @@ import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Multiset implementation specialized for enum elements, supporting all single-element operations * in O(1). * *

    See the Guava User Guide article on {@code - * Multiset}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}. * * @author Jared Levy * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible +@J2ktIncompatible +@SuppressWarnings("EnumOrdinal") // This is one of the low-level utilities where it's suitable. public final class EnumMultiset> extends AbstractMultiset implements Serializable { /** Creates an empty {@code EnumMultiset}. */ public static > EnumMultiset create(Class type) { - return new EnumMultiset(type); + return new EnumMultiset<>(type); } /** @@ -93,7 +96,7 @@ private EnumMultiset(Class type) { this.counts = new int[enumConstants.length]; } - private boolean isActuallyE(@NullableDecl Object o) { + private boolean isActuallyE(@Nullable Object o) { if (o instanceof Enum) { Enum e = (Enum) o; int index = e.ordinal(); @@ -106,7 +109,7 @@ private boolean isActuallyE(@NullableDecl Object o) { * Returns {@code element} cast to {@code E}, if it actually is a nonnull E. Otherwise, throws * either a NullPointerException or a ClassCastException as appropriate. */ - void checkIsE(@NullableDecl Object element) { + private void checkIsE(Object element) { checkNotNull(element); if (!isActuallyE(element)) { throw new ClassCastException("Expected an " + type + " but got " + element); @@ -124,8 +127,9 @@ public int size() { } @Override - public int count(@NullableDecl Object element) { - if (!isActuallyE(element)) { + public int count(@Nullable Object element) { + // isActuallyE checks for null, but we check explicitly to help nullness checkers. + if (element == null || !isActuallyE(element)) { return 0; } Enum e = (Enum) element; @@ -156,8 +160,9 @@ public int add(E element, int occurrences) { // Modification Operations @CanIgnoreReturnValue @Override - public int remove(@NullableDecl Object element, int occurrences) { - if (!isActuallyE(element)) { + public int remove(@Nullable Object element, int occurrences) { + // isActuallyE checks for null, but we check explicitly to help nullness checkers. + if (element == null || !isActuallyE(element)) { return 0; } Enum e = (Enum) element; @@ -258,7 +263,7 @@ E output(int index) { Iterator> entryIterator() { return new Itr>() { @Override - Entry output(final int index) { + Entry output(int index) { return new Multisets.AbstractEntry() { @Override public E getElement() { @@ -294,13 +299,12 @@ private void writeObject(ObjectOutputStream stream) throws IOException { private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); @SuppressWarnings("unchecked") // reading data stored by writeObject - Class localType = (Class) stream.readObject(); + Class localType = (Class) requireNonNull(stream.readObject()); type = localType; enumConstants = type.getEnumConstants(); counts = new int[enumConstants.length]; Serialization.populateMultiset(this, stream); } - @GwtIncompatible // Not needed in emulated source - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/EvictingQueue.java b/android/guava/src/com/google/common/collect/EvictingQueue.java index 37a65f3e0deb..957d10b7eb76 100644 --- a/android/guava/src/com/google/common/collect/EvictingQueue.java +++ b/android/guava/src/com/google/common/collect/EvictingQueue.java @@ -19,8 +19,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; @@ -43,7 +44,6 @@ * @author Kurt Alfred Kluever * @since 15.0 */ -@Beta @GwtCompatible public final class EvictingQueue extends ForwardingQueue implements Serializable { @@ -53,7 +53,7 @@ public final class EvictingQueue extends ForwardingQueue implements Serial private EvictingQueue(int maxSize) { checkArgument(maxSize >= 0, "maxSize (%s) must >= 0", maxSize); - this.delegate = new ArrayDeque(maxSize); + this.delegate = new ArrayDeque<>(maxSize); this.maxSize = maxSize; } @@ -64,7 +64,7 @@ private EvictingQueue(int maxSize) { * queue. */ public static EvictingQueue create(int maxSize) { - return new EvictingQueue(maxSize); + return new EvictingQueue<>(maxSize); } /** @@ -126,17 +126,20 @@ public boolean addAll(Collection collection) { } @Override - public boolean contains(Object object) { - return delegate().contains(checkNotNull(object)); + @J2ktIncompatible // Incompatible return type change. Use inherited implementation + public Object[] toArray() { + /* + * If we could, we'd declare the no-arg `Collection.toArray()` to return "Object[] but elements + * have the same nullness as E." Since we can't, we declare it to return nullable elements, and + * we can override it in our non-null-guaranteeing subtypes to present a better signature to + * their users. + * + * However, the checker *we* use has this special knowledge about `Collection.toArray()` anyway, + * so in our implementation code, we can rely on that. That's why the expression below + * type-checks. + */ + return super.toArray(); } - @Override - @CanIgnoreReturnValue - public boolean remove(Object object) { - return delegate().remove(checkNotNull(object)); - } - - // TODO(kak): Do we want to checkNotNull each element in containsAll, removeAll, and retainAll? - - private static final long serialVersionUID = 0L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0L; } diff --git a/android/guava/src/com/google/common/collect/ExplicitOrdering.java b/android/guava/src/com/google/common/collect/ExplicitOrdering.java index 710c1aea0722..5ac02376f5e6 100644 --- a/android/guava/src/com/google/common/collect/ExplicitOrdering.java +++ b/android/guava/src/com/google/common/collect/ExplicitOrdering.java @@ -17,12 +17,14 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** An ordering that compares objects according to a given order. */ -@GwtCompatible(serializable = true) +@GwtCompatible final class ExplicitOrdering extends Ordering implements Serializable { final ImmutableMap rankMap; @@ -48,7 +50,7 @@ private int rank(T value) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof ExplicitOrdering) { ExplicitOrdering that = (ExplicitOrdering) object; return this.rankMap.equals(that.rankMap); @@ -66,5 +68,5 @@ public String toString() { return "Ordering.explicit(" + rankMap.keySet() + ")"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/FilteredEntryMultimap.java b/android/guava/src/com/google/common/collect/FilteredEntryMultimap.java index 5860d885c625..406e65f67232 100644 --- a/android/guava/src/com/google/common/collect/FilteredEntryMultimap.java +++ b/android/guava/src/com/google/common/collect/FilteredEntryMultimap.java @@ -20,20 +20,26 @@ import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.Maps.immutableEntry; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableSet; import com.google.common.annotations.GwtCompatible; import com.google.common.base.MoreObjects; import com.google.common.base.Predicate; import com.google.common.collect.Maps.ViewCachingAbstractMap; import com.google.j2objc.annotations.WeakOuter; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link Multimaps#filterEntries(Multimap, Predicate)}. @@ -42,7 +48,8 @@ * @author Louis Wasserman */ @GwtCompatible -class FilteredEntryMultimap extends AbstractMultimap implements FilteredMultimap { +class FilteredEntryMultimap + extends AbstractMultimap implements FilteredMultimap { final Multimap unfiltered; final Predicate> predicate; @@ -66,24 +73,24 @@ public int size() { return entries().size(); } - private boolean satisfies(K key, V value) { - return predicate.apply(Maps.immutableEntry(key, value)); + private boolean satisfies(@ParametricNullness K key, @ParametricNullness V value) { + return predicate.apply(immutableEntry(key, value)); } final class ValuePredicate implements Predicate { - private final K key; + @ParametricNullness private final K key; - ValuePredicate(K key) { + ValuePredicate(@ParametricNullness K key) { this.key = key; } @Override - public boolean apply(@NullableDecl V value) { + public boolean apply(@ParametricNullness V value) { return satisfies(key, value); } } - static Collection filterCollection( + static Collection filterCollection( Collection collection, Predicate predicate) { if (collection instanceof Set) { return Sets.filter((Set) collection, predicate); @@ -93,20 +100,19 @@ static Collection filterCollection( } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return asMap().get(key) != null; } @Override - public Collection removeAll(@NullableDecl Object key) { + public Collection removeAll(@Nullable Object key) { return MoreObjects.firstNonNull(asMap().remove(key), unmodifiableEmptyCollection()); } + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types Collection unmodifiableEmptyCollection() { // These return false, rather than throwing a UOE, on remove calls. - return (unfiltered instanceof SetMultimap) - ? Collections.emptySet() - : Collections.emptyList(); + return (unfiltered instanceof SetMultimap) ? emptySet() : emptyList(); } @Override @@ -115,7 +121,7 @@ public void clear() { } @Override - public Collection get(final K key) { + public Collection get(@ParametricNullness K key) { return filterCollection(unfiltered.get(key), new ValuePredicate(key)); } @@ -151,7 +157,8 @@ boolean removeEntriesIf(Predicate>> predicate) { Entry> entry = entryIterator.next(); K key = entry.getKey(); Collection collection = filterCollection(entry.getValue(), new ValuePredicate(key)); - if (!collection.isEmpty() && predicate.apply(Maps.immutableEntry(key, collection))) { + if (!collection.isEmpty() + && predicate.apply(Maps.>immutableEntry(key, collection))) { if (collection.size() == entry.getValue().size()) { entryIterator.remove(); } else { @@ -164,9 +171,9 @@ boolean removeEntriesIf(Predicate>> predicate) { } @WeakOuter - class AsMap extends ViewCachingAbstractMap> { + private final class AsMap extends ViewCachingAbstractMap> { @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } @@ -176,7 +183,7 @@ public void clear() { } @Override - public Collection get(@NullableDecl Object key) { + public @Nullable Collection get(@Nullable Object key) { Collection result = unfiltered.asMap().get(key); if (result == null) { return null; @@ -188,14 +195,14 @@ public Collection get(@NullableDecl Object key) { } @Override - public Collection remove(@NullableDecl Object key) { + public @Nullable Collection remove(@Nullable Object key) { Collection collection = unfiltered.asMap().get(key); if (collection == null) { return null; } @SuppressWarnings("unchecked") // it's definitely equal to a K K k = (K) key; - List result = Lists.newArrayList(); + List result = new ArrayList<>(); Iterator itr = collection.iterator(); while (itr.hasNext()) { V v = itr.next(); @@ -207,32 +214,32 @@ public Collection remove(@NullableDecl Object key) { if (result.isEmpty()) { return null; } else if (unfiltered instanceof SetMultimap) { - return Collections.unmodifiableSet(Sets.newLinkedHashSet(result)); + return unmodifiableSet(new LinkedHashSet<>(result)); } else { - return Collections.unmodifiableList(result); + return unmodifiableList(result); } } @Override Set createKeySet() { @WeakOuter - class KeySetImpl extends Maps.KeySet> { + final class KeySetImpl extends Maps.KeySet> { KeySetImpl() { super(AsMap.this); } @Override public boolean removeAll(Collection c) { - return removeEntriesIf(Maps.keyPredicateOnEntries(in(c))); + return removeEntriesIf(Maps.keyPredicateOnEntries(in(c))); } @Override public boolean retainAll(Collection c) { - return removeEntriesIf(Maps.keyPredicateOnEntries(not(in(c)))); + return removeEntriesIf(Maps.keyPredicateOnEntries(not(in(c)))); } @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { return AsMap.this.remove(o) != null; } } @@ -242,7 +249,7 @@ public boolean remove(@NullableDecl Object o) { @Override Set>> createEntrySet() { @WeakOuter - class EntrySetImpl extends Maps.EntrySet> { + final class EntrySetImpl extends Maps.EntrySet> { @Override Map> map() { return AsMap.this; @@ -255,14 +262,14 @@ public Iterator>> iterator() { unfiltered.asMap().entrySet().iterator(); @Override - protected Entry> computeNext() { + protected @Nullable Entry> computeNext() { while (backingIterator.hasNext()) { Entry> entry = backingIterator.next(); K key = entry.getKey(); Collection collection = filterCollection(entry.getValue(), new ValuePredicate(key)); if (!collection.isEmpty()) { - return Maps.immutableEntry(key, collection); + return immutableEntry(key, collection); } } return endOfData(); @@ -291,13 +298,18 @@ public int size() { @Override Collection> createValues() { @WeakOuter - class ValuesImpl extends Maps.Values> { + final class ValuesImpl extends Maps.Values> { ValuesImpl() { super(AsMap.this); } @Override - public boolean remove(@NullableDecl Object o) { + /* + * For discussion of equality in Multimap value collections, see the suppression for + * UndefinedEquals in AbstractMapBasedMultimap. + */ + @SuppressWarnings("UndefinedEquals") + public boolean remove(@Nullable Object o) { if (o instanceof Collection) { Collection c = (Collection) o; Iterator>> entryIterator = @@ -322,12 +334,12 @@ public boolean remove(@NullableDecl Object o) { @Override public boolean removeAll(Collection c) { - return removeEntriesIf(Maps.>valuePredicateOnEntries(in(c))); + return removeEntriesIf(Maps.valuePredicateOnEntries(in(c))); } @Override public boolean retainAll(Collection c) { - return removeEntriesIf(Maps.>valuePredicateOnEntries(not(in(c)))); + return removeEntriesIf(Maps.valuePredicateOnEntries(not(in(c)))); } } return new ValuesImpl(); @@ -340,13 +352,13 @@ Multiset createKeys() { } @WeakOuter - class Keys extends Multimaps.Keys { + final class Keys extends Multimaps.Keys { Keys() { super(FilteredEntryMultimap.this); } @Override - public int remove(@NullableDecl Object key, int occurrences) { + public int remove(@Nullable Object key, int occurrences) { checkNonnegative(occurrences, "occurrences"); if (occurrences == 0) { return count(key); @@ -390,15 +402,11 @@ public int size() { return FilteredEntryMultimap.this.keySet().size(); } - private boolean removeEntriesIf(final Predicate> predicate) { + private boolean removeEntriesIf(Predicate> predicate) { return FilteredEntryMultimap.this.removeEntriesIf( - new Predicate>>() { - @Override - public boolean apply(Map.Entry> entry) { - return predicate.apply( - Multisets.immutableEntry(entry.getKey(), entry.getValue().size())); - } - }); + (Map.Entry> entry) -> + predicate.apply( + Multisets.immutableEntry(entry.getKey(), entry.getValue().size()))); } @Override diff --git a/android/guava/src/com/google/common/collect/FilteredEntrySetMultimap.java b/android/guava/src/com/google/common/collect/FilteredEntrySetMultimap.java index 94740a4cf1a6..c01894aad5ab 100644 --- a/android/guava/src/com/google/common/collect/FilteredEntrySetMultimap.java +++ b/android/guava/src/com/google/common/collect/FilteredEntrySetMultimap.java @@ -20,6 +20,7 @@ import com.google.common.base.Predicate; import java.util.Map.Entry; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link Multimaps#filterEntries(SetMultimap, Predicate)}. @@ -27,8 +28,8 @@ * @author Louis Wasserman */ @GwtCompatible -final class FilteredEntrySetMultimap extends FilteredEntryMultimap - implements FilteredSetMultimap { +final class FilteredEntrySetMultimap + extends FilteredEntryMultimap implements FilteredSetMultimap { FilteredEntrySetMultimap(SetMultimap unfiltered, Predicate> predicate) { super(unfiltered, predicate); @@ -40,17 +41,17 @@ public SetMultimap unfiltered() { } @Override - public Set get(K key) { + public Set get(@ParametricNullness K key) { return (Set) super.get(key); } @Override - public Set removeAll(Object key) { + public Set removeAll(@Nullable Object key) { return (Set) super.removeAll(key); } @Override - public Set replaceValues(K key, Iterable values) { + public Set replaceValues(@ParametricNullness K key, Iterable values) { return (Set) super.replaceValues(key, values); } diff --git a/android/guava/src/com/google/common/collect/FilteredKeyListMultimap.java b/android/guava/src/com/google/common/collect/FilteredKeyListMultimap.java index 2a5522541222..d7b66a71994b 100644 --- a/android/guava/src/com/google/common/collect/FilteredKeyListMultimap.java +++ b/android/guava/src/com/google/common/collect/FilteredKeyListMultimap.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.base.Predicate; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link Multimaps#filterKeys(ListMultimap, Predicate)}. @@ -27,8 +27,8 @@ * @author Louis Wasserman */ @GwtCompatible -final class FilteredKeyListMultimap extends FilteredKeyMultimap - implements ListMultimap { +final class FilteredKeyListMultimap + extends FilteredKeyMultimap implements ListMultimap { FilteredKeyListMultimap(ListMultimap unfiltered, Predicate keyPredicate) { super(unfiltered, keyPredicate); } @@ -39,17 +39,17 @@ public ListMultimap unfiltered() { } @Override - public List get(K key) { + public List get(@ParametricNullness K key) { return (List) super.get(key); } @Override - public List removeAll(@NullableDecl Object key) { + public List removeAll(@Nullable Object key) { return (List) super.removeAll(key); } @Override - public List replaceValues(K key, Iterable values) { + public List replaceValues(@ParametricNullness K key, Iterable values) { return (List) super.replaceValues(key, values); } } diff --git a/android/guava/src/com/google/common/collect/FilteredKeyMultimap.java b/android/guava/src/com/google/common/collect/FilteredKeyMultimap.java index 2a3003d15c92..280735c5b477 100644 --- a/android/guava/src/com/google/common/collect/FilteredKeyMultimap.java +++ b/android/guava/src/com/google/common/collect/FilteredKeyMultimap.java @@ -16,19 +16,20 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndex; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Predicate; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.j2objc.annotations.WeakOuter; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link Multimaps#filterKeys(Multimap, Predicate)}. @@ -36,7 +37,8 @@ * @author Louis Wasserman */ @GwtCompatible -class FilteredKeyMultimap extends AbstractMultimap implements FilteredMultimap { +class FilteredKeyMultimap + extends AbstractMultimap implements FilteredMultimap { final Multimap unfiltered; final Predicate keyPredicate; @@ -65,7 +67,7 @@ public int size() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { if (unfiltered.containsKey(key)) { @SuppressWarnings("unchecked") // k is equal to a K, if not one itself K k = (K) key; @@ -75,15 +77,16 @@ public boolean containsKey(@NullableDecl Object key) { } @Override - public Collection removeAll(Object key) { + public Collection removeAll(@Nullable Object key) { return containsKey(key) ? unfiltered.removeAll(key) : unmodifiableEmptyCollection(); } + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types Collection unmodifiableEmptyCollection() { if (unfiltered instanceof SetMultimap) { - return ImmutableSet.of(); + return emptySet(); } else { - return ImmutableList.of(); + return emptyList(); } } @@ -98,7 +101,7 @@ Set createKeySet() { } @Override - public Collection get(K key) { + public Collection get(@ParametricNullness K key) { if (keyPredicate.apply(key)) { return unfiltered.get(key); } else if (unfiltered instanceof SetMultimap) { @@ -108,15 +111,16 @@ public Collection get(K key) { } } - static class AddRejectingSet extends ForwardingSet { - final K key; + private static final class AddRejectingSet + extends ForwardingSet { + @ParametricNullness final K key; - AddRejectingSet(K key) { + AddRejectingSet(@ParametricNullness K key) { this.key = key; } @Override - public boolean add(V element) { + public boolean add(@ParametricNullness V element) { throw new IllegalArgumentException("Key does not satisfy predicate: " + key); } @@ -128,25 +132,27 @@ public boolean addAll(Collection collection) { @Override protected Set delegate() { - return Collections.emptySet(); + return emptySet(); } } - static class AddRejectingList extends ForwardingList { - final K key; + private static final class AddRejectingList< + K extends @Nullable Object, V extends @Nullable Object> + extends ForwardingList { + @ParametricNullness final K key; - AddRejectingList(K key) { + AddRejectingList(@ParametricNullness K key) { this.key = key; } @Override - public boolean add(V v) { + public boolean add(@ParametricNullness V v) { add(0, v); return true; } @Override - public void add(int index, V element) { + public void add(int index, @ParametricNullness V element) { checkPositionIndex(index, 0); throw new IllegalArgumentException("Key does not satisfy predicate: " + key); } @@ -165,9 +171,10 @@ public boolean addAll(int index, Collection elements) { throw new IllegalArgumentException("Key does not satisfy predicate: " + key); } + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types @Override protected List delegate() { - return Collections.emptyList(); + return emptyList(); } } @@ -190,7 +197,7 @@ protected Collection> delegate() { @Override @SuppressWarnings("unchecked") - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { if (o instanceof Entry) { Entry entry = (Entry) o; if (unfiltered.containsKey(entry.getKey()) diff --git a/android/guava/src/com/google/common/collect/FilteredKeySetMultimap.java b/android/guava/src/com/google/common/collect/FilteredKeySetMultimap.java index 1ec8e6532e39..569ba78b8d67 100644 --- a/android/guava/src/com/google/common/collect/FilteredKeySetMultimap.java +++ b/android/guava/src/com/google/common/collect/FilteredKeySetMultimap.java @@ -20,7 +20,7 @@ import com.google.common.base.Predicate; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link Multimaps#filterKeys(SetMultimap, Predicate)}. @@ -28,8 +28,8 @@ * @author Louis Wasserman */ @GwtCompatible -final class FilteredKeySetMultimap extends FilteredKeyMultimap - implements FilteredSetMultimap { +final class FilteredKeySetMultimap + extends FilteredKeyMultimap implements FilteredSetMultimap { FilteredKeySetMultimap(SetMultimap unfiltered, Predicate keyPredicate) { super(unfiltered, keyPredicate); @@ -41,17 +41,17 @@ public SetMultimap unfiltered() { } @Override - public Set get(K key) { + public Set get(@ParametricNullness K key) { return (Set) super.get(key); } @Override - public Set removeAll(Object key) { + public Set removeAll(@Nullable Object key) { return (Set) super.removeAll(key); } @Override - public Set replaceValues(K key, Iterable values) { + public Set replaceValues(@ParametricNullness K key, Iterable values) { return (Set) super.replaceValues(key, values); } @@ -65,14 +65,14 @@ Set> createEntries() { return new EntrySet(); } - class EntrySet extends Entries implements Set> { + private final class EntrySet extends Entries implements Set> { @Override public int hashCode() { return Sets.hashCodeImpl(this); } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { return Sets.equalsImpl(this, o); } } diff --git a/android/guava/src/com/google/common/collect/FilteredMultimap.java b/android/guava/src/com/google/common/collect/FilteredMultimap.java index ef5ed4ab26f3..173302e29b5f 100644 --- a/android/guava/src/com/google/common/collect/FilteredMultimap.java +++ b/android/guava/src/com/google/common/collect/FilteredMultimap.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.base.Predicate; import java.util.Map.Entry; +import org.jspecify.annotations.Nullable; /** * An interface for all filtered multimap types. @@ -26,7 +27,8 @@ * @author Louis Wasserman */ @GwtCompatible -interface FilteredMultimap extends Multimap { +interface FilteredMultimap + extends Multimap { Multimap unfiltered(); Predicate> entryPredicate(); diff --git a/android/guava/src/com/google/common/collect/FilteredMultimapValues.java b/android/guava/src/com/google/common/collect/FilteredMultimapValues.java index b92707af5d57..5d74cf39acb6 100644 --- a/android/guava/src/com/google/common/collect/FilteredMultimapValues.java +++ b/android/guava/src/com/google/common/collect/FilteredMultimapValues.java @@ -15,17 +15,20 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.and; +import static com.google.common.base.Predicates.in; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Maps.valuePredicateOnEntries; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; import com.google.common.base.Predicate; -import com.google.common.base.Predicates; import com.google.j2objc.annotations.Weak; import java.util.AbstractCollection; import java.util.Collection; import java.util.Iterator; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * Implementation for {@link FilteredMultimap#values()}. @@ -33,7 +36,8 @@ * @author Louis Wasserman */ @GwtCompatible -final class FilteredMultimapValues extends AbstractCollection { +final class FilteredMultimapValues + extends AbstractCollection { @Weak private final FilteredMultimap multimap; FilteredMultimapValues(FilteredMultimap multimap) { @@ -46,7 +50,7 @@ public Iterator iterator() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { return multimap.containsValue(o); } @@ -56,12 +60,12 @@ public int size() { } @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { Predicate> entryPredicate = multimap.entryPredicate(); for (Iterator> unfilteredItr = multimap.unfiltered().entries().iterator(); unfilteredItr.hasNext(); ) { Entry entry = unfilteredItr.next(); - if (entryPredicate.apply(entry) && Objects.equal(entry.getValue(), o)) { + if (entryPredicate.apply(entry) && Objects.equals(entry.getValue(), o)) { unfilteredItr.remove(); return true; } @@ -73,19 +77,14 @@ public boolean remove(@NullableDecl Object o) { public boolean removeAll(Collection c) { return Iterables.removeIf( multimap.unfiltered().entries(), - // explicit > is required to build with JDK6 - Predicates.>and( - multimap.entryPredicate(), Maps.valuePredicateOnEntries(Predicates.in(c)))); + and(multimap.entryPredicate(), valuePredicateOnEntries(in(c)))); } @Override public boolean retainAll(Collection c) { return Iterables.removeIf( multimap.unfiltered().entries(), - // explicit > is required to build with JDK6 - Predicates.>and( - multimap.entryPredicate(), - Maps.valuePredicateOnEntries(Predicates.not(Predicates.in(c))))); + and(multimap.entryPredicate(), valuePredicateOnEntries(not(in(c))))); } @Override diff --git a/android/guava/src/com/google/common/collect/FilteredSetMultimap.java b/android/guava/src/com/google/common/collect/FilteredSetMultimap.java index a0a149fd7d5b..7737377ea202 100644 --- a/android/guava/src/com/google/common/collect/FilteredSetMultimap.java +++ b/android/guava/src/com/google/common/collect/FilteredSetMultimap.java @@ -17,6 +17,7 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.Nullable; /** * A supertype for filtered {@link SetMultimap} implementations. @@ -24,7 +25,8 @@ * @author Louis Wasserman */ @GwtCompatible -interface FilteredSetMultimap extends FilteredMultimap, SetMultimap { +interface FilteredSetMultimap + extends FilteredMultimap, SetMultimap { @Override SetMultimap unfiltered(); } diff --git a/android/guava/src/com/google/common/collect/FluentIterable.java b/android/guava/src/com/google/common/collect/FluentIterable.java index 66ae0986b037..3ec64e5b7545 100644 --- a/android/guava/src/com/google/common/collect/FluentIterable.java +++ b/android/guava/src/com/google/common/collect/FluentIterable.java @@ -16,7 +16,6 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Function; @@ -24,13 +23,17 @@ import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.stream.Stream; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * An expanded {@code Iterable} API, providing functionality similar to Java 8's powerful Streams include primitive-specialized variants such as {@code IntStream}, the use of which * is strongly recommended. - *

  • Streams are standard Java, not requiring a third-party dependency (but do render your code - * incompatible with Java 7 and earlier). + *
  • Streams are standard Java, not requiring a third-party dependency (but requiring library + * desugaring or API Level 24 + * under Android). * * *

    Example

    @@ -84,18 +90,18 @@ * transforms it by invoking {@code toString()} on each element, and returns the first 10 elements * as a {@code List}: * - *
    {@code
    + * {@snippet :
      * ImmutableList results =
      *     FluentIterable.from(database.getClientList())
      *         .filter(Client::isActiveInLastMonth)
      *         .transform(Object::toString)
      *         .limit(10)
      *         .toList();
    - * }
    + * } * * The approximate stream equivalent is: * - *
    {@code
    + * {@snippet :
      * List results =
      *     database.getClientList()
      *         .stream()
    @@ -103,17 +109,17 @@
      *         .map(Object::toString)
      *         .limit(10)
      *         .collect(Collectors.toList());
    - * }
    + * } * * @author Marcin Mikosik * @since 12.0 */ -@GwtCompatible(emulated = true) -public abstract class FluentIterable implements Iterable { +@GwtCompatible +public abstract class FluentIterable implements Iterable { // We store 'iterable' and use it instead of 'this' to allow Iterables to perform instanceof // checks on the _original_ iterable when FluentIterable.from is used. // To avoid a self retain cycle under j2objc, we store Optional.absent() instead of - // Optional.of(this). To access the iterator delegate, call #getDelegate(), which converts to + // Optional.of(this). To access the delegate iterable, call #getDelegate(), which converts to // absent() back to 'this'. private final Optional> iterableDelegate; @@ -123,8 +129,7 @@ protected FluentIterable() { } FluentIterable(Iterable iterable) { - checkNotNull(iterable); - this.iterableDelegate = Optional.fromNullable(this != iterable ? iterable : null); + this.iterableDelegate = Optional.of(iterable); } private Iterable getDelegate() { @@ -135,10 +140,10 @@ private Iterable getDelegate() { * Returns a fluent iterable that wraps {@code iterable}, or {@code iterable} itself if it is * already a {@code FluentIterable}. * - *

    {@code Stream} equivalent: {@code iterable.stream()} if {@code iterable} is a {@link - * Collection}; {@code StreamSupport.stream(iterable.spliterator(), false)} otherwise. + *

    {@code Stream} equivalent: {@link Collection#stream} if {@code iterable} is a {@link + * Collection}; {@link Streams#stream(Iterable)} otherwise. */ - public static FluentIterable from(final Iterable iterable) { + public static FluentIterable from(Iterable iterable) { return (iterable instanceof FluentIterable) ? (FluentIterable) iterable : new FluentIterable(iterable) { @@ -159,8 +164,7 @@ public Iterator iterator() { * * @since 20.0 (since 18.0 as an overload of {@code of}) */ - @Beta - public static FluentIterable from(E[] elements) { + public static FluentIterable from(E[] elements) { return from(Arrays.asList(elements)); } @@ -173,7 +177,10 @@ public static FluentIterable from(E[] elements) { * FluentIterable} */ @Deprecated - public static FluentIterable from(FluentIterable iterable) { + @InlineMe( + replacement = "checkNotNull(iterable)", + staticImports = {"com.google.common.base.Preconditions.checkNotNull"}) + public static FluentIterable from(FluentIterable iterable) { return checkNotNull(iterable); } @@ -189,8 +196,8 @@ public static FluentIterable from(FluentIterable iterable) { * * @since 20.0 */ - @Beta - public static FluentIterable concat(Iterable a, Iterable b) { + public static FluentIterable concat( + Iterable a, Iterable b) { return concatNoDefensiveCopy(a, b); } @@ -207,8 +214,7 @@ public static FluentIterable concat(Iterable a, Iterable FluentIterable concat( + public static FluentIterable concat( Iterable a, Iterable b, Iterable c) { return concatNoDefensiveCopy(a, b, c); } @@ -227,8 +233,7 @@ public static FluentIterable concat( * * @since 20.0 */ - @Beta - public static FluentIterable concat( + public static FluentIterable concat( Iterable a, Iterable b, Iterable c, @@ -251,8 +256,9 @@ public static FluentIterable concat( * @throws NullPointerException if any of the provided iterables is {@code null} * @since 20.0 */ - @Beta - public static FluentIterable concat(Iterable... inputs) { + @SafeVarargs + public static FluentIterable concat( + Iterable... inputs) { return concatNoDefensiveCopy(Arrays.copyOf(inputs, inputs.length)); } @@ -270,21 +276,20 @@ public static FluentIterable concat(Iterable... inputs) { * * @since 20.0 */ - @Beta - public static FluentIterable concat( - final Iterable> inputs) { + public static FluentIterable concat( + Iterable> inputs) { checkNotNull(inputs); return new FluentIterable() { @Override public Iterator iterator() { - return Iterators.concat(Iterators.transform(inputs.iterator(), Iterables.toIterator())); + return Iterators.concat(Iterators.transform(inputs.iterator(), Iterable::iterator)); } }; } /** Concatenates a varargs array of iterables without making a defensive copy of the array. */ - private static FluentIterable concatNoDefensiveCopy( - final Iterable... inputs) { + private static FluentIterable concatNoDefensiveCopy( + Iterable... inputs) { for (Iterable input : inputs) { checkNotNull(input); } @@ -306,13 +311,13 @@ public Iterator get(int i) { /** * Returns a fluent iterable containing no elements. * - *

    {@code Stream} equivalent: {@code Stream.empty()}. + *

    {@code Stream} equivalent: {@link Stream#empty}. * * @since 20.0 */ - @Beta - public static FluentIterable of() { - return FluentIterable.from(ImmutableList.of()); + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types + public static FluentIterable of() { + return FluentIterable.from(Collections.emptyList()); } /** @@ -323,8 +328,8 @@ public static FluentIterable of() { * * @since 20.0 */ - @Beta - public static FluentIterable of(@NullableDecl E element, E... elements) { + public static FluentIterable of( + @ParametricNullness E element, E... elements) { return from(Lists.asList(element, elements)); } @@ -343,7 +348,7 @@ public String toString() { /** * Returns the number of elements in this fluent iterable. * - *

    {@code Stream} equivalent: {@code stream.count()}. + *

    {@code Stream} equivalent: {@link Stream#count}. */ public final int size() { return Iterables.size(getDelegate()); @@ -355,7 +360,7 @@ public final int size() { * *

    {@code Stream} equivalent: {@code stream.anyMatch(Predicate.isEqual(target))}. */ - public final boolean contains(@NullableDecl Object target) { + public final boolean contains(@Nullable Object target) { return Iterables.contains(getDelegate(), target); } @@ -391,7 +396,6 @@ public final FluentIterable cycle() { * * @since 18.0 */ - @Beta public final FluentIterable append(Iterable other) { return FluentIterable.concat(getDelegate(), other); } @@ -404,7 +408,6 @@ public final FluentIterable append(Iterable other) { * * @since 18.0 */ - @Beta public final FluentIterable append(E... elements) { return FluentIterable.concat(getDelegate(), Arrays.asList(elements)); } @@ -426,11 +429,11 @@ public final FluentIterable filter(Predicate predicate) { * This does perform a little more work than necessary, so another option is to insert an * unchecked cast at some later point: * - *

    -   * {@code @SuppressWarnings("unchecked") // safe because of ::isInstance check
    +   * {@snippet :
    +   * @SuppressWarnings("unchecked") // safe because of ::isInstance check
        * ImmutableList result =
    -   *     (ImmutableList) stream.filter(NewType.class::isInstance).collect(toImmutableList());}
    -   * 
    + * (ImmutableList) stream.filter(NewType.class::isInstance).collect(toImmutableList()); + * } */ @GwtIncompatible // Class.isInstance public final FluentIterable filter(Class type) { @@ -465,8 +468,9 @@ public final boolean allMatch(Predicate predicate) { * *

    {@code Stream} equivalent: {@code stream.filter(predicate).findFirst()}. */ - public final Optional firstMatch(Predicate predicate) { - return Iterables.tryFind(getDelegate(), predicate); + public final Optional<@NonNull E> firstMatch(Predicate predicate) { + // Unsafe, but we can't do much about it now. + return Iterables.<@NonNull E>tryFind((Iterable<@NonNull E>) getDelegate(), predicate); } /** @@ -479,7 +483,8 @@ public final Optional firstMatch(Predicate predicate) { * *

    {@code Stream} equivalent: {@link Stream#map}. */ - public final FluentIterable transform(Function function) { + public final FluentIterable transform( + Function function) { return from(Iterables.transform(getDelegate(), function)); } @@ -496,7 +501,7 @@ public final FluentIterable transform(Function function) { * * @since 13.0 (required {@code Function>} until 14.0) */ - public FluentIterable transformAndConcat( + public FluentIterable transformAndConcat( Function> function) { return FluentIterable.concat(transform(function)); } @@ -511,9 +516,10 @@ public FluentIterable transformAndConcat( * @throws NullPointerException if the first element is null; if this is a possibility, use {@code * iterator().next()} or {@link Iterables#getFirst} instead. */ - public final Optional first() { + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final Optional<@NonNull E> first() { Iterator iterator = getDelegate().iterator(); - return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.absent(); + return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.absent(); } /** @@ -527,7 +533,8 @@ public final Optional first() { * @throws NullPointerException if the last element is null; if this is a possibility, use {@link * Iterables#getLast} instead. */ - public final Optional last() { + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final Optional<@NonNull E> last() { // Iterables#getLast was inlined here so we don't have to throw/catch a NSEE // TODO(kevinb): Support a concurrently modified collection? @@ -567,7 +574,7 @@ public final Optional last() { * iterable skips all of its elements. * *

    Modifications to this fluent iterable before a call to {@code iterator()} are reflected in - * the returned fluent iterable. That is, the its iterator skips the first {@code numberToSkip} + * the returned fluent iterable. That is, the iterator skips the first {@code numberToSkip} * elements that exist when the iterator is created, not when {@code skip()} is called. * *

    The returned fluent iterable's iterator supports {@code remove()} if the {@code Iterator} of @@ -610,14 +617,15 @@ public final boolean isEmpty() { * Returns an {@code ImmutableList} containing all of the elements from this fluent iterable in * proper sequence. * - *

    {@code Stream} equivalent: {@code ImmutableList.copyOf(stream.iterator())}, or pass - * {@link ImmutableList#toImmutableList} to {@code stream.collect()}. + *

    {@code Stream} equivalent: pass {@link ImmutableList#toImmutableList} to {@code + * stream.collect()}. * * @throws NullPointerException if any element is {@code null} * @since 14.0 (since 12.0 as {@code toImmutableList()}). */ - public final ImmutableList toList() { - return ImmutableList.copyOf(getDelegate()); + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final ImmutableList<@NonNull E> toList() { + return ImmutableList.copyOf((Iterable<@NonNull E>) getDelegate()); } /** @@ -625,30 +633,31 @@ public final ImmutableList toList() { * FluentIterable} in the order specified by {@code comparator}. To produce an {@code * ImmutableList} sorted by its natural ordering, use {@code toSortedList(Ordering.natural())}. * - *

    {@code Stream} equivalent: {@code - * ImmutableList.copyOf(stream.sorted(comparator).iterator())}, or pass {@link - * ImmutableList#toImmutableList} to {@code stream.sorted(comparator).collect()}. + *

    {@code Stream} equivalent: pass {@link ImmutableList#toImmutableList} to {@code + * stream.sorted(comparator).collect()}. * * @param comparator the function by which to sort list elements * @throws NullPointerException if any element of this iterable is {@code null} * @since 14.0 (since 13.0 as {@code toSortedImmutableList()}). */ - public final ImmutableList toSortedList(Comparator comparator) { - return Ordering.from(comparator).immutableSortedCopy(getDelegate()); + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final ImmutableList<@NonNull E> toSortedList(Comparator comparator) { + return Ordering.from(comparator).immutableSortedCopy((Iterable<@NonNull E>) getDelegate()); } /** * Returns an {@code ImmutableSet} containing all of the elements from this fluent iterable with * duplicates removed. * - *

    {@code Stream} equivalent: {@code ImmutableSet.copyOf(stream.iterator())}, or pass - * {@link ImmutableSet#toImmutableSet} to {@code stream.collect()}. + *

    {@code Stream} equivalent: pass {@link ImmutableSet#toImmutableSet} to {@code + * stream.collect()}. * * @throws NullPointerException if any element is {@code null} * @since 14.0 (since 12.0 as {@code toImmutableSet()}). */ - public final ImmutableSet toSet() { - return ImmutableSet.copyOf(getDelegate()); + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final ImmutableSet<@NonNull E> toSet() { + return ImmutableSet.copyOf((Iterable<@NonNull E>) getDelegate()); } /** @@ -657,29 +666,30 @@ public final ImmutableSet toSet() { * {@code comparator.compare(x, y) == 0}) removed. To produce an {@code ImmutableSortedSet} sorted * by its natural ordering, use {@code toSortedSet(Ordering.natural())}. * - *

    {@code Stream} equivalent: {@code ImmutableSortedSet.copyOf(comparator, - * stream.iterator())}, or pass {@link ImmutableSortedSet#toImmutableSortedSet} to {@code - * stream.collect()}. + *

    {@code Stream} equivalent: pass {@link ImmutableSortedSet#toImmutableSortedSet} to + * {@code stream.collect()}. * * @param comparator the function by which to sort set elements * @throws NullPointerException if any element of this iterable is {@code null} * @since 14.0 (since 12.0 as {@code toImmutableSortedSet()}). */ - public final ImmutableSortedSet toSortedSet(Comparator comparator) { - return ImmutableSortedSet.copyOf(comparator, getDelegate()); + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final ImmutableSortedSet<@NonNull E> toSortedSet(Comparator comparator) { + return ImmutableSortedSet.copyOf(comparator, (Iterable<@NonNull E>) getDelegate()); } /** * Returns an {@code ImmutableMultiset} containing all of the elements from this fluent iterable. * - *

    {@code Stream} equivalent: {@code ImmutableMultiset.copyOf(stream.iterator())}, or - * pass {@link ImmutableMultiset#toImmutableMultiset} to {@code stream.collect()}. + *

    {@code Stream} equivalent: pass {@link ImmutableMultiset#toImmutableMultiset} to + * {@code stream.collect()}. * * @throws NullPointerException if any element is null * @since 19.0 */ - public final ImmutableMultiset toMultiset() { - return ImmutableMultiset.copyOf(getDelegate()); + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final ImmutableMultiset<@NonNull E> toMultiset() { + return ImmutableMultiset.copyOf((Iterable<@NonNull E>) getDelegate()); } /** @@ -691,16 +701,16 @@ public final ImmutableMultiset toMultiset() { * {@code valueFunction} will be applied to more than one instance of that key and, if it is, * which result will be mapped to that key in the returned map. * - *

    {@code Stream} equivalent: use {@code stream.collect(ImmutableMap.toImmutableMap(k -> - * k, valueFunction))}. {@code ImmutableMap.copyOf(stream.collect(Collectors.toMap(k -> k, - * valueFunction)))} behaves similarly, but may not preserve the order of entries. + *

    {@code Stream} equivalent: {@code stream.collect(ImmutableMap.toImmutableMap(k -> k, + * valueFunction))}. * * @throws NullPointerException if any element of this iterable is {@code null}, or if {@code * valueFunction} produces {@code null} for any key * @since 14.0 */ - public final ImmutableMap toMap(Function valueFunction) { - return Maps.toMap(getDelegate(), valueFunction); + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final ImmutableMap<@NonNull E, V> toMap(Function valueFunction) { + return Maps.toMap((Iterable<@NonNull E>) getDelegate(), valueFunction); } /** @@ -712,17 +722,17 @@ public final ImmutableMap toMap(Function valueFunction) * In the returned multimap, keys appear in the order they are first encountered, and the values * corresponding to each key appear in the same order as they are encountered. * - *

    {@code Stream} equivalent: {@code stream.collect(Collectors.groupingBy(keyFunction))} - * behaves similarly, but returns a mutable {@code Map>} instead, and may not preserve - * the order of entries. + *

    {@code Stream} equivalent: {@code + * stream.collect(ImmutableListMultimap.toImmutableListMultimap(keyFunction, v -> v))}. * * @param keyFunction the function used to produce the key for each value * @throws NullPointerException if any element of this iterable is {@code null}, or if {@code * keyFunction} produces {@code null} for any key * @since 14.0 */ - public final ImmutableListMultimap index(Function keyFunction) { - return Multimaps.index(getDelegate(), keyFunction); + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final ImmutableListMultimap index(Function keyFunction) { + return Multimaps.index((Iterable<@NonNull E>) getDelegate(), keyFunction); } /** @@ -731,22 +741,20 @@ public final ImmutableListMultimap index(Function keyFun * map whose key is the result of applying {@code keyFunction} to that value. These entries appear * in the same order as they appeared in this fluent iterable. Example usage: * - *

    {@code
    +   * {@snippet :
        * Color red = new Color("red", 255, 0, 0);
        * ...
        * FluentIterable allColors = FluentIterable.from(ImmutableSet.of(red, green, blue));
        *
        * Map colorForName = allColors.uniqueIndex(toStringFunction());
        * assertThat(colorForName).containsEntry("red", red);
    -   * }
    + * } * *

    If your index may associate multiple values with each key, use {@link #index(Function) * index}. * - *

    {@code Stream} equivalent: use {@code - * stream.collect(ImmutableMap.toImmutableMap(keyFunction, v -> v))}. {@code - * ImmutableMap.copyOf(stream.collect(Collectors.toMap(keyFunction, v -> v)))}, but be aware that - * this may not preserve the order of entries. + *

    {@code Stream} equivalent: {@code + * stream.collect(ImmutableMap.toImmutableMap(keyFunction, v -> v))}. * * @param keyFunction the function used to produce the key for each value * @return a map mapping the result of evaluating the function {@code keyFunction} on each value @@ -757,8 +765,9 @@ public final ImmutableListMultimap index(Function keyFun * keyFunction} produces {@code null} for any key * @since 14.0 */ - public final ImmutableMap uniqueIndex(Function keyFunction) { - return Maps.uniqueIndex(getDelegate(), keyFunction); + @SuppressWarnings("nullness") // Unsafe, but we can't do much about it now. + public final ImmutableMap uniqueIndex(Function keyFunction) { + return Maps.uniqueIndex((Iterable<@NonNull E>) getDelegate(), keyFunction); } /** @@ -774,8 +783,8 @@ public final ImmutableMap uniqueIndex(Function keyFuncti * copied */ @GwtIncompatible // Array.newArray(Class, int) - public final E[] toArray(Class type) { - return Iterables.toArray(getDelegate(), type); + public final E[] toArray(Class<@NonNull E> type) { + return Iterables.toArray(getDelegate(), type); } /** @@ -813,7 +822,6 @@ public final > C copyInto(C collection) { * * @since 18.0 */ - @Beta public final String join(Joiner joiner) { return joiner.join(this); } @@ -830,16 +838,8 @@ public final String join(Joiner joiner) { * @throws IndexOutOfBoundsException if {@code position} is negative or greater than or equal to * the size of this fluent iterable */ - // TODO(kevinb): add @NullableDecl? + @ParametricNullness public final E get(int position) { return Iterables.get(getDelegate(), position); } - - /** Function that transforms {@code Iterable} into a fluent iterable. */ - private static class FromIterableFunction implements Function, FluentIterable> { - @Override - public FluentIterable apply(Iterable fromObject) { - return FluentIterable.from(fromObject); - } - } } diff --git a/android/guava/src/com/google/common/collect/ForwardingBlockingDeque.java b/android/guava/src/com/google/common/collect/ForwardingBlockingDeque.java index 7d3895d01502..4be9c3072dff 100644 --- a/android/guava/src/com/google/common/collect/ForwardingBlockingDeque.java +++ b/android/guava/src/com/google/common/collect/ForwardingBlockingDeque.java @@ -17,9 +17,11 @@ package com.google.common.collect; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.Collection; import java.util.concurrent.BlockingDeque; import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.Nullable; /** * A {@link BlockingDeque} which forwards all its method calls to another {@code BlockingDeque}. @@ -45,6 +47,7 @@ * com.google.common.util.concurrent.ForwardingBlockingDeque} instead. */ @Deprecated +@J2ktIncompatible @GwtIncompatible public abstract class ForwardingBlockingDeque extends ForwardingDeque implements BlockingDeque { @@ -91,12 +94,12 @@ public E takeLast() throws InterruptedException { } @Override - public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable E pollFirst(long timeout, TimeUnit unit) throws InterruptedException { return delegate().pollFirst(timeout, unit); } @Override - public E pollLast(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable E pollLast(long timeout, TimeUnit unit) throws InterruptedException { return delegate().pollLast(timeout, unit); } @@ -116,7 +119,7 @@ public E take() throws InterruptedException { } @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable E poll(long timeout, TimeUnit unit) throws InterruptedException { return delegate().poll(timeout, unit); } diff --git a/android/guava/src/com/google/common/collect/ForwardingCollection.java b/android/guava/src/com/google/common/collect/ForwardingCollection.java index 416ff968a715..8868b5d4e41c 100644 --- a/android/guava/src/com/google/common/collect/ForwardingCollection.java +++ b/android/guava/src/com/google/common/collect/ForwardingCollection.java @@ -17,11 +17,11 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * A collection which forwards all its method calls to another collection. Subclasses should @@ -46,7 +46,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingCollection extends ForwardingObject implements Collection { +public abstract class ForwardingCollection extends ForwardingObject + implements Collection { // TODO(lowasser): identify places where thread safety is actually lost /** Constructor for use by subclasses. */ @@ -77,19 +78,19 @@ public boolean isEmpty() { } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { return delegate().contains(object); } @CanIgnoreReturnValue @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { return delegate().add(element); } @CanIgnoreReturnValue @Override - public boolean remove(Object object) { + public boolean remove(@Nullable Object object) { return delegate().remove(object); } @@ -116,13 +117,14 @@ public void clear() { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { return delegate().toArray(); } @CanIgnoreReturnValue @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return delegate().toArray(array); } @@ -133,7 +135,7 @@ public T[] toArray(T[] array) { * * @since 7.0 */ - protected boolean standardContains(@NullableDecl Object object) { + protected boolean standardContains(@Nullable Object object) { return Iterators.contains(iterator(), object); } @@ -165,10 +167,10 @@ protected boolean standardAddAll(Collection collection) { * * @since 7.0 */ - protected boolean standardRemove(@NullableDecl Object object) { + protected boolean standardRemove(@Nullable Object object) { Iterator iterator = iterator(); while (iterator.hasNext()) { - if (Objects.equal(iterator.next(), object)) { + if (Objects.equals(iterator.next(), object)) { iterator.remove(); return true; } @@ -238,8 +240,8 @@ protected String standardToString() { * * @since 7.0 */ - protected Object[] standardToArray() { - Object[] newArray = new Object[size()]; + protected @Nullable Object[] standardToArray() { + @Nullable Object[] newArray = new @Nullable Object[size()]; return toArray(newArray); } @@ -250,7 +252,7 @@ protected Object[] standardToArray() { * * @since 7.0 */ - protected T[] standardToArray(T[] array) { + protected T[] standardToArray(T[] array) { return ObjectArrays.toArrayImpl(this, array); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingConcurrentMap.java b/android/guava/src/com/google/common/collect/ForwardingConcurrentMap.java index 0910424b7e58..51eb005641f2 100644 --- a/android/guava/src/com/google/common/collect/ForwardingConcurrentMap.java +++ b/android/guava/src/com/google/common/collect/ForwardingConcurrentMap.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.concurrent.ConcurrentMap; +import org.jspecify.annotations.Nullable; /** * A concurrent map which forwards all its method calls to another concurrent map. Subclasses should @@ -47,24 +48,25 @@ protected ForwardingConcurrentMap() {} @CanIgnoreReturnValue @Override - public V putIfAbsent(K key, V value) { + public @Nullable V putIfAbsent(K key, V value) { return delegate().putIfAbsent(key, value); } @CanIgnoreReturnValue @Override - public boolean remove(Object key, Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { return delegate().remove(key, value); } @CanIgnoreReturnValue @Override - public V replace(K key, V value) { + public @Nullable V replace(K key, V value) { return delegate().replace(key, value); } @CanIgnoreReturnValue @Override + @SuppressWarnings("nullness") // https://github.com/jspecify/jdk/issues/118 public boolean replace(K key, V oldValue, V newValue) { return delegate().replace(key, oldValue, newValue); } diff --git a/android/guava/src/com/google/common/collect/ForwardingDeque.java b/android/guava/src/com/google/common/collect/ForwardingDeque.java index 87ac71bd67ec..33663142d773 100644 --- a/android/guava/src/com/google/common/collect/ForwardingDeque.java +++ b/android/guava/src/com/google/common/collect/ForwardingDeque.java @@ -17,9 +17,11 @@ package com.google.common.collect; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Deque; import java.util.Iterator; +import org.jspecify.annotations.Nullable; /** * A deque which forwards all its method calls to another deque. Subclasses should override one or @@ -38,8 +40,10 @@ * @author Kurt Alfred Kluever * @since 12.0 */ +@J2ktIncompatible @GwtIncompatible -public abstract class ForwardingDeque extends ForwardingQueue implements Deque { +public abstract class ForwardingDeque extends ForwardingQueue + implements Deque { /** Constructor for use by subclasses. */ protected ForwardingDeque() {} @@ -48,12 +52,12 @@ protected ForwardingDeque() {} protected abstract Deque delegate(); @Override - public void addFirst(E e) { + public void addFirst(@ParametricNullness E e) { delegate().addFirst(e); } @Override - public void addLast(E e) { + public void addLast(@ParametricNullness E e) { delegate().addLast(e); } @@ -63,81 +67,86 @@ public Iterator descendingIterator() { } @Override + @ParametricNullness public E getFirst() { return delegate().getFirst(); } @Override + @ParametricNullness public E getLast() { return delegate().getLast(); } @CanIgnoreReturnValue // TODO(cpovirk): Consider removing this? @Override - public boolean offerFirst(E e) { + public boolean offerFirst(@ParametricNullness E e) { return delegate().offerFirst(e); } @CanIgnoreReturnValue // TODO(cpovirk): Consider removing this? @Override - public boolean offerLast(E e) { + public boolean offerLast(@ParametricNullness E e) { return delegate().offerLast(e); } @Override - public E peekFirst() { + public @Nullable E peekFirst() { return delegate().peekFirst(); } @Override - public E peekLast() { + public @Nullable E peekLast() { return delegate().peekLast(); } @CanIgnoreReturnValue // TODO(cpovirk): Consider removing this? @Override - public E pollFirst() { + public @Nullable E pollFirst() { return delegate().pollFirst(); } @CanIgnoreReturnValue // TODO(cpovirk): Consider removing this? @Override - public E pollLast() { + public @Nullable E pollLast() { return delegate().pollLast(); } @CanIgnoreReturnValue @Override + @ParametricNullness public E pop() { return delegate().pop(); } @Override - public void push(E e) { + public void push(@ParametricNullness E e) { delegate().push(e); } @CanIgnoreReturnValue @Override + @ParametricNullness public E removeFirst() { return delegate().removeFirst(); } @CanIgnoreReturnValue @Override + @ParametricNullness public E removeLast() { return delegate().removeLast(); } @CanIgnoreReturnValue @Override - public boolean removeFirstOccurrence(Object o) { + public boolean removeFirstOccurrence(@Nullable Object o) { return delegate().removeFirstOccurrence(o); } @CanIgnoreReturnValue @Override - public boolean removeLastOccurrence(Object o) { + public boolean removeLastOccurrence(@Nullable Object o) { return delegate().removeLastOccurrence(o); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingImmutableList.java b/android/guava/src/com/google/common/collect/ForwardingImmutableList.java index 2b9092ea4c93..184740946cd6 100644 --- a/android/guava/src/com/google/common/collect/ForwardingImmutableList.java +++ b/android/guava/src/com/google/common/collect/ForwardingImmutableList.java @@ -23,7 +23,7 @@ * * @author Chris Povirk */ -@GwtCompatible(emulated = true) +@GwtCompatible abstract class ForwardingImmutableList { private ForwardingImmutableList() {} } diff --git a/android/guava/src/com/google/common/collect/ForwardingImmutableMap.java b/android/guava/src/com/google/common/collect/ForwardingImmutableMap.java index a36715743f0a..52bf8c6b7a83 100644 --- a/android/guava/src/com/google/common/collect/ForwardingImmutableMap.java +++ b/android/guava/src/com/google/common/collect/ForwardingImmutableMap.java @@ -23,7 +23,7 @@ * * @author Chris Povirk */ -@GwtCompatible(emulated = true) +@GwtCompatible abstract class ForwardingImmutableMap { private ForwardingImmutableMap() {} } diff --git a/android/guava/src/com/google/common/collect/ForwardingImmutableSet.java b/android/guava/src/com/google/common/collect/ForwardingImmutableSet.java index c7d7bf6d778b..ae0668a9823d 100644 --- a/android/guava/src/com/google/common/collect/ForwardingImmutableSet.java +++ b/android/guava/src/com/google/common/collect/ForwardingImmutableSet.java @@ -23,7 +23,7 @@ * * @author Chris Povirk */ -@GwtCompatible(emulated = true) +@GwtCompatible abstract class ForwardingImmutableSet { private ForwardingImmutableSet() {} } diff --git a/android/guava/src/com/google/common/collect/ForwardingIterator.java b/android/guava/src/com/google/common/collect/ForwardingIterator.java index 5ecd3d293126..47449aa6207a 100644 --- a/android/guava/src/com/google/common/collect/ForwardingIterator.java +++ b/android/guava/src/com/google/common/collect/ForwardingIterator.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Iterator; +import org.jspecify.annotations.Nullable; /** * An iterator which forwards all its method calls to another iterator. Subclasses should override @@ -36,7 +37,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingIterator extends ForwardingObject implements Iterator { +public abstract class ForwardingIterator extends ForwardingObject + implements Iterator { /** Constructor for use by subclasses. */ protected ForwardingIterator() {} @@ -51,6 +53,7 @@ public boolean hasNext() { @CanIgnoreReturnValue @Override + @ParametricNullness public T next() { return delegate().next(); } diff --git a/android/guava/src/com/google/common/collect/ForwardingList.java b/android/guava/src/com/google/common/collect/ForwardingList.java index bfd208313410..eae6d3e8a06b 100644 --- a/android/guava/src/com/google/common/collect/ForwardingList.java +++ b/android/guava/src/com/google/common/collect/ForwardingList.java @@ -16,14 +16,13 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A list which forwards all its method calls to another list. Subclasses should override one or @@ -51,7 +50,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingList extends ForwardingCollection implements List { +public abstract class ForwardingList extends ForwardingCollection + implements List { // TODO(lowasser): identify places where thread safety is actually lost /** Constructor for use by subclasses. */ @@ -61,7 +61,7 @@ protected ForwardingList() {} protected abstract List delegate(); @Override - public void add(int index, E element) { + public void add(int index, @ParametricNullness E element) { delegate().add(index, element); } @@ -72,17 +72,18 @@ public boolean addAll(int index, Collection elements) { } @Override + @ParametricNullness public E get(int index) { return delegate().get(index); } @Override - public int indexOf(Object element) { + public int indexOf(@Nullable Object element) { return delegate().indexOf(element); } @Override - public int lastIndexOf(Object element) { + public int lastIndexOf(@Nullable Object element) { return delegate().lastIndexOf(element); } @@ -98,13 +99,15 @@ public ListIterator listIterator(int index) { @CanIgnoreReturnValue @Override + @ParametricNullness public E remove(int index) { return delegate().remove(index); } @CanIgnoreReturnValue @Override - public E set(int index, E element) { + @ParametricNullness + public E set(int index, @ParametricNullness E element) { return delegate().set(index, element); } @@ -114,7 +117,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return object == this || delegate().equals(object); } @@ -130,7 +133,7 @@ public int hashCode() { * * @since 7.0 */ - protected boolean standardAdd(E element) { + protected boolean standardAdd(@ParametricNullness E element) { add(size(), element); return true; } @@ -153,7 +156,7 @@ protected boolean standardAddAll(int index, Iterable elements) { * * @since 7.0 */ - protected int standardIndexOf(@NullableDecl Object element) { + protected int standardIndexOf(@Nullable Object element) { return Lists.indexOfImpl(this, element); } @@ -164,7 +167,7 @@ protected int standardIndexOf(@NullableDecl Object element) { * * @since 7.0 */ - protected int standardLastIndexOf(@NullableDecl Object element) { + protected int standardLastIndexOf(@Nullable Object element) { return Lists.lastIndexOfImpl(this, element); } @@ -198,7 +201,6 @@ protected ListIterator standardListIterator() { * * @since 7.0 */ - @Beta protected ListIterator standardListIterator(int start) { return Lists.listIteratorImpl(this, start); } @@ -209,7 +211,6 @@ protected ListIterator standardListIterator(int start) { * * @since 7.0 */ - @Beta protected List standardSubList(int fromIndex, int toIndex) { return Lists.subListImpl(this, fromIndex, toIndex); } @@ -221,8 +222,7 @@ protected List standardSubList(int fromIndex, int toIndex) { * * @since 7.0 */ - @Beta - protected boolean standardEquals(@NullableDecl Object object) { + protected boolean standardEquals(@Nullable Object object) { return Lists.equalsImpl(this, object); } @@ -233,7 +233,6 @@ protected boolean standardEquals(@NullableDecl Object object) { * * @since 7.0 */ - @Beta protected int standardHashCode() { return Lists.hashCodeImpl(this); } diff --git a/android/guava/src/com/google/common/collect/ForwardingListIterator.java b/android/guava/src/com/google/common/collect/ForwardingListIterator.java index bc2a5ad4ff58..5b2518c23c09 100644 --- a/android/guava/src/com/google/common/collect/ForwardingListIterator.java +++ b/android/guava/src/com/google/common/collect/ForwardingListIterator.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ListIterator; +import org.jspecify.annotations.Nullable; /** * A list iterator which forwards all its method calls to another list iterator. Subclasses should @@ -36,8 +37,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingListIterator extends ForwardingIterator - implements ListIterator { +public abstract class ForwardingListIterator + extends ForwardingIterator implements ListIterator { /** Constructor for use by subclasses. */ protected ForwardingListIterator() {} @@ -46,7 +47,7 @@ protected ForwardingListIterator() {} protected abstract ListIterator delegate(); @Override - public void add(E element) { + public void add(@ParametricNullness E element) { delegate().add(element); } @@ -62,6 +63,7 @@ public int nextIndex() { @CanIgnoreReturnValue @Override + @ParametricNullness public E previous() { return delegate().previous(); } @@ -72,7 +74,7 @@ public int previousIndex() { } @Override - public void set(E element) { + public void set(@ParametricNullness E element) { delegate().set(element); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingListMultimap.java b/android/guava/src/com/google/common/collect/ForwardingListMultimap.java index 8cf3d703b483..5ba9b978d160 100644 --- a/android/guava/src/com/google/common/collect/ForwardingListMultimap.java +++ b/android/guava/src/com/google/common/collect/ForwardingListMultimap.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A list multimap which forwards all its method calls to another list multimap. Subclasses should @@ -34,8 +34,8 @@ * @since 3.0 */ @GwtCompatible -public abstract class ForwardingListMultimap extends ForwardingMultimap - implements ListMultimap { +public abstract class ForwardingListMultimap + extends ForwardingMultimap implements ListMultimap { /** Constructor for use by subclasses. */ protected ForwardingListMultimap() {} @@ -44,19 +44,19 @@ protected ForwardingListMultimap() {} protected abstract ListMultimap delegate(); @Override - public List get(@NullableDecl K key) { + public List get(@ParametricNullness K key) { return delegate().get(key); } @CanIgnoreReturnValue @Override - public List removeAll(@NullableDecl Object key) { + public List removeAll(@Nullable Object key) { return delegate().removeAll(key); } @CanIgnoreReturnValue @Override - public List replaceValues(K key, Iterable values) { + public List replaceValues(@ParametricNullness K key, Iterable values) { return delegate().replaceValues(key, values); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingMap.java b/android/guava/src/com/google/common/collect/ForwardingMap.java index 4032bf9e6190..71c46352be19 100644 --- a/android/guava/src/com/google/common/collect/ForwardingMap.java +++ b/android/guava/src/com/google/common/collect/ForwardingMap.java @@ -16,15 +16,14 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A map which forwards all its method calls to another map. Subclasses should override one or more @@ -41,7 +40,7 @@ * default} methods. Instead, it inherits their default implementations. When those implementations * invoke methods, they invoke methods on the {@code ForwardingMap}. * - *

    Each of the {@code standard} methods, where appropriate, use {@link Objects#equal} to test + *

    Each of the {@code standard} methods, where appropriate, use {@link Objects#equals} to test * equality for both keys and values. This may not be the desired behavior for map implementations * that use non-standard notions of key equality, such as a {@code SortedMap} whose comparator is * not consistent with {@code equals}. @@ -55,7 +54,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingMap extends ForwardingObject implements Map { +public abstract class ForwardingMap + extends ForwardingObject implements Map { // TODO(lowasser): identify places where thread safety is actually lost /** Constructor for use by subclasses. */ @@ -76,7 +76,7 @@ public boolean isEmpty() { @CanIgnoreReturnValue @Override - public V remove(Object key) { + public @Nullable V remove(@Nullable Object key) { return delegate().remove(key); } @@ -86,23 +86,23 @@ public void clear() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return delegate().containsKey(key); } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return delegate().containsValue(value); } @Override - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { return delegate().get(key); } @CanIgnoreReturnValue @Override - public V put(K key, V value) { + public @Nullable V put(@ParametricNullness K key, @ParametricNullness V value) { return delegate().put(key, value); } @@ -127,7 +127,7 @@ public Set> entrySet() { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return object == this || delegate().equals(object); } @@ -157,12 +157,11 @@ protected void standardPutAll(Map map) { * * @since 7.0 */ - @Beta - protected V standardRemove(@NullableDecl Object key) { + protected @Nullable V standardRemove(@Nullable Object key) { Iterator> entryIterator = entrySet().iterator(); while (entryIterator.hasNext()) { Entry entry = entryIterator.next(); - if (Objects.equal(entry.getKey(), key)) { + if (Objects.equals(entry.getKey(), key)) { V value = entry.getValue(); entryIterator.remove(); return value; @@ -191,7 +190,6 @@ protected void standardClear() { * * @since 10.0 */ - @Beta protected class StandardKeySet extends Maps.KeySet { /** Constructor for use by subclasses. */ public StandardKeySet() { @@ -206,8 +204,7 @@ public StandardKeySet() { * * @since 7.0 */ - @Beta - protected boolean standardContainsKey(@NullableDecl Object key) { + protected boolean standardContainsKey(@Nullable Object key) { return Maps.containsKeyImpl(this, key); } @@ -220,7 +217,6 @@ protected boolean standardContainsKey(@NullableDecl Object key) { * * @since 10.0 */ - @Beta protected class StandardValues extends Maps.Values { /** Constructor for use by subclasses. */ public StandardValues() { @@ -235,7 +231,7 @@ public StandardValues() { * * @since 7.0 */ - protected boolean standardContainsValue(@NullableDecl Object value) { + protected boolean standardContainsValue(@Nullable Object value) { return Maps.containsValueImpl(this, value); } @@ -248,10 +244,9 @@ protected boolean standardContainsValue(@NullableDecl Object value) { * * @since 10.0 */ - @Beta protected abstract class StandardEntrySet extends Maps.EntrySet { /** Constructor for use by subclasses. */ - public StandardEntrySet() {} + protected StandardEntrySet() {} @Override Map map() { @@ -277,7 +272,7 @@ protected boolean standardIsEmpty() { * * @since 7.0 */ - protected boolean standardEquals(@NullableDecl Object object) { + protected boolean standardEquals(@Nullable Object object) { return Maps.equalsImpl(this, object); } diff --git a/android/guava/src/com/google/common/collect/ForwardingMapEntry.java b/android/guava/src/com/google/common/collect/ForwardingMapEntry.java index 198b94bd5506..660da5f7bbe0 100644 --- a/android/guava/src/com/google/common/collect/ForwardingMapEntry.java +++ b/android/guava/src/com/google/common/collect/ForwardingMapEntry.java @@ -16,12 +16,12 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Map; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * A map entry which forwards all its method calls to another map entry. Subclasses should override @@ -34,7 +34,7 @@ * should override {@code equals} as well, either providing your own implementation, or delegating * to the provided {@code standardEquals} method. * - *

    Each of the {@code standard} methods, where appropriate, use {@link Objects#equal} to test + *

    Each of the {@code standard} methods, where appropriate, use {@link Objects#equals} to test * equality for both keys and values. This may not be the desired behavior for map implementations * that use non-standard notions of key equality, such as the entry of a {@code SortedMap} whose * comparator is not consistent with {@code equals}. @@ -47,7 +47,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingMapEntry extends ForwardingObject implements Map.Entry { +public abstract class ForwardingMapEntry + extends ForwardingObject implements Map.Entry { // TODO(lowasser): identify places where thread safety is actually lost /** Constructor for use by subclasses. */ @@ -57,22 +58,26 @@ protected ForwardingMapEntry() {} protected abstract Entry delegate(); @Override + @ParametricNullness public K getKey() { return delegate().getKey(); } @Override + @ParametricNullness public V getValue() { return delegate().getValue(); } @Override - public V setValue(V value) { + @ParametricNullness + @CanIgnoreReturnValue + public V setValue(@ParametricNullness V value) { return delegate().setValue(value); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return delegate().equals(object); } @@ -88,11 +93,11 @@ public int hashCode() { * * @since 7.0 */ - protected boolean standardEquals(@NullableDecl Object object) { + protected boolean standardEquals(@Nullable Object object) { if (object instanceof Entry) { Entry that = (Entry) object; - return Objects.equal(this.getKey(), that.getKey()) - && Objects.equal(this.getValue(), that.getValue()); + return Objects.equals(this.getKey(), that.getKey()) + && Objects.equals(this.getValue(), that.getValue()); } return false; } @@ -117,7 +122,6 @@ protected int standardHashCode() { * * @since 7.0 */ - @Beta protected String standardToString() { return getKey() + "=" + getValue(); } diff --git a/android/guava/src/com/google/common/collect/ForwardingMultimap.java b/android/guava/src/com/google/common/collect/ForwardingMultimap.java index 991d0cf9a089..817d91c032f8 100644 --- a/android/guava/src/com/google/common/collect/ForwardingMultimap.java +++ b/android/guava/src/com/google/common/collect/ForwardingMultimap.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A multimap which forwards all its method calls to another multimap. Subclasses should override @@ -37,7 +37,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingMultimap extends ForwardingObject implements Multimap { +public abstract class ForwardingMultimap + extends ForwardingObject implements Multimap { /** Constructor for use by subclasses. */ protected ForwardingMultimap() {} @@ -56,17 +57,17 @@ public void clear() { } @Override - public boolean containsEntry(@NullableDecl Object key, @NullableDecl Object value) { + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { return delegate().containsEntry(key, value); } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return delegate().containsKey(key); } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return delegate().containsValue(value); } @@ -76,7 +77,7 @@ public Collection> entries() { } @Override - public Collection get(@NullableDecl K key) { + public Collection get(@ParametricNullness K key) { return delegate().get(key); } @@ -97,13 +98,13 @@ public Set keySet() { @CanIgnoreReturnValue @Override - public boolean put(K key, V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { return delegate().put(key, value); } @CanIgnoreReturnValue @Override - public boolean putAll(K key, Iterable values) { + public boolean putAll(@ParametricNullness K key, Iterable values) { return delegate().putAll(key, values); } @@ -115,19 +116,19 @@ public boolean putAll(Multimap multimap) { @CanIgnoreReturnValue @Override - public boolean remove(@NullableDecl Object key, @NullableDecl Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { return delegate().remove(key, value); } @CanIgnoreReturnValue @Override - public Collection removeAll(@NullableDecl Object key) { + public Collection removeAll(@Nullable Object key) { return delegate().removeAll(key); } @CanIgnoreReturnValue @Override - public Collection replaceValues(K key, Iterable values) { + public Collection replaceValues(@ParametricNullness K key, Iterable values) { return delegate().replaceValues(key, values); } @@ -142,7 +143,9 @@ public Collection values() { } @Override - public boolean equals(@NullableDecl Object object) { + // A forwarding implementation can't do any better than the underlying object. + @SuppressWarnings("UndefinedEquals") + public boolean equals(@Nullable Object object) { return object == this || delegate().equals(object); } diff --git a/android/guava/src/com/google/common/collect/ForwardingMultiset.java b/android/guava/src/com/google/common/collect/ForwardingMultiset.java index 9a7084f54950..8185375eb7ba 100644 --- a/android/guava/src/com/google/common/collect/ForwardingMultiset.java +++ b/android/guava/src/com/google/common/collect/ForwardingMultiset.java @@ -16,14 +16,13 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; import java.util.Iterator; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A multiset which forwards all its method calls to another multiset. Subclasses should override @@ -48,7 +47,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingMultiset extends ForwardingCollection implements Multiset { +public abstract class ForwardingMultiset extends ForwardingCollection + implements Multiset { /** Constructor for use by subclasses. */ protected ForwardingMultiset() {} @@ -57,19 +57,19 @@ protected ForwardingMultiset() {} protected abstract Multiset delegate(); @Override - public int count(Object element) { + public int count(@Nullable Object element) { return delegate().count(element); } @CanIgnoreReturnValue @Override - public int add(E element, int occurrences) { + public int add(@ParametricNullness E element, int occurrences) { return delegate().add(element, occurrences); } @CanIgnoreReturnValue @Override - public int remove(Object element, int occurrences) { + public int remove(@Nullable Object element, int occurrences) { return delegate().remove(element, occurrences); } @@ -84,7 +84,7 @@ public Set> entrySet() { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return object == this || delegate().equals(object); } @@ -95,13 +95,13 @@ public int hashCode() { @CanIgnoreReturnValue @Override - public int setCount(E element, int count) { + public int setCount(@ParametricNullness E element, int count) { return delegate().setCount(element, count); } @CanIgnoreReturnValue @Override - public boolean setCount(E element, int oldCount, int newCount) { + public boolean setCount(@ParametricNullness E element, int oldCount, int newCount) { return delegate().setCount(element, oldCount, newCount); } @@ -112,7 +112,7 @@ public boolean setCount(E element, int oldCount, int newCount) { * @since 7.0 */ @Override - protected boolean standardContains(@NullableDecl Object object) { + protected boolean standardContains(@Nullable Object object) { return count(object) > 0; } @@ -135,10 +135,9 @@ protected void standardClear() { * * @since 7.0 */ - @Beta - protected int standardCount(@NullableDecl Object object) { + protected int standardCount(@Nullable Object object) { for (Entry entry : this.entrySet()) { - if (Objects.equal(entry.getElement(), object)) { + if (Objects.equals(entry.getElement(), object)) { return entry.getCount(); } } @@ -152,7 +151,7 @@ protected int standardCount(@NullableDecl Object object) { * * @since 7.0 */ - protected boolean standardAdd(E element) { + protected boolean standardAdd(@ParametricNullness E element) { add(element, 1); return true; } @@ -164,7 +163,6 @@ protected boolean standardAdd(E element) { * * @since 7.0 */ - @Beta @Override protected boolean standardAddAll(Collection elementsToAdd) { return Multisets.addAllImpl(this, elementsToAdd); @@ -178,7 +176,7 @@ protected boolean standardAddAll(Collection elementsToAdd) { * @since 7.0 */ @Override - protected boolean standardRemove(Object element) { + protected boolean standardRemove(@Nullable Object element) { return remove(element, 1) > 0; } @@ -214,7 +212,7 @@ protected boolean standardRetainAll(Collection elementsToRetain) { * * @since 7.0 */ - protected int standardSetCount(E element, int count) { + protected int standardSetCount(@ParametricNullness E element, int count) { return Multisets.setCountImpl(this, element, count); } @@ -225,7 +223,7 @@ protected int standardSetCount(E element, int count) { * * @since 7.0 */ - protected boolean standardSetCount(E element, int oldCount, int newCount) { + protected boolean standardSetCount(@ParametricNullness E element, int oldCount, int newCount) { return Multisets.setCountImpl(this, element, oldCount, newCount); } @@ -240,7 +238,6 @@ protected boolean standardSetCount(E element, int oldCount, int newCount) { * * @since 10.0 */ - @Beta protected class StandardElementSet extends Multisets.ElementSet { /** Constructor for use by subclasses. */ public StandardElementSet() {} @@ -285,7 +282,7 @@ protected int standardSize() { * * @since 7.0 */ - protected boolean standardEquals(@NullableDecl Object object) { + protected boolean standardEquals(@Nullable Object object) { return Multisets.equalsImpl(this, object); } diff --git a/android/guava/src/com/google/common/collect/ForwardingNavigableMap.java b/android/guava/src/com/google/common/collect/ForwardingNavigableMap.java index c8d0fd54b62d..35bfda60fc01 100644 --- a/android/guava/src/com/google/common/collect/ForwardingNavigableMap.java +++ b/android/guava/src/com/google/common/collect/ForwardingNavigableMap.java @@ -16,16 +16,15 @@ package com.google.common.collect; -import static com.google.common.collect.CollectPreconditions.checkRemove; import static com.google.common.collect.Maps.keyOrNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import java.util.Iterator; import java.util.NavigableMap; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.SortedMap; +import org.jspecify.annotations.Nullable; /** * A navigable map which forwards all its method calls to another navigable map. Subclasses should @@ -54,8 +53,8 @@ * @since 12.0 */ @GwtIncompatible -public abstract class ForwardingNavigableMap extends ForwardingSortedMap - implements NavigableMap { +public abstract class ForwardingNavigableMap + extends ForwardingSortedMap implements NavigableMap { /** Constructor for use by subclasses. */ protected ForwardingNavigableMap() {} @@ -64,7 +63,7 @@ protected ForwardingNavigableMap() {} protected abstract NavigableMap delegate(); @Override - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(@ParametricNullness K key) { return delegate().lowerEntry(key); } @@ -73,12 +72,12 @@ public Entry lowerEntry(K key) { * #headMap(Object, boolean)}. If you override {@code headMap}, you may wish to override {@code * lowerEntry} to forward to this implementation. */ - protected Entry standardLowerEntry(K key) { + protected @Nullable Entry standardLowerEntry(@ParametricNullness K key) { return headMap(key, false).lastEntry(); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(@ParametricNullness K key) { return delegate().lowerKey(key); } @@ -87,12 +86,12 @@ public K lowerKey(K key) { * {@link #lowerEntry}, you may wish to override {@code lowerKey} to forward to this * implementation. */ - protected K standardLowerKey(K key) { + protected @Nullable K standardLowerKey(@ParametricNullness K key) { return keyOrNull(lowerEntry(key)); } @Override - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(@ParametricNullness K key) { return delegate().floorEntry(key); } @@ -101,12 +100,12 @@ public Entry floorEntry(K key) { * #headMap(Object, boolean)}. If you override {@code headMap}, you may wish to override {@code * floorEntry} to forward to this implementation. */ - protected Entry standardFloorEntry(K key) { + protected @Nullable Entry standardFloorEntry(@ParametricNullness K key) { return headMap(key, true).lastEntry(); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(@ParametricNullness K key) { return delegate().floorKey(key); } @@ -115,12 +114,12 @@ public K floorKey(K key) { * {@code floorEntry}, you may wish to override {@code floorKey} to forward to this * implementation. */ - protected K standardFloorKey(K key) { + protected @Nullable K standardFloorKey(@ParametricNullness K key) { return keyOrNull(floorEntry(key)); } @Override - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(@ParametricNullness K key) { return delegate().ceilingEntry(key); } @@ -129,12 +128,12 @@ public Entry ceilingEntry(K key) { * #tailMap(Object, boolean)}. If you override {@code tailMap}, you may wish to override {@code * ceilingEntry} to forward to this implementation. */ - protected Entry standardCeilingEntry(K key) { + protected @Nullable Entry standardCeilingEntry(@ParametricNullness K key) { return tailMap(key, true).firstEntry(); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(@ParametricNullness K key) { return delegate().ceilingKey(key); } @@ -143,12 +142,12 @@ public K ceilingKey(K key) { * {@code ceilingEntry}, you may wish to override {@code ceilingKey} to forward to this * implementation. */ - protected K standardCeilingKey(K key) { + protected @Nullable K standardCeilingKey(@ParametricNullness K key) { return keyOrNull(ceilingEntry(key)); } @Override - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(@ParametricNullness K key) { return delegate().higherEntry(key); } @@ -157,12 +156,12 @@ public Entry higherEntry(K key) { * #tailMap(Object, boolean)}. If you override {@code tailMap}, you may wish to override {@code * higherEntry} to forward to this implementation. */ - protected Entry standardHigherEntry(K key) { + protected @Nullable Entry standardHigherEntry(@ParametricNullness K key) { return tailMap(key, false).firstEntry(); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(@ParametricNullness K key) { return delegate().higherKey(key); } @@ -171,12 +170,12 @@ public K higherKey(K key) { * {@code higherEntry}, you may wish to override {@code higherKey} to forward to this * implementation. */ - protected K standardHigherKey(K key) { + protected @Nullable K standardHigherKey(@ParametricNullness K key) { return keyOrNull(higherEntry(key)); } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return delegate().firstEntry(); } @@ -185,8 +184,8 @@ public Entry firstEntry() { * #entrySet}. If you override {@code entrySet}, you may wish to override {@code firstEntry} to * forward to this implementation. */ - protected Entry standardFirstEntry() { - return Iterables.getFirst(entrySet(), null); + protected @Nullable Entry standardFirstEntry() { + return Iterables.<@Nullable Entry>getFirst(entrySet(), null); } /** @@ -204,7 +203,7 @@ protected K standardFirstKey() { } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return delegate().lastEntry(); } @@ -213,8 +212,8 @@ public Entry lastEntry() { * #entrySet} of {@link #descendingMap}. If you override {@code descendingMap}, you may wish to * override {@code lastEntry} to forward to this implementation. */ - protected Entry standardLastEntry() { - return Iterables.getFirst(descendingMap().entrySet(), null); + protected @Nullable Entry standardLastEntry() { + return Iterables.<@Nullable Entry>getFirst(descendingMap().entrySet(), null); } /** @@ -231,7 +230,7 @@ protected K standardLastKey() { } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return delegate().pollFirstEntry(); } @@ -240,12 +239,12 @@ public Entry pollFirstEntry() { * entrySet}. If you override {@code entrySet}, you may wish to override {@code pollFirstEntry} to * forward to this implementation. */ - protected Entry standardPollFirstEntry() { + protected @Nullable Entry standardPollFirstEntry() { return Iterators.pollNext(entrySet().iterator()); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return delegate().pollLastEntry(); } @@ -254,7 +253,7 @@ public Entry pollLastEntry() { * entrySet} of {@code descendingMap}. If you override {@code descendingMap}, you may wish to * override {@code pollFirstEntry} to forward to this implementation. */ - protected Entry standardPollLastEntry() { + protected @Nullable Entry standardPollLastEntry() { return Iterators.pollNext(descendingMap().entrySet().iterator()); } @@ -274,7 +273,6 @@ public NavigableMap descendingMap() { * * @since 12.0 */ - @Beta protected class StandardDescendingMap extends Maps.DescendingMap { /** Constructor for use by subclasses. */ public StandardDescendingMap() {} @@ -287,8 +285,8 @@ NavigableMap forward() { @Override protected Iterator> entryIterator() { return new Iterator>() { - private Entry toRemove = null; - private Entry nextOrNull = forward().lastEntry(); + private @Nullable Entry toRemove = null; + private @Nullable Entry nextOrNull = forward().lastEntry(); @Override public boolean hasNext() { @@ -296,8 +294,8 @@ public boolean hasNext() { } @Override - public java.util.Map.Entry next() { - if (!hasNext()) { + public Entry next() { + if (nextOrNull == null) { throw new NoSuchElementException(); } try { @@ -310,7 +308,9 @@ public java.util.Map.Entry next() { @Override public void remove() { - checkRemove(toRemove != null); + if (toRemove == null) { + throw new IllegalStateException("no calls to next() since the last call to remove()"); + } forward().remove(toRemove.getKey()); toRemove = null; } @@ -331,7 +331,6 @@ public NavigableSet navigableKeySet() { * * @since 12.0 */ - @Beta protected class StandardNavigableKeySet extends Maps.NavigableKeySet { /** Constructor for use by subclasses. */ public StandardNavigableKeySet() { @@ -351,7 +350,6 @@ public NavigableSet descendingKeySet() { * descendingMap}, you may wish to override {@code descendingKeySet} to forward to this * implementation. */ - @Beta protected NavigableSet standardDescendingKeySet() { return descendingMap().navigableKeySet(); } @@ -362,22 +360,27 @@ protected NavigableSet standardDescendingKeySet() { * wish to override {@code subMap} to forward to this implementation. */ @Override - protected SortedMap standardSubMap(K fromKey, K toKey) { + protected SortedMap standardSubMap( + @ParametricNullness K fromKey, @ParametricNullness K toKey) { return subMap(fromKey, true, toKey, false); } @Override - public NavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + public NavigableMap subMap( + @ParametricNullness K fromKey, + boolean fromInclusive, + @ParametricNullness K toKey, + boolean toInclusive) { return delegate().subMap(fromKey, fromInclusive, toKey, toInclusive); } @Override - public NavigableMap headMap(K toKey, boolean inclusive) { + public NavigableMap headMap(@ParametricNullness K toKey, boolean inclusive) { return delegate().headMap(toKey, inclusive); } @Override - public NavigableMap tailMap(K fromKey, boolean inclusive) { + public NavigableMap tailMap(@ParametricNullness K fromKey, boolean inclusive) { return delegate().tailMap(fromKey, inclusive); } @@ -386,7 +389,7 @@ public NavigableMap tailMap(K fromKey, boolean inclusive) { * boolean)}. If you override {@code headMap(K, boolean)}, you may wish to override {@code * headMap} to forward to this implementation. */ - protected SortedMap standardHeadMap(K toKey) { + protected SortedMap standardHeadMap(@ParametricNullness K toKey) { return headMap(toKey, false); } @@ -395,7 +398,7 @@ protected SortedMap standardHeadMap(K toKey) { * boolean)}. If you override {@code tailMap(K, boolean)}, you may wish to override {@code * tailMap} to forward to this implementation. */ - protected SortedMap standardTailMap(K fromKey) { + protected SortedMap standardTailMap(@ParametricNullness K fromKey) { return tailMap(fromKey, true); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingNavigableSet.java b/android/guava/src/com/google/common/collect/ForwardingNavigableSet.java index 827698edd545..8cf8286325ce 100644 --- a/android/guava/src/com/google/common/collect/ForwardingNavigableSet.java +++ b/android/guava/src/com/google/common/collect/ForwardingNavigableSet.java @@ -16,11 +16,11 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import java.util.Iterator; import java.util.NavigableSet; import java.util.SortedSet; +import org.jspecify.annotations.Nullable; /** * A navigable set which forwards all its method calls to another navigable set. Subclasses should @@ -49,8 +49,8 @@ * @since 12.0 */ @GwtIncompatible -public abstract class ForwardingNavigableSet extends ForwardingSortedSet - implements NavigableSet { +public abstract class ForwardingNavigableSet + extends ForwardingSortedSet implements NavigableSet { /** Constructor for use by subclasses. */ protected ForwardingNavigableSet() {} @@ -59,7 +59,7 @@ protected ForwardingNavigableSet() {} protected abstract NavigableSet delegate(); @Override - public E lower(E e) { + public @Nullable E lower(@ParametricNullness E e) { return delegate().lower(e); } @@ -68,12 +68,12 @@ public E lower(E e) { * {@link #headSet(Object, boolean)}. If you override {@link #headSet(Object, boolean)}, you may * wish to override {@link #lower} to forward to this implementation. */ - protected E standardLower(E e) { + protected @Nullable E standardLower(@ParametricNullness E e) { return Iterators.getNext(headSet(e, false).descendingIterator(), null); } @Override - public E floor(E e) { + public @Nullable E floor(@ParametricNullness E e) { return delegate().floor(e); } @@ -82,12 +82,12 @@ public E floor(E e) { * {@link #headSet(Object, boolean)}. If you override {@link #headSet(Object, boolean)}, you may * wish to override {@link #floor} to forward to this implementation. */ - protected E standardFloor(E e) { + protected @Nullable E standardFloor(@ParametricNullness E e) { return Iterators.getNext(headSet(e, true).descendingIterator(), null); } @Override - public E ceiling(E e) { + public @Nullable E ceiling(@ParametricNullness E e) { return delegate().ceiling(e); } @@ -96,12 +96,12 @@ public E ceiling(E e) { * #tailSet(Object, boolean)}. If you override {@link #tailSet(Object, boolean)}, you may wish to * override {@link #ceiling} to forward to this implementation. */ - protected E standardCeiling(E e) { + protected @Nullable E standardCeiling(@ParametricNullness E e) { return Iterators.getNext(tailSet(e, true).iterator(), null); } @Override - public E higher(E e) { + public @Nullable E higher(@ParametricNullness E e) { return delegate().higher(e); } @@ -110,12 +110,12 @@ public E higher(E e) { * #tailSet(Object, boolean)}. If you override {@link #tailSet(Object, boolean)}, you may wish to * override {@link #higher} to forward to this implementation. */ - protected E standardHigher(E e) { + protected @Nullable E standardHigher(@ParametricNullness E e) { return Iterators.getNext(tailSet(e, false).iterator(), null); } @Override - public E pollFirst() { + public @Nullable E pollFirst() { return delegate().pollFirst(); } @@ -124,12 +124,12 @@ public E pollFirst() { * override {@link #iterator} you may wish to override {@link #pollFirst} to forward to this * implementation. */ - protected E standardPollFirst() { + protected @Nullable E standardPollFirst() { return Iterators.pollNext(iterator()); } @Override - public E pollLast() { + public @Nullable E pollLast() { return delegate().pollLast(); } @@ -138,14 +138,16 @@ public E pollLast() { * If you override {@link #descendingIterator} you may wish to override {@link #pollLast} to * forward to this implementation. */ - protected E standardPollLast() { + protected @Nullable E standardPollLast() { return Iterators.pollNext(descendingIterator()); } + @ParametricNullness protected E standardFirst() { return iterator().next(); } + @ParametricNullness protected E standardLast() { return descendingIterator().next(); } @@ -164,7 +166,6 @@ public NavigableSet descendingSet() { * * @since 12.0 */ - @Beta protected class StandardDescendingSet extends Sets.DescendingSet { /** Constructor for use by subclasses. */ public StandardDescendingSet() { @@ -179,7 +180,10 @@ public Iterator descendingIterator() { @Override public NavigableSet subSet( - E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + @ParametricNullness E fromElement, + boolean fromInclusive, + @ParametricNullness E toElement, + boolean toInclusive) { return delegate().subSet(fromElement, fromInclusive, toElement, toInclusive); } @@ -188,9 +192,11 @@ public NavigableSet subSet( * {@code headSet} and {@code tailSet} methods. In many cases, you may wish to override {@link * #subSet(Object, boolean, Object, boolean)} to forward to this implementation. */ - @Beta protected NavigableSet standardSubSet( - E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + @ParametricNullness E fromElement, + boolean fromInclusive, + @ParametricNullness E toElement, + boolean toInclusive) { return tailSet(fromElement, fromInclusive).headSet(toElement, toInclusive); } @@ -201,12 +207,13 @@ protected NavigableSet standardSubSet( * implementation. */ @Override - protected SortedSet standardSubSet(E fromElement, E toElement) { + protected SortedSet standardSubSet( + @ParametricNullness E fromElement, @ParametricNullness E toElement) { return subSet(fromElement, true, toElement, false); } @Override - public NavigableSet headSet(E toElement, boolean inclusive) { + public NavigableSet headSet(@ParametricNullness E toElement, boolean inclusive) { return delegate().headSet(toElement, inclusive); } @@ -215,12 +222,12 @@ public NavigableSet headSet(E toElement, boolean inclusive) { * boolean)} method. If you override {@link #headSet(Object, boolean)}, you may wish to override * {@link #headSet(Object)} to forward to this implementation. */ - protected SortedSet standardHeadSet(E toElement) { + protected SortedSet standardHeadSet(@ParametricNullness E toElement) { return headSet(toElement, false); } @Override - public NavigableSet tailSet(E fromElement, boolean inclusive) { + public NavigableSet tailSet(@ParametricNullness E fromElement, boolean inclusive) { return delegate().tailSet(fromElement, inclusive); } @@ -229,7 +236,7 @@ public NavigableSet tailSet(E fromElement, boolean inclusive) { * boolean)} method. If you override {@link #tailSet(Object, boolean)}, you may wish to override * {@link #tailSet(Object)} to forward to this implementation. */ - protected SortedSet standardTailSet(E fromElement) { + protected SortedSet standardTailSet(@ParametricNullness E fromElement) { return tailSet(fromElement, true); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingQueue.java b/android/guava/src/com/google/common/collect/ForwardingQueue.java index f77e5608d3ff..8fbe467a07eb 100644 --- a/android/guava/src/com/google/common/collect/ForwardingQueue.java +++ b/android/guava/src/com/google/common/collect/ForwardingQueue.java @@ -20,6 +20,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.NoSuchElementException; import java.util.Queue; +import org.jspecify.annotations.Nullable; /** * A queue which forwards all its method calls to another queue. Subclasses should override one or @@ -44,7 +45,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingQueue extends ForwardingCollection implements Queue { +public abstract class ForwardingQueue extends ForwardingCollection + implements Queue { /** Constructor for use by subclasses. */ protected ForwardingQueue() {} @@ -54,28 +56,30 @@ protected ForwardingQueue() {} @CanIgnoreReturnValue // TODO(cpovirk): Consider removing this? @Override - public boolean offer(E o) { + public boolean offer(@ParametricNullness E o) { return delegate().offer(o); } @CanIgnoreReturnValue // TODO(cpovirk): Consider removing this? @Override - public E poll() { + public @Nullable E poll() { return delegate().poll(); } @CanIgnoreReturnValue @Override + @ParametricNullness public E remove() { return delegate().remove(); } @Override - public E peek() { + public @Nullable E peek() { return delegate().peek(); } @Override + @ParametricNullness public E element() { return delegate().element(); } @@ -86,7 +90,7 @@ public E element() { * * @since 7.0 */ - protected boolean standardOffer(E e) { + protected boolean standardOffer(@ParametricNullness E e) { try { return add(e); } catch (IllegalStateException caught) { @@ -100,7 +104,7 @@ protected boolean standardOffer(E e) { * * @since 7.0 */ - protected E standardPeek() { + protected @Nullable E standardPeek() { try { return element(); } catch (NoSuchElementException caught) { @@ -114,7 +118,7 @@ protected E standardPeek() { * * @since 7.0 */ - protected E standardPoll() { + protected @Nullable E standardPoll() { try { return remove(); } catch (NoSuchElementException caught) { diff --git a/android/guava/src/com/google/common/collect/ForwardingSet.java b/android/guava/src/com/google/common/collect/ForwardingSet.java index 73b1413a3c5d..6a2444402102 100644 --- a/android/guava/src/com/google/common/collect/ForwardingSet.java +++ b/android/guava/src/com/google/common/collect/ForwardingSet.java @@ -21,7 +21,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Collection; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A set which forwards all its method calls to another set. Subclasses should override one or more @@ -46,7 +46,8 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingSet extends ForwardingCollection implements Set { +public abstract class ForwardingSet extends ForwardingCollection + implements Set { // TODO(lowasser): identify places where thread safety is actually lost /** Constructor for use by subclasses. */ @@ -56,7 +57,7 @@ protected ForwardingSet() {} protected abstract Set delegate(); @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return object == this || delegate().equals(object); } @@ -84,7 +85,7 @@ protected boolean standardRemoveAll(Collection collection) { * * @since 7.0 */ - protected boolean standardEquals(@NullableDecl Object object) { + protected boolean standardEquals(@Nullable Object object) { return Sets.equalsImpl(this, object); } diff --git a/android/guava/src/com/google/common/collect/ForwardingSetMultimap.java b/android/guava/src/com/google/common/collect/ForwardingSetMultimap.java index 61a0de2bd19b..84876397917d 100644 --- a/android/guava/src/com/google/common/collect/ForwardingSetMultimap.java +++ b/android/guava/src/com/google/common/collect/ForwardingSetMultimap.java @@ -20,7 +20,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A set multimap which forwards all its method calls to another set multimap. Subclasses should @@ -35,8 +35,10 @@ * @since 3.0 */ @GwtCompatible -public abstract class ForwardingSetMultimap extends ForwardingMultimap - implements SetMultimap { +public abstract class ForwardingSetMultimap + extends ForwardingMultimap implements SetMultimap { + /** Constructor for use by subclasses. */ + public ForwardingSetMultimap() {} @Override protected abstract SetMultimap delegate(); @@ -47,19 +49,19 @@ public Set> entries() { } @Override - public Set get(@NullableDecl K key) { + public Set get(@ParametricNullness K key) { return delegate().get(key); } @CanIgnoreReturnValue @Override - public Set removeAll(@NullableDecl Object key) { + public Set removeAll(@Nullable Object key) { return delegate().removeAll(key); } @CanIgnoreReturnValue @Override - public Set replaceValues(K key, Iterable values) { + public Set replaceValues(@ParametricNullness K key, Iterable values) { return delegate().replaceValues(key, values); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingSortedMap.java b/android/guava/src/com/google/common/collect/ForwardingSortedMap.java index 4866fb9850b6..2dcfba314183 100644 --- a/android/guava/src/com/google/common/collect/ForwardingSortedMap.java +++ b/android/guava/src/com/google/common/collect/ForwardingSortedMap.java @@ -18,12 +18,11 @@ import static com.google.common.base.Preconditions.checkArgument; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import java.util.Comparator; import java.util.NoSuchElementException; import java.util.SortedMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A sorted map which forwards all its method calls to another sorted map. Subclasses should @@ -51,8 +50,13 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingSortedMap extends ForwardingMap - implements SortedMap { +/* + * We provide and encourage use of ForwardingNavigableSet over this class, but we still provide this + * one to preserve compatibility. + */ +@SuppressWarnings("JdkObsolete") +public abstract class ForwardingSortedMap + extends ForwardingMap implements SortedMap { // TODO(lowasser): identify places where thread safety is actually lost /** Constructor for use by subclasses. */ @@ -62,32 +66,34 @@ protected ForwardingSortedMap() {} protected abstract SortedMap delegate(); @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return delegate().comparator(); } @Override + @ParametricNullness public K firstKey() { return delegate().firstKey(); } @Override - public SortedMap headMap(K toKey) { + public SortedMap headMap(@ParametricNullness K toKey) { return delegate().headMap(toKey); } @Override + @ParametricNullness public K lastKey() { return delegate().lastKey(); } @Override - public SortedMap subMap(K fromKey, K toKey) { + public SortedMap subMap(@ParametricNullness K fromKey, @ParametricNullness K toKey) { return delegate().subMap(fromKey, toKey); } @Override - public SortedMap tailMap(K fromKey) { + public SortedMap tailMap(@ParametricNullness K fromKey) { return delegate().tailMap(fromKey); } @@ -98,7 +104,6 @@ public SortedMap tailMap(K fromKey) { * * @since 15.0 */ - @Beta protected class StandardKeySet extends Maps.SortedKeySet { /** Constructor for use by subclasses. */ public StandardKeySet() { @@ -106,14 +111,14 @@ public StandardKeySet() { } } - // unsafe, but worst case is a CCE is thrown, which callers will be expecting - @SuppressWarnings("unchecked") - private int unsafeCompare(Object k1, Object k2) { - Comparator comparator = comparator(); + // unsafe, but worst case is a CCE or NPE is thrown, which callers will be expecting + @SuppressWarnings({"unchecked", "nullness"}) + static int unsafeCompare( + @Nullable Comparator comparator, @Nullable Object o1, @Nullable Object o2) { if (comparator == null) { - return ((Comparable) k1).compareTo(k2); + return ((Comparable<@Nullable Object>) o1).compareTo(o2); } else { - return ((Comparator) comparator).compare(k1, k2); + return ((Comparator<@Nullable Object>) comparator).compare(o1, o2); } } @@ -125,14 +130,13 @@ private int unsafeCompare(Object k1, Object k2) { * @since 7.0 */ @Override - @Beta - protected boolean standardContainsKey(@NullableDecl Object key) { + protected boolean standardContainsKey(@Nullable Object key) { try { - // any CCE will be caught - @SuppressWarnings("unchecked") - SortedMap self = (SortedMap) this; + // any CCE or NPE will be caught + @SuppressWarnings({"unchecked", "nullness"}) + SortedMap<@Nullable Object, V> self = (SortedMap<@Nullable Object, V>) this; Object ceilingKey = self.tailMap(key).firstKey(); - return unsafeCompare(ceilingKey, key) == 0; + return unsafeCompare(comparator(), ceilingKey, key) == 0; } catch (ClassCastException | NoSuchElementException | NullPointerException e) { return false; } @@ -145,9 +149,8 @@ protected boolean standardContainsKey(@NullableDecl Object key) { * * @since 7.0 */ - @Beta protected SortedMap standardSubMap(K fromKey, K toKey) { - checkArgument(unsafeCompare(fromKey, toKey) <= 0, "fromKey must be <= toKey"); + checkArgument(unsafeCompare(comparator(), fromKey, toKey) <= 0, "fromKey must be <= toKey"); return tailMap(fromKey).headMap(toKey); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingSortedMultiset.java b/android/guava/src/com/google/common/collect/ForwardingSortedMultiset.java index 1d34fb3d559f..d76d1be4b91f 100644 --- a/android/guava/src/com/google/common/collect/ForwardingSortedMultiset.java +++ b/android/guava/src/com/google/common/collect/ForwardingSortedMultiset.java @@ -14,11 +14,11 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import java.util.Comparator; import java.util.Iterator; import java.util.NavigableSet; +import org.jspecify.annotations.Nullable; /** * A sorted multiset which forwards all its method calls to another sorted multiset. Subclasses @@ -42,10 +42,9 @@ * @author Louis Wasserman * @since 15.0 */ -@Beta -@GwtCompatible(emulated = true) -public abstract class ForwardingSortedMultiset extends ForwardingMultiset - implements SortedMultiset { +@GwtCompatible +public abstract class ForwardingSortedMultiset + extends ForwardingMultiset implements SortedMultiset { /** Constructor for use by subclasses. */ protected ForwardingSortedMultiset() {} @@ -110,7 +109,7 @@ SortedMultiset forwardMultiset() { } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return delegate().firstEntry(); } @@ -120,7 +119,7 @@ public Entry firstEntry() { *

    If you override {@link #entrySet()}, you may wish to override {@link #firstEntry()} to * forward to this implementation. */ - protected Entry standardFirstEntry() { + protected @Nullable Entry standardFirstEntry() { Iterator> entryIterator = entrySet().iterator(); if (!entryIterator.hasNext()) { return null; @@ -130,7 +129,7 @@ protected Entry standardFirstEntry() { } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return delegate().lastEntry(); } @@ -141,7 +140,7 @@ public Entry lastEntry() { *

    If you override {@link #descendingMultiset} or {@link #entrySet()}, you may wish to override * {@link #firstEntry()} to forward to this implementation. */ - protected Entry standardLastEntry() { + protected @Nullable Entry standardLastEntry() { Iterator> entryIterator = descendingMultiset().entrySet().iterator(); if (!entryIterator.hasNext()) { return null; @@ -151,7 +150,7 @@ protected Entry standardLastEntry() { } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return delegate().pollFirstEntry(); } @@ -161,7 +160,7 @@ public Entry pollFirstEntry() { *

    If you override {@link #entrySet()}, you may wish to override {@link #pollFirstEntry()} to * forward to this implementation. */ - protected Entry standardPollFirstEntry() { + protected @Nullable Entry standardPollFirstEntry() { Iterator> entryIterator = entrySet().iterator(); if (!entryIterator.hasNext()) { return null; @@ -173,7 +172,7 @@ protected Entry standardPollFirstEntry() { } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return delegate().pollLastEntry(); } @@ -184,7 +183,7 @@ public Entry pollLastEntry() { *

    If you override {@link #descendingMultiset()} or {@link #entrySet()}, you may wish to * override {@link #pollLastEntry()} to forward to this implementation. */ - protected Entry standardPollLastEntry() { + protected @Nullable Entry standardPollLastEntry() { Iterator> entryIterator = descendingMultiset().entrySet().iterator(); if (!entryIterator.hasNext()) { return null; @@ -196,13 +195,16 @@ protected Entry standardPollLastEntry() { } @Override - public SortedMultiset headMultiset(E upperBound, BoundType boundType) { + public SortedMultiset headMultiset(@ParametricNullness E upperBound, BoundType boundType) { return delegate().headMultiset(upperBound, boundType); } @Override public SortedMultiset subMultiset( - E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType) { + @ParametricNullness E lowerBound, + BoundType lowerBoundType, + @ParametricNullness E upperBound, + BoundType upperBoundType) { return delegate().subMultiset(lowerBound, lowerBoundType, upperBound, upperBoundType); } @@ -215,12 +217,15 @@ public SortedMultiset subMultiset( * #subMultiset(Object, BoundType, Object, BoundType)} to forward to this implementation. */ protected SortedMultiset standardSubMultiset( - E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType) { + @ParametricNullness E lowerBound, + BoundType lowerBoundType, + @ParametricNullness E upperBound, + BoundType upperBoundType) { return tailMultiset(lowerBound, lowerBoundType).headMultiset(upperBound, upperBoundType); } @Override - public SortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + public SortedMultiset tailMultiset(@ParametricNullness E lowerBound, BoundType boundType) { return delegate().tailMultiset(lowerBound, boundType); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingSortedSet.java b/android/guava/src/com/google/common/collect/ForwardingSortedSet.java index 9879944838e8..42d96d89c3fb 100644 --- a/android/guava/src/com/google/common/collect/ForwardingSortedSet.java +++ b/android/guava/src/com/google/common/collect/ForwardingSortedSet.java @@ -16,13 +16,14 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; +import static com.google.common.collect.ForwardingSortedMap.unsafeCompare; + import com.google.common.annotations.GwtCompatible; import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A sorted set which forwards all its method calls to another sorted set. Subclasses should @@ -52,7 +53,13 @@ * @since 2.0 */ @GwtCompatible -public abstract class ForwardingSortedSet extends ForwardingSet implements SortedSet { +/* + * We provide and encourage use of ForwardingNavigableSet over this class, but we still provide this + * one to preserve compatibility. + */ +@SuppressWarnings("JdkObsolete") +public abstract class ForwardingSortedSet extends ForwardingSet + implements SortedSet { /** Constructor for use by subclasses. */ protected ForwardingSortedSet() {} @@ -61,44 +68,37 @@ protected ForwardingSortedSet() {} protected abstract SortedSet delegate(); @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return delegate().comparator(); } @Override + @ParametricNullness public E first() { return delegate().first(); } @Override - public SortedSet headSet(E toElement) { + public SortedSet headSet(@ParametricNullness E toElement) { return delegate().headSet(toElement); } @Override + @ParametricNullness public E last() { return delegate().last(); } @Override - public SortedSet subSet(E fromElement, E toElement) { + public SortedSet subSet(@ParametricNullness E fromElement, @ParametricNullness E toElement) { return delegate().subSet(fromElement, toElement); } @Override - public SortedSet tailSet(E fromElement) { + public SortedSet tailSet(@ParametricNullness E fromElement) { return delegate().tailSet(fromElement); } - // unsafe, but worst case is a CCE is thrown, which callers will be expecting - @SuppressWarnings("unchecked") - private int unsafeCompare(@NullableDecl Object o1, @NullableDecl Object o2) { - Comparator comparator = comparator(); - return (comparator == null) - ? ((Comparable) o1).compareTo(o2) - : ((Comparator) comparator).compare(o1, o2); - } - /** * A sensible definition of {@link #contains} in terms of the {@code first()} method of {@link * #tailSet}. If you override {@link #tailSet}, you may wish to override {@link #contains} to @@ -107,14 +107,13 @@ private int unsafeCompare(@NullableDecl Object o1, @NullableDecl Object o2) { * @since 7.0 */ @Override - @Beta - protected boolean standardContains(@NullableDecl Object object) { + protected boolean standardContains(@Nullable Object object) { try { - // any ClassCastExceptions are caught - @SuppressWarnings("unchecked") - SortedSet self = (SortedSet) this; + // any ClassCastExceptions and NullPointerExceptions are caught + @SuppressWarnings({"unchecked", "nullness"}) + SortedSet<@Nullable Object> self = (SortedSet<@Nullable Object>) this; Object ceiling = self.tailSet(object).first(); - return unsafeCompare(ceiling, object) == 0; + return unsafeCompare(comparator(), ceiling, object) == 0; } catch (ClassCastException | NoSuchElementException | NullPointerException e) { return false; } @@ -128,16 +127,15 @@ protected boolean standardContains(@NullableDecl Object object) { * @since 7.0 */ @Override - @Beta - protected boolean standardRemove(@NullableDecl Object object) { + protected boolean standardRemove(@Nullable Object object) { try { - // any ClassCastExceptions are caught - @SuppressWarnings("unchecked") - SortedSet self = (SortedSet) this; - Iterator iterator = self.tailSet(object).iterator(); + // any ClassCastExceptions and NullPointerExceptions are caught + @SuppressWarnings({"unchecked", "nullness"}) + SortedSet<@Nullable Object> self = (SortedSet<@Nullable Object>) this; + Iterator iterator = self.tailSet(object).iterator(); if (iterator.hasNext()) { Object ceiling = iterator.next(); - if (unsafeCompare(ceiling, object) == 0) { + if (unsafeCompare(comparator(), ceiling, object) == 0) { iterator.remove(); return true; } @@ -155,8 +153,8 @@ protected boolean standardRemove(@NullableDecl Object object) { * * @since 7.0 */ - @Beta - protected SortedSet standardSubSet(E fromElement, E toElement) { + protected SortedSet standardSubSet( + @ParametricNullness E fromElement, @ParametricNullness E toElement) { return tailSet(fromElement).headSet(toElement); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingSortedSetMultimap.java b/android/guava/src/com/google/common/collect/ForwardingSortedSetMultimap.java index 78319a7732db..ff405066f856 100644 --- a/android/guava/src/com/google/common/collect/ForwardingSortedSetMultimap.java +++ b/android/guava/src/com/google/common/collect/ForwardingSortedSetMultimap.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Comparator; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A sorted set multimap which forwards all its method calls to another sorted set multimap. @@ -34,8 +34,9 @@ * @since 3.0 */ @GwtCompatible -public abstract class ForwardingSortedSetMultimap extends ForwardingSetMultimap - implements SortedSetMultimap { +public abstract class ForwardingSortedSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends ForwardingSetMultimap implements SortedSetMultimap { /** Constructor for use by subclasses. */ protected ForwardingSortedSetMultimap() {} @@ -44,22 +45,22 @@ protected ForwardingSortedSetMultimap() {} protected abstract SortedSetMultimap delegate(); @Override - public SortedSet get(@NullableDecl K key) { + public SortedSet get(@ParametricNullness K key) { return delegate().get(key); } @Override - public SortedSet removeAll(@NullableDecl Object key) { + public SortedSet removeAll(@Nullable Object key) { return delegate().removeAll(key); } @Override - public SortedSet replaceValues(K key, Iterable values) { + public SortedSet replaceValues(@ParametricNullness K key, Iterable values) { return delegate().replaceValues(key, values); } @Override - public Comparator valueComparator() { + public @Nullable Comparator valueComparator() { return delegate().valueComparator(); } } diff --git a/android/guava/src/com/google/common/collect/ForwardingTable.java b/android/guava/src/com/google/common/collect/ForwardingTable.java index 71a54cfbcfea..51f5861b051e 100644 --- a/android/guava/src/com/google/common/collect/ForwardingTable.java +++ b/android/guava/src/com/google/common/collect/ForwardingTable.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * A table which forwards all its method calls to another table. Subclasses should override one or @@ -31,7 +32,9 @@ * @since 7.0 */ @GwtCompatible -public abstract class ForwardingTable extends ForwardingObject implements Table { +public abstract class ForwardingTable< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + extends ForwardingObject implements Table { /** Constructor for use by subclasses. */ protected ForwardingTable() {} @@ -49,7 +52,7 @@ public void clear() { } @Override - public Map column(C columnKey) { + public Map column(@ParametricNullness C columnKey) { return delegate().column(columnKey); } @@ -64,27 +67,27 @@ public Map> columnMap() { } @Override - public boolean contains(Object rowKey, Object columnKey) { + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { return delegate().contains(rowKey, columnKey); } @Override - public boolean containsColumn(Object columnKey) { + public boolean containsColumn(@Nullable Object columnKey) { return delegate().containsColumn(columnKey); } @Override - public boolean containsRow(Object rowKey) { + public boolean containsRow(@Nullable Object rowKey) { return delegate().containsRow(rowKey); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(@Nullable Object value) { return delegate().containsValue(value); } @Override - public V get(Object rowKey, Object columnKey) { + public @Nullable V get(@Nullable Object rowKey, @Nullable Object columnKey) { return delegate().get(rowKey, columnKey); } @@ -95,7 +98,8 @@ public boolean isEmpty() { @CanIgnoreReturnValue @Override - public V put(R rowKey, C columnKey, V value) { + public @Nullable V put( + @ParametricNullness R rowKey, @ParametricNullness C columnKey, @ParametricNullness V value) { return delegate().put(rowKey, columnKey, value); } @@ -106,12 +110,12 @@ public void putAll(Table table) { @CanIgnoreReturnValue @Override - public V remove(Object rowKey, Object columnKey) { + public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { return delegate().remove(rowKey, columnKey); } @Override - public Map row(R rowKey) { + public Map row(@ParametricNullness R rowKey) { return delegate().row(rowKey); } @@ -136,7 +140,7 @@ public Collection values() { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return (obj == this) || delegate().equals(obj); } diff --git a/android/guava/src/com/google/common/collect/GeneralRange.java b/android/guava/src/com/google/common/collect/GeneralRange.java index 69561872bde8..d04d35aee288 100644 --- a/android/guava/src/com/google/common/collect/GeneralRange.java +++ b/android/guava/src/com/google/common/collect/GeneralRange.java @@ -18,12 +18,14 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.BoundType.CLOSED; import static com.google.common.collect.BoundType.OPEN; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.io.Serializable; import java.util.Comparator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * A generalized interval on any ordering, for internal use. Supports {@code null}. Unlike {@link @@ -34,16 +36,17 @@ * * @author Louis Wasserman */ -@GwtCompatible(serializable = true) -final class GeneralRange implements Serializable { +@GwtCompatible +final class GeneralRange implements Serializable { /** Converts a Range to a GeneralRange. */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 static GeneralRange from(Range range) { - @NullableDecl T lowerEndpoint = range.hasLowerBound() ? range.lowerEndpoint() : null; + T lowerEndpoint = range.hasLowerBound() ? range.lowerEndpoint() : null; BoundType lowerBoundType = range.hasLowerBound() ? range.lowerBoundType() : OPEN; - @NullableDecl T upperEndpoint = range.hasUpperBound() ? range.upperEndpoint() : null; + T upperEndpoint = range.hasUpperBound() ? range.upperEndpoint() : null; BoundType upperBoundType = range.hasUpperBound() ? range.upperBoundType() : OPEN; - return new GeneralRange( + return new GeneralRange<>( Ordering.natural(), range.hasLowerBound(), lowerEndpoint, @@ -54,56 +57,56 @@ static GeneralRange from(Range range) { } /** Returns the whole range relative to the specified comparator. */ - static GeneralRange all(Comparator comparator) { - return new GeneralRange(comparator, false, null, OPEN, false, null, OPEN); + static GeneralRange all(Comparator comparator) { + return new GeneralRange<>(comparator, false, null, OPEN, false, null, OPEN); } /** * Returns everything above the endpoint relative to the specified comparator, with the specified * endpoint behavior. */ - static GeneralRange downTo( - Comparator comparator, @NullableDecl T endpoint, BoundType boundType) { - return new GeneralRange(comparator, true, endpoint, boundType, false, null, OPEN); + static GeneralRange downTo( + Comparator comparator, @ParametricNullness T endpoint, BoundType boundType) { + return new GeneralRange<>(comparator, true, endpoint, boundType, false, null, OPEN); } /** * Returns everything below the endpoint relative to the specified comparator, with the specified * endpoint behavior. */ - static GeneralRange upTo( - Comparator comparator, @NullableDecl T endpoint, BoundType boundType) { - return new GeneralRange(comparator, false, null, OPEN, true, endpoint, boundType); + static GeneralRange upTo( + Comparator comparator, @ParametricNullness T endpoint, BoundType boundType) { + return new GeneralRange<>(comparator, false, null, OPEN, true, endpoint, boundType); } /** * Returns everything between the endpoints relative to the specified comparator, with the * specified endpoint behavior. */ - static GeneralRange range( + static GeneralRange range( Comparator comparator, - @NullableDecl T lower, + @ParametricNullness T lower, BoundType lowerType, - @NullableDecl T upper, + @ParametricNullness T upper, BoundType upperType) { - return new GeneralRange(comparator, true, lower, lowerType, true, upper, upperType); + return new GeneralRange<>(comparator, true, lower, lowerType, true, upper, upperType); } private final Comparator comparator; private final boolean hasLowerBound; - @NullableDecl private final T lowerEndpoint; + private final @Nullable T lowerEndpoint; private final BoundType lowerBoundType; private final boolean hasUpperBound; - @NullableDecl private final T upperEndpoint; + private final @Nullable T upperEndpoint; private final BoundType upperBoundType; private GeneralRange( Comparator comparator, boolean hasLowerBound, - @NullableDecl T lowerEndpoint, + @Nullable T lowerEndpoint, BoundType lowerBoundType, boolean hasUpperBound, - @NullableDecl T upperEndpoint, + @Nullable T upperEndpoint, BoundType upperBoundType) { this.comparator = checkNotNull(comparator); this.hasLowerBound = hasLowerBound; @@ -113,19 +116,31 @@ private GeneralRange( this.upperEndpoint = upperEndpoint; this.upperBoundType = checkNotNull(upperBoundType); + // Trigger any exception that the comparator would throw for the endpoints. + /* + * uncheckedCastNullableTToT is safe as long as the callers are careful to pass a "real" T + * whenever they pass `true` for the matching `has*Bound` parameter. + */ if (hasLowerBound) { - comparator.compare(lowerEndpoint, lowerEndpoint); + int unused = + comparator.compare( + uncheckedCastNullableTToT(lowerEndpoint), uncheckedCastNullableTToT(lowerEndpoint)); } if (hasUpperBound) { - comparator.compare(upperEndpoint, upperEndpoint); + int unused = + comparator.compare( + uncheckedCastNullableTToT(upperEndpoint), uncheckedCastNullableTToT(upperEndpoint)); } + if (hasLowerBound && hasUpperBound) { - int cmp = comparator.compare(lowerEndpoint, upperEndpoint); + int cmp = + comparator.compare( + uncheckedCastNullableTToT(lowerEndpoint), uncheckedCastNullableTToT(upperEndpoint)); // be consistent with Range checkArgument( cmp <= 0, "lowerEndpoint (%s) > upperEndpoint (%s)", lowerEndpoint, upperEndpoint); if (cmp == 0) { - checkArgument(lowerBoundType != OPEN | upperBoundType != OPEN); + checkArgument(lowerBoundType != OPEN || upperBoundType != OPEN); } } } @@ -143,41 +158,45 @@ boolean hasUpperBound() { } boolean isEmpty() { - return (hasUpperBound() && tooLow(getUpperEndpoint())) - || (hasLowerBound() && tooHigh(getLowerEndpoint())); + // The casts are safe because of the has*Bound() checks. + return (hasUpperBound() && tooLow(uncheckedCastNullableTToT(getUpperEndpoint()))) + || (hasLowerBound() && tooHigh(uncheckedCastNullableTToT(getLowerEndpoint()))); } - boolean tooLow(@NullableDecl T t) { + boolean tooLow(@ParametricNullness T t) { if (!hasLowerBound()) { return false; } - T lbound = getLowerEndpoint(); + // The cast is safe because of the hasLowerBound() check. + T lbound = uncheckedCastNullableTToT(getLowerEndpoint()); int cmp = comparator.compare(t, lbound); return cmp < 0 | (cmp == 0 & getLowerBoundType() == OPEN); } - boolean tooHigh(@NullableDecl T t) { + boolean tooHigh(@ParametricNullness T t) { if (!hasUpperBound()) { return false; } - T ubound = getUpperEndpoint(); + // The cast is safe because of the hasUpperBound() check. + T ubound = uncheckedCastNullableTToT(getUpperEndpoint()); int cmp = comparator.compare(t, ubound); return cmp > 0 | (cmp == 0 & getUpperBoundType() == OPEN); } - boolean contains(@NullableDecl T t) { + boolean contains(@ParametricNullness T t) { return !tooLow(t) && !tooHigh(t); } /** * Returns the intersection of the two ranges, or an empty range if their intersection is empty. */ + @SuppressWarnings("nullness") // TODO(cpovirk): Add casts as needed. Will be noisy and annoying... GeneralRange intersect(GeneralRange other) { checkNotNull(other); checkArgument(comparator.equals(other.comparator)); boolean hasLowBound = this.hasLowerBound; - @NullableDecl T lowEnd = getLowerEndpoint(); + T lowEnd = getLowerEndpoint(); BoundType lowType = getLowerBoundType(); if (!hasLowerBound()) { hasLowBound = other.hasLowerBound; @@ -192,7 +211,7 @@ GeneralRange intersect(GeneralRange other) { } boolean hasUpBound = this.hasUpperBound; - @NullableDecl T upEnd = getUpperEndpoint(); + T upEnd = getUpperEndpoint(); BoundType upType = getUpperBoundType(); if (!hasUpperBound()) { hasUpBound = other.hasUpperBound; @@ -216,11 +235,11 @@ GeneralRange intersect(GeneralRange other) { } } - return new GeneralRange(comparator, hasLowBound, lowEnd, lowType, hasUpBound, upEnd, upType); + return new GeneralRange<>(comparator, hasLowBound, lowEnd, lowType, hasUpBound, upEnd, upType); } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof GeneralRange) { GeneralRange r = (GeneralRange) obj; return comparator.equals(r.comparator) @@ -228,15 +247,15 @@ public boolean equals(@NullableDecl Object obj) { && hasUpperBound == r.hasUpperBound && getLowerBoundType().equals(r.getLowerBoundType()) && getUpperBoundType().equals(r.getUpperBoundType()) - && Objects.equal(getLowerEndpoint(), r.getLowerEndpoint()) - && Objects.equal(getUpperEndpoint(), r.getUpperEndpoint()); + && Objects.equals(getLowerEndpoint(), r.getLowerEndpoint()) + && Objects.equals(getUpperEndpoint(), r.getUpperEndpoint()); } return false; } @Override public int hashCode() { - return Objects.hashCode( + return Objects.hash( comparator, getLowerEndpoint(), getLowerBoundType(), @@ -244,15 +263,15 @@ public int hashCode() { getUpperBoundType()); } - @NullableDecl private transient GeneralRange reverse; + @LazyInit private transient @Nullable GeneralRange reverse; /** Returns the same range relative to the reversed comparator. */ GeneralRange reverse() { GeneralRange result = reverse; if (result == null) { result = - new GeneralRange( - Ordering.from(comparator).reverse(), + new GeneralRange<>( + reverseComparator(comparator), hasUpperBound, getUpperEndpoint(), getUpperBoundType(), @@ -265,6 +284,12 @@ GeneralRange reverse() { return result; } + // This method helps J2KT's type inference. + private static Comparator reverseComparator( + Comparator comparator) { + return Ordering.from(comparator).reverse(); + } + @Override public String toString() { return comparator @@ -276,7 +301,7 @@ public String toString() { + (upperBoundType == CLOSED ? ']' : ')'); } - T getLowerEndpoint() { + @Nullable T getLowerEndpoint() { return lowerEndpoint; } @@ -284,7 +309,7 @@ BoundType getLowerBoundType() { return lowerBoundType; } - T getUpperEndpoint() { + @Nullable T getUpperEndpoint() { return upperEndpoint; } diff --git a/android/guava/src/com/google/common/collect/HashBasedTable.java b/android/guava/src/com/google/common/collect/HashBasedTable.java index 07c144f6d28b..5d35cb0e14ad 100644 --- a/android/guava/src/com/google/common/collect/HashBasedTable.java +++ b/android/guava/src/com/google/common/collect/HashBasedTable.java @@ -19,12 +19,12 @@ import static com.google.common.collect.CollectPreconditions.checkNonnegative; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Supplier; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; /** * Implementation of {@link Table} using linked hash tables. This guarantees predictable iteration @@ -43,14 +43,14 @@ * concurrently and one of the threads modifies the table, it must be synchronized externally. * *

    See the Guava User Guide article on {@code Table}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#table">{@code Table}. * * @author Jared Levy * @since 7.0 */ -@GwtCompatible(serializable = true) +@GwtCompatible public class HashBasedTable extends StandardTable { - private static class Factory implements Supplier>, Serializable { + private static final class Factory implements Supplier>, Serializable { final int expectedSize; Factory(int expectedSize) { @@ -62,7 +62,7 @@ public Map get() { return Maps.newLinkedHashMapWithExpectedSize(expectedSize); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** Creates an empty {@code HashBasedTable}. */ @@ -103,43 +103,5 @@ public static HashBasedTable create( super(backingMap, factory); } - // Overriding so NullPointerTester test passes. - - @Override - public boolean contains(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { - return super.contains(rowKey, columnKey); - } - - @Override - public boolean containsColumn(@NullableDecl Object columnKey) { - return super.containsColumn(columnKey); - } - - @Override - public boolean containsRow(@NullableDecl Object rowKey) { - return super.containsRow(rowKey); - } - - @Override - public boolean containsValue(@NullableDecl Object value) { - return super.containsValue(value); - } - - @Override - public V get(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { - return super.get(rowKey, columnKey); - } - - @Override - public boolean equals(@NullableDecl Object obj) { - return super.equals(obj); - } - - @CanIgnoreReturnValue - @Override - public V remove(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { - return super.remove(rowKey, columnKey); - } - - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/HashBiMap.java b/android/guava/src/com/google/common/collect/HashBiMap.java index 18951fe08be9..e936acd5ef90 100644 --- a/android/guava/src/com/google/common/collect/HashBiMap.java +++ b/android/guava/src/com/google/common/collect/HashBiMap.java @@ -15,10 +15,12 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static com.google.common.collect.NullnessCasts.unsafeNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Objects; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedWith; @@ -33,8 +35,9 @@ import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@link BiMap} backed by two hash tables. This implementation allows null keys and values. A @@ -43,17 +46,18 @@ *

    This implementation guarantees insertion-based iteration order of its keys. * *

    See the Guava User Guide article on {@code BiMap} . + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap">{@code BiMap} . * * @author Louis Wasserman * @author Mike Bostock * @since 2.0 */ @GwtCompatible -public final class HashBiMap extends AbstractMap implements BiMap, Serializable { +public final class HashBiMap + extends AbstractMap implements BiMap, Serializable { /** Returns a new, empty {@code HashBiMap} with the default initial capacity (16). */ - public static HashBiMap create() { + public static HashBiMap create() { return create(16); } @@ -63,7 +67,8 @@ public static HashBiMap create() { * @param expectedSize the expected number of entries * @throws IllegalArgumentException if the specified expected size is negative */ - public static HashBiMap create(int expectedSize) { + public static HashBiMap create( + int expectedSize) { return new HashBiMap<>(expectedSize); } @@ -71,7 +76,8 @@ public static HashBiMap create(int expectedSize) { * Constructs a new bimap containing initial values from {@code map}. The bimap is created with an * initial capacity sufficient to hold the mappings in the specified map. */ - public static HashBiMap create(Map map) { + public static HashBiMap create( + Map map) { HashBiMap bimap = create(map.size()); bimap.putAll(map); return bimap; @@ -81,26 +87,35 @@ public static HashBiMap create(Map map) { private static final int ENDPOINT = -2; /** Maps an "entry" to the key of that entry. */ - transient K[] keys; + transient @Nullable K[] keys; + /** Maps an "entry" to the value of that entry. */ - transient V[] values; + transient @Nullable V[] values; transient int size; transient int modCount; + /** Maps a bucket to the "entry" of its first element. */ private transient int[] hashTableKToV; + /** Maps a bucket to the "entry" of its first element. */ private transient int[] hashTableVToK; + /** Maps an "entry" to the "entry" that follows it in its bucket. */ private transient int[] nextInBucketKToV; + /** Maps an "entry" to the "entry" that follows it in its bucket. */ private transient int[] nextInBucketVToK; + /** The "entry" of the first element in insertion order. */ - @NullableDecl private transient int firstInInsertionOrder; + private transient int firstInInsertionOrder; + /** The "entry" of the last element in insertion order. */ - @NullableDecl private transient int lastInInsertionOrder; + private transient int lastInInsertionOrder; + /** Maps an "entry" to the "entry" that precedes it in insertion order. */ private transient int[] prevInInsertionOrder; + /** Maps an "entry" to the "entry" that follows it in insertion order. */ private transient int[] nextInInsertionOrder; @@ -193,19 +208,19 @@ private int bucket(int hash) { } /** Given a key, returns the index of the entry in the tables, or ABSENT if not found. */ - int findEntryByKey(@NullableDecl Object key) { + int findEntryByKey(@Nullable Object key) { return findEntryByKey(key, Hashing.smearedHash(key)); } /** * Given a key and its hash, returns the index of the entry in the tables, or ABSENT if not found. */ - int findEntryByKey(@NullableDecl Object key, int keyHash) { + int findEntryByKey(@Nullable Object key, int keyHash) { return findEntry(key, keyHash, hashTableKToV, nextInBucketKToV, keys); } /** Given a value, returns the index of the entry in the tables, or ABSENT if not found. */ - int findEntryByValue(@NullableDecl Object value) { + int findEntryByValue(@Nullable Object value) { return findEntryByValue(value, Hashing.smearedHash(value)); } @@ -213,14 +228,18 @@ int findEntryByValue(@NullableDecl Object value) { * Given a value and its hash, returns the index of the entry in the tables, or ABSENT if not * found. */ - int findEntryByValue(@NullableDecl Object value, int valueHash) { + int findEntryByValue(@Nullable Object value, int valueHash) { return findEntry(value, valueHash, hashTableVToK, nextInBucketVToK, values); } int findEntry( - @NullableDecl Object o, int oHash, int[] hashTable, int[] nextInBucket, Object[] array) { + @Nullable Object o, + int oHash, + int[] hashTable, + int[] nextInBucket, + @Nullable Object[] array) { for (int entry = hashTable[bucket(oHash)]; entry != ABSENT; entry = nextInBucket[entry]) { - if (Objects.equal(array[entry], o)) { + if (Objects.equals(array[entry], o)) { return entry; } } @@ -228,7 +247,7 @@ int findEntry( } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return findEntryByKey(key) != ABSENT; } @@ -243,36 +262,33 @@ public boolean containsKey(@NullableDecl Object key) { * @return true if a mapping exists from a key to the specified value */ @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return findEntryByValue(value) != ABSENT; } @Override - @NullableDecl - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { int entry = findEntryByKey(key); return (entry == ABSENT) ? null : values[entry]; } - @NullableDecl - K getInverse(@NullableDecl Object value) { + @Nullable K getInverse(@Nullable Object value) { int entry = findEntryByValue(value); return (entry == ABSENT) ? null : keys[entry]; } @Override @CanIgnoreReturnValue - public V put(@NullableDecl K key, @NullableDecl V value) { + public @Nullable V put(@ParametricNullness K key, @ParametricNullness V value) { return put(key, value, false); } - @NullableDecl - V put(@NullableDecl K key, @NullableDecl V value, boolean force) { + @Nullable V put(@ParametricNullness K key, @ParametricNullness V value, boolean force) { int keyHash = Hashing.smearedHash(key); int entryForKey = findEntryByKey(key, keyHash); if (entryForKey != ABSENT) { V oldValue = values[entryForKey]; - if (Objects.equal(oldValue, value)) { + if (Objects.equals(oldValue, value)) { return value; } else { replaceValueInEntry(entryForKey, value, force); @@ -306,18 +322,17 @@ V put(@NullableDecl K key, @NullableDecl V value, boolean force) { @Override @CanIgnoreReturnValue - @NullableDecl - public V forcePut(@NullableDecl K key, @NullableDecl V value) { + public @Nullable V forcePut(@ParametricNullness K key, @ParametricNullness V value) { return put(key, value, true); } - @NullableDecl - K putInverse(@NullableDecl V value, @NullableDecl K key, boolean force) { + @CanIgnoreReturnValue + @Nullable K putInverse(@ParametricNullness V value, @ParametricNullness K key, boolean force) { int valueHash = Hashing.smearedHash(value); int entryForValue = findEntryByValue(value, valueHash); if (entryForValue != ABSENT) { K oldKey = keys[entryForValue]; - if (Objects.equal(oldKey, key)) { + if (Objects.equals(oldKey, key)) { return key; } else { replaceKeyInEntry(entryForValue, key, force); @@ -457,7 +472,7 @@ private void deleteFromTableVToK(int entry, int valueHash) { * Updates the specified entry to point to the new value: removes the old value from the V-to-K * mapping and puts the new one in. The entry does not move in the insertion order of the bimap. */ - private void replaceValueInEntry(int entry, @NullableDecl V newValue, boolean force) { + private void replaceValueInEntry(int entry, @ParametricNullness V newValue, boolean force) { checkArgument(entry != ABSENT); int newValueHash = Hashing.smearedHash(newValue); int newValueIndex = findEntryByValue(newValue, newValueHash); @@ -482,7 +497,7 @@ private void replaceValueInEntry(int entry, @NullableDecl V newValue, boolean fo * mapping and puts the new one in. The entry is moved to the end of the insertion order, or to * the position of the new key if it was previously present. */ - private void replaceKeyInEntry(int entry, @NullableDecl K newKey, boolean force) { + private void replaceKeyInEntry(int entry, @ParametricNullness K newKey, boolean force) { checkArgument(entry != ABSENT); int newKeyHash = Hashing.smearedHash(newKey); int newKeyIndex = findEntryByKey(newKey, newKeyHash); @@ -528,27 +543,25 @@ private void replaceKeyInEntry(int entry, @NullableDecl K newKey, boolean force) @Override @CanIgnoreReturnValue - @NullableDecl - public V remove(@NullableDecl Object key) { + public @Nullable V remove(@Nullable Object key) { int keyHash = Hashing.smearedHash(key); int entry = findEntryByKey(key, keyHash); if (entry == ABSENT) { return null; } else { - @NullableDecl V value = values[entry]; + V value = values[entry]; removeEntryKeyHashKnown(entry, keyHash); return value; } } - @NullableDecl - K removeInverse(@NullableDecl Object value) { + @Nullable K removeInverse(@Nullable Object value) { int valueHash = Hashing.smearedHash(value); int entry = findEntryByValue(value, valueHash); if (entry == ABSENT) { return null; } else { - @NullableDecl K key = keys[entry]; + K key = keys[entry]; removeEntryValueHashKnown(entry, valueHash); return key; } @@ -663,13 +676,16 @@ public void clear() { } /** Shared supertype of keySet, values, entrySet, and inverse.entrySet. */ - abstract static class View extends AbstractSet { + abstract static class View< + K extends @Nullable Object, V extends @Nullable Object, T extends @Nullable Object> + extends AbstractSet { final HashBiMap biMap; View(HashBiMap biMap) { this.biMap = biMap; } + @ParametricNullness abstract T forEntry(int entry); @Override @@ -696,6 +712,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -732,7 +749,7 @@ public void clear() { } } - private transient Set keySet; + @LazyInit private transient Set keySet; @Override public Set keySet() { @@ -746,17 +763,19 @@ final class KeySet extends View { } @Override + @ParametricNullness K forEntry(int entry) { - return keys[entry]; + // The cast is safe because we call forEntry only for indexes that contain entries. + return uncheckedCastNullableTToT(keys[entry]); } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { return HashBiMap.this.containsKey(o); } @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { int oHash = Hashing.smearedHash(o); int entry = findEntryByKey(o, oHash); if (entry != ABSENT) { @@ -768,7 +787,7 @@ public boolean remove(@NullableDecl Object o) { } } - private transient Set valueSet; + @LazyInit private transient Set valueSet; @Override public Set values() { @@ -782,17 +801,19 @@ final class ValueSet extends View { } @Override + @ParametricNullness V forEntry(int entry) { - return values[entry]; + // The cast is safe because we call forEntry only for indexes that contain entries. + return uncheckedCastNullableTToT(values[entry]); } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { return HashBiMap.this.containsValue(o); } @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { int oHash = Hashing.smearedHash(o); int entry = findEntryByValue(o, oHash); if (entry != ABSENT) { @@ -804,7 +825,7 @@ public boolean remove(@NullableDecl Object o) { } } - private transient Set> entrySet; + @LazyInit private transient Set> entrySet; @Override public Set> entrySet() { @@ -818,27 +839,27 @@ final class EntrySet extends View> { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Entry) { Entry e = (Entry) o; - @NullableDecl Object k = e.getKey(); - @NullableDecl Object v = e.getValue(); + Object k = e.getKey(); + Object v = e.getValue(); int eIndex = findEntryByKey(k); - return eIndex != ABSENT && Objects.equal(v, values[eIndex]); + return eIndex != ABSENT && Objects.equals(v, values[eIndex]); } return false; } @Override @CanIgnoreReturnValue - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { if (o instanceof Entry) { Entry e = (Entry) o; - @NullableDecl Object k = e.getKey(); - @NullableDecl Object v = e.getValue(); + Object k = e.getKey(); + Object v = e.getValue(); int kHash = Hashing.smearedHash(k); int eIndex = findEntryByKey(k, kHash); - if (eIndex != ABSENT && Objects.equal(v, values[eIndex])) { + if (eIndex != ABSENT && Objects.equals(v, values[eIndex])) { removeEntryKeyHashKnown(eIndex, kHash); return true; } @@ -855,43 +876,65 @@ Entry forEntry(int entry) { /** * An {@code Entry} implementation that attempts to follow its key around the map -- that is, if * the key is moved, deleted, or reinserted, it will account for that -- while not doing any extra - * work if the key has not moved. + * work if the key has not moved. One quirk: The {@link #getValue()} method can return {@code + * null} even for a map which supposedly does not contain null elements, if the key is not present + * when {@code getValue()} is called. */ final class EntryForKey extends AbstractMapEntry { - @NullableDecl final K key; + @ParametricNullness final K key; int index; EntryForKey(int index) { - this.key = keys[index]; + // The cast is safe because we call forEntry only for indexes that contain entries. + this.key = uncheckedCastNullableTToT(keys[index]); this.index = index; } void updateIndex() { - if (index == ABSENT || index > size || !Objects.equal(keys[index], key)) { + if (index == ABSENT || index > size || !Objects.equals(keys[index], key)) { index = findEntryByKey(key); } } @Override + @ParametricNullness public K getKey() { return key; } @Override - @NullableDecl + @ParametricNullness public V getValue() { updateIndex(); - return (index == ABSENT) ? null : values[index]; + /* + * If the entry has been removed from the map, we return null, even though that might not be a + * valid value. That's the best we can do, short of holding a reference to the most recently + * seen value. And while we *could* do that, we aren't required to: Map.Entry explicitly says + * that behavior is undefined when the backing map is modified through another API. (It even + * permits us to throw IllegalStateException. Maybe we should have done that, but we probably + * shouldn't change now for fear of breaking people.) + * + * If the entry is still in the map, then updateIndex ensured that `index` points to the right + * element. Because that element is present, uncheckedCastNullableTToT is safe. + */ + return (index == ABSENT) ? unsafeNull() : uncheckedCastNullableTToT(values[index]); } @Override - public V setValue(V value) { + @ParametricNullness + public V setValue(@ParametricNullness V value) { updateIndex(); if (index == ABSENT) { - return HashBiMap.this.put(key, value); + HashBiMap.this.put(key, value); + return unsafeNull(); // See the discussion in getValue(). } - V oldValue = values[index]; - if (Objects.equal(oldValue, value)) { + /* + * The cast is safe because updateIndex found the entry for this key. (If it hadn't, then we + * would have returned above.) Thus, we know that it and its corresponding value are in + * position `index`. + */ + V oldValue = uncheckedCastNullableTToT(values[index]); + if (Objects.equals(oldValue, value)) { return value; } replaceValueInEntry(index, value, false); @@ -899,15 +942,16 @@ public V setValue(V value) { } } - @LazyInit @RetainedWith @NullableDecl private transient BiMap inverse; + @LazyInit @RetainedWith private transient @Nullable BiMap inverse; @Override public BiMap inverse() { BiMap result = inverse; - return (result == null) ? inverse = new Inverse(this) : result; + return (result == null) ? inverse = new Inverse<>(this) : result; } - static class Inverse extends AbstractMap implements BiMap, Serializable { + private static final class Inverse + extends AbstractMap implements BiMap, Serializable { private final HashBiMap forward; Inverse(HashBiMap forward) { @@ -920,32 +964,29 @@ public int size() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return forward.containsValue(key); } @Override - @NullableDecl - public K get(@NullableDecl Object key) { + public @Nullable K get(@Nullable Object key) { return forward.getInverse(key); } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return forward.containsKey(value); } @Override @CanIgnoreReturnValue - @NullableDecl - public K put(@NullableDecl V value, @NullableDecl K key) { + public @Nullable K put(@ParametricNullness V value, @ParametricNullness K key) { return forward.putInverse(value, key, false); } @Override @CanIgnoreReturnValue - @NullableDecl - public K forcePut(@NullableDecl V value, @NullableDecl K key) { + public @Nullable K forcePut(@ParametricNullness V value, @ParametricNullness K key) { return forward.putInverse(value, key, true); } @@ -956,8 +997,7 @@ public BiMap inverse() { @Override @CanIgnoreReturnValue - @NullableDecl - public K remove(@NullableDecl Object value) { + public @Nullable K remove(@Nullable Object value) { return forward.removeInverse(value); } @@ -991,32 +1031,33 @@ private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOE } } - static class InverseEntrySet extends View> { + private static final class InverseEntrySet + extends View> { InverseEntrySet(HashBiMap biMap) { super(biMap); } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Entry) { Entry e = (Entry) o; Object v = e.getKey(); Object k = e.getValue(); int eIndex = biMap.findEntryByValue(v); - return eIndex != ABSENT && Objects.equal(biMap.keys[eIndex], k); + return eIndex != ABSENT && Objects.equals(biMap.keys[eIndex], k); } return false; } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { if (o instanceof Entry) { Entry e = (Entry) o; Object v = e.getKey(); Object k = e.getValue(); int vHash = Hashing.smearedHash(v); int eIndex = biMap.findEntryByValue(v, vHash); - if (eIndex != ABSENT && Objects.equal(biMap.keys[eIndex], k)) { + if (eIndex != ABSENT && Objects.equals(biMap.keys[eIndex], k)) { biMap.removeEntryValueHashKnown(eIndex, vHash); return true; } @@ -1035,42 +1076,49 @@ Entry forEntry(int entry) { * the value is moved, deleted, or reinserted, it will account for that -- while not doing any * extra work if the value has not moved. */ - static final class EntryForValue extends AbstractMapEntry { + static final class EntryForValue + extends AbstractMapEntry { final HashBiMap biMap; - final V value; + @ParametricNullness final V value; int index; EntryForValue(HashBiMap biMap, int index) { this.biMap = biMap; - this.value = biMap.values[index]; + // The cast is safe because we call forEntry only for indexes that contain entries. + this.value = uncheckedCastNullableTToT(biMap.values[index]); this.index = index; } private void updateIndex() { - if (index == ABSENT || index > biMap.size || !Objects.equal(value, biMap.values[index])) { + if (index == ABSENT || index > biMap.size || !Objects.equals(value, biMap.values[index])) { index = biMap.findEntryByValue(value); } } @Override + @ParametricNullness public V getKey() { return value; } @Override + @ParametricNullness public K getValue() { updateIndex(); - return (index == ABSENT) ? null : biMap.keys[index]; + // For discussion of unsafeNull() and uncheckedCastNullableTToT(), see EntryForKey.getValue(). + return (index == ABSENT) ? unsafeNull() : uncheckedCastNullableTToT(biMap.keys[index]); } @Override - public K setValue(K key) { + @ParametricNullness + public K setValue(@ParametricNullness K key) { updateIndex(); if (index == ABSENT) { - return biMap.putInverse(value, key, false); + biMap.putInverse(value, key, false); + return unsafeNull(); // see EntryForKey.setValue() } - K oldKey = biMap.keys[index]; - if (Objects.equal(oldKey, key)) { + K oldKey = uncheckedCastNullableTToT(biMap.keys[index]); // see EntryForKey.setValue() + if (Objects.equals(oldKey, key)) { return key; } biMap.replaceKeyInEntry(index, key, false); @@ -1081,17 +1129,21 @@ public K setValue(K key) { /** * @serialData the number of entries, first key, first value, second key, second value, and so on. */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMap(this, stream); } - @GwtIncompatible // java.io.ObjectInputStream - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int size = Serialization.readCount(stream); init(16); // resist hostile attempts to allocate gratuitous heap Serialization.populateMap(this, stream, size); } + + // TODO(cpovirk): Should we have a serialVersionUID here? } diff --git a/android/guava/src/com/google/common/collect/HashMultimap.java b/android/guava/src/com/google/common/collect/HashMultimap.java index 92972608ecde..e334ecbb6ffe 100644 --- a/android/guava/src/com/google/common/collect/HashMultimap.java +++ b/android/guava/src/com/google/common/collect/HashMultimap.java @@ -18,6 +18,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.io.IOException; @@ -26,6 +27,7 @@ import java.util.Collection; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link Multimap} using hash tables. @@ -41,11 +43,15 @@ * concurrent update operations, wrap your multimap with a call to {@link * Multimaps#synchronizedSetMultimap}. * + *

    Warning: Do not modify either a key or a value of a {@code HashMultimap} in a + * way that affects its {@link Object#equals} behavior. Undefined behavior and bugs will result. + * * @author Jared Levy * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) -public final class HashMultimap extends HashMultimapGwtSerializationDependencies { +@GwtCompatible +public final class HashMultimap + extends AbstractSetMultimap { private static final int DEFAULT_VALUES_PER_KEY = 2; @VisibleForTesting transient int expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; @@ -53,10 +59,12 @@ public final class HashMultimap extends HashMultimapGwtSerializationDepend /** * Creates a new, empty {@code HashMultimap} with the default initial capacities. * - *

    This method will soon be deprecated in favor of {@code - * MultimapBuilder.hashKeys().hashSetValues().build()}. + *

    You may also consider the equivalent {@code + * MultimapBuilder.hashKeys().hashSetValues().build()}, which provides more control over the + * underlying data structure. */ - public static HashMultimap create() { + public static + HashMultimap create() { return new HashMultimap<>(); } @@ -64,15 +72,17 @@ public static HashMultimap create() { * Constructs an empty {@code HashMultimap} with enough capacity to hold the specified numbers of * keys and values without rehashing. * - *

    This method will soon be deprecated in favor of {@code - * MultimapBuilder.hashKeys(expectedKeys).hashSetValues(expectedValuesPerKey).build()}. + *

    You may also consider the equivalent {@code + * MultimapBuilder.hashKeys(expectedKeys).hashSetValues(expectedValuesPerKey).build()}, which + * provides more control over the underlying data structure. * * @param expectedKeys the expected number of distinct keys * @param expectedValuesPerKey the expected average number of values per key * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is * negative */ - public static HashMultimap create(int expectedKeys, int expectedValuesPerKey) { + public static HashMultimap create( + int expectedKeys, int expectedValuesPerKey) { return new HashMultimap<>(expectedKeys, expectedValuesPerKey); } @@ -81,12 +91,14 @@ public static HashMultimap create(int expectedKeys, int expectedVal * key-value mapping appears multiple times in the input multimap, it only appears once in the * constructed multimap. * - *

    This method will soon be deprecated in favor of {@code - * MultimapBuilder.hashKeys().hashSetValues().build(multimap)}. + *

    You may also consider the equivalent {@code + * MultimapBuilder.hashKeys().hashSetValues().build(multimap)}, which provides more control over + * the underlying data structure. * * @param multimap the multimap whose contents are copied to this multimap */ - public static HashMultimap create(Multimap multimap) { + public static HashMultimap create( + Multimap multimap) { return new HashMultimap<>(multimap); } @@ -95,13 +107,13 @@ private HashMultimap() { } private HashMultimap(int expectedKeys, int expectedValuesPerKey) { - super(Platform.>newHashMapWithExpectedSize(expectedKeys)); + super(Platform.newHashMapWithExpectedSize(expectedKeys)); Preconditions.checkArgument(expectedValuesPerKey >= 0); this.expectedValuesPerKey = expectedValuesPerKey; } private HashMultimap(Multimap multimap) { - super(Platform.>newHashMapWithExpectedSize(multimap.keySet().size())); + super(Platform.newHashMapWithExpectedSize(multimap.keySet().size())); putAll(multimap); } @@ -114,21 +126,23 @@ private HashMultimap(Multimap multimap) { */ @Override Set createCollection() { - return Platform.newHashSetWithExpectedSize(expectedValuesPerKey); + return Platform.newHashSetWithExpectedSize(expectedValuesPerKey); } /** * @serialData expectedValuesPerKey, number of distinct keys, and then for each distinct key: the * key, number of values for that key, and the key's values */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMultimap(this, stream); } - @GwtIncompatible // java.io.ObjectInputStream - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; int distinctKeys = Serialization.readCount(stream); @@ -137,6 +151,5 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo Serialization.populateMultimap(this, stream, distinctKeys); } - @GwtIncompatible // Not needed in emulated source - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/HashMultimapGwtSerializationDependencies.java b/android/guava/src/com/google/common/collect/HashMultimapGwtSerializationDependencies.java deleted file mode 100644 index 0922c3839080..000000000000 --- a/android/guava/src/com/google/common/collect/HashMultimapGwtSerializationDependencies.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2016 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import com.google.common.annotations.GwtCompatible; -import java.util.Collection; -import java.util.Map; - -/** - * A dummy superclass to support GWT serialization of the element types of a {@link HashMultimap}. - * The GWT supersource for this class contains a field for each type. - * - *

    For details about this hack, see {@code GwtSerializationDependencies}, which takes the same - * approach but with a subclass rather than a superclass. - * - *

    TODO(cpovirk): Consider applying this subclass approach to our other types. - */ -@GwtCompatible(emulated = true) -abstract class HashMultimapGwtSerializationDependencies extends AbstractSetMultimap { - HashMultimapGwtSerializationDependencies(Map> map) { - super(map); - } -} diff --git a/android/guava/src/com/google/common/collect/HashMultiset.java b/android/guava/src/com/google/common/collect/HashMultiset.java index a78c6915e26c..32062984f051 100644 --- a/android/guava/src/com/google/common/collect/HashMultiset.java +++ b/android/guava/src/com/google/common/collect/HashMultiset.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * Multiset implementation that uses hashing for key and entry access. @@ -26,11 +28,11 @@ * @author Jared Levy * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) -public class HashMultiset extends AbstractMapBasedMultiset { +@GwtCompatible +public final class HashMultiset extends AbstractMapBasedMultiset { /** Creates a new, empty {@code HashMultiset} using the default initial capacity. */ - public static HashMultiset create() { + public static HashMultiset create() { return create(ObjectCountHashMap.DEFAULT_SIZE); } @@ -41,8 +43,8 @@ public static HashMultiset create() { * @param distinctElements the expected number of distinct elements * @throws IllegalArgumentException if {@code distinctElements} is negative */ - public static HashMultiset create(int distinctElements) { - return new HashMultiset(distinctElements); + public static HashMultiset create(int distinctElements) { + return new HashMultiset<>(distinctElements); } /** @@ -52,7 +54,8 @@ public static HashMultiset create(int distinctElements) { * * @param elements the elements that the multiset should contain */ - public static HashMultiset create(Iterable elements) { + public static HashMultiset create( + Iterable elements) { HashMultiset multiset = create(Multisets.inferDistinctElements(elements)); Iterables.addAll(multiset, elements); return multiset; @@ -63,10 +66,9 @@ public static HashMultiset create(Iterable elements) { } @Override - void init(int distinctElements) { - backingMap = new ObjectCountHashMap<>(distinctElements); + ObjectCountHashMap newBackingMap(int distinctElements) { + return new ObjectCountHashMap<>(distinctElements); } - @GwtIncompatible // Not needed in emulated source. - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/Hashing.java b/android/guava/src/com/google/common/collect/Hashing.java index d5cab1faf1e0..1ea3af1c91d8 100644 --- a/android/guava/src/com/google/common/collect/Hashing.java +++ b/android/guava/src/com/google/common/collect/Hashing.java @@ -16,9 +16,11 @@ package com.google.common.collect; +import static java.lang.Math.max; + import com.google.common.annotations.GwtCompatible; import com.google.common.primitives.Ints; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static methods for implementing hash-based collections. @@ -50,7 +52,7 @@ static int smear(int hashCode) { return (int) (C2 * Integer.rotateLeft((int) (hashCode * C1), 15)); } - static int smearedHash(@NullableDecl Object o) { + static int smearedHash(@Nullable Object o) { return smear((o == null) ? 0 : o.hashCode()); } @@ -59,7 +61,7 @@ static int smearedHash(@NullableDecl Object o) { static int closedTableSize(int expectedEntries, double loadFactor) { // Get the recommended table size. // Round down to the nearest power of 2. - expectedEntries = Math.max(expectedEntries, 2); + expectedEntries = max(expectedEntries, 2); int tableSize = Integer.highestOneBit(expectedEntries); // Check to make sure that we will not exceed the maximum load factor. if (expectedEntries > (int) (loadFactor * tableSize)) { diff --git a/android/guava/src/com/google/common/collect/IgnoreJRERequirement.java b/android/guava/src/com/google/common/collect/IgnoreJRERequirement.java new file mode 100644 index 000000000000..9d9fb5da9f78 --- /dev/null +++ b/android/guava/src/com/google/common/collect/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/collect/ImmutableAsList.java b/android/guava/src/com/google/common/collect/ImmutableAsList.java index 528a8dca1f85..baf5afb54645 100644 --- a/android/guava/src/com/google/common/collect/ImmutableAsList.java +++ b/android/guava/src/com/google/common/collect/ImmutableAsList.java @@ -18,9 +18,11 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import org.jspecify.annotations.Nullable; /** * List returned by {@link ImmutableCollection#asList} that delegates {@code contains} checks to the @@ -29,13 +31,13 @@ * @author Jared Levy * @author Louis Wasserman */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") abstract class ImmutableAsList extends ImmutableList { abstract ImmutableCollection delegateCollection(); @Override - public boolean contains(Object target) { + public boolean contains(@Nullable Object target) { // The collection's contains() is at least as fast as ImmutableList's // and is often faster. return delegateCollection().contains(target); @@ -57,8 +59,9 @@ boolean isPartialView() { } /** Serialized form that leads to the same performance as the original list. */ - @GwtIncompatible // serialization - static class SerializedForm implements Serializable { + @GwtIncompatible + @J2ktIncompatible + private static final class SerializedForm implements Serializable { final ImmutableCollection collection; SerializedForm(ImmutableCollection collection) { @@ -69,16 +72,18 @@ Object readResolve() { return collection.asList(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - @GwtIncompatible // serialization - private void readObject(ObjectInputStream stream) throws InvalidObjectException { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Use SerializedForm"); } - @GwtIncompatible // serialization - @Override + @GwtIncompatible + @J2ktIncompatible + @Override Object writeReplace() { return new SerializedForm(delegateCollection()); } diff --git a/android/guava/src/com/google/common/collect/ImmutableBiMap.java b/android/guava/src/com/google/common/collect/ImmutableBiMap.java index 26e55cdcb3a5..6e292ceeea35 100644 --- a/android/guava/src/com/google/common/collect/ImmutableBiMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableBiMap.java @@ -19,13 +19,22 @@ import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; /** * A {@link BiMap} whose contents will never change, with many other important properties detailed @@ -34,9 +43,29 @@ * @author Jared Levy * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible public abstract class ImmutableBiMap extends ImmutableMap implements BiMap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableBiMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * Entries appear in the result {@code ImmutableBiMap} in encounter order. + * + *

    If the mapped keys or values contain duplicates (according to {@link + * Object#equals(Object)}), an {@code IllegalArgumentException} is thrown when the collection + * operation is performed. (This differs from the {@code Collector} returned by {@link + * Collectors#toMap(Function, Function)}, which throws an {@code IllegalStateException}.) + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableBiMap(keyFunction, valueFunction); + } + /** * Returns the empty bimap. * @@ -106,7 +135,174 @@ public static ImmutableBiMap of( new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5}, 5); } - // looking for of() with > 5 entries? Use the builder instead. + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + * @since 31.0 + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + return new RegularImmutableBiMap( + new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6}, 6); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + * @since 31.0 + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + checkEntryNotNull(k7, v7); + return new RegularImmutableBiMap( + new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7}, 7); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + * @since 31.0 + */ + public static ImmutableBiMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + checkEntryNotNull(k7, v7); + checkEntryNotNull(k8, v8); + return new RegularImmutableBiMap( + new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8}, 8); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + * @since 31.0 + */ + public static ImmutableBiMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + checkEntryNotNull(k7, v7); + checkEntryNotNull(k8, v8); + checkEntryNotNull(k9, v9); + return new RegularImmutableBiMap( + new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9}, 9); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + * @since 31.0 + */ + public static ImmutableBiMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9, + K k10, + V v10) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + checkEntryNotNull(k7, v7); + checkEntryNotNull(k8, v8); + checkEntryNotNull(k9, v9); + checkEntryNotNull(k10, v10); + return new RegularImmutableBiMap( + new Object[] { + k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9, k10, v10 + }, + 10); + } + + // looking for of() with > 10 entries? Use the builder or ofEntries instead. + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are provided + * @since 31.0 + */ + @SafeVarargs + public static ImmutableBiMap ofEntries(Entry... entries) { + @SuppressWarnings("unchecked") // we will only ever read these + Entry[] entries2 = (Entry[]) entries; + return copyOf(Arrays.asList(entries2)); + } /** * Returns a new builder. The generated builder is equivalent to the builder created by the {@link @@ -128,7 +324,6 @@ public static Builder builder() { * * @since 23.1 */ - @Beta public static Builder builderWithExpectedSize(int expectedSize) { checkNonnegative(expectedSize, "expectedSize"); return new Builder<>(expectedSize); @@ -138,14 +333,14 @@ public static Builder builderWithExpectedSize(int expectedSize) { * A builder for creating immutable bimap instances, especially {@code public static final} bimaps * ("constant bimaps"). Example: * - *

    {@code
    +   * {@snippet :
        * static final ImmutableBiMap WORD_TO_INT =
        *     new ImmutableBiMap.Builder()
        *         .put("one", 1)
        *         .put("two", 2)
        *         .put("three", 3)
    -   *         .build();
    -   * }
    + * .buildOrThrow(); + * } * *

    For small immutable bimaps, the {@code ImmutableBiMap.of()} methods are even more * convenient. @@ -157,8 +352,8 @@ public static Builder builderWithExpectedSize(int expectedSize) { * want a different order, consider using {@link #orderEntriesByValue(Comparator)}, which changes * this builder to sort entries by value. * - *

    Builder instances can be reused - it is safe to call {@link #build} multiple times to build - * multiple bimaps in series. Each bimap is a superset of the bimaps created before it. + *

    Builder instances can be reused - it is safe to call {@link #buildOrThrow} multiple times to + * build multiple bimaps in series. Each bimap is a superset of the bimaps created before it. * * @since 2.0 */ @@ -220,7 +415,6 @@ public Builder putAll(Map map) { * @since 19.0 */ @CanIgnoreReturnValue - @Beta @Override public Builder putAll(Iterable> entries) { super.putAll(entries); @@ -238,7 +432,6 @@ public Builder putAll(Iterable> * @since 19.0 */ @CanIgnoreReturnValue - @Beta @Override public Builder orderEntriesByValue(Comparator valueComparator) { super.orderEntriesByValue(valueComparator); @@ -257,17 +450,55 @@ Builder combine(ImmutableMap.Builder builder) { * order in which entries were inserted into the builder, unless {@link #orderEntriesByValue} * was called, in which case entries are sorted by value. * + *

    Prefer the equivalent method {@link #buildOrThrow()} to make it explicit that the method + * will throw an exception if there are duplicate keys or values. The {@code build()} method + * will soon be deprecated. + * * @throws IllegalArgumentException if duplicate keys or values were added */ @Override public ImmutableBiMap build() { + return buildOrThrow(); + } + + /** + * Returns a newly-created immutable bimap, or throws an exception if any key or value was added + * more than once. The iteration order of the returned bimap is the order in which entries were + * inserted into the builder, unless {@link #orderEntriesByValue} was called, in which case + * entries are sorted by value. + * + * @throws IllegalArgumentException if duplicate keys or values were added + * @since 31.0 + */ + @Override + public ImmutableBiMap buildOrThrow() { if (size == 0) { return of(); } - sortEntries(); + if (valueComparator != null) { + if (entriesUsed) { + alternatingKeysAndValues = Arrays.copyOf(alternatingKeysAndValues, 2 * size); + } + sortEntries(alternatingKeysAndValues, size, valueComparator); + } entriesUsed = true; return new RegularImmutableBiMap(alternatingKeysAndValues, size); } + + /** + * Throws {@link UnsupportedOperationException}. This method is inherited from {@link + * ImmutableMap.Builder}, but it does not make sense for bimaps. + * + * @throws UnsupportedOperationException always + * @deprecated This method does not make sense for bimaps and should not be called. + * @since 31.1 + */ + @DoNotCall + @Deprecated + @Override + public ImmutableBiMap buildKeepingLast() { + throw new UnsupportedOperationException("Not supported for bimaps"); + } } /** @@ -308,7 +539,6 @@ public static ImmutableBiMap copyOf(Map m * @throws NullPointerException if any key, value, or entry is null * @since 19.0 */ - @Beta public static ImmutableBiMap copyOf( Iterable> entries) { int estimatedSize = @@ -352,7 +582,7 @@ final ImmutableSet createValues() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final V forcePut(K key, V value) { + public final @Nullable V forcePut(K key, V value) { throw new UnsupportedOperationException(); } @@ -364,7 +594,8 @@ public final V forcePut(K key, V value) { *

    Since the bimap is immutable, ImmutableBiMap doesn't require special logic for keeping the * bimap and its inverse in sync during serialization, the way AbstractBiMap does. */ - private static class SerializedForm extends ImmutableMap.SerializedForm { + @J2ktIncompatible // serialization + private static final class SerializedForm extends ImmutableMap.SerializedForm { SerializedForm(ImmutableBiMap bimap) { super(bimap); } @@ -374,11 +605,57 @@ Builder makeBuilder(int size) { return new Builder<>(size); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @Override + @J2ktIncompatible // serialization Object writeReplace() { return new SerializedForm<>(this); } + + @J2ktIncompatible // serialization + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + /** + * Not supported. Use {@link #toImmutableBiMap} instead. This method exists only to hide {@link + * ImmutableMap#toImmutableMap(Function, Function)} from consumers of {@code ImmutableBiMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableBiMap#toImmutableBiMap(Function, Function)}. + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @Deprecated + @DoNotCall("Use toImmutableBiMap") + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. This method does not make sense for {@code BiMap}. This method exists only to + * hide {@link ImmutableMap#toImmutableMap(Function, Function, BinaryOperator)} from consumers of + * {@code ImmutableBiMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Merging values does not make sense for a {@code BiMap}. + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @Deprecated + @DoNotCall("Use toImmutableBiMap") + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + throw new UnsupportedOperationException(); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java b/android/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java index b5dbdc80357e..323a7a5347d8 100644 --- a/android/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableClassToInstanceMap.java @@ -25,7 +25,8 @@ import com.google.errorprone.annotations.Immutable; import java.io.Serializable; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A {@link ClassToInstanceMap} whose contents will never change, with many other important @@ -36,11 +37,13 @@ */ @Immutable(containerOf = "B") @GwtIncompatible -public final class ImmutableClassToInstanceMap extends ForwardingMap, B> +// TODO(b/278589132): Remove the redundant "@NonNull" on B once it's no longer required by J2KT. +public final class ImmutableClassToInstanceMap + extends ForwardingMap, B> implements ClassToInstanceMap, Serializable { private static final ImmutableClassToInstanceMap EMPTY = - new ImmutableClassToInstanceMap<>(ImmutableMap., Object>of()); + new ImmutableClassToInstanceMap<>(ImmutableMap.of()); /** * Returns an empty {@code ImmutableClassToInstanceMap}. @@ -60,8 +63,7 @@ public static ImmutableClassToInstanceMap of() { * @since 19.0 */ public static ImmutableClassToInstanceMap of(Class type, T value) { - ImmutableMap, B> map = ImmutableMap., B>of(type, value); - return new ImmutableClassToInstanceMap(map); + return new ImmutableClassToInstanceMap<>(ImmutableMap.of(type, value)); } /** @@ -69,20 +71,20 @@ public static ImmutableClassToInstanceMap of(Class type, * Builder} constructor. */ public static Builder builder() { - return new Builder(); + return new Builder<>(); } /** * A builder for creating immutable class-to-instance maps. Example: * - *
    {@code
    +   * {@snippet :
        * static final ImmutableClassToInstanceMap HANDLERS =
        *     new ImmutableClassToInstanceMap.Builder()
        *         .put(FooHandler.class, new FooHandler())
        *         .put(BarHandler.class, new SubBarHandler())
        *         .put(Handler.class, new QuuxHandler())
        *         .build();
    -   * }
    + * } * *

    After invoking {@link #build()} it is still possible to add more entries and build again. * Thus each map generated by this builder will be a superset of any map generated before it. @@ -90,6 +92,9 @@ public static Builder builder() { * @since 2.0 */ public static final class Builder { + /** Creates a new builder. */ + public Builder() {} + private final ImmutableMap.Builder, B> mapBuilder = ImmutableMap.builder(); /** @@ -119,7 +124,7 @@ public Builder putAll(Map, ? exten return this; } - private static T cast(Class type, B value) { + private static T cast(Class type, Object value) { return Primitives.wrap(type).cast(value); } @@ -130,11 +135,11 @@ private static T cast(Class type, B value) { * @throws IllegalArgumentException if duplicate keys were added */ public ImmutableClassToInstanceMap build() { - ImmutableMap, B> map = mapBuilder.build(); + ImmutableMap, B> map = mapBuilder.buildOrThrow(); if (map.isEmpty()) { return of(); } else { - return new ImmutableClassToInstanceMap(map); + return new ImmutableClassToInstanceMap<>(map); } } } @@ -153,8 +158,10 @@ public ImmutableClassToInstanceMap build() { public static ImmutableClassToInstanceMap copyOf( Map, ? extends S> map) { if (map instanceof ImmutableClassToInstanceMap) { + @SuppressWarnings("rawtypes") // JDT-based J2KT Java frontend does not permit the direct cast + Map rawMap = map; @SuppressWarnings("unchecked") // covariant casts safe (unmodifiable) - ImmutableClassToInstanceMap cast = (ImmutableClassToInstanceMap) map; + ImmutableClassToInstanceMap cast = (ImmutableClassToInstanceMap) rawMap; return cast; } return new Builder().putAll(map).build(); @@ -173,8 +180,7 @@ protected Map, B> delegate() { @Override @SuppressWarnings("unchecked") // value could not get in if not a T - @NullableDecl - public T getInstance(Class type) { + public @Nullable T getInstance(Class type) { return (T) delegate.get(checkNotNull(type)); } @@ -188,7 +194,7 @@ public T getInstance(Class type) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public T putInstance(Class type, T value) { + public @Nullable T putInstance(Class type, T value) { throw new UnsupportedOperationException(); } diff --git a/android/guava/src/com/google/common/collect/ImmutableCollection.java b/android/guava/src/com/google/common/collect/ImmutableCollection.java index 23d9eca944b0..35ba746f2259 100644 --- a/android/guava/src/com/google/common/collect/ImmutableCollection.java +++ b/android/guava/src/com/google/common/collect/ImmutableCollection.java @@ -21,9 +21,13 @@ import static com.google.common.collect.ObjectArrays.checkElementsNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.DoNotMock; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.AbstractCollection; import java.util.Arrays; @@ -32,7 +36,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Spliterator; +import java.util.Spliterators; +import org.jspecify.annotations.Nullable; /** * A {@link Collection} whose contents will never change, and which offers a few additional @@ -138,7 +144,7 @@ * *

    Example usage

    * - *
    {@code
    + * {@snippet :
      * class Foo {
      *   private static final ImmutableSet RESERVED_CODES =
      *       ImmutableSet.of("AZ", "CQ", "ZX");
    @@ -150,21 +156,30 @@
      *     checkArgument(Collections.disjoint(this.codes, RESERVED_CODES));
      *   }
      * }
    - * }
    + * } * *

    See also

    * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @since 2.0 */ @DoNotMock("Use ImmutableList.of or another implementation") -@GwtCompatible(emulated = true) +@GwtCompatible @SuppressWarnings("serial") // we're overriding default serialization // TODO(kevinb): I think we should push everything down to "BaseImmutableCollection" or something, // just to do everything we can to emphasize the "practically an interface" nature of this class. public abstract class ImmutableCollection extends AbstractCollection implements Serializable { + /* + * We expect SIZED (and SUBSIZED, if applicable) to be added by the spliterator factory methods. + * These are properties of the collection as a whole; SIZED and SUBSIZED are more properties of + * the spliterator implementation. + */ + // @IgnoreJRERequirement is not necessary because this compiles down to a constant. + // (which is fortunate because Animal Sniffer doesn't look for @IgnoreJRERequirement on fields) + static final int SPLITERATOR_CHARACTERISTICS = + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED; ImmutableCollection() {} @@ -172,16 +187,37 @@ public abstract class ImmutableCollection extends AbstractCollection imple @Override public abstract UnmodifiableIterator iterator(); + @Override + @IgnoreJRERequirement // used only from APIs with Java 8 types in them + // (not used within guava-android as of this writing, but we include it in the jar as a test) + public Spliterator spliterator() { + return Spliterators.spliterator(this, SPLITERATOR_CHARACTERISTICS); + } + private static final Object[] EMPTY_ARRAY = {}; @Override + @J2ktIncompatible // Incompatible return type change. Use inherited (unoptimized) implementation public final Object[] toArray() { return toArray(EMPTY_ARRAY); } @CanIgnoreReturnValue @Override - public final T[] toArray(T[] other) { + /* + * This suppression is here for two reasons: + * + * 1. b/192354773 in our checker affects toArray declarations. + * + * 2. `other[size] = null` is unsound. We could "fix" this by requiring callers to pass in an + * array with a nullable element type. But probably they usually want an array with a non-nullable + * type. That said, we could *accept* a `@Nullable T[]` (which, given that we treat arrays as + * covariant, would still permit a plain `T[]`) and return a plain `T[]`. But of course that would + * require its own suppression, since it is also unsound. toArray(T[]) is just a mess from a + * nullness perspective. The signature below at least has the virtue of being relatively simple. + */ + @SuppressWarnings("nullness") + public final T[] toArray(T[] other) { checkNotNull(other); int size = size(); @@ -199,8 +235,7 @@ public final T[] toArray(T[] other) { } /** If this collection is backed by an array of its elements in insertion order, returns it. */ - @NullableDecl - Object[] internalArray() { + @Nullable Object @Nullable [] internalArray() { return null; } @@ -221,7 +256,7 @@ int internalArrayEnd() { } @Override - public abstract boolean contains(@NullableDecl Object object); + public abstract boolean contains(@Nullable Object object); /** * Guaranteed to throw an exception and leave the collection unmodified. @@ -247,7 +282,7 @@ public final boolean add(E e) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final boolean remove(Object object) { + public final boolean remove(@Nullable Object object) { throw new UnsupportedOperationException(); } @@ -317,7 +352,7 @@ public final void clear() { * @since 2.0 */ public ImmutableList asList() { - return isEmpty() ? ImmutableList.of() : ImmutableList.asImmutableList(toArray()); + return isEmpty() ? ImmutableList.of() : ImmutableList.asImmutableList(toArray()); } /** @@ -333,18 +368,25 @@ public ImmutableList asList() { * offset. Returns {@code offset + size()}. */ @CanIgnoreReturnValue - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { for (E e : this) { dst[offset++] = e; } return offset; } - Object writeReplace() { + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { // We serialize by default to ImmutableList, the simplest thing that works. return new ImmutableList.SerializedForm(toArray()); } + @J2ktIncompatible // serialization + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + /** * Abstract base class for builders of {@link ImmutableCollection} types. * @@ -356,7 +398,9 @@ public abstract static class Builder { static int expandedCapacity(int oldCapacity, int minCapacity) { if (minCapacity < 0) { - throw new AssertionError("cannot store more than MAX_VALUE elements"); + throw new IllegalArgumentException("cannot store more than Integer.MAX_VALUE elements"); + } else if (minCapacity <= oldCapacity) { + return oldCapacity; } // careful of overflow! int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; @@ -449,13 +493,14 @@ public Builder addAll(Iterator elements) { } abstract static class ArrayBasedBuilder extends ImmutableCollection.Builder { - Object[] contents; + // The first `size` elements are non-null. + @Nullable Object[] contents; int size; boolean forceCopy; ArrayBasedBuilder(int initialCapacity) { checkNonnegative(initialCapacity, "initialCapacity"); - this.contents = new Object[initialCapacity]; + this.contents = new @Nullable Object[initialCapacity]; this.size = 0; } @@ -464,13 +509,12 @@ abstract static class ArrayBasedBuilder extends ImmutableCollection.Builder contents.length || forceCopy) { + this.contents = Arrays.copyOf(this.contents, newCapacity); forceCopy = false; } } @@ -479,7 +523,7 @@ private void getReadyToExpandTo(int minCapacity) { @Override public ArrayBasedBuilder add(E element) { checkNotNull(element); - getReadyToExpandTo(size + 1); + ensureRoomFor(1); contents[size++] = element; return this; } @@ -491,9 +535,17 @@ public Builder add(E... elements) { return this; } - final void addAll(Object[] elements, int n) { + final void addAll(@Nullable Object[] elements, int n) { checkElementsNotNull(elements, n); - getReadyToExpandTo(size + n); + ensureRoomFor(n); + /* + * The following call is not statically checked, since arraycopy accepts plain Object for its + * parameters. If it were statically checked, the checker would still be OK with it, since + * we're copying into a `contents` array whose type allows it to contain nulls. Still, it's + * worth noting that we promise not to put nulls into the array in the first `size` elements. + * We uphold that promise here because our callers promise that `elements` will not contain + * nulls in its first `n` elements. + */ System.arraycopy(elements, 0, contents, size, n); size += n; } @@ -503,7 +555,7 @@ final void addAll(Object[] elements, int n) { public Builder addAll(Iterable elements) { if (elements instanceof Collection) { Collection collection = (Collection) elements; - getReadyToExpandTo(size + collection.size()); + ensureRoomFor(collection.size()); if (collection instanceof ImmutableCollection) { ImmutableCollection immutableCollection = (ImmutableCollection) collection; size = immutableCollection.copyIntoArray(contents, size); @@ -514,4 +566,6 @@ public Builder addAll(Iterable elements) { return this; } } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableEnumMap.java b/android/guava/src/com/google/common/collect/ImmutableEnumMap.java index beab47a9a0f7..e53a04e7821b 100644 --- a/android/guava/src/com/google/common/collect/ImmutableEnumMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableEnumMap.java @@ -17,19 +17,24 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterables.getOnlyElement; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMap.IteratorBasedImmutableMap; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.EnumMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link ImmutableMap} backed by a non-empty {@link java.util.EnumMap}. * * @author Louis Wasserman */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // we're overriding default serialization final class ImmutableEnumMap, V> extends IteratorBasedImmutableMap { static , V> ImmutableMap asImmutable(EnumMap map) { @@ -37,7 +42,7 @@ static , V> ImmutableMap asImmutable(EnumMap map) case 0: return ImmutableMap.of(); case 1: - Entry entry = Iterables.getOnlyElement(map.entrySet()); + Entry entry = getOnlyElement(map.entrySet()); return ImmutableMap.of(entry.getKey(), entry.getValue()); default: return new ImmutableEnumMap<>(map); @@ -62,17 +67,17 @@ public int size() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return delegate.containsKey(key); } @Override - public V get(Object key) { + public @Nullable V get(@Nullable Object key) { return delegate.get(key); } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -94,14 +99,21 @@ boolean isPartialView() { // All callers of the constructor are restricted to >. @Override + @J2ktIncompatible // serialization Object writeReplace() { return new EnumSerializedForm<>(delegate); } + @J2ktIncompatible // serialization + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use EnumSerializedForm"); + } + /* * This class is used to serialize ImmutableEnumMap instances. */ - private static class EnumSerializedForm, V> implements Serializable { + @J2ktIncompatible // serialization + private static final class EnumSerializedForm, V> implements Serializable { final EnumMap delegate; EnumSerializedForm(EnumMap delegate) { @@ -112,6 +124,6 @@ Object readResolve() { return new ImmutableEnumMap<>(delegate); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/collect/ImmutableEnumSet.java b/android/guava/src/com/google/common/collect/ImmutableEnumSet.java index 4e189ffd0ade..21439ac5b148 100644 --- a/android/guava/src/com/google/common/collect/ImmutableEnumSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableEnumSet.java @@ -16,29 +16,35 @@ package com.google.common.collect; +import static com.google.common.collect.Iterables.getOnlyElement; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.concurrent.LazyInit; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Collection; import java.util.EnumSet; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link ImmutableSet} backed by a non-empty {@link java.util.EnumSet}. * * @author Jared Levy */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // we're overriding default serialization final class ImmutableEnumSet> extends ImmutableSet { - @SuppressWarnings("rawtypes") // necessary to compile against Java 8 - static ImmutableSet asImmutable(EnumSet set) { + static > ImmutableSet asImmutable(EnumSet set) { switch (set.size()) { case 0: return ImmutableSet.of(); case 1: - return ImmutableSet.of(Iterables.getOnlyElement(set)); + return ImmutableSet.of(getOnlyElement(set)); default: - return new ImmutableEnumSet(set); + return new ImmutableEnumSet<>(set); } } @@ -72,7 +78,7 @@ public int size() { } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { return delegate.contains(object); } @@ -90,7 +96,7 @@ public boolean isEmpty() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -118,16 +124,22 @@ public String toString() { return delegate.toString(); } - // All callers of the constructor are restricted to >. @Override + @J2ktIncompatible // serialization Object writeReplace() { return new EnumSerializedForm(delegate); } + @J2ktIncompatible // serialization + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + /* * This class is used to serialize ImmutableEnumSet instances. */ - private static class EnumSerializedForm> implements Serializable { + @J2ktIncompatible // serialization + private static final class EnumSerializedForm> implements Serializable { final EnumSet delegate; EnumSerializedForm(EnumSet delegate) { @@ -139,6 +151,6 @@ Object readResolve() { return new ImmutableEnumSet(delegate.clone()); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/collect/ImmutableList.java b/android/guava/src/com/google/common/collect/ImmutableList.java index da2efb1312bf..8d8cbafbf234 100644 --- a/android/guava/src/com/google/common/collect/ImmutableList.java +++ b/android/guava/src/com/google/common/collect/ImmutableList.java @@ -25,10 +25,12 @@ import static com.google.common.collect.ObjectArrays.checkElementsNotNull; import static com.google.common.collect.RegularImmutableList.EMPTY; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; +import com.google.errorprone.annotations.InlineMe; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; @@ -39,24 +41,37 @@ import java.util.Iterator; import java.util.List; import java.util.RandomAccess; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A {@link List} whose contents will never change, with many other important properties detailed at * {@link ImmutableCollection}. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @see ImmutableMap * @see ImmutableSet * @author Kevin Bourrillion * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // we're overriding default serialization public abstract class ImmutableList extends ImmutableCollection implements List, RandomAccess { + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableList}, in encounter order. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toImmutableList() { + return CollectCollectors.toImmutableList(); + } + /** * Returns the empty immutable list. This list behaves and performs comparably to {@link * Collections#emptyList}, and is preferable mainly for consistency and maintainability of your @@ -75,10 +90,10 @@ public static ImmutableList of() { * comparably to {@link Collections#singletonList}, but will not accept a null element. It is * preferable mainly for consistency and maintainability of your code. * - * @throws NullPointerException if {@code element} is null + * @throws NullPointerException if the element is null */ - public static ImmutableList of(E element) { - return construct(element); + public static ImmutableList of(E e1) { + return construct(e1); } /** @@ -229,8 +244,8 @@ public static ImmutableList copyOf(Iterable elements) { * *

    Note that if {@code list} is a {@code List}, then {@code ImmutableList.copyOf(list)} * returns an {@code ImmutableList} containing each of the strings in {@code list}, while - * ImmutableList.of(list)} returns an {@code ImmutableList>} containing one element - * (the given list itself). + * {@code ImmutableList.of(list)} returns an {@code ImmutableList>} containing one + * element (the given list itself). * *

    This method is safe to use even when {@code elements} is a synchronized or concurrent * collection that is currently being modified by another thread. @@ -241,7 +256,7 @@ public static ImmutableList copyOf(Collection elements) { if (elements instanceof ImmutableCollection) { @SuppressWarnings("unchecked") // all supported methods are covariant ImmutableList list = ((ImmutableCollection) elements).asList(); - return list.isPartialView() ? ImmutableList.asImmutableList(list.toArray()) : list; + return list.isPartialView() ? asImmutableList(list.toArray()) : list; } return construct(elements.toArray()); } @@ -285,7 +300,7 @@ public static ImmutableList copyOf(E[] elements) { * ImmutableSortedSet.copyOf(elements)}; if you want a {@code List} you can use its {@code * asList()} view. * - *

    Java 8 users: If you want to convert a {@link java.util.stream.Stream} to a sorted + *

    Java 8+ users: If you want to convert a {@link java.util.stream.Stream} to a sorted * {@code ImmutableList}, use {@code stream.sorted().collect(toImmutableList())}. * * @throws NullPointerException if any element in the input is null @@ -308,7 +323,7 @@ public static > ImmutableList sortedCopyOf( * ImmutableSortedSet.copyOf(comparator, elements)}; if you want a {@code List} you can use its * {@code asList()} view. * - *

    Java 8 users: If you want to convert a {@link java.util.stream.Stream} to a sorted + *

    Java 8+ users: If you want to convert a {@link java.util.stream.Stream} to a sorted * {@code ImmutableList}, use {@code stream.sorted(comparator).collect(toImmutableList())}. * * @throws NullPointerException if any element in the input is null @@ -339,7 +354,7 @@ static ImmutableList asImmutableList(Object[] elements) { } /** Views the array as an immutable list. Does not check for nulls. */ - static ImmutableList asImmutableList(Object[] elements, int length) { + static ImmutableList asImmutableList(@Nullable Object[] elements, int length) { if (length == 0) { return of(); } @@ -372,10 +387,12 @@ public UnmodifiableListIterator listIterator(int index) { } /** A singleton implementation of iterator() for the empty ImmutableList. */ + // TODO(b/345814817): Move this to RegularImmutableList? + @SuppressWarnings("ClassInitializationDeadlock") private static final UnmodifiableListIterator EMPTY_ITR = new Itr(RegularImmutableList.EMPTY, 0); - static class Itr extends AbstractIndexedListIterator { + private static final class Itr extends AbstractIndexedListIterator { private final ImmutableList list; Itr(ImmutableList list, int index) { @@ -390,17 +407,17 @@ protected E get(int index) { } @Override - public int indexOf(@NullableDecl Object object) { + public int indexOf(@Nullable Object object) { return (object == null) ? -1 : Lists.indexOfImpl(this, object); } @Override - public int lastIndexOf(@NullableDecl Object object) { + public int lastIndexOf(@Nullable Object object) { return (object == null) ? -1 : Lists.lastIndexOfImpl(this, object); } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return indexOf(object) >= 0; } @@ -410,6 +427,12 @@ public boolean contains(@NullableDecl Object object) { * Returns an immutable list of the elements between the specified {@code fromIndex}, inclusive, * and {@code toIndex}, exclusive. (If {@code fromIndex} and {@code toIndex} are equal, the empty * immutable list is returned.) + * + *

    Note: in almost all circumstances, the returned {@link ImmutableList} retains a + * strong reference to {@code this}, which may prevent the original list from being garbage + * collected. If you want the original list to be eligible for garbage collection, you should + * create and use a copy of the sub list (e.g., {@code + * ImmutableList.copyOf(originalList.subList(...))}). */ @Override public ImmutableList subList(int fromIndex, int toIndex) { @@ -432,7 +455,7 @@ ImmutableList subListUnchecked(int fromIndex, int toIndex) { return new SubList(fromIndex, toIndex - fromIndex); } - class SubList extends ImmutableList { + private final class SubList extends ImmutableList { final transient int offset; final transient int length; @@ -447,7 +470,7 @@ public int size() { } @Override - Object[] internalArray() { + @Nullable Object @Nullable [] internalArray() { return ImmutableList.this.internalArray(); } @@ -477,6 +500,15 @@ public ImmutableList subList(int fromIndex, int toIndex) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } /** @@ -540,6 +572,7 @@ public final E remove(int index) { * @since 2.0 * @deprecated There is no reason to use this; it always returns {@code this}. */ + @InlineMe(replacement = "this") @Deprecated @Override public final ImmutableList asList() { @@ -547,7 +580,7 @@ public final ImmutableList asList() { } @Override - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { // this loop is faster for RandomAccess instances, which ImmutableLists are int size = size(); for (int i = 0; i < size; i++) { @@ -567,7 +600,7 @@ public ImmutableList reverse() { return (size() <= 1) ? this : new ReverseImmutableList(this); } - private static class ReverseImmutableList extends ImmutableList { + private static final class ReverseImmutableList extends ImmutableList { private final transient ImmutableList forwardList; ReverseImmutableList(ImmutableList backingList) { @@ -588,18 +621,18 @@ public ImmutableList reverse() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return forwardList.contains(object); } @Override - public int indexOf(@NullableDecl Object object) { + public int indexOf(@Nullable Object object) { int index = forwardList.lastIndexOf(object); return (index >= 0) ? reverseIndex(index) : -1; } @Override - public int lastIndexOf(@NullableDecl Object object) { + public int lastIndexOf(@Nullable Object object) { int index = forwardList.indexOf(object); return (index >= 0) ? reverseIndex(index) : -1; } @@ -625,10 +658,19 @@ public int size() { boolean isPartialView() { return forwardList.isPartialView(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { return Lists.equalsImpl(this, obj); } @@ -649,7 +691,8 @@ public int hashCode() { * Serializes ImmutableLists as their logical contents. This ensures that * implementation types do not leak into the serialized representation. */ - static class SerializedForm implements Serializable { + @J2ktIncompatible // serialization + static final class SerializedForm implements Serializable { final Object[] elements; SerializedForm(Object[] elements) { @@ -660,15 +703,18 @@ Object readResolve() { return copyOf(elements); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } + @J2ktIncompatible // serialization private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Use SerializedForm"); } @Override - Object writeReplace() { + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { return new SerializedForm(toArray()); } @@ -677,7 +723,7 @@ Object writeReplace() { * Builder} constructor. */ public static Builder builder() { - return new Builder(); + return new Builder<>(); } /** @@ -692,23 +738,22 @@ public static Builder builder() { * * @since 23.1 */ - @Beta public static Builder builderWithExpectedSize(int expectedSize) { checkNonnegative(expectedSize, "expectedSize"); - return new ImmutableList.Builder(expectedSize); + return new ImmutableList.Builder<>(expectedSize); } /** * A builder for creating immutable list instances, especially {@code public static final} lists * ("constant lists"). Example: * - *

    {@code
    -   * public static final ImmutableList GOOGLE_COLORS
    -   *     = new ImmutableList.Builder()
    +   * {@snippet :
    +   * public static final ImmutableList GOOGLE_COLORS =
    +   *     new ImmutableList.Builder()
        *         .addAll(WEBSAFE_COLORS)
        *         .add(new Color(0, 191, 255))
        *         .build();
    -   * }
    + * } * *

    Elements appear in the resulting list in the same order they were added to the builder. * @@ -801,5 +846,22 @@ public ImmutableList build() { forceCopy = true; return asImmutableList(contents, size); } + + /** + * Returns a newly-created {@code ImmutableList} based on the contents of the {@code Builder}, + * sorted according to the specified comparator. + */ + @SuppressWarnings("unchecked") + ImmutableList buildSorted(Comparator comparator) { + // Currently only used by ImmutableListMultimap.Builder.orderValuesBy. + // In particular, this implies that the comparator can never get "removed," so this can't + // invalidate future builds. + + forceCopy = true; + Arrays.sort((E[]) contents, 0, size, comparator); + return asImmutableList(contents, size); + } } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xcafebabe; } diff --git a/android/guava/src/com/google/common/collect/ImmutableListMultimap.java b/android/guava/src/com/google/common/collect/ImmutableListMultimap.java index a47f81db2194..39a0ab2ab79b 100644 --- a/android/guava/src/com/google/common/collect/ImmutableListMultimap.java +++ b/android/guava/src/com/google/common/collect/ImmutableListMultimap.java @@ -16,9 +16,12 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static java.util.Objects.requireNonNull; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.concurrent.LazyInit; @@ -31,21 +34,99 @@ import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; /** * A {@link ListMultimap} whose contents will never change, with many other important properties * detailed at {@link ImmutableCollection}. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Jared Levy * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible public class ImmutableListMultimap extends ImmutableMultimap implements ListMultimap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableListMultimap} + * whose keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

    For streams with defined encounter order (as defined in the Ordering section of the {@link + * java.util.stream} Javadoc), that order is preserved, but entries are grouped by key. + * + *

    Example: + * + * {@snippet : + * static final Multimap FIRST_LETTER_MULTIMAP = + * Stream.of("banana", "apple", "carrot", "asparagus", "cherry") + * .collect(toImmutableListMultimap(str -> str.charAt(0), str -> str.substring(1))); + * + * // is equivalent to + * + * static final Multimap FIRST_LETTER_MULTIMAP = + * new ImmutableListMultimap.Builder() + * .put('b', "anana") + * .putAll('a', "pple", "sparagus") + * .putAll('c', "arrot", "herry") + * .build(); + * } + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableListMultimap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableListMultimap(keyFunction, valueFunction); + } + + /** + * Returns a {@code Collector} accumulating entries into an {@code ImmutableListMultimap}. Each + * input element is mapped to a key and a stream of values, each of which are put into the + * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the + * streams of values. + * + *

    Example: + * + * {@snippet : + * static final ImmutableListMultimap FIRST_LETTER_MULTIMAP = + * Stream.of("banana", "apple", "carrot", "asparagus", "cherry") + * .collect( + * flatteningToImmutableListMultimap( + * str -> str.charAt(0), + * str -> str.substring(1).chars().mapToObj(c -> (char) c)); + * + * // is equivalent to + * + * static final ImmutableListMultimap FIRST_LETTER_MULTIMAP = + * ImmutableListMultimap.builder() + * .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a')) + * .putAll('a', Arrays.asList('p', 'p', 'l', 'e')) + * .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't')) + * .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's')) + * .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y')) + * .build(); + * } + * + * } + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> flatteningToImmutableListMultimap( + Function keyFunction, + Function> valuesFunction) { + return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction); + } /** * Returns the empty multimap. @@ -115,18 +196,31 @@ public static Builder builder() { return new Builder<>(); } + /** + * Returns a new builder with a hint for how many distinct keys are expected to be added. The + * generated builder is equivalent to that returned by {@link #builder}, but may perform better if + * {@code expectedKeys} is a good estimate. + * + * @throws IllegalArgumentException if {@code expectedKeys} is negative + * @since 33.3.0 + */ + public static Builder builderWithExpectedKeys(int expectedKeys) { + checkNonnegative(expectedKeys, "expectedKeys"); + return new Builder<>(expectedKeys); + } + /** * A builder for creating immutable {@code ListMultimap} instances, especially {@code public * static final} multimaps ("constant multimaps"). Example: * - *

    {@code
    +   * {@snippet :
        * static final Multimap STRING_TO_INTEGER_MULTIMAP =
        *     new ImmutableListMultimap.Builder()
        *         .put("one", 1)
        *         .putAll("several", 1, 2, 3)
        *         .putAll("many", 1, 2, 3, 4, 5)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple multimaps in series. Each multimap contains the key-value mappings in the previously @@ -141,6 +235,23 @@ public static final class Builder extends ImmutableMultimap.Builder */ public Builder() {} + /** Creates a new builder with a hint for the number of distinct keys. */ + Builder(int expectedKeys) { + super(expectedKeys); + } + + /** + * {@inheritDoc} + * + * @since 33.3.0 + */ + @CanIgnoreReturnValue + @Override + public Builder expectedValuesPerKey(int expectedValuesPerKey) { + super.expectedValuesPerKey(expectedValuesPerKey); + return this; + } + @CanIgnoreReturnValue @Override public Builder put(K key, V value) { @@ -166,7 +277,6 @@ public Builder put(Entry entry) { * @since 19.0 */ @CanIgnoreReturnValue - @Beta @Override public Builder putAll(Iterable> entries) { super.putAll(entries); @@ -194,6 +304,13 @@ public Builder putAll(Multimap multimap) { return this; } + @CanIgnoreReturnValue + @Override + Builder combine(ImmutableMultimap.Builder other) { + super.combine(other); + return this; + } + /** * {@inheritDoc} * @@ -218,13 +335,6 @@ public Builder orderValuesBy(Comparator valueComparator) { return this; } - @CanIgnoreReturnValue - @Override - Builder combine(ImmutableMultimap.Builder other) { - super.combine(other); - return this; - } - /** Returns a newly-created immutable list multimap. */ @Override public ImmutableListMultimap build() { @@ -269,7 +379,6 @@ public static ImmutableListMultimap copyOf( * @throws NullPointerException if any key, value, or entry is null * @since 19.0 */ - @Beta public static ImmutableListMultimap copyOf( Iterable> entries) { return new Builder().putAll(entries).build(); @@ -278,7 +387,7 @@ public static ImmutableListMultimap copyOf( /** Creates an ImmutableListMultimap from an asMap.entrySet. */ static ImmutableListMultimap fromMapEntries( Collection>> mapEntries, - @NullableDecl Comparator valueComparator) { + @Nullable Comparator valueComparator) { if (mapEntries.isEmpty()) { return of(); } @@ -299,7 +408,30 @@ static ImmutableListMultimap fromMapEntries( } } - return new ImmutableListMultimap<>(builder.build(), size); + return new ImmutableListMultimap<>(builder.buildOrThrow(), size); + } + + /** Creates an ImmutableListMultimap from an asMap.entrySet. */ + static ImmutableListMultimap fromMapBuilderEntries( + Collection>> mapEntries, + @Nullable Comparator valueComparator) { + if (mapEntries.isEmpty()) { + return of(); + } + ImmutableMap.Builder> builder = + new ImmutableMap.Builder<>(mapEntries.size()); + int size = 0; + + for (Entry> entry : mapEntries) { + K key = entry.getKey(); + ImmutableList.Builder values = (ImmutableList.Builder) entry.getValue(); + ImmutableList list = + (valueComparator == null) ? values.build() : values.buildSorted(valueComparator); + builder.put(key, list); + size += list.size(); + } + + return new ImmutableListMultimap<>(builder.buildOrThrow(), size); } ImmutableListMultimap(ImmutableMap> map, int size) { @@ -314,13 +446,13 @@ static ImmutableListMultimap fromMapEntries( * parameters used to build this multimap. */ @Override - public ImmutableList get(@NullableDecl K key) { + public ImmutableList get(K key) { // This cast is safe as its type is known in constructor. ImmutableList list = (ImmutableList) map.get(key); - return (list == null) ? ImmutableList.of() : list; + return (list == null) ? ImmutableList.of() : list; } - @LazyInit @RetainedWith private transient ImmutableListMultimap inverse; + @LazyInit @RetainedWith private transient @Nullable ImmutableListMultimap inverse; /** * {@inheritDoc} @@ -357,7 +489,7 @@ private ImmutableListMultimap invert() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final ImmutableList removeAll(Object key) { + public final ImmutableList removeAll(@Nullable Object key) { throw new UnsupportedOperationException(); } @@ -379,14 +511,16 @@ public final ImmutableList replaceValues(K key, Iterable values) * @serialData number of distinct keys, and then for each distinct key: the key, the number of * values for that key, and the key's values */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMultimap(this, stream); } - @GwtIncompatible // java.io.ObjectInputStream - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int keyCount = stream.readInt(); if (keyCount < 0) { @@ -396,7 +530,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo int tmpSize = 0; for (int i = 0; i < keyCount; i++) { - Object key = stream.readObject(); + Object key = requireNonNull(stream.readObject()); int valueCount = stream.readInt(); if (valueCount <= 0) { throw new InvalidObjectException("Invalid value count " + valueCount); @@ -404,7 +538,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo ImmutableList.Builder valuesBuilder = ImmutableList.builder(); for (int j = 0; j < valueCount; j++) { - valuesBuilder.add(stream.readObject()); + valuesBuilder.add(requireNonNull(stream.readObject())); } builder.put(key, valuesBuilder.build()); tmpSize += valueCount; @@ -412,7 +546,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo ImmutableMap> tmpMap; try { - tmpMap = builder.build(); + tmpMap = builder.buildOrThrow(); } catch (IllegalArgumentException e) { throw (InvalidObjectException) new InvalidObjectException(e.getMessage()).initCause(e); } @@ -421,6 +555,5 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); } - @GwtIncompatible // Not needed in emulated source - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ImmutableMap.java b/android/guava/src/com/google/common/collect/ImmutableMap.java index 7c2a0e179e08..2728851254c1 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableMap.java @@ -20,43 +20,97 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static java.lang.System.arraycopy; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.DoNotMock; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedWith; import com.google.j2objc.annotations.WeakOuter; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.AbstractMap; import java.util.Arrays; +import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.SortedMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; /** * A {@link Map} whose contents will never change, with many other important properties detailed at * {@link ImmutableCollection}. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Jesse Wilson * @author Kevin Bourrillion * @since 2.0 */ @DoNotMock("Use ImmutableMap.of or another implementation") -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // we're overriding default serialization public abstract class ImmutableMap implements Map, Serializable { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * Entries appear in the result {@code ImmutableMap} in encounter order. + * + *

    If the mapped keys contain duplicates (according to {@link Object#equals(Object)}, an {@code + * IllegalArgumentException} is thrown when the collection operation is performed. (This differs + * from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which + * throws an {@code IllegalStateException}.) + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableMap(keyFunction, valueFunction); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * + *

    If the mapped keys contain duplicates (according to {@link Object#equals(Object)}), the + * values are merged using the specified merging function. If the merging function returns {@code + * null}, then the collector removes the value that has been computed for the key thus far (though + * future occurrences of the key would reinsert it). + * + *

    Entries will appear in the encounter order of the first occurrence of the key. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + return CollectCollectors.toImmutableMap(keyFunction, valueFunction, mergeFunction); + } + /** * Returns the empty map. This map behaves and performs comparably to {@link * Collections#emptyMap}, and is preferable mainly for consistency and maintainability of your @@ -130,7 +184,174 @@ public static ImmutableMap of( return RegularImmutableMap.create(5, new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5}); } - // looking for of() with > 5 entries? Use the builder instead. + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + * @since 31.0 + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + return RegularImmutableMap.create( + 6, new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6}); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + * @since 31.0 + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + checkEntryNotNull(k7, v7); + return RegularImmutableMap.create( + 7, new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7}); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + * @since 31.0 + */ + public static ImmutableMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + checkEntryNotNull(k7, v7); + checkEntryNotNull(k8, v8); + return RegularImmutableMap.create( + 8, new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8}); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + * @since 31.0 + */ + public static ImmutableMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + checkEntryNotNull(k7, v7); + checkEntryNotNull(k8, v8); + checkEntryNotNull(k9, v9); + return RegularImmutableMap.create( + 9, new Object[] {k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9}); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + * @since 31.0 + */ + public static ImmutableMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9, + K k10, + V v10) { + checkEntryNotNull(k1, v1); + checkEntryNotNull(k2, v2); + checkEntryNotNull(k3, v3); + checkEntryNotNull(k4, v4); + checkEntryNotNull(k5, v5); + checkEntryNotNull(k6, v6); + checkEntryNotNull(k7, v7); + checkEntryNotNull(k8, v8); + checkEntryNotNull(k9, v9); + checkEntryNotNull(k10, v10); + return RegularImmutableMap.create( + 10, + new Object[] { + k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9, k10, v10 + }); + } + + // looking for of() with > 10 entries? Use the builder or ofEntries instead. + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + * @since 31.0 + */ + @SafeVarargs + public static ImmutableMap ofEntries(Entry... entries) { + @SuppressWarnings("unchecked") // we will only ever read these + Entry[] entries2 = (Entry[]) entries; + return copyOf(Arrays.asList(entries2)); + } /** * Verifies that {@code key} and {@code value} are non-null, and returns a new immutable entry @@ -164,14 +385,13 @@ public static Builder builder() { * * @since 23.1 */ - @Beta public static Builder builderWithExpectedSize(int expectedSize) { checkNonnegative(expectedSize, "expectedSize"); return new Builder<>(expectedSize); } static void checkNoConflict( - boolean safe, String conflictDescription, Entry entry1, Entry entry2) { + boolean safe, String conflictDescription, Object entry1, Object entry2) { if (!safe) { throw conflictException(conflictDescription, entry1, entry2); } @@ -187,14 +407,14 @@ static IllegalArgumentException conflictException( * A builder for creating immutable map instances, especially {@code public static final} maps * ("constant maps"). Example: * - *

    {@code
    +   * {@snippet :
        * static final ImmutableMap WORD_TO_INT =
        *     new ImmutableMap.Builder()
        *         .put("one", 1)
        *         .put("two", 2)
        *         .put("three", 3)
    -   *         .build();
    -   * }
    + * .buildOrThrow(); + * } * *

    For small immutable maps, the {@code ImmutableMap.of()} methods are even more * convenient. @@ -207,18 +427,24 @@ static IllegalArgumentException conflictException( * sort by keys, or call {@link #orderEntriesByValue(Comparator)}, which changes this builder to * sort entries by value. * - *

    Builder instances can be reused - it is safe to call {@link #build} multiple times to build - * multiple maps in series. Each map is a superset of the maps created before it. + *

    Builder instances can be reused - it is safe to call {@link #buildOrThrow} multiple times to + * build multiple maps in series. Each map is a superset of the maps created before it. * * @since 2.0 */ @DoNotMock public static class Builder { - @NullableDecl Comparator valueComparator; - Object[] alternatingKeysAndValues; + @Nullable Comparator valueComparator; + @Nullable Object[] alternatingKeysAndValues; int size; boolean entriesUsed; + /** + * If non-null, a duplicate key we found in a previous buildKeepingLast() or buildOrThrow() + * call. A later buildOrThrow() can simply report this duplicate immediately. + */ + @Nullable DuplicateKey duplicateKey; + /** * Creates a new builder. The returned builder is equivalent to the builder generated by {@link * ImmutableMap#builder}. @@ -227,9 +453,9 @@ public Builder() { this(ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) Builder(int initialCapacity) { - this.alternatingKeysAndValues = new Object[2 * initialCapacity]; + this.alternatingKeysAndValues = new @Nullable Object[2 * initialCapacity]; this.size = 0; this.entriesUsed = false; } @@ -246,8 +472,9 @@ private void ensureCapacity(int minCapacity) { } /** - * Associates {@code key} with {@code value} in the built map. Duplicate keys are not allowed, - * and will cause {@link #build} to fail. + * Associates {@code key} with {@code value} in the built map. If the same key is put more than + * once, {@link #buildOrThrow} will fail, while {@link #buildKeepingLast} will keep the last + * value put for that key. */ @CanIgnoreReturnValue public Builder put(K key, V value) { @@ -260,8 +487,9 @@ public Builder put(K key, V value) { } /** - * Adds the given {@code entry} to the map, making it immutable if necessary. Duplicate keys are - * not allowed, and will cause {@link #build} to fail. + * Adds the given {@code entry} to the map, making it immutable if necessary. If the same key is + * put more than once, {@link #buildOrThrow} will fail, while {@link #buildKeepingLast} will + * keep the last value put for that key. * * @since 11.0 */ @@ -271,8 +499,9 @@ public Builder put(Entry entry) { } /** - * Associates all of the given map's keys and values in the built map. Duplicate keys are not - * allowed, and will cause {@link #build} to fail. + * Associates all of the given map's keys and values in the built map. If the same key is put + * more than once, {@link #buildOrThrow} will fail, while {@link #buildKeepingLast} will keep + * the last value put for that key. * * @throws NullPointerException if any key or value in {@code map} is null */ @@ -282,14 +511,14 @@ public Builder putAll(Map map) { } /** - * Adds all of the given entries to the built map. Duplicate keys are not allowed, and will - * cause {@link #build} to fail. + * Adds all of the given entries to the built map. If the same key is put more than once, {@link + * #buildOrThrow} will fail, while {@link #buildKeepingLast} will keep the last value put for + * that key. * * @throws NullPointerException if any key, value, or entry is null * @since 19.0 */ @CanIgnoreReturnValue - @Beta public Builder putAll(Iterable> entries) { if (entries instanceof Collection) { ensureCapacity(size + ((Collection) entries).size()); @@ -311,7 +540,6 @@ public Builder putAll(Iterable> * @since 19.0 */ @CanIgnoreReturnValue - @Beta public Builder orderEntriesByValue(Comparator valueComparator) { checkState(this.valueComparator == null, "valueComparator was already set"); this.valueComparator = checkNotNull(valueComparator, "valueComparator"); @@ -322,7 +550,7 @@ public Builder orderEntriesByValue(Comparator valueComparator) Builder combine(Builder other) { checkNotNull(other); ensureCapacity(this.size + other.size); - System.arraycopy( + arraycopy( other.alternatingKeysAndValues, 0, this.alternatingKeysAndValues, @@ -332,50 +560,156 @@ Builder combine(Builder other) { return this; } - /* - * TODO(kevinb): Should build() and the ImmutableBiMap & ImmutableSortedMap - * versions throw an IllegalStateException instead? - */ + private ImmutableMap build(boolean throwIfDuplicateKeys) { + if (throwIfDuplicateKeys && duplicateKey != null) { + throw duplicateKey.exception(); + } + /* + * If entries is full, then this implementation may end up using the entries array + * directly and writing over the entry objects with non-terminal entries, but this is + * safe; if this Builder is used further, it will grow the entries array (so it can't + * affect the original array), and future build() calls will always copy any entry + * objects that cannot be safely reused. + */ + // localAlternatingKeysAndValues is an alias for the alternatingKeysAndValues field, except if + // we end up removing duplicates in a copy of the array. + @Nullable Object[] localAlternatingKeysAndValues; + int localSize = size; + if (valueComparator == null) { + localAlternatingKeysAndValues = alternatingKeysAndValues; + } else { + if (entriesUsed) { + alternatingKeysAndValues = Arrays.copyOf(alternatingKeysAndValues, 2 * size); + } + localAlternatingKeysAndValues = alternatingKeysAndValues; + if (!throwIfDuplicateKeys) { + // We want to retain only the last-put value for any given key, before sorting. + // This could be improved, but orderEntriesByValue is rather rarely used anyway. + localAlternatingKeysAndValues = lastEntryForEachKey(localAlternatingKeysAndValues, size); + if (localAlternatingKeysAndValues.length < alternatingKeysAndValues.length) { + localSize = localAlternatingKeysAndValues.length >>> 1; + } + } + sortEntries(localAlternatingKeysAndValues, localSize, valueComparator); + } + entriesUsed = true; + ImmutableMap map = + RegularImmutableMap.create(localSize, localAlternatingKeysAndValues, this); + if (throwIfDuplicateKeys && duplicateKey != null) { + throw duplicateKey.exception(); + } + return map; + } /** * Returns a newly-created immutable map. The iteration order of the returned map is the order * in which entries were inserted into the builder, unless {@link #orderEntriesByValue} was * called, in which case entries are sorted by value. * + *

    Prefer the equivalent method {@link #buildOrThrow()} to make it explicit that the method + * will throw an exception if there are duplicate keys. The {@code build()} method will soon be + * deprecated. + * * @throws IllegalArgumentException if duplicate keys were added */ - @SuppressWarnings("unchecked") public ImmutableMap build() { - /* - * If entries is full, then this implementation may end up using the entries array - * directly and writing over the entry objects with non-terminal entries, but this is - * safe; if this Builder is used further, it will grow the entries array (so it can't - * affect the original array), and future build() calls will always copy any entry - * objects that cannot be safely reused. - */ - sortEntries(); - entriesUsed = true; - return RegularImmutableMap.create(size, alternatingKeysAndValues); + return buildOrThrow(); } - void sortEntries() { - if (valueComparator != null) { - if (entriesUsed) { - alternatingKeysAndValues = Arrays.copyOf(alternatingKeysAndValues, 2 * size); - } - Entry[] entries = new Entry[size]; - for (int i = 0; i < size; i++) { - entries[i] = - new AbstractMap.SimpleImmutableEntry( - (K) alternatingKeysAndValues[2 * i], (V) alternatingKeysAndValues[2 * i + 1]); + /** + * Returns a newly-created immutable map, or throws an exception if any key was added more than + * once. The iteration order of the returned map is the order in which entries were inserted + * into the builder, unless {@link #orderEntriesByValue} was called, in which case entries are + * sorted by value. + * + * @throws IllegalArgumentException if duplicate keys were added + * @since 31.0 + */ + public ImmutableMap buildOrThrow() { + return build(true); + } + + /** + * Returns a newly-created immutable map, using the last value for any key that was added more + * than once. The iteration order of the returned map is the order in which entries were + * inserted into the builder, unless {@link #orderEntriesByValue} was called, in which case + * entries are sorted by value. If a key was added more than once, it appears in iteration order + * based on the first time it was added, again unless {@link #orderEntriesByValue} was called. + * + *

    In the current implementation, all values associated with a given key are stored in the + * {@code Builder} object, even though only one of them will be used in the built map. If there + * can be many repeated keys, it may be more space-efficient to use a {@link + * java.util.LinkedHashMap LinkedHashMap} and {@link ImmutableMap#copyOf(Map)} rather than + * {@code ImmutableMap.Builder}. + * + * @since 31.1 + */ + public ImmutableMap buildKeepingLast() { + return build(false); + } + + static void sortEntries( + @Nullable Object[] alternatingKeysAndValues, + int size, + Comparator valueComparator) { + @SuppressWarnings({"rawtypes", "unchecked"}) + Entry[] entries = new Entry[size]; + for (int i = 0; i < size; i++) { + // requireNonNull is safe because the first `2*size` elements have been filled in. + Object key = requireNonNull(alternatingKeysAndValues[2 * i]); + @SuppressWarnings("unchecked") + V value = (V) requireNonNull(alternatingKeysAndValues[2 * i + 1]); + entries[i] = new AbstractMap.SimpleImmutableEntry(key, value); + } + Arrays.sort(entries, 0, size, Ordering.from(valueComparator).onResultOf(Entry::getValue)); + for (int i = 0; i < size; i++) { + alternatingKeysAndValues[2 * i] = entries[i].getKey(); + alternatingKeysAndValues[2 * i + 1] = entries[i].getValue(); + } + } + + private @Nullable Object[] lastEntryForEachKey( + @Nullable Object[] localAlternatingKeysAndValues, int size) { + Set seenKeys = new HashSet<>(); + BitSet dups = new BitSet(); // slots that are overridden by a later duplicate key + for (int i = size - 1; i >= 0; i--) { + Object key = requireNonNull(localAlternatingKeysAndValues[2 * i]); + if (!seenKeys.add(key)) { + dups.set(i); } - Arrays.sort( - entries, 0, size, Ordering.from(valueComparator).onResultOf(Maps.valueFunction())); - for (int i = 0; i < size; i++) { - alternatingKeysAndValues[2 * i] = entries[i].getKey(); - alternatingKeysAndValues[2 * i + 1] = entries[i].getValue(); + } + if (dups.isEmpty()) { + return localAlternatingKeysAndValues; + } + Object[] newAlternatingKeysAndValues = new Object[(size - dups.cardinality()) * 2]; + for (int inI = 0, outI = 0; inI < size * 2; ) { + if (dups.get(inI >>> 1)) { + inI += 2; + } else { + newAlternatingKeysAndValues[outI++] = + requireNonNull(localAlternatingKeysAndValues[inI++]); + newAlternatingKeysAndValues[outI++] = + requireNonNull(localAlternatingKeysAndValues[inI++]); } } + return newAlternatingKeysAndValues; + } + + static final class DuplicateKey { + private final Object key; + private final Object value1; + private final Object value2; + + DuplicateKey(Object key, Object value1, Object value2) { + this.key = key; + this.value1 = value1; + this.value2 = value2; + } + + IllegalArgumentException exception() { + return new IllegalArgumentException( + "Multiple entries with same key: " + key + "=" + value1 + " and " + key + "=" + value2); + } } } @@ -410,7 +744,6 @@ public static ImmutableMap copyOf(Map map * @throws IllegalArgumentException if two entries have the same key * @since 19.0 */ - @Beta public static ImmutableMap copyOf( Iterable> entries) { int initialCapacity = @@ -434,7 +767,7 @@ ImmutableSet createKeySet() { @Override ImmutableSet> createEntrySet() { - class EntrySetImpl extends ImmutableMapEntrySet { + final class EntrySetImpl extends ImmutableMapEntrySet { @Override ImmutableMap map() { return IteratorBasedImmutableMap.this; @@ -444,6 +777,15 @@ ImmutableMap map() { public UnmodifiableIterator> iterator() { return entryIterator(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } return new EntrySetImpl(); } @@ -452,6 +794,15 @@ public UnmodifiableIterator> iterator() { ImmutableCollection createValues() { return new ImmutableMapValues<>(this); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } ImmutableMap() {} @@ -466,7 +817,7 @@ ImmutableCollection createValues() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final V put(K k, V v) { + public final @Nullable V put(K k, V v) { throw new UnsupportedOperationException(); } @@ -479,7 +830,7 @@ public final V put(K k, V v) { @CanIgnoreReturnValue @Deprecated @Override - public final V remove(Object o) { + public final @Nullable V remove(@Nullable Object o) { throw new UnsupportedOperationException(); } @@ -515,18 +866,18 @@ public boolean isEmpty() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return values().contains(value); } // Overriding to mark it Nullable @Override - public abstract V get(@NullableDecl Object key); + public abstract @Nullable V get(@Nullable Object key); /** * {@inheritDoc} @@ -537,15 +888,46 @@ public boolean containsValue(@NullableDecl Object value) { * * @since 23.5 (but since 21.0 in the JRE flavor). - * Note that API Level 24 users can call this method with any version of Guava. + * Note, however, that Java 8+ users can call this method with any version and flavor of + * Guava. */ - // @Override under Java 8 / API Level 24 - public final V getOrDefault(@NullableDecl Object key, @NullableDecl V defaultValue) { + @Override + public final @Nullable V getOrDefault(@Nullable Object key, @Nullable V defaultValue) { + /* + * Even though it's weird to pass a defaultValue that is null, some callers do so. Those who + * pass a literal "null" should probably just use `get`, but I would expect other callers to + * pass an expression that *might* be null. This could happen with: + * + * - a `getFooOrDefault(@Nullable Foo defaultValue)` method that returns + * `map.getOrDefault(FOO_KEY, defaultValue)` + * + * - a call that consults a chain of maps, as in `mapA.getOrDefault(key, mapB.getOrDefault(key, + * ...))` + * + * So it makes sense for the parameter (and thus the return type) to be @Nullable. + * + * Two other points: + * + * 1. We'll want to use something like @PolyNull once we can make that work for the various + * platforms we target. + * + * 2. Kotlin's Map type has a getOrDefault method that accepts and returns a "plain V," in + * contrast to the "V?" type that we're using. As a result, Kotlin sees a conflict between the + * nullness annotations in ImmutableMap and those in its own Map type. In response, it considers + * the parameter and return type both to be platform types. As a result, Kotlin permits calls + * that can lead to NullPointerException. That's unfortunate. But hopefully most Kotlin callers + * use `get(key) ?: defaultValue` instead of this method, anyway. + */ V result = get(key); - return (result != null) ? result : defaultValue; + // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker. + if (result != null) { + return result; + } else { + return defaultValue; + } } - @LazyInit @RetainedWith private transient ImmutableSet> entrySet; + @LazyInit @RetainedWith private transient @Nullable ImmutableSet> entrySet; /** * Returns an immutable set of the mappings in this map. The iteration order is specified by the @@ -559,7 +941,7 @@ public ImmutableSet> entrySet() { abstract ImmutableSet> createEntrySet(); - @LazyInit @RetainedWith private transient ImmutableSet keySet; + @LazyInit @RetainedWith private transient @Nullable ImmutableSet keySet; /** * Returns an immutable set of the keys in this map, in the same order that they appear in {@link @@ -579,7 +961,7 @@ public ImmutableSet keySet() { abstract ImmutableSet createKeySet(); UnmodifiableIterator keyIterator() { - final UnmodifiableIterator> entryIterator = entrySet().iterator(); + UnmodifiableIterator> entryIterator = entrySet().iterator(); return new UnmodifiableIterator() { @Override public boolean hasNext() { @@ -593,7 +975,7 @@ public K next() { }; } - @LazyInit @RetainedWith private transient ImmutableCollection values; + @LazyInit @RetainedWith private transient @Nullable ImmutableCollection values; /** * Returns an immutable collection of the values in this map, in the same order that they appear @@ -613,7 +995,7 @@ public ImmutableCollection values() { abstract ImmutableCollection createValues(); // cached so that this.multimapView().inverse() only computes inverse once - @LazyInit private transient ImmutableSetMultimap multimapView; + @LazyInit private transient @Nullable ImmutableSetMultimap multimapView; /** * Returns a multimap view of the map. @@ -646,12 +1028,12 @@ ImmutableSet createKeySet() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return ImmutableMap.this.containsKey(key); } @Override - public ImmutableSet get(@NullableDecl Object key) { + public @Nullable ImmutableSet get(@Nullable Object key) { V outerValue = ImmutableMap.this.get(key); return (outerValue == null) ? null : ImmutableSet.of(outerValue); } @@ -674,7 +1056,7 @@ boolean isHashCodeFast() { @Override UnmodifiableIterator>> entryIterator() { - final Iterator> backingIterator = ImmutableMap.this.entrySet().iterator(); + Iterator> backingIterator = ImmutableMap.this.entrySet().iterator(); return new UnmodifiableIterator>>() { @Override public boolean hasNext() { @@ -683,7 +1065,7 @@ public boolean hasNext() { @Override public Entry> next() { - final Entry backingEntry = backingIterator.next(); + Entry backingEntry = backingIterator.next(); return new AbstractMapEntry>() { @Override public K getKey() { @@ -698,10 +1080,19 @@ public ImmutableSet getValue() { } }; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return Maps.equalsImpl(this, object); } @@ -726,6 +1117,7 @@ public String toString() { * reconstructed using public factory methods. This ensures that the implementation types remain * as implementation details. */ + @J2ktIncompatible // serialization static class SerializedForm implements Serializable { // This object retains references to collections returned by keySet() and value(). This saves // bytes when the both the map and its keySet or value collection are written to the same @@ -742,7 +1134,8 @@ static class SerializedForm implements Serializable { Object[] keys = new Object[map.size()]; Object[] values = new Object[map.size()]; int i = 0; - for (Entry entry : map.entrySet()) { + // "extends Object" works around https://github.com/typetools/checker-framework/issues/3013 + for (Entry entry : map.entrySet()) { keys[i] = entry.getKey(); values[i] = entry.getValue(); i++; @@ -773,7 +1166,7 @@ final Object readResolve() { builder.put(keyIter.next(), valueIter.next()); } - return builder.build(); + return builder.buildOrThrow(); } @SuppressWarnings("unchecked") @@ -786,7 +1179,7 @@ final Object legacyReadResolve() { for (int i = 0; i < keys.length; i++) { builder.put(keys[i], values[i]); } - return builder.build(); + return builder.buildOrThrow(); } /** @@ -796,7 +1189,7 @@ Builder makeBuilder(int size) { return new Builder<>(size); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -804,7 +1197,15 @@ Builder makeBuilder(int size) { * method. Publicly-accessible subclasses must override this method and should return a subclass * of SerializedForm whose readResolve() method returns objects of the subclass type. */ + @J2ktIncompatible // serialization Object writeReplace() { return new SerializedForm<>(this); } + + @J2ktIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableMapEntrySet.java b/android/guava/src/com/google/common/collect/ImmutableMapEntrySet.java index 72fc5cf3c91d..f4c90b74e305 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMapEntrySet.java +++ b/android/guava/src/com/google/common/collect/ImmutableMapEntrySet.java @@ -16,11 +16,16 @@ package com.google.common.collect; +import static com.google.common.collect.ImmutableList.asImmutableList; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * {@code entrySet()} implementation for {@link ImmutableMap}. @@ -28,14 +33,14 @@ * @author Jesse Wilson * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible abstract class ImmutableMapEntrySet extends ImmutableSet> { static final class RegularEntrySet extends ImmutableMapEntrySet { private final transient ImmutableMap map; private final transient ImmutableList> entries; RegularEntrySet(ImmutableMap map, Entry[] entries) { - this(map, ImmutableList.>asImmutableList(entries)); + this(map, asImmutableList(entries)); } RegularEntrySet(ImmutableMap map, ImmutableList> entries) { @@ -50,7 +55,7 @@ ImmutableMap map() { @Override @GwtIncompatible("not used in GWT") - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { return entries.copyIntoArray(dst, offset); } @@ -63,6 +68,15 @@ public UnmodifiableIterator> iterator() { ImmutableList> createAsList() { return entries; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } ImmutableMapEntrySet() {} @@ -75,7 +89,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { if (object instanceof Entry) { Entry entry = (Entry) object; V value = map().get(entry.getKey()); @@ -100,14 +114,22 @@ public int hashCode() { return map().hashCode(); } - @GwtIncompatible // serialization - @Override + @GwtIncompatible + @J2ktIncompatible + @Override Object writeReplace() { return new EntrySetSerializedForm<>(map()); } - @GwtIncompatible // serialization - private static class EntrySetSerializedForm implements Serializable { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use EntrySetSerializedForm"); + } + + @GwtIncompatible + @J2ktIncompatible + private static final class EntrySetSerializedForm implements Serializable { final ImmutableMap map; EntrySetSerializedForm(ImmutableMap map) { @@ -118,6 +140,6 @@ Object readResolve() { return map.entrySet(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/collect/ImmutableMapKeySet.java b/android/guava/src/com/google/common/collect/ImmutableMapKeySet.java index 77babc276e5d..23731140afb3 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMapKeySet.java +++ b/android/guava/src/com/google/common/collect/ImmutableMapKeySet.java @@ -18,8 +18,9 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * {@code keySet()} implementation for {@link ImmutableMap}. @@ -27,7 +28,7 @@ * @author Jesse Wilson * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible final class ImmutableMapKeySet extends IndexedImmutableSet { private final ImmutableMap map; @@ -46,7 +47,7 @@ public UnmodifiableIterator iterator() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return map.containsKey(object); } @@ -60,14 +61,16 @@ boolean isPartialView() { return true; } - @GwtIncompatible // serialization @Override - Object writeReplace() { + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { return new KeySetSerializedForm(map); } - @GwtIncompatible // serialization - private static class KeySetSerializedForm implements Serializable { + @GwtIncompatible + @J2ktIncompatible + private static final class KeySetSerializedForm implements Serializable { final ImmutableMap map; KeySetSerializedForm(ImmutableMap map) { @@ -78,6 +81,6 @@ Object readResolve() { return map.keySet(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/collect/ImmutableMapValues.java b/android/guava/src/com/google/common/collect/ImmutableMapValues.java index 4a0e3960d581..f652074c118c 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMapValues.java +++ b/android/guava/src/com/google/common/collect/ImmutableMapValues.java @@ -18,9 +18,10 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * {@code values()} implementation for {@link ImmutableMap}. @@ -28,7 +29,7 @@ * @author Jesse Wilson * @author Kevin Bourrillion */ -@GwtCompatible(emulated = true) +@GwtCompatible final class ImmutableMapValues extends ImmutableCollection { private final ImmutableMap map; @@ -59,7 +60,7 @@ public V next() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return object != null && Iterators.contains(iterator(), object); } @@ -70,7 +71,7 @@ boolean isPartialView() { @Override public ImmutableList asList() { - final ImmutableList> entryList = map.entrySet().asList(); + ImmutableList> entryList = map.entrySet().asList(); return new ImmutableList() { @Override public V get(int index) { @@ -86,27 +87,46 @@ boolean isPartialView() { public int size() { return entryList.size(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } }; } - @GwtIncompatible // serialization @Override - Object writeReplace() { + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { return new SerializedForm(map); } - @GwtIncompatible // serialization - private static class SerializedForm implements Serializable { + @GwtIncompatible + @J2ktIncompatible + /* + * The mainline copy of ImmutableMapValues doesn't produce this serialized form anymore, though + * the backport does. For now, we're keeping the class declaration in *both* flavors so that both + * flavors can read old data or data from the other flavor. However, we strongly discourage + * relying on this, as we have made incompatible changes to serialized forms in the past and + * expect to do so again, as discussed in https://github.com/google/guava#important-warnings. + */ + @SuppressWarnings("unused") + private static final class SerializedForm implements Serializable { final ImmutableMap map; SerializedForm(ImmutableMap map) { this.map = map; } - Object readResolve() { + Object readResolve() { return map.values(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/collect/ImmutableMultimap.java b/android/guava/src/com/google/common/collect/ImmutableMultimap.java index 61472f65dcb7..ff00bc3d34f3 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMultimap.java +++ b/android/guava/src/com/google/common/collect/ImmutableMultimap.java @@ -18,24 +18,31 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Maps.immutableEntry; +import static java.lang.Math.max; +import static java.util.Arrays.asList; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.DoNotMock; import com.google.j2objc.annotations.Weak; import com.google.j2objc.annotations.WeakOuter; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@link Multimap} whose contents will never change, with many other important properties @@ -58,12 +65,12 @@ * immediately after the last entry having that key. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Jared Levy * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public abstract class ImmutableMultimap extends BaseImmutableMultimap implements Serializable { @@ -121,18 +128,31 @@ public static Builder builder() { return new Builder<>(); } + /** + * Returns a new builder with a hint for how many distinct keys are expected to be added. The + * generated builder is equivalent to that returned by {@link #builder}, but may perform better if + * {@code expectedKeys} is a good estimate. + * + * @throws IllegalArgumentException if {@code expectedKeys} is negative + * @since 33.3.0 + */ + public static Builder builderWithExpectedKeys(int expectedKeys) { + checkNonnegative(expectedKeys, "expectedKeys"); + return new Builder<>(expectedKeys); + } + /** * A builder for creating immutable multimap instances, especially {@code public static final} * multimaps ("constant multimaps"). Example: * - *

    {@code
    +   * {@snippet :
        * static final Multimap STRING_TO_INTEGER_MULTIMAP =
        *     new ImmutableMultimap.Builder()
        *         .put("one", 1)
        *         .putAll("several", 1, 2, 3)
        *         .putAll("many", 1, 2, 3, 4, 5)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple multimaps in series. Each multimap contains the key-value mappings in the previously @@ -142,31 +162,86 @@ public static Builder builder() { */ @DoNotMock public static class Builder { - Map> builderMap; - @NullableDecl Comparator keyComparator; - @NullableDecl Comparator valueComparator; + @Nullable Map> builderMap; + @Nullable Comparator keyComparator; + @Nullable Comparator valueComparator; + int expectedValuesPerKey = ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY; /** * Creates a new builder. The returned builder is equivalent to the builder generated by {@link * ImmutableMultimap#builder}. */ - public Builder() { - this.builderMap = Platform.preservesInsertionOrderOnPutsMap(); + public Builder() {} + + /** Creates a new builder with a hint for the number of distinct keys. */ + Builder(int expectedKeys) { + if (expectedKeys > 0) { + builderMap = Platform.preservesInsertionOrderOnPutsMapWithExpectedSize(expectedKeys); + } + // otherwise, leave it null to be constructed lazily + } + + Map> ensureBuilderMapNonNull() { + Map> result = builderMap; + if (result == null) { + result = Platform.preservesInsertionOrderOnPutsMap(); + builderMap = result; + } + return result; + } + + ImmutableCollection.Builder newValueCollectionBuilderWithExpectedSize(int expectedSize) { + return ImmutableList.builderWithExpectedSize(expectedSize); } - Collection newMutableValueCollection() { - return new ArrayList<>(); + /** + * Provides a hint for how many values will be associated with each key newly added to the + * builder after this call. This does not change semantics, but may improve performance if + * {@code expectedValuesPerKey} is a good estimate. + * + *

    This may be called more than once; each newly added key will use the most recent call to + * {@link #expectedValuesPerKey} as its hint. + * + * @throws IllegalArgumentException if {@code expectedValuesPerKey} is negative + * @since 33.3.0 + */ + @CanIgnoreReturnValue + public Builder expectedValuesPerKey(int expectedValuesPerKey) { + checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + + // Always presize to at least 1, since we only bother creating a value collection if there's + // at least one element. + this.expectedValuesPerKey = max(expectedValuesPerKey, 1); + + return this; + } + + /** + * By default, if we are handed a value collection bigger than expectedValuesPerKey, presize to + * accept that many elements. + * + *

    This gets overridden in ImmutableSetMultimap.Builder to only trust the size of {@code + * values} if it is a Set and therefore probably already deduplicated. + */ + int expectedValueCollectionSize(int defaultExpectedValues, Iterable values) { + if (values instanceof Collection) { + Collection collection = (Collection) values; + return max(defaultExpectedValues, collection.size()); + } else { + return defaultExpectedValues; + } } /** Adds a key-value mapping to the built multimap. */ @CanIgnoreReturnValue public Builder put(K key, V value) { checkEntryNotNull(key, value); - Collection valueCollection = builderMap.get(key); - if (valueCollection == null) { - builderMap.put(key, valueCollection = newMutableValueCollection()); + ImmutableCollection.Builder valuesBuilder = ensureBuilderMapNonNull().get(key); + if (valuesBuilder == null) { + valuesBuilder = newValueCollectionBuilderWithExpectedSize(expectedValuesPerKey); + ensureBuilderMapNonNull().put(key, valuesBuilder); } - valueCollection.add(value); + valuesBuilder.add(value); return this; } @@ -186,7 +261,6 @@ public Builder put(Entry entry) { * @since 19.0 */ @CanIgnoreReturnValue - @Beta public Builder putAll(Iterable> entries) { for (Entry entry : entries) { put(entry); @@ -205,25 +279,22 @@ public Builder putAll(K key, Iterable values) { if (key == null) { throw new NullPointerException("null key in entry: null=" + Iterables.toString(values)); } - Collection valueCollection = builderMap.get(key); - if (valueCollection != null) { - for (V value : values) { - checkEntryNotNull(key, value); - valueCollection.add(value); - } - return this; - } Iterator valuesItr = values.iterator(); if (!valuesItr.hasNext()) { return this; } - valueCollection = newMutableValueCollection(); + ImmutableCollection.Builder valuesBuilder = ensureBuilderMapNonNull().get(key); + if (valuesBuilder == null) { + valuesBuilder = + newValueCollectionBuilderWithExpectedSize( + expectedValueCollectionSize(expectedValuesPerKey, values)); + ensureBuilderMapNonNull().put(key, valuesBuilder); + } while (valuesItr.hasNext()) { V value = valuesItr.next(); checkEntryNotNull(key, value); - valueCollection.add(value); + valuesBuilder.add(value); } - builderMap.put(key, valueCollection); return this; } @@ -235,7 +306,7 @@ public Builder putAll(K key, Iterable values) { */ @CanIgnoreReturnValue public Builder putAll(K key, V... values) { - return putAll(key, Arrays.asList(values)); + return putAll(key, asList(values)); } /** @@ -279,19 +350,24 @@ public Builder orderValuesBy(Comparator valueComparator) { @CanIgnoreReturnValue Builder combine(Builder other) { - for (Map.Entry> entry : other.builderMap.entrySet()) { - putAll(entry.getKey(), entry.getValue()); + if (other.builderMap != null) { + for (Map.Entry> entry : other.builderMap.entrySet()) { + putAll(entry.getKey(), entry.getValue().build()); + } } return this; } /** Returns a newly-created immutable multimap. */ public ImmutableMultimap build() { - Collection>> mapEntries = builderMap.entrySet(); + if (builderMap == null) { + return ImmutableListMultimap.of(); + } + Collection>> mapEntries = builderMap.entrySet(); if (keyComparator != null) { mapEntries = Ordering.from(keyComparator).onKeys().immutableSortedCopy(mapEntries); } - return ImmutableListMultimap.fromMapEntries(mapEntries, valueComparator); + return ImmutableListMultimap.fromMapBuilderEntries(mapEntries, valueComparator); } } @@ -324,7 +400,6 @@ public static ImmutableMultimap copyOf(Multimap ImmutableMultimap copyOf( Iterable> entries) { return ImmutableListMultimap.copyOf(entries); @@ -336,12 +411,15 @@ public static ImmutableMultimap copyOf( // These constants allow the deserialization code to set final fields. This // holder class makes sure they are not initialized unless an instance is // deserialized. - @GwtIncompatible // java serialization is not supported - static class FieldSettersHolder { - static final Serialization.FieldSetter MAP_FIELD_SETTER = + @GwtIncompatible + @J2ktIncompatible + static final class FieldSettersHolder { + static final Serialization.FieldSetter> MAP_FIELD_SETTER = Serialization.getFieldSetter(ImmutableMultimap.class, "map"); - static final Serialization.FieldSetter SIZE_FIELD_SETTER = + static final Serialization.FieldSetter> SIZE_FIELD_SETTER = Serialization.getFieldSetter(ImmutableMultimap.class, "size"); + + private FieldSettersHolder() {} } ImmutableMultimap(ImmutableMap> map, int size) { @@ -360,7 +438,11 @@ static class FieldSettersHolder { @CanIgnoreReturnValue @Deprecated @Override - public ImmutableCollection removeAll(Object key) { + @DoNotCall("Always throws UnsupportedOperationException") + // DoNotCall wants this to be final, but we want to override it to return more specific types. + // Inheritance is closed, and all subtypes are @DoNotCall, so this is safe to suppress. + @SuppressWarnings("DoNotCall") + public ImmutableCollection removeAll(@Nullable Object key) { throw new UnsupportedOperationException(); } @@ -373,6 +455,10 @@ public ImmutableCollection removeAll(Object key) { @CanIgnoreReturnValue @Deprecated @Override + @DoNotCall("Always throws UnsupportedOperationException") + // DoNotCall wants this to be final, but we want to override it to return more specific types. + // Inheritance is closed, and all subtypes are @DoNotCall, so this is safe to suppress. + @SuppressWarnings("DoNotCall") public ImmutableCollection replaceValues(K key, Iterable values) { throw new UnsupportedOperationException(); } @@ -385,7 +471,8 @@ public ImmutableCollection replaceValues(K key, Iterable values) */ @Deprecated @Override - public void clear() { + @DoNotCall("Always throws UnsupportedOperationException") + public final void clear() { throw new UnsupportedOperationException(); } @@ -414,7 +501,8 @@ public void clear() { @CanIgnoreReturnValue @Deprecated @Override - public boolean put(K key, V value) { + @DoNotCall("Always throws UnsupportedOperationException") + public final boolean put(K key, V value) { throw new UnsupportedOperationException(); } @@ -427,7 +515,8 @@ public boolean put(K key, V value) { @CanIgnoreReturnValue @Deprecated @Override - public boolean putAll(K key, Iterable values) { + @DoNotCall("Always throws UnsupportedOperationException") + public final boolean putAll(K key, Iterable values) { throw new UnsupportedOperationException(); } @@ -440,7 +529,8 @@ public boolean putAll(K key, Iterable values) { @CanIgnoreReturnValue @Deprecated @Override - public boolean putAll(Multimap multimap) { + @DoNotCall("Always throws UnsupportedOperationException") + public final boolean putAll(Multimap multimap) { throw new UnsupportedOperationException(); } @@ -453,7 +543,8 @@ public boolean putAll(Multimap multimap) { @CanIgnoreReturnValue @Deprecated @Override - public boolean remove(Object key, Object value) { + @DoNotCall("Always throws UnsupportedOperationException") + public final boolean remove(@Nullable Object key, @Nullable Object value) { throw new UnsupportedOperationException(); } @@ -470,12 +561,12 @@ boolean isPartialView() { // accessors @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return map.containsKey(key); } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return value != null && super.containsValue(value); } @@ -526,7 +617,7 @@ ImmutableCollection> createEntries() { return new EntryCollection<>(this); } - private static class EntryCollection extends ImmutableCollection> { + private static final class EntryCollection extends ImmutableCollection> { @Weak final ImmutableMultimap multimap; EntryCollection(ImmutableMultimap multimap) { @@ -549,7 +640,7 @@ public int size() { } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { if (object instanceof Entry) { Entry entry = (Entry) object; return multimap.containsEntry(entry.getKey(), entry.getValue()); @@ -557,7 +648,16 @@ public boolean contains(Object object) { return false; } - private static final long serialVersionUID = 0; + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @Override @@ -565,8 +665,8 @@ UnmodifiableIterator> entryIterator() { return new UnmodifiableIterator>() { final Iterator>> asMapItr = map.entrySet().iterator(); - K currentKey = null; - Iterator valueItr = Iterators.emptyIterator(); + @Nullable K currentKey = null; + Iterator valueItr = emptyIterator(); @Override public boolean hasNext() { @@ -580,7 +680,11 @@ public Entry next() { currentKey = entry.getKey(); valueItr = entry.getValue().iterator(); } - return Maps.immutableEntry(currentKey, valueItr.next()); + /* + * requireNonNull is safe: The first call to this method always enters the !hasNext() case + * and populates currentKey, after which it's never cleared. + */ + return immutableEntry(requireNonNull(currentKey), valueItr.next()); } }; } @@ -602,14 +706,14 @@ ImmutableMultiset createKeys() { @SuppressWarnings("serial") // Uses writeReplace, not default serialization @WeakOuter - class Keys extends ImmutableMultiset { + private final class Keys extends ImmutableMultiset { @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return containsKey(object); } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { Collection values = map.get(element); return (values == null) ? 0 : values.size(); } @@ -636,13 +740,21 @@ boolean isPartialView() { } @GwtIncompatible + @J2ktIncompatible @Override Object writeReplace() { return new KeysSerializedForm(ImmutableMultimap.this); } + + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use KeysSerializedForm"); + } } @GwtIncompatible + @J2ktIncompatible private static final class KeysSerializedForm implements Serializable { final ImmutableMultimap multimap; @@ -672,8 +784,8 @@ ImmutableCollection createValues() { @Override UnmodifiableIterator valueIterator() { return new UnmodifiableIterator() { - Iterator> valueCollectionItr = map.values().iterator(); - Iterator valueItr = Iterators.emptyIterator(); + final Iterator> valueCollectionItr = map.values().iterator(); + Iterator valueItr = emptyIterator(); @Override public boolean hasNext() { @@ -698,7 +810,7 @@ private static final class Values extends ImmutableCollection { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return multimap.containsValue(object); } @@ -709,7 +821,7 @@ public UnmodifiableIterator iterator() { @GwtIncompatible // not present in emulated superclass @Override - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { for (ImmutableCollection valueCollection : multimap.map.values()) { offset = valueCollection.copyIntoArray(dst, offset); } @@ -726,8 +838,17 @@ boolean isPartialView() { return true; } - private static final long serialVersionUID = 0; + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ImmutableMultiset.java b/android/guava/src/com/google/common/collect/ImmutableMultiset.java index 053e754b5f31..11f12c67a4ac 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMultiset.java +++ b/android/guava/src/com/google/common/collect/ImmutableMultiset.java @@ -17,19 +17,26 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.WeakOuter; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A {@link Multiset} whose contents will never change, with many other important properties @@ -40,16 +47,47 @@ * element when the multiset was created. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Jared Levy * @author Louis Wasserman * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // we're overriding default serialization -public abstract class ImmutableMultiset extends ImmutableMultisetGwtSerializationDependencies - implements Multiset { +public abstract class ImmutableMultiset extends ImmutableCollection implements Multiset { + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableMultiset}. Elements iterate in order by the first appearance of that element in + * encounter order. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toImmutableMultiset() { + return CollectCollectors.toImmutableMultiset(Function.identity(), e -> 1); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableMultiset} whose + * elements are the result of applying {@code elementFunction} to the inputs, with counts equal to + * the result of applying {@code countFunction} to the inputs. + * + *

    If the mapped elements contain duplicates (according to {@link Object#equals}), the first + * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of + * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. + * + * @since 33.2.0 (available since 22.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableMultiset( + Function elementFunction, + ToIntFunction countFunction) { + return CollectCollectors.toImmutableMultiset(elementFunction, countFunction); + } + /** * Returns the empty immutable multiset. * @@ -63,12 +101,11 @@ public static ImmutableMultiset of() { /** * Returns an immutable multiset containing a single element. * - * @throws NullPointerException if {@code element} is null + * @throws NullPointerException if the element is null * @since 6.0 (source-compatible since 2.0) */ - @SuppressWarnings("unchecked") // generic array created but never written - public static ImmutableMultiset of(E element) { - return copyFromElements(element); + public static ImmutableMultiset of(E e1) { + return copyFromElements(e1); } /** @@ -77,7 +114,6 @@ public static ImmutableMultiset of(E element) { * @throws NullPointerException if any element is null * @since 6.0 (source-compatible since 2.0) */ - @SuppressWarnings("unchecked") // public static ImmutableMultiset of(E e1, E e2) { return copyFromElements(e1, e2); } @@ -89,7 +125,6 @@ public static ImmutableMultiset of(E e1, E e2) { * @throws NullPointerException if any element is null * @since 6.0 (source-compatible since 2.0) */ - @SuppressWarnings("unchecked") // public static ImmutableMultiset of(E e1, E e2, E e3) { return copyFromElements(e1, e2, e3); } @@ -101,7 +136,6 @@ public static ImmutableMultiset of(E e1, E e2, E e3) { * @throws NullPointerException if any element is null * @since 6.0 (source-compatible since 2.0) */ - @SuppressWarnings("unchecked") // public static ImmutableMultiset of(E e1, E e2, E e3, E e4) { return copyFromElements(e1, e2, e3, e4); } @@ -113,7 +147,6 @@ public static ImmutableMultiset of(E e1, E e2, E e3, E e4) { * @throws NullPointerException if any element is null * @since 6.0 (source-compatible since 2.0) */ - @SuppressWarnings("unchecked") // public static ImmutableMultiset of(E e1, E e2, E e3, E e4, E e5) { return copyFromElements(e1, e2, e3, e4, e5); } @@ -125,7 +158,6 @@ public static ImmutableMultiset of(E e1, E e2, E e3, E e4, E e5) { * @throws NullPointerException if any element is null * @since 6.0 (source-compatible since 2.0) */ - @SuppressWarnings("unchecked") // public static ImmutableMultiset of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) { return new Builder().add(e1).add(e2).add(e3).add(e4).add(e5).add(e6).add(others).build(); } @@ -188,10 +220,10 @@ static ImmutableMultiset copyFromEntries( @Override public UnmodifiableIterator iterator() { - final Iterator> entryIterator = entrySet().iterator(); + Iterator> entryIterator = entrySet().iterator(); return new UnmodifiableIterator() { int remaining; - @NullableDecl E element; + @Nullable E element; @Override public boolean hasNext() { @@ -206,12 +238,16 @@ public E next() { remaining = entry.getCount(); } remaining--; - return element; + /* + * requireNonNull is safe because `remaining` starts at 0, forcing us to initialize + * `element` above. After that, we never clear it. + */ + return requireNonNull(element); } }; } - @LazyInit private transient ImmutableList asList; + @LazyInit private transient @Nullable ImmutableList asList; @Override public ImmutableList asList() { @@ -220,7 +256,7 @@ public ImmutableList asList() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return count(object) > 0; } @@ -248,7 +284,7 @@ public final int add(E element, int occurrences) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final int remove(Object element, int occurrences) { + public final int remove(@Nullable Object element, int occurrences) { throw new UnsupportedOperationException(); } @@ -282,7 +318,7 @@ public final boolean setCount(E element, int oldCount, int newCount) { @GwtIncompatible // not present in emulated superclass @Override - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { for (Multiset.Entry entry : entrySet()) { Arrays.fill(dst, offset, offset + entry.getCount(), entry.getElement()); offset += entry.getCount(); @@ -291,7 +327,7 @@ int copyIntoArray(Object[] dst, int offset) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return Multisets.equalsImpl(this, object); } @@ -305,11 +341,13 @@ public String toString() { return entrySet().toString(); } - /** @since 21.0 (present with return type {@code Set} since 2.0) */ + /** + * @since 21.0 (present with return type {@code Set} since 2.0) + */ @Override public abstract ImmutableSet elementSet(); - @LazyInit private transient ImmutableSet> entrySet; + @LazyInit private transient @Nullable ImmutableSet> entrySet; @Override public ImmutableSet> entrySet() { @@ -318,7 +356,7 @@ public ImmutableSet> entrySet() { } private ImmutableSet> createEntrySet() { - return isEmpty() ? ImmutableSet.>of() : new EntrySet(); + return isEmpty() ? ImmutableSet.of() : new EntrySet(); } abstract Entry getEntry(int index); @@ -341,7 +379,7 @@ public int size() { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Entry) { Entry entry = (Entry) o; if (entry.getCount() <= 0) { @@ -359,44 +397,59 @@ public int hashCode() { } @GwtIncompatible + @J2ktIncompatible @Override - Object writeReplace() { + Object writeReplace() { return new EntrySetSerializedForm(ImmutableMultiset.this); } - private static final long serialVersionUID = 0; + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use EntrySetSerializedForm"); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @GwtIncompatible - static class EntrySetSerializedForm implements Serializable { + @J2ktIncompatible + private static final class EntrySetSerializedForm implements Serializable { final ImmutableMultiset multiset; EntrySetSerializedForm(ImmutableMultiset multiset) { this.multiset = multiset; } - Object readResolve() { + Object readResolve() { return multiset.entrySet(); } } @GwtIncompatible + @J2ktIncompatible @Override - abstract Object writeReplace(); + abstract Object writeReplace(); + + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } /** * Returns a new builder. The generated builder is equivalent to the builder created by the {@link * Builder} constructor. */ public static Builder builder() { - return new Builder(); + return new Builder<>(); } /** * A builder for creating immutable multiset instances, especially {@code public static final} * multisets ("constant multisets"). Example: * - *

    {@code
    +   * {@snippet :
        * public static final ImmutableMultiset BEANS =
        *     new ImmutableMultiset.Builder()
        *         .addCopies(Bean.COCOA, 4)
    @@ -404,7 +457,7 @@ public static  Builder builder() {
        *         .addCopies(Bean.RED, 8)
        *         .addCopies(Bean.BLACK_EYED, 10)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple multisets in series. @@ -412,13 +465,19 @@ public static Builder builder() { * @since 2.0 */ public static class Builder extends ImmutableCollection.Builder { - ObjectCountHashMap contents; + /* + * `contents` is null only for instances of the subclass, ImmutableSortedMultiset.Builder. That + * subclass overrides all the methods that access it here. Thus, all the methods here can safely + * assume that this field is non-null. + */ + @Nullable ObjectCountHashMap contents; /** * If build() has been called on the current contents multiset, we need to copy it on any future * modifications, or we'll modify the already-built ImmutableMultiset. */ boolean buildInvoked = false; + /** * In the event of a setCount(elem, 0) call, we may need to remove elements, which destroys the * insertion order property of ObjectCountHashMap. In that event, we need to convert to a @@ -483,6 +542,7 @@ public Builder add(E... elements) { */ @CanIgnoreReturnValue public Builder addCopies(E element, int occurrences) { + requireNonNull(contents); // see the comment on the field if (occurrences == 0) { return this; } @@ -508,6 +568,7 @@ public Builder addCopies(E element, int occurrences) { */ @CanIgnoreReturnValue public Builder setCount(E element, int count) { + requireNonNull(contents); // see the comment on the field if (count == 0 && !isLinkedHash) { contents = new ObjectCountLinkedHashMap(contents); isLinkedHash = true; @@ -537,8 +598,9 @@ public Builder setCount(E element, int count) { @CanIgnoreReturnValue @Override public Builder addAll(Iterable elements) { + requireNonNull(contents); // see the comment on the field if (elements instanceof Multiset) { - Multiset multiset = Multisets.cast(elements); + Multiset multiset = (Multiset) elements; ObjectCountHashMap backingMap = tryGetMap(multiset); if (backingMap != null) { contents.ensureCapacity(Math.max(contents.size(), backingMap.size())); @@ -577,8 +639,7 @@ public Builder addAll(Iterator elements) { * efficient to iterate over it by index rather than an entry iterator, which will need to * allocate an object for each entry, so we check for that. */ - @NullableDecl - static ObjectCountHashMap tryGetMap(Iterable multiset) { + static @Nullable ObjectCountHashMap tryGetMap(Iterable multiset) { if (multiset instanceof RegularImmutableMultiset) { return ((RegularImmutableMultiset) multiset).contents; } else if (multiset instanceof AbstractMapBasedMultiset) { @@ -594,6 +655,7 @@ static ObjectCountHashMap tryGetMap(Iterable multiset) { */ @Override public ImmutableMultiset build() { + requireNonNull(contents); // see the comment on the field if (contents.size() == 0) { return of(); } @@ -608,4 +670,6 @@ public ImmutableMultiset build() { return new RegularImmutableMultiset(contents); } } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java b/android/guava/src/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java deleted file mode 100644 index a8b1899d280c..000000000000 --- a/android/guava/src/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import com.google.common.annotations.GwtCompatible; - -/** - * A dummy superclass to support GWT serialization of the element type of an {@link - * ImmutableMultiset}. The GWT supersource for this class contains a field of type {@code E}. - * - *

    For details about this hack, see {@code GwtSerializationDependencies}, which takes the same - * approach but with a subclass rather than a superclass. - * - *

    TODO(cpovirk): Consider applying this subclass approach to our other types. - * - *

    For {@code ImmutableMultiset} in particular, I ran into a problem with the {@code - * GwtSerializationDependencies} approach: When autogenerating a serializer for the new class, GWT - * tries to refer to our dummy serializer for the superclass, - * ImmutableMultiset_CustomFieldSerializer. But that type has no methods (since it's never actually - * used). We could probably fix the problem by adding dummy methods to that class, but that is - * starting to sound harder than taking the superclass approach, which I've been coming to like, - * anyway, since it doesn't require us to declare dummy methods (though occasionally constructors) - * and make types non-final. - */ -@GwtCompatible(emulated = true) -abstract class ImmutableMultisetGwtSerializationDependencies extends ImmutableCollection {} diff --git a/android/guava/src/com/google/common/collect/ImmutableRangeMap.java b/android/guava/src/com/google/common/collect/ImmutableRangeMap.java index 2183ef85e675..fe692ad91b91 100644 --- a/android/guava/src/com/google/common/collect/ImmutableRangeMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableRangeMap.java @@ -17,21 +17,28 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.collect.Range.rangeLexOrdering; +import static java.util.Collections.sort; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.SortedLists.KeyAbsentBehavior; import com.google.common.collect.SortedLists.KeyPresentBehavior; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.DoNotMock; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.Function; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A {@link RangeMap} whose contents will never change, with many other important properties @@ -40,12 +47,25 @@ * @author Louis Wasserman * @since 14.0 */ -@Beta @GwtIncompatible // NavigableMap public class ImmutableRangeMap, V> implements RangeMap, Serializable { private static final ImmutableRangeMap, Object> EMPTY = - new ImmutableRangeMap<>(ImmutableList.>>of(), ImmutableList.of()); + new ImmutableRangeMap<>(ImmutableList.of(), ImmutableList.of()); + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableRangeMap}. As in {@link Builder}, overlapping ranges are not permitted. + * + * @since 33.2.0 (available since 23.1 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static , V> + Collector> toImmutableRangeMap( + Function> keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableRangeMap(keyFunction, valueFunction); + } /** * Returns an empty immutable range map. @@ -70,7 +90,7 @@ public static , V> ImmutableRangeMap copyOf( } Map, ? extends V> map = rangeMap.asMapOfRanges(); ImmutableList.Builder> rangesBuilder = new ImmutableList.Builder<>(map.size()); - ImmutableList.Builder valuesBuilder = new ImmutableList.Builder(map.size()); + ImmutableList.Builder valuesBuilder = new ImmutableList.Builder<>(map.size()); for (Entry, ? extends V> entry : map.entrySet()) { rangesBuilder.add(entry.getKey()); valuesBuilder.add(entry.getValue()); @@ -93,7 +113,7 @@ public static final class Builder, V> { private final List, V>> entries; public Builder() { - this.entries = Lists.newArrayList(); + this.entries = new ArrayList<>(); } /** @@ -106,7 +126,7 @@ public Builder put(Range range, V value) { checkNotNull(range); checkNotNull(value); checkArgument(!range.isEmpty(), "Range must not be empty, but was %s", range); - entries.add(Maps.immutableEntry(range, value)); + entries.add(immutableEntry(range, value)); return this; } @@ -132,9 +152,9 @@ Builder combine(Builder builder) { * @throws IllegalArgumentException if any two ranges inserted into this builder overlap */ public ImmutableRangeMap build() { - Collections.sort(entries, Range.rangeLexOrdering().onKeys()); + sort(entries, Range.rangeLexOrdering().onKeys()); ImmutableList.Builder> rangesBuilder = new ImmutableList.Builder<>(entries.size()); - ImmutableList.Builder valuesBuilder = new ImmutableList.Builder(entries.size()); + ImmutableList.Builder valuesBuilder = new ImmutableList.Builder<>(entries.size()); for (int i = 0; i < entries.size(); i++) { Range range = entries.get(i).getKey(); if (i > 0) { @@ -160,12 +180,11 @@ public ImmutableRangeMap build() { } @Override - @NullableDecl - public V get(K key) { + public @Nullable V get(K key) { int index = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, Cut.belowValue(key), KeyPresentBehavior.ANY_PRESENT, KeyAbsentBehavior.NEXT_LOWER); @@ -178,12 +197,11 @@ public V get(K key) { } @Override - @NullableDecl - public Entry, V> getEntry(K key) { + public @Nullable Entry, V> getEntry(K key) { int index = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, Cut.belowValue(key), KeyPresentBehavior.ANY_PRESENT, KeyAbsentBehavior.NEXT_LOWER); @@ -191,7 +209,7 @@ public Entry, V> getEntry(K key) { return null; } else { Range range = ranges.get(index); - return range.contains(key) ? Maps.immutableEntry(range, values.get(index)) : null; + return range.contains(key) ? immutableEntry(range, values.get(index)) : null; } } @@ -240,7 +258,7 @@ public final void putCoalescing(Range range, V value) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final void putAll(RangeMap rangeMap) { + public final void putAll(RangeMap rangeMap) { throw new UnsupportedOperationException(); } @@ -276,7 +294,7 @@ public ImmutableMap, V> asMapOfRanges() { return ImmutableMap.of(); } RegularImmutableSortedSet> rangeSet = - new RegularImmutableSortedSet<>(ranges, Range.rangeLexOrdering()); + new RegularImmutableSortedSet<>(ranges, rangeLexOrdering()); return new ImmutableSortedMap<>(rangeSet, values); } @@ -291,7 +309,7 @@ public ImmutableMap, V> asDescendingMapOfRanges() { } @Override - public ImmutableRangeMap subRangeMap(final Range range) { + public ImmutableRangeMap subRangeMap(Range range) { if (checkNotNull(range).isEmpty()) { return ImmutableRangeMap.of(); } else if (ranges.isEmpty() || range.encloses(span())) { @@ -300,22 +318,22 @@ public ImmutableRangeMap subRangeMap(final Range range) { int lowerIndex = SortedLists.binarySearch( ranges, - Range.upperBoundFn(), + Range::upperBound, range.lowerBound, KeyPresentBehavior.FIRST_AFTER, KeyAbsentBehavior.NEXT_HIGHER); int upperIndex = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, range.upperBound, KeyPresentBehavior.ANY_PRESENT, KeyAbsentBehavior.NEXT_HIGHER); if (lowerIndex >= upperIndex) { return ImmutableRangeMap.of(); } - final int off = lowerIndex; - final int len = upperIndex - lowerIndex; + int off = lowerIndex; + int len = upperIndex - lowerIndex; ImmutableList> subRanges = new ImmutableList>() { @Override @@ -337,8 +355,16 @@ public Range get(int index) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; - final ImmutableRangeMap outer = this; + ImmutableRangeMap outer = this; return new ImmutableRangeMap(subRanges, values.subList(lowerIndex, upperIndex)) { @Override public ImmutableRangeMap subRangeMap(Range subRange) { @@ -348,6 +374,14 @@ public ImmutableRangeMap subRangeMap(Range subRange) { return ImmutableRangeMap.of(); } } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; } @@ -357,7 +391,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof RangeMap) { RangeMap rangeMap = (RangeMap) o; return asMapOfRanges().equals(rangeMap.asMapOfRanges()); @@ -374,7 +408,7 @@ public String toString() { * This class is used to serialize ImmutableRangeMap instances. Serializes the {@link * #asMapOfRanges()} form. */ - private static class SerializedForm, V> implements Serializable { + private static final class SerializedForm, V> implements Serializable { private final ImmutableMap, V> mapOfRanges; @@ -398,12 +432,17 @@ Object createRangeMap() { return builder.build(); } - private static final long serialVersionUID = 0; + @J2ktIncompatible private static final long serialVersionUID = 0; } Object writeReplace() { return new SerializedForm<>(asMapOfRanges()); } - private static final long serialVersionUID = 0; + @J2ktIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ImmutableRangeSet.java b/android/guava/src/com/google/common/collect/ImmutableRangeSet.java index 4a7dec19d41f..1bef7dd8a39b 100644 --- a/android/guava/src/com/google/common/collect/ImmutableRangeSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableRangeSet.java @@ -17,25 +17,36 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Iterators.peekingIterator; +import static com.google.common.collect.Range.rangeLexOrdering; import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_HIGHER; import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_LOWER; import static com.google.common.collect.SortedLists.KeyPresentBehavior.ANY_PRESENT; +import static java.util.Collections.sort; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.SortedLists.KeyAbsentBehavior; import com.google.common.collect.SortedLists.KeyPresentBehavior; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.concurrent.LazyInit; +import com.google.j2objc.annotations.RetainedWith; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A {@link RangeSet} whose contents will never change, with many other important properties @@ -44,16 +55,29 @@ * @author Louis Wasserman * @since 14.0 */ -@Beta +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 @GwtIncompatible public final class ImmutableRangeSet extends AbstractRangeSet implements Serializable { private static final ImmutableRangeSet> EMPTY = - new ImmutableRangeSet<>(ImmutableList.>>of()); + new ImmutableRangeSet<>(ImmutableList.of()); private static final ImmutableRangeSet> ALL = - new ImmutableRangeSet<>(ImmutableList.of(Range.>all())); + new ImmutableRangeSet<>(ImmutableList.of(Range.all())); + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableRangeSet}. As in {@link Builder}, overlapping ranges are not permitted and adjacent + * ranges will be merged. + * + * @since 33.2.0 (available since 23.1 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static > + Collector, ?, ImmutableRangeSet> toImmutableRangeSet() { + return CollectCollectors.toImmutableRangeSet(); + } /** * Returns an empty immutable range set. @@ -76,7 +100,7 @@ public static ImmutableRangeSet of(Range range) { } else if (range.equals(Range.all())) { return all(); } else { - return new ImmutableRangeSet(ImmutableList.of(range)); + return new ImmutableRangeSet<>(ImmutableList.of(range)); } } @@ -91,7 +115,7 @@ public static ImmutableRangeSet copyOf(RangeSet ran checkNotNull(rangeSet); if (rangeSet.isEmpty()) { return of(); - } else if (rangeSet.encloses(Range.all())) { + } else if (rangeSet.encloses(Range.all())) { return all(); } @@ -101,7 +125,7 @@ public static ImmutableRangeSet copyOf(RangeSet ran return immutableRangeSet; } } - return new ImmutableRangeSet(ImmutableList.copyOf(rangeSet.asRanges())); + return new ImmutableRangeSet<>(ImmutableList.copyOf(rangeSet.asRanges())); } /** @@ -129,22 +153,24 @@ public static > ImmutableRangeSet unionOf(Iterable> ranges) { - this.ranges = ranges; + this(ranges, /* complement= */ null); } - private ImmutableRangeSet(ImmutableList> ranges, ImmutableRangeSet complement) { + private ImmutableRangeSet( + ImmutableList> ranges, @Nullable ImmutableRangeSet complement) { this.ranges = ranges; this.complement = complement; } private final transient ImmutableList> ranges; + private final transient @Nullable ImmutableRangeSet complement; @Override public boolean intersects(Range otherRange) { int ceilingIndex = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, otherRange.lowerBound, Ordering.natural(), ANY_PRESENT, @@ -164,7 +190,7 @@ public boolean encloses(Range otherRange) { int index = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, otherRange.lowerBound, Ordering.natural(), ANY_PRESENT, @@ -173,11 +199,11 @@ public boolean encloses(Range otherRange) { } @Override - public Range rangeContaining(C value) { + public @Nullable Range rangeContaining(C value) { int index = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, Cut.belowValue(value), Ordering.natural(), ANY_PRESENT, @@ -285,7 +311,7 @@ public ImmutableSet> asRanges() { if (ranges.isEmpty()) { return ImmutableSet.of(); } - return new RegularImmutableSortedSet<>(ranges, Range.rangeLexOrdering()); + return new RegularImmutableSortedSet<>(ranges, rangeLexOrdering()); } @Override @@ -296,9 +322,11 @@ public ImmutableSet> asDescendingSetOfRanges() { return new RegularImmutableSortedSet<>(ranges.reverse(), Range.rangeLexOrdering().reverse()); } - @LazyInit private transient ImmutableRangeSet complement; + private static final class ComplementRanges + extends ImmutableList> { + + private final ImmutableList> ranges; - private final class ComplementRanges extends ImmutableList> { // True if the "positive" range set is empty or bounded below. private final boolean positiveBoundedBelow; @@ -307,7 +335,8 @@ private final class ComplementRanges extends ImmutableList> { private final int size; - ComplementRanges() { + ComplementRanges(ImmutableList> ranges) { + this.ranges = ranges; this.positiveBoundedBelow = ranges.get(0).hasLowerBound(); this.positiveBoundedAbove = Iterables.getLast(ranges).hasUpperBound(); @@ -332,14 +361,14 @@ public Range get(int index) { Cut lowerBound; if (positiveBoundedBelow) { - lowerBound = (index == 0) ? Cut.belowAll() : ranges.get(index - 1).upperBound; + lowerBound = (index == 0) ? Cut.belowAll() : ranges.get(index - 1).upperBound; } else { lowerBound = ranges.get(index).upperBound; } Cut upperBound; if (positiveBoundedAbove && index == size - 1) { - upperBound = Cut.aboveAll(); + upperBound = Cut.aboveAll(); } else { upperBound = ranges.get(index + (positiveBoundedBelow ? 0 : 1)).lowerBound; } @@ -351,22 +380,37 @@ public Range get(int index) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } @Override public ImmutableRangeSet complement() { - ImmutableRangeSet result = complement; - if (result != null) { - return result; + if (complement != null) { + return complement; } else if (ranges.isEmpty()) { - return complement = all(); + return all(); } else if (ranges.size() == 1 && ranges.get(0).equals(Range.all())) { - return complement = of(); + return of(); } else { - ImmutableList> complementRanges = new ComplementRanges(); - result = complement = new ImmutableRangeSet(complementRanges, this); + return lazyComplement(); } - return result; + } + + @LazyInit @RetainedWith private transient @Nullable ImmutableRangeSet lazyComplement; + + private ImmutableRangeSet lazyComplement() { + ImmutableRangeSet result = lazyComplement; + return result == null + ? lazyComplement = + new ImmutableRangeSet<>(new ComplementRanges<>(ranges), /* complement= */ this) + : result; } /** @@ -414,19 +458,19 @@ public ImmutableRangeSet difference(RangeSet other) { * Returns a list containing the nonempty intersections of {@code range} with the ranges in this * range set. */ - private ImmutableList> intersectRanges(final Range range) { + private ImmutableList> intersectRanges(Range range) { if (ranges.isEmpty() || range.isEmpty()) { return ImmutableList.of(); } else if (range.encloses(span())) { return ranges; } - final int fromIndex; + int fromIndex; if (range.hasLowerBound()) { fromIndex = SortedLists.binarySearch( ranges, - Range.upperBoundFn(), + Range::upperBound, range.lowerBound, KeyPresentBehavior.FIRST_AFTER, KeyAbsentBehavior.NEXT_HIGHER); @@ -439,14 +483,14 @@ private ImmutableList> intersectRanges(final Range range) { toIndex = SortedLists.binarySearch( ranges, - Range.lowerBoundFn(), + Range::lowerBound, range.upperBound, KeyPresentBehavior.FIRST_PRESENT, KeyAbsentBehavior.NEXT_HIGHER); } else { toIndex = ranges.size(); } - final int length = toIndex - fromIndex; + int length = toIndex - fromIndex; if (length == 0) { return ImmutableList.of(); } else { @@ -470,6 +514,15 @@ public Range get(int index) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } }; } } @@ -482,7 +535,7 @@ public ImmutableRangeSet subRangeSet(Range range) { if (range.encloses(span)) { return this; } else if (range.isConnected(span)) { - return new ImmutableRangeSet(intersectRanges(range)); + return new ImmutableRangeSet<>(intersectRanges(range)); } } return of(); @@ -501,7 +554,7 @@ public ImmutableRangeSet subRangeSet(Range range) { * such a set can be performed efficiently, but others (such as {@link Set#hashCode} or {@link * Collections#frequency}) can cause major performance problems. * - *

    The returned set's {@link Object#toString} method returns a short-hand form of the set's + *

    The returned set's {@link Object#toString} method returns a shorthand form of the set's * contents, such as {@code "[1..100]}"}. * * @throws IllegalArgumentException if neither this range nor the domain has a lower bound, or if @@ -538,7 +591,7 @@ private final class AsSet extends ImmutableSortedSet { this.domain = domain; } - @NullableDecl private transient Integer size; + @LazyInit private transient @Nullable Integer size; @Override public int size() { @@ -561,10 +614,10 @@ public int size() { public UnmodifiableIterator iterator() { return new AbstractIterator() { final Iterator> rangeItr = ranges.iterator(); - Iterator elemItr = Iterators.emptyIterator(); + Iterator elemItr = emptyIterator(); @Override - protected C computeNext() { + protected @Nullable C computeNext() { while (!elemItr.hasNext()) { if (rangeItr.hasNext()) { elemItr = ContiguousSet.create(rangeItr.next(), domain).iterator(); @@ -582,10 +635,10 @@ protected C computeNext() { public UnmodifiableIterator descendingIterator() { return new AbstractIterator() { final Iterator> rangeItr = ranges.reverse().iterator(); - Iterator elemItr = Iterators.emptyIterator(); + Iterator elemItr = emptyIterator(); @Override - protected C computeNext() { + protected @Nullable C computeNext() { while (!elemItr.hasNext()) { if (rangeItr.hasNext()) { elemItr = ContiguousSet.create(rangeItr.next(), domain).descendingIterator(); @@ -625,7 +678,7 @@ ImmutableSortedSet tailSetImpl(C fromElement, boolean inclusive) { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { if (o == null) { return false; } @@ -639,10 +692,10 @@ public boolean contains(@NullableDecl Object o) { } @Override - int indexOf(Object target) { + int indexOf(@Nullable Object target) { if (contains(target)) { @SuppressWarnings("unchecked") // if it's contained, it's definitely a C - C c = (C) target; + C c = (C) requireNonNull(target); long total = 0; for (Range range : ranges) { if (range.contains(c)) { @@ -658,7 +711,7 @@ int indexOf(Object target) { @Override ImmutableSortedSet createDescendingSet() { - return new DescendingImmutableSortedSet(this); + return new DescendingImmutableSortedSet<>(this); } @Override @@ -672,12 +725,18 @@ public String toString() { } @Override + @J2ktIncompatible // serialization Object writeReplace() { return new AsSetSerializedForm(ranges, domain); } + + @J2ktIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } } - private static class AsSetSerializedForm implements Serializable { + private static final class AsSetSerializedForm implements Serializable { private final ImmutableList> ranges; private final DiscreteDomain domain; @@ -703,7 +762,7 @@ boolean isPartialView() { /** Returns a new builder for an immutable range set. */ public static > Builder builder() { - return new Builder(); + return new Builder<>(); } /** @@ -715,7 +774,7 @@ public static class Builder> { private final List> ranges; public Builder() { - this.ranges = Lists.newArrayList(); + this.ranges = new ArrayList<>(); } // TODO(lowasser): consider adding union, in addition to add, that does allow overlap @@ -772,8 +831,8 @@ Builder combine(Builder builder) { public ImmutableRangeSet build() { ImmutableList.Builder> mergedRangesBuilder = new ImmutableList.Builder<>(ranges.size()); - Collections.sort(ranges, Range.rangeLexOrdering()); - PeekingIterator> peekingItr = Iterators.peekingIterator(ranges.iterator()); + sort(ranges, rangeLexOrdering()); + PeekingIterator> peekingItr = peekingIterator(ranges.iterator()); while (peekingItr.hasNext()) { Range range = peekingItr.next(); while (peekingItr.hasNext()) { @@ -794,11 +853,10 @@ public ImmutableRangeSet build() { ImmutableList> mergedRanges = mergedRangesBuilder.build(); if (mergedRanges.isEmpty()) { return of(); - } else if (mergedRanges.size() == 1 - && Iterables.getOnlyElement(mergedRanges).equals(Range.all())) { + } else if (mergedRanges.size() == 1 && getOnlyElement(mergedRanges).equals(Range.all())) { return all(); } else { - return new ImmutableRangeSet(mergedRanges); + return new ImmutableRangeSet<>(mergedRanges); } } } @@ -821,7 +879,13 @@ Object readResolve() { } } + @J2ktIncompatible // java.io.ObjectInputStream Object writeReplace() { return new SerializedForm(ranges); } + + @J2ktIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } } diff --git a/android/guava/src/com/google/common/collect/ImmutableSet.java b/android/guava/src/com/google/common/collect/ImmutableSet.java index 47acb171abfc..e355478c4f77 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableSet.java @@ -19,15 +19,21 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.ImmutableList.asImmutableList; import static com.google.common.collect.ObjectArrays.checkElementNotNull; +import static java.lang.Math.max; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedWith; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; @@ -35,7 +41,8 @@ import java.util.Iterator; import java.util.Set; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A {@link Set} whose contents will never change, with many other important properties detailed at @@ -43,9 +50,25 @@ * * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // we're overriding default serialization public abstract class ImmutableSet extends ImmutableCollection implements Set { + // @IgnoreJRERequirement is not necessary because this compiles down to a constant. + // (which is fortunate because Animal Sniffer doesn't look for @IgnoreJRERequirement on fields) + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableSet}. Elements appear in the resulting set in the encounter order of the stream; if + * the stream contains duplicates (according to {@link Object#equals(Object)}), only the first + * duplicate in encounter order will appear in the result. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toImmutableSet() { + return CollectCollectors.toImmutableSet(); + } + /** * Returns the empty immutable set. Preferred over {@link Collections#emptySet} for code * consistency, and because the return type conveys the immutability guarantee. @@ -58,12 +81,12 @@ public static ImmutableSet of() { } /** - * Returns an immutable set containing {@code element}. Preferred over {@link + * Returns an immutable set containing the given element. Preferred over {@link * Collections#singleton} for code consistency, {@code null} rejection, and because the return * type conveys the immutability guarantee. */ - public static ImmutableSet of(E element) { - return new SingletonImmutableSet(element); + public static ImmutableSet of(E e1) { + return new SingletonImmutableSet<>(e1); } /** @@ -140,13 +163,14 @@ public static ImmutableSet of(E e1, E e2, E e3, E e4, E e5, E e6, E... ot * * @throws NullPointerException if any of the first {@code n} elements of {@code elements} is null */ - private static ImmutableSet construct(int n, Object... elements) { + private static ImmutableSet construct(int n, @Nullable Object... elements) { switch (n) { case 0: return of(); case 1: @SuppressWarnings("unchecked") // safe; elements contains only E's - E elem = (E) elements[0]; + // requireNonNull is safe because the first `n` elements are non-null. + E elem = (E) requireNonNull(elements[0]); return of(elem); default: // continue below to handle the general case @@ -177,13 +201,14 @@ private static ImmutableSet construct(int n, Object... elements) { if (uniques == 1) { // There is only one element or elements are all duplicates @SuppressWarnings("unchecked") // we are careful to only pass in E - E element = (E) elements[0]; - return new SingletonImmutableSet(element, hashCode); + // requireNonNull is safe because the first `uniques` elements are non-null. + E element = (E) requireNonNull(elements[0]); + return new SingletonImmutableSet(element); } else if (chooseTableSize(uniques) < tableSize / 2) { // Resize the table when the array includes too many duplicates. return construct(uniques, elements); } else { - Object[] uniqueElements = + @Nullable Object[] uniqueElements = shouldTrim(uniques, elements.length) ? Arrays.copyOf(elements, uniques) : elements; return new RegularImmutableSet(uniqueElements, hashCode, table, mask, uniques); } @@ -209,7 +234,7 @@ private static boolean shouldTrim(int actualUnique, int expectedUnique) { */ @VisibleForTesting static int chooseTableSize(int setSize) { - setSize = Math.max(setSize, 2); + setSize = max(setSize, 2); // Correct the size for open addressing to match desired load factor. if (setSize < CUTOFF) { // Round up to the next highest power of 2. @@ -317,7 +342,7 @@ boolean isHashCodeFast() { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -340,7 +365,7 @@ public int hashCode() { @Override public abstract UnmodifiableIterator iterator(); - @LazyInit @RetainedWith @NullableDecl private transient ImmutableList asList; + @LazyInit @RetainedWith private transient @Nullable ImmutableList asList; @Override public ImmutableList asList() { @@ -349,7 +374,7 @@ public ImmutableList asList() { } ImmutableList createAsList() { - return ImmutableList.asImmutableList(toArray()); + return asImmutableList(toArray()); } /* @@ -359,7 +384,8 @@ ImmutableList createAsList() { * static factories. This is necessary to ensure that the existence of a * particular implementation type is an implementation detail. */ - private static class SerializedForm implements Serializable { + @J2ktIncompatible // serialization + private static final class SerializedForm implements Serializable { final Object[] elements; SerializedForm(Object[] elements) { @@ -370,20 +396,26 @@ Object readResolve() { return copyOf(elements); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @Override - Object writeReplace() { + @J2ktIncompatible + Object writeReplace() { return new SerializedForm(toArray()); } + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + /** * Returns a new builder. The generated builder is equivalent to the builder created by the {@link * Builder} constructor. */ public static Builder builder() { - return new Builder(); + return new Builder<>(); } /** @@ -398,22 +430,21 @@ public static Builder builder() { * * @since 23.1 */ - @Beta public static Builder builderWithExpectedSize(int expectedSize) { checkNonnegative(expectedSize, "expectedSize"); - return new Builder(expectedSize); + return new Builder<>(expectedSize, true); } /** * A builder for creating {@code ImmutableSet} instances. Example: * - *

    {@code
    +   * {@snippet :
        * static final ImmutableSet GOOGLE_COLORS =
        *     ImmutableSet.builder()
        *         .addAll(WEBSAFE_COLORS)
        *         .add(new Color(0, 191, 255))
        *         .build();
    -   * }
    + * } * *

    Elements appear in the resulting set in the same order they were first added to the builder. * @@ -423,7 +454,7 @@ public static Builder builderWithExpectedSize(int expectedSize) { * @since 2.0 */ public static class Builder extends ImmutableCollection.ArrayBasedBuilder { - @NullableDecl @VisibleForTesting Object[] hashTable; + @VisibleForTesting @Nullable Object @Nullable [] hashTable; private int hashCode; /** @@ -434,9 +465,11 @@ public Builder() { super(DEFAULT_INITIAL_CAPACITY); } - Builder(int capacity) { + Builder(int capacity, boolean makeHashTable) { super(capacity); - this.hashTable = new Object[chooseTableSize(capacity)]; + if (makeHashTable) { + this.hashTable = new @Nullable Object[chooseTableSize(capacity)]; + } } /** @@ -448,8 +481,8 @@ public Builder() { * @return this {@code Builder} object * @throws NullPointerException if {@code element} is null */ - @CanIgnoreReturnValue @Override + @CanIgnoreReturnValue public Builder add(E element) { checkNotNull(element); if (hashTable != null && chooseTableSize(size) <= hashTable.length) { @@ -484,6 +517,7 @@ public Builder add(E... elements) { } private void addDeduping(E element) { + requireNonNull(hashTable); // safe because we check for null before calling this method int mask = hashTable.length - 1; int hash = element.hashCode(); for (int i = Hashing.smear(hash); ; i++) { @@ -545,7 +579,8 @@ public Builder addAll(Iterator elements) { Builder combine(Builder other) { if (hashTable != null) { for (int i = 0; i < other.size; ++i) { - add((E) other.contents[i]); + // requireNonNull is safe because the first `size` elements are non-null. + add((E) requireNonNull(other.contents[i])); } } else { addAll(other.contents, other.size); @@ -563,11 +598,15 @@ public ImmutableSet build() { case 0: return of(); case 1: - return (ImmutableSet) of(contents[0]); + /* + * requireNonNull is safe because we ensure that the first `size` elements have been + * populated. + */ + return (ImmutableSet) of(requireNonNull(contents[0])); default: ImmutableSet result; if (hashTable != null && chooseTableSize(size) == hashTable.length) { - Object[] uniqueElements = + @Nullable Object[] uniqueElements = shouldTrim(size, contents.length) ? Arrays.copyOf(contents, size) : contents; result = new RegularImmutableSet( @@ -584,4 +623,6 @@ public ImmutableSet build() { } } } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableSetMultimap.java b/android/guava/src/com/google/common/collect/ImmutableSetMultimap.java index 6d4e9dd5cab0..314b5541d3f6 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSetMultimap.java +++ b/android/guava/src/com/google/common/collect/ImmutableSetMultimap.java @@ -17,10 +17,14 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static java.lang.Math.max; +import static java.util.Arrays.asList; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.MoreObjects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; @@ -31,26 +35,117 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; /** * A {@link SetMultimap} whose contents will never change, with many other important properties * detailed at {@link ImmutableCollection}. * + *

    Warning: As in all {@link SetMultimap}s, do not modify either a key or a value + * of a {@code ImmutableSetMultimap} in a way that affects its {@link Object#equals} behavior. + * Undefined behavior and bugs will result. + * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Mike Ward * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible public class ImmutableSetMultimap extends ImmutableMultimap implements SetMultimap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSetMultimap} + * whose keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

    For streams with defined encounter order (as defined in the Ordering section of the {@link + * java.util.stream} Javadoc), that order is preserved, but entries are grouped by key. + * + *

    Example: + * + * {@snippet : + * static final Multimap FIRST_LETTER_MULTIMAP = + * Stream.of("banana", "apple", "carrot", "asparagus", "cherry") + * .collect(toImmutableSetMultimap(str -> str.charAt(0), str -> str.substring(1))); + * + * // is equivalent to + * + * static final Multimap FIRST_LETTER_MULTIMAP = + * new ImmutableSetMultimap.Builder() + * .put('b', "anana") + * .putAll('a', "pple", "sparagus") + * .putAll('c', "arrot", "herry") + * .build(); + * } + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableSetMultimap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableSetMultimap(keyFunction, valueFunction); + } + + /** + * Returns a {@code Collector} accumulating entries into an {@code ImmutableSetMultimap}. Each + * input element is mapped to a key and a stream of values, each of which are put into the + * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the + * streams of values. + * + *

    Example: + * + * {@snippet : + * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP = + * Stream.of("banana", "apple", "carrot", "asparagus", "cherry") + * .collect( + * flatteningToImmutableSetMultimap( + * str -> str.charAt(0), + * str -> str.substring(1).chars().mapToObj(c -> (char) c)); + * + * // is equivalent to + * + * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP = + * ImmutableSetMultimap.builder() + * .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a')) + * .putAll('a', Arrays.asList('p', 'p', 'l', 'e')) + * .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't')) + * .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's')) + * .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y')) + * .build(); + * + * // after deduplication, the resulting multimap is equivalent to + * + * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP = + * ImmutableSetMultimap.builder() + * .putAll('b', Arrays.asList('a', 'n')) + * .putAll('a', Arrays.asList('p', 'l', 'e', 's', 'a', 'r', 'g', 'u')) + * .putAll('c', Arrays.asList('a', 'r', 'o', 't', 'h', 'e', 'y')) + * .build(); + * } + * + * } + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> flatteningToImmutableSetMultimap( + Function keyFunction, + Function> valuesFunction) { + return CollectCollectors.flatteningToImmutableSetMultimap(keyFunction, valuesFunction); + } /** * Returns the empty multimap. @@ -129,18 +224,31 @@ public static Builder builder() { return new Builder<>(); } + /** + * Returns a new builder with a hint for how many distinct keys are expected to be added. The + * generated builder is equivalent to that returned by {@link #builder}, but may perform better if + * {@code expectedKeys} is a good estimate. + * + * @throws IllegalArgumentException if {@code expectedKeys} is negative + * @since 33.3.0 + */ + public static Builder builderWithExpectedKeys(int expectedKeys) { + checkNonnegative(expectedKeys, "expectedKeys"); + return new Builder<>(expectedKeys); + } + /** * A builder for creating immutable {@code SetMultimap} instances, especially {@code public static * final} multimaps ("constant multimaps"). Example: * - *

    {@code
    +   * {@snippet :
        * static final Multimap STRING_TO_INTEGER_MULTIMAP =
        *     new ImmutableSetMultimap.Builder()
        *         .put("one", 1)
        *         .putAll("several", 1, 2, 3)
        *         .putAll("many", 1, 2, 3, 4, 5)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple multimaps in series. Each multimap contains the key-value mappings in the previously @@ -153,13 +261,43 @@ public static final class Builder extends ImmutableMultimap.Builder * Creates a new builder. The returned builder is equivalent to the builder generated by {@link * ImmutableSetMultimap#builder}. */ - public Builder() { - super(); + public Builder() {} + + Builder(int expectedKeys) { + super(expectedKeys); } @Override - Collection newMutableValueCollection() { - return Platform.preservesInsertionOrderOnAddsSet(); + ImmutableCollection.Builder newValueCollectionBuilderWithExpectedSize(int expectedSize) { + return (valueComparator == null) + ? ImmutableSet.builderWithExpectedSize(expectedSize) + : new ImmutableSortedSet.Builder(valueComparator, expectedSize); + } + + @Override + int expectedValueCollectionSize(int defaultExpectedValues, Iterable values) { + // Only trust the size of `values` if it is a Set and therefore probably already deduplicated. + if (values instanceof Set) { + Set collection = (Set) values; + return max(defaultExpectedValues, collection.size()); + } else { + return defaultExpectedValues; + } + } + + /** + * {@inheritDoc} + * + *

    Note that {@code expectedValuesPerKey} is taken to mean the expected number of + * distinct values per key. + * + * @since 33.3.0 + */ + @CanIgnoreReturnValue + @Override + public Builder expectedValuesPerKey(int expectedValuesPerKey) { + super.expectedValuesPerKey(expectedValuesPerKey); + return this; } /** Adds a key-value mapping to the built multimap if it is not already present. */ @@ -188,7 +326,6 @@ public Builder put(Entry entry) { * @since 19.0 */ @CanIgnoreReturnValue - @Beta @Override public Builder putAll(Iterable> entries) { super.putAll(entries); @@ -205,7 +342,7 @@ public Builder putAll(K key, Iterable values) { @CanIgnoreReturnValue @Override public Builder putAll(K key, V... values) { - return putAll(key, Arrays.asList(values)); + return putAll(key, asList(values)); } @CanIgnoreReturnValue @@ -258,11 +395,14 @@ public Builder orderValuesBy(Comparator valueComparator) { /** Returns a newly-created immutable set multimap. */ @Override public ImmutableSetMultimap build() { - Collection>> mapEntries = builderMap.entrySet(); + if (builderMap == null) { + return ImmutableSetMultimap.of(); + } + Collection>> mapEntries = builderMap.entrySet(); if (keyComparator != null) { mapEntries = Ordering.from(keyComparator).onKeys().immutableSortedCopy(mapEntries); } - return fromMapEntries(mapEntries, valueComparator); + return fromMapBuilderEntries(mapEntries, valueComparator); } } @@ -284,7 +424,8 @@ public static ImmutableSetMultimap copyOf( } private static ImmutableSetMultimap copyOf( - Multimap multimap, Comparator valueComparator) { + Multimap multimap, + @Nullable Comparator valueComparator) { checkNotNull(multimap); // eager for GWT if (multimap.isEmpty() && valueComparator == null) { return of(); @@ -310,7 +451,6 @@ private static ImmutableSetMultimap copyOf( * @throws NullPointerException if any key, value, or entry is null * @since 19.0 */ - @Beta public static ImmutableSetMultimap copyOf( Iterable> entries) { return new Builder().putAll(entries).build(); @@ -319,7 +459,7 @@ public static ImmutableSetMultimap copyOf( /** Creates an ImmutableSetMultimap from an asMap.entrySet. */ static ImmutableSetMultimap fromMapEntries( Collection>> mapEntries, - @NullableDecl Comparator valueComparator) { + @Nullable Comparator valueComparator) { if (mapEntries.isEmpty()) { return of(); } @@ -337,7 +477,33 @@ static ImmutableSetMultimap fromMapEntries( } } - return new ImmutableSetMultimap<>(builder.build(), size, valueComparator); + return new ImmutableSetMultimap<>(builder.buildOrThrow(), size, valueComparator); + } + + /** Creates an ImmutableSetMultimap from a map to builders. */ + static ImmutableSetMultimap fromMapBuilderEntries( + Collection>> mapEntries, + @Nullable Comparator valueComparator) { + if (mapEntries.isEmpty()) { + return of(); + } + ImmutableMap.Builder> builder = + new ImmutableMap.Builder<>(mapEntries.size()); + int size = 0; + + for (Entry> entry : mapEntries) { + K key = entry.getKey(); + ImmutableSet.Builder values = (ImmutableSet.Builder) entry.getValue(); + // If orderValuesBy got called at the very end, we may need to do the ImmutableSet to + // ImmutableSortedSet copy for each of these. + ImmutableSet set = valueSet(valueComparator, values.build()); + if (!set.isEmpty()) { + builder.put(key, set); + size += set.size(); + } + } + + return new ImmutableSetMultimap<>(builder.buildOrThrow(), size, valueComparator); } /** @@ -349,7 +515,7 @@ static ImmutableSetMultimap fromMapEntries( ImmutableSetMultimap( ImmutableMap> map, int size, - @NullableDecl Comparator valueComparator) { + @Nullable Comparator valueComparator) { super(map, size); this.emptySet = emptySet(valueComparator); } @@ -362,13 +528,13 @@ static ImmutableSetMultimap fromMapEntries( * parameters used to build this multimap. */ @Override - public ImmutableSet get(@NullableDecl K key) { + public ImmutableSet get(K key) { // This cast is safe as its type is known in constructor. ImmutableSet set = (ImmutableSet) map.get(key); return MoreObjects.firstNonNull(set, emptySet); } - @LazyInit @RetainedWith @NullableDecl private transient ImmutableSetMultimap inverse; + @LazyInit @RetainedWith private transient @Nullable ImmutableSetMultimap inverse; /** * {@inheritDoc} @@ -403,7 +569,7 @@ private ImmutableSetMultimap invert() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final ImmutableSet removeAll(Object key) { + public final ImmutableSet removeAll(@Nullable Object key) { throw new UnsupportedOperationException(); } @@ -421,7 +587,7 @@ public final ImmutableSet replaceValues(K key, Iterable values) throw new UnsupportedOperationException(); } - @LazyInit @RetainedWith @NullableDecl private transient ImmutableSet> entries; + @LazyInit @RetainedWith private transient @Nullable ImmutableSet> entries; /** * Returns an immutable collection of all key-value pairs in the multimap. Its iterator traverses @@ -441,7 +607,7 @@ private static final class EntrySet extends ImmutableSet> { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { if (object instanceof Entry) { Entry entry = (Entry) object; return multimap.containsEntry(entry.getKey(), entry.getValue()); @@ -463,23 +629,32 @@ public UnmodifiableIterator> iterator() { boolean isPartialView() { return false; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } private static ImmutableSet valueSet( - @NullableDecl Comparator valueComparator, Collection values) { + @Nullable Comparator valueComparator, Collection values) { return (valueComparator == null) ? ImmutableSet.copyOf(values) : ImmutableSortedSet.copyOf(valueComparator, values); } - private static ImmutableSet emptySet(@NullableDecl Comparator valueComparator) { + private static ImmutableSet emptySet(@Nullable Comparator valueComparator) { return (valueComparator == null) - ? ImmutableSet.of() - : ImmutableSortedSet.emptySet(valueComparator); + ? ImmutableSet.of() + : ImmutableSortedSet.emptySet(valueComparator); } private static ImmutableSet.Builder valuesBuilder( - @NullableDecl Comparator valueComparator) { + @Nullable Comparator valueComparator) { return (valueComparator == null) ? new ImmutableSet.Builder() : new ImmutableSortedSet.Builder(valueComparator); @@ -489,27 +664,30 @@ private static ImmutableSet.Builder valuesBuilder( * @serialData number of distinct keys, and then for each distinct key: the key, the number of * values for that key, and the key's values */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(valueComparator()); Serialization.writeMultimap(this, stream); } - @NullableDecl - Comparator valueComparator() { + @Nullable Comparator valueComparator() { return emptySet instanceof ImmutableSortedSet ? ((ImmutableSortedSet) emptySet).comparator() : null; } - @GwtIncompatible // java serialization + @GwtIncompatible + @J2ktIncompatible private static final class SetFieldSettersHolder { - static final Serialization.FieldSetter EMPTY_SET_FIELD_SETTER = - Serialization.getFieldSetter(ImmutableSetMultimap.class, "emptySet"); + static final Serialization.FieldSetter> + EMPTY_SET_FIELD_SETTER = + Serialization.getFieldSetter(ImmutableSetMultimap.class, "emptySet"); } - @GwtIncompatible // java.io.ObjectInputStream + @GwtIncompatible + @J2ktIncompatible // Serialization type safety is at the caller's mercy. @SuppressWarnings("unchecked") private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { @@ -523,7 +701,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo int tmpSize = 0; for (int i = 0; i < keyCount; i++) { - Object key = stream.readObject(); + Object key = requireNonNull(stream.readObject()); int valueCount = stream.readInt(); if (valueCount <= 0) { throw new InvalidObjectException("Invalid value count " + valueCount); @@ -531,7 +709,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo ImmutableSet.Builder valuesBuilder = valuesBuilder(valueComparator); for (int j = 0; j < valueCount; j++) { - valuesBuilder.add(stream.readObject()); + valuesBuilder.add(requireNonNull(stream.readObject())); } ImmutableSet valueSet = valuesBuilder.build(); if (valueSet.size() != valueCount) { @@ -543,7 +721,7 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo ImmutableMap> tmpMap; try { - tmpMap = builder.build(); + tmpMap = builder.buildOrThrow(); } catch (IllegalArgumentException e) { throw (InvalidObjectException) new InvalidObjectException(e.getMessage()).initCause(e); } @@ -553,6 +731,5 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo SetFieldSettersHolder.EMPTY_SET_FIELD_SETTER.set(this, emptySet(valueComparator)); } - @GwtIncompatible // not needed in emulated source. - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedMap.java b/android/guava/src/com/google/common/collect/ImmutableSortedMap.java index 78535cbb1e87..9ee0f06fac03 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSortedMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableSortedMap.java @@ -20,11 +20,16 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; import static com.google.common.collect.Maps.keyOrNull; +import static java.util.Arrays.sort; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.util.AbstractMap; import java.util.Arrays; import java.util.Comparator; @@ -32,7 +37,11 @@ import java.util.NavigableMap; import java.util.SortedMap; import java.util.TreeMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; /** * A {@link NavigableMap} whose contents will never change, with many other important properties @@ -45,32 +54,72 @@ * not correctly obey its specification. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Jared Levy * @author Louis Wasserman * @since 2.0 (implements {@code NavigableMap} since 12.0) */ -@GwtCompatible(serializable = true, emulated = true) -public final class ImmutableSortedMap extends ImmutableSortedMapFauxverideShim +@GwtCompatible +public final class ImmutableSortedMap extends ImmutableMap implements NavigableMap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSortedMap} whose + * keys and values are the result of applying the provided mapping functions to the input + * elements. The generated map is sorted by the specified comparator. + * + *

    If the mapped keys contain duplicates (according to the specified comparator), an {@code + * IllegalArgumentException} is thrown when the collection operation is performed. (This differs + * from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which + * throws an {@code IllegalStateException}.) + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableSortedMap(comparator, keyFunction, valueFunction); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSortedMap} whose + * keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

    If the mapped keys contain duplicates (according to the comparator), the values are merged + * using the specified merging function. Entries will appear in the encounter order of the first + * occurrence of the key. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + return CollectCollectors.toImmutableSortedMap( + comparator, keyFunction, valueFunction, mergeFunction); + } /* * TODO(kevinb): Confirm that ImmutableSortedMap is faster to construct and * uses less memory than TreeMap; then say so in the class Javadoc. */ - private static final Comparator NATURAL_ORDER = Ordering.natural(); + private static final Comparator NATURAL_ORDER = Ordering.natural(); - private static final ImmutableSortedMap NATURAL_EMPTY_MAP = - new ImmutableSortedMap<>( - ImmutableSortedSet.emptySet(Ordering.natural()), ImmutableList.of()); + private static final ImmutableSortedMap, Object> NATURAL_EMPTY_MAP = + new ImmutableSortedMap<>(ImmutableSortedSet.emptySet(Ordering.natural()), ImmutableList.of()); static ImmutableSortedMap emptyMap(Comparator comparator) { if (Ordering.natural().equals(comparator)) { return of(); } else { - return new ImmutableSortedMap<>( - ImmutableSortedSet.emptySet(comparator), ImmutableList.of()); + return new ImmutableSortedMap<>(ImmutableSortedSet.emptySet(comparator), ImmutableList.of()); } } @@ -104,10 +153,9 @@ private static ImmutableSortedMap of(Comparator comparat * * @throws IllegalArgumentException if the two keys are equal according to their natural ordering */ - @SuppressWarnings("unchecked") public static , V> ImmutableSortedMap of( K k1, V v1, K k2, V v2) { - return ofEntries(entryOf(k1, v1), entryOf(k2, v2)); + return fromEntries(entryOf(k1, v1), entryOf(k2, v2)); } /** @@ -116,10 +164,9 @@ public static , V> ImmutableSortedMap of( * * @throws IllegalArgumentException if any two keys are equal according to their natural ordering */ - @SuppressWarnings("unchecked") public static , V> ImmutableSortedMap of( K k1, V v1, K k2, V v2, K k3, V v3) { - return ofEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + return fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); } /** @@ -128,10 +175,9 @@ public static , V> ImmutableSortedMap of( * * @throws IllegalArgumentException if any two keys are equal according to their natural ordering */ - @SuppressWarnings("unchecked") public static , V> ImmutableSortedMap of( K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { - return ofEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + return fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); } /** @@ -140,16 +186,165 @@ public static , V> ImmutableSortedMap of( * * @throws IllegalArgumentException if any two keys are equal according to their natural ordering */ - @SuppressWarnings("unchecked") public static , V> ImmutableSortedMap of( K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { - return ofEntries( + return fromEntries( entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); } - private static , V> ImmutableSortedMap ofEntries( - Entry... entries) { - return fromEntries(Ordering.natural(), false, entries, entries.length); + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + * @since 31.0 + */ + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + * @since 31.0 + */ + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + * @since 31.0 + */ + public static , V> ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + * @since 31.0 + */ + public static , V> ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9) { + /* + * This explicit type parameter works around what seems to be a javac bug in certain + * configurations: b/339186525#comment6 + */ + return ImmutableSortedMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8), + entryOf(k9, v9)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + * @since 31.0 + */ + public static , V> ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9, + K k10, + V v10) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8), + entryOf(k9, v9), + entryOf(k10, v10)); } /** @@ -202,7 +397,6 @@ public static ImmutableSortedMap copyOf( * @throws IllegalArgumentException if any two keys are equal according to the comparator * @since 19.0 */ - @Beta public static ImmutableSortedMap copyOf( Iterable> entries) { // Hack around K not being a subtype of Comparable. @@ -220,7 +414,6 @@ public static ImmutableSortedMap copyOf( * @throws IllegalArgumentException if any two keys are equal according to the comparator * @since 19.0 */ - @Beta public static ImmutableSortedMap copyOf( Iterable> entries, Comparator comparator) { @@ -279,6 +472,11 @@ private static ImmutableSortedMap copyOfInternal( return fromEntries(comparator, sameComparator, map.entrySet()); } + private static , V> ImmutableSortedMap fromEntries( + Entry... entries) { + return fromEntries(Ordering.natural(), false, entries, entries.length); + } + /** * Accepts a collection of possibly-null entries. If {@code sameComparator}, then it is assumed * that they do not need to be sorted or checked for dupes. @@ -296,24 +494,27 @@ private static ImmutableSortedMap fromEntries( } private static ImmutableSortedMap fromEntries( - final Comparator comparator, + Comparator comparator, boolean sameComparator, - Entry[] entryArray, + @Nullable Entry[] entryArray, int size) { switch (size) { case 0: return emptyMap(comparator); case 1: - return ImmutableSortedMap.of( - comparator, entryArray[0].getKey(), entryArray[0].getValue()); + // requireNonNull is safe because the first `size` elements have been filled in. + Entry onlyEntry = requireNonNull(entryArray[0]); + return of(comparator, onlyEntry.getKey(), onlyEntry.getValue()); default: Object[] keys = new Object[size]; Object[] values = new Object[size]; if (sameComparator) { // Need to check for nulls, but don't need to sort or validate. for (int i = 0; i < size; i++) { - Object key = entryArray[i].getKey(); - Object value = entryArray[i].getValue(); + // requireNonNull is safe because the first `size` elements have been filled in. + Entry entry = requireNonNull(entryArray[i]); + Object key = entry.getKey(); + Object value = entry.getValue(); checkEntryNotNull(key, value); keys[i] = key; values[i] = value; @@ -322,28 +523,32 @@ private static ImmutableSortedMap fromEntries( // Need to sort and check for nulls and dupes. // Inline the Comparator implementation rather than transforming with a Function // to save code size. - Arrays.sort( + sort( entryArray, 0, size, - new Comparator>() { - @Override - public int compare(Entry e1, Entry e2) { - return comparator.compare(e1.getKey(), e2.getKey()); - } + (e1, e2) -> { + // requireNonNull is safe because the first `size` elements have been filled in. + requireNonNull(e1); + requireNonNull(e2); + return comparator.compare(e1.getKey(), e2.getKey()); }); - K prevKey = entryArray[0].getKey(); + // requireNonNull is safe because the first `size` elements have been filled in. + Entry firstEntry = requireNonNull(entryArray[0]); + K prevKey = firstEntry.getKey(); keys[0] = prevKey; - values[0] = entryArray[0].getValue(); + values[0] = firstEntry.getValue(); checkEntryNotNull(keys[0], values[0]); for (int i = 1; i < size; i++) { - K key = entryArray[i].getKey(); - V value = entryArray[i].getValue(); + // requireNonNull is safe because the first `size` elements have been filled in. + Entry prevEntry = requireNonNull(entryArray[i - 1]); + Entry entry = requireNonNull(entryArray[i]); + K key = entry.getKey(); + V value = entry.getValue(); checkEntryNotNull(key, value); keys[i] = key; values[i] = value; - checkNoConflict( - comparator.compare(prevKey, key) != 0, "key", entryArray[i - 1], entryArray[i]); + checkNoConflict(comparator.compare(prevKey, key) != 0, "key", prevEntry, entry); prevKey = key; } } @@ -378,48 +583,47 @@ public static Builder orderedBy(Comparator comparator) { * their natural ordering. */ public static , V> Builder reverseOrder() { - return new Builder<>(Ordering.natural().reverse()); + return new Builder<>(Ordering.natural().reverse()); } /** * A builder for creating immutable sorted map instances, especially {@code public static final} * maps ("constant maps"). Example: * - *
    {@code
    +   * {@snippet :
        * static final ImmutableSortedMap INT_TO_WORD =
        *     new ImmutableSortedMap.Builder(Ordering.natural())
        *         .put(1, "one")
        *         .put(2, "two")
        *         .put(3, "three")
    -   *         .build();
    -   * }
    + * .buildOrThrow(); + * } * *

    For small immutable sorted maps, the {@code ImmutableSortedMap.of()} methods are even * more convenient. * - *

    Builder instances can be reused - it is safe to call {@link #build} multiple times to build - * multiple maps in series. Each map is a superset of the maps created before it. + *

    Builder instances can be reused - it is safe to call {@link #buildOrThrow} multiple times to + * build multiple maps in series. Each map is a superset of the maps created before it. * * @since 2.0 */ public static class Builder extends ImmutableMap.Builder { - private transient Object[] keys; - private transient Object[] values; + private transient @Nullable Object[] keys; + private transient @Nullable Object[] values; private final Comparator comparator; /** * Creates a new builder. The returned builder is equivalent to the builder generated by {@link * ImmutableSortedMap#orderedBy}. */ - @SuppressWarnings("unchecked") public Builder(Comparator comparator) { this(comparator, ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY); } private Builder(Comparator comparator, int initialCapacity) { this.comparator = checkNotNull(comparator); - this.keys = new Object[initialCapacity]; - this.values = new Object[initialCapacity]; + this.keys = new @Nullable Object[initialCapacity]; + this.values = new @Nullable Object[initialCapacity]; } private void ensureCapacity(int minCapacity) { @@ -483,7 +687,6 @@ public Builder putAll(Map map) { * @since 19.0 */ @CanIgnoreReturnValue - @Beta @Override public Builder putAll(Iterable> entries) { super.putAll(entries); @@ -497,7 +700,6 @@ public Builder putAll(Iterable> * @deprecated Unsupported by ImmutableSortedMap.Builder. */ @CanIgnoreReturnValue - @Beta @Override @Deprecated @DoNotCall("Always throws UnsupportedOperationException") @@ -505,7 +707,12 @@ public final Builder orderEntriesByValue(Comparator valueCompar throw new UnsupportedOperationException("Not available on ImmutableSortedMap.Builder"); } - @CanIgnoreReturnValue + /* + * While the current implementation returns `this`, that's not something we mean to guarantee. + * Anyway, the purpose of this method is to implement a BinaryOperator combiner for a Collector, + * so its return value will get used naturally. + */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") Builder combine(ImmutableSortedMap.Builder other) { ensureCapacity(size + other.size); System.arraycopy(other.keys, 0, this.keys, this.size, other.size); @@ -517,19 +724,43 @@ Builder combine(ImmutableSortedMap.Builder other) { /** * Returns a newly-created immutable sorted map. * + *

    Prefer the equivalent method {@link #buildOrThrow()} to make it explicit that the method + * will throw an exception if there are duplicate keys. The {@code build()} method will soon be + * deprecated. + * * @throws IllegalArgumentException if any two keys are equal according to the comparator (which * might be the keys' natural order) */ @Override public ImmutableSortedMap build() { + return buildOrThrow(); + } + + /** + * Returns a newly-created immutable sorted map, or throws an exception if any two keys are + * equal. + * + * @throws IllegalArgumentException if any two keys are equal according to the comparator (which + * might be the keys' natural order) + * @since 31.0 + */ + @Override + @SuppressWarnings("unchecked") // see inline comments + public ImmutableSortedMap buildOrThrow() { switch (size) { case 0: return emptyMap(comparator); case 1: - return of(comparator, (K) keys[0], (V) values[0]); + // We're careful to put only K and V instances in. + // requireNonNull is safe because the first `size` elements have been filled in. + ImmutableSortedMap result = + of(comparator, (K) requireNonNull(keys[0]), (V) requireNonNull(values[0])); + return result; default: Object[] sortedKeys = Arrays.copyOf(keys, size); - Arrays.sort((K[]) sortedKeys, comparator); + // We're careful to put only K instances in. + K[] sortedKs = (K[]) sortedKeys; + Arrays.sort(sortedKs, comparator); Object[] sortedValues = new Object[size]; // We might, somehow, be able to reorder values in-place. But it doesn't seem like @@ -537,6 +768,7 @@ public ImmutableSortedMap build() { // one array of size n, we might as well allocate two -- to say nothing of the allocation // done in Arrays.sort. for (int i = 0; i < size; i++) { + // We're careful to put only K instances in. if (i > 0 && comparator.compare((K) sortedKeys[i - 1], (K) sortedKeys[i]) == 0) { throw new IllegalArgumentException( "keys required to be distinct but compared as equal: " @@ -544,8 +776,11 @@ public ImmutableSortedMap build() { + " and " + sortedKeys[i]); } - int index = Arrays.binarySearch((K[]) sortedKeys, (K) keys[i], comparator); - sortedValues[index] = values[i]; + // requireNonNull is safe because the first `size` elements have been filled in. + // We're careful to put only K instances in. + int index = + Arrays.binarySearch((K[]) sortedKeys, (K) requireNonNull(keys[i]), comparator); + sortedValues[index] = requireNonNull(values[i]); } return new ImmutableSortedMap( new RegularImmutableSortedSet( @@ -553,11 +788,29 @@ public ImmutableSortedMap build() { ImmutableList.asImmutableList(sortedValues)); } } + + /** + * Throws UnsupportedOperationException. A future version may support this operation. Then the + * value for any given key will be the one that was last supplied in a {@code put} operation for + * that key. + * + * @throws UnsupportedOperationException always + * @since 31.1 + * @deprecated This method is not currently implemented, and may never be. + */ + @DoNotCall + @Deprecated + @Override + public final ImmutableSortedMap buildKeepingLast() { + // TODO(emcmanus): implement + throw new UnsupportedOperationException( + "ImmutableSortedMap.Builder does not yet implement buildKeepingLast()"); + } } private final transient RegularImmutableSortedSet keySet; private final transient ImmutableList valueList; - private transient ImmutableSortedMap descendingMap; + private final transient @Nullable ImmutableSortedMap descendingMap; ImmutableSortedMap(RegularImmutableSortedSet keySet, ImmutableList valueList) { this(keySet, valueList, null); @@ -566,7 +819,7 @@ public ImmutableSortedMap build() { ImmutableSortedMap( RegularImmutableSortedSet keySet, ImmutableList valueList, - ImmutableSortedMap descendingMap) { + @Nullable ImmutableSortedMap descendingMap) { this.keySet = keySet; this.valueList = valueList; this.descendingMap = descendingMap; @@ -578,7 +831,7 @@ public int size() { } @Override - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { int index = keySet.indexOf(key); return (index == -1) ? null : valueList.get(index); } @@ -596,7 +849,7 @@ public ImmutableSet> entrySet() { @Override ImmutableSet> createEntrySet() { - class EntrySet extends ImmutableMapEntrySet { + final class EntrySet extends ImmutableMapEntrySet { @Override public UnmodifiableIterator> iterator() { return asList().iterator(); @@ -620,6 +873,15 @@ boolean isPartialView() { public int size() { return ImmutableSortedMap.this.size(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } }; } @@ -627,8 +889,17 @@ public int size() { ImmutableMap map() { return ImmutableSortedMap.this; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } - return isEmpty() ? ImmutableSet.>of() : new EntrySet(); + return isEmpty() ? ImmutableSet.of() : new EntrySet(); } /** Returns an immutable sorted set of the keys in this map. */ @@ -789,52 +1060,52 @@ public ImmutableSortedMap tailMap(K fromKey, boolean inclusive) { } @Override - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(K key) { return headMap(key, false).lastEntry(); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(K key) { return keyOrNull(lowerEntry(key)); } @Override - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(K key) { return headMap(key, true).lastEntry(); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(K key) { return keyOrNull(floorEntry(key)); } @Override - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(K key) { return tailMap(key, true).firstEntry(); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(K key) { return keyOrNull(ceilingEntry(key)); } @Override - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(K key) { return tailMap(key, false).firstEntry(); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(K key) { return keyOrNull(higherEntry(key)); } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return isEmpty() ? null : entrySet().asList().get(0); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return isEmpty() ? null : entrySet().asList().get(size() - 1); } @@ -848,7 +1119,7 @@ public Entry lastEntry() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final Entry pollFirstEntry() { + public final @Nullable Entry pollFirstEntry() { throw new UnsupportedOperationException(); } @@ -862,22 +1133,24 @@ public final Entry pollFirstEntry() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final Entry pollLastEntry() { + public final @Nullable Entry pollLastEntry() { throw new UnsupportedOperationException(); } @Override public ImmutableSortedMap descendingMap() { - // TODO(kevinb): the descendingMap is never actually cached at all. Either it should be or the - // code below simplified. + // TODO(kevinb): The descendingMap is never actually cached at all. Either: + // + // - Cache it, and annotate the field with @LazyInit. + // - Simplify the code below, and consider eliminating the field (b/287198172), which is also + // set by one of the constructors. ImmutableSortedMap result = descendingMap; if (result == null) { if (isEmpty()) { - return result = emptyMap(Ordering.from(comparator()).reverse()); + return emptyMap(Ordering.from(comparator()).reverse()); } else { - return result = - new ImmutableSortedMap<>( - (RegularImmutableSortedSet) keySet.descendingSet(), valueList.reverse(), this); + return new ImmutableSortedMap<>( + (RegularImmutableSortedSet) keySet.descendingSet(), valueList.reverse(), this); } } return result; @@ -898,7 +1171,8 @@ public ImmutableSortedSet descendingKeySet() { * are reconstructed using public factory methods. This ensures that the implementation types * remain as implementation details. */ - private static class SerializedForm extends ImmutableMap.SerializedForm { + @J2ktIncompatible // serialization + private static final class SerializedForm extends ImmutableMap.SerializedForm { private final Comparator comparator; SerializedForm(ImmutableSortedMap sortedMap) { @@ -911,15 +1185,312 @@ Builder makeBuilder(int size) { return new Builder<>(comparator); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @Override + @J2ktIncompatible // serialization Object writeReplace() { return new SerializedForm<>(this); } + @J2ktIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + // This class is never actually serialized directly, but we have to make the // warning go away (and suppressing would suppress for all nested classes too) - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; + + /** + * Not supported. Use {@link #toImmutableSortedMap}, which offers better type-safety, instead. + * This method exists only to hide {@link ImmutableMap#toImmutableMap} from consumers of {@code + * ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#toImmutableSortedMap}. + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @DoNotCall("Use toImmutableSortedMap") + @Deprecated + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link #toImmutableSortedMap}, which offers better type-safety, instead. + * This method exists only to hide {@link ImmutableMap#toImmutableMap} from consumers of {@code + * ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#toImmutableSortedMap}. + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @DoNotCall("Use toImmutableSortedMap") + @Deprecated + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link #naturalOrder}, which offers better type-safety, instead. This method + * exists only to hide {@link ImmutableMap#builder} from consumers of {@code ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#naturalOrder}, which offers better type-safety. + */ + @DoNotCall("Use naturalOrder") + @Deprecated + public static ImmutableSortedMap.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported for ImmutableSortedMap. + * + * @throws UnsupportedOperationException always + * @deprecated Not supported for ImmutableSortedMap. + */ + @DoNotCall("Use naturalOrder (which does not accept an expected size)") + @Deprecated + public static ImmutableSortedMap.Builder builderWithExpectedSize(int expectedSize) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain a non-{@code Comparable} + * key. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this dummy + * version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a key of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object)}. + */ + @DoNotCall("Pass a key of type Comparable") + @Deprecated + public static ImmutableSortedMap of(K k1, V v1) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of(K k1, V v1, K k2, V v2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls to will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of(K k1, V v1, K k2, V v2, K k3, V v3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @DoNotCall("Pass keys of type Comparable") + @Deprecated + public static ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9, + K k10, + V v10) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@code ImmutableSortedMap.copyOf(ImmutableMap.ofEntries(...))}. + * + * @deprecated Use {@code ImmutableSortedMap.copyOf(ImmutableMap.ofEntries(...))}. + */ + @DoNotCall("ImmutableSortedMap.ofEntries not currently available; use ImmutableSortedMap.copyOf") + @Deprecated + @SafeVarargs + public static ImmutableSortedMap ofEntries( + Entry... entries) { + throw new UnsupportedOperationException(); + } } diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedMapFauxverideShim.java b/android/guava/src/com/google/common/collect/ImmutableSortedMapFauxverideShim.java deleted file mode 100644 index 1e875a3b0ca3..000000000000 --- a/android/guava/src/com/google/common/collect/ImmutableSortedMapFauxverideShim.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2009 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import com.google.common.annotations.GwtIncompatible; - -/** - * "Overrides" the {@link ImmutableMap} static methods that lack {@link ImmutableSortedMap} - * equivalents with deprecated, exception-throwing versions. See {@link - * ImmutableSortedSetFauxverideShim} for details. - * - * @author Chris Povirk - */ -@GwtIncompatible -abstract class ImmutableSortedMapFauxverideShim extends ImmutableMap { - /** - * Not supported. Use {@link ImmutableSortedMap#naturalOrder}, which offers better type-safety, - * instead. This method exists only to hide {@link ImmutableMap#builder} from consumers of {@code - * ImmutableSortedMap}. - * - * @throws UnsupportedOperationException always - * @deprecated Use {@link ImmutableSortedMap#naturalOrder}, which offers better type-safety. - */ - @Deprecated - public static ImmutableSortedMap.Builder builder() { - throw new UnsupportedOperationException(); - } - - /** - * Not supported for ImmutableSortedMap. - * - * @throws UnsupportedOperationException always - * @deprecated Not supported for ImmutableSortedMap. - */ - @Deprecated - public static ImmutableSortedMap.Builder builderWithExpectedSize(int expectedSize) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a map that may contain a non-{@code Comparable} - * key. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this dummy - * version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass a key of type {@code Comparable} to use {@link - * ImmutableSortedMap#of(Comparable, Object)}. - */ - @Deprecated - public static ImmutableSortedMap of(K k1, V v1) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a map that may contain non-{@code Comparable} - * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass keys of type {@code Comparable} to use {@link - * ImmutableSortedMap#of(Comparable, Object, Comparable, Object)}. - */ - @Deprecated - public static ImmutableSortedMap of(K k1, V v1, K k2, V v2) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a map that may contain non-{@code Comparable} - * keys. Proper calls to will resolve to the version in {@code ImmutableSortedMap}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass keys of type {@code Comparable} to use {@link - * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object)}. - */ - @Deprecated - public static ImmutableSortedMap of(K k1, V v1, K k2, V v2, K k3, V v3) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a map that may contain non-{@code Comparable} - * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass keys of type {@code Comparable} to use {@link - * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, - * Comparable, Object)}. - */ - @Deprecated - public static ImmutableSortedMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a map that may contain non-{@code Comparable} - * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass keys of type {@code Comparable} to use {@link - * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, - * Comparable, Object, Comparable, Object)}. - */ - @Deprecated - public static ImmutableSortedMap of( - K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { - throw new UnsupportedOperationException(); - } - - // No copyOf() fauxveride; see ImmutableSortedSetFauxverideShim. -} diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java b/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java index 5218b60d501b..2f2399849fef 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java +++ b/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java @@ -18,18 +18,26 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.math.IntMath; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.concurrent.LazyInit; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A {@link SortedMultiset} whose contents will never change, with many other important properties @@ -42,16 +50,78 @@ * collection will not correctly obey its specification. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Louis Wasserman * @since 12.0 */ @GwtIncompatible // hasn't been tested yet -public abstract class ImmutableSortedMultiset extends ImmutableSortedMultisetFauxverideShim +public abstract class ImmutableSortedMultiset extends ImmutableMultiset implements SortedMultiset { // TODO(lowasser): GWT compatibility + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableMultiset}. Elements are sorted by the specified comparator. + * + *

    Warning: {@code comparator} should be consistent with {@code equals} as + * explained in the {@link Comparator} documentation. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toImmutableSortedMultiset( + Comparator comparator) { + return toImmutableSortedMultiset(comparator, Function.identity(), e -> 1); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableSortedMultiset} + * whose elements are the result of applying {@code elementFunction} to the inputs, with counts + * equal to the result of applying {@code countFunction} to the inputs. + * + *

    If the mapped elements contain duplicates (according to {@code comparator}), the first + * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of + * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. + * + * @since 33.2.0 (available since 22.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableSortedMultiset( + Comparator comparator, + Function elementFunction, + ToIntFunction countFunction) { + checkNotNull(comparator); + checkNotNull(elementFunction); + checkNotNull(countFunction); + return Collector.of( + () -> TreeMultiset.create(comparator), + (multiset, t) -> mapAndAdd(t, multiset, elementFunction, countFunction), + (multiset1, multiset2) -> { + multiset1.addAll(multiset2); + return multiset1; + }, + (Multiset multiset) -> copyOfSortedEntries(comparator, multiset.entrySet())); + } + + @IgnoreJRERequirement // helper for toImmutableSortedMultiset + /* + * If we make these calls inline inside toImmutableSortedMultiset, we get an Animal Sniffer error, + * despite the @IgnoreJRERequirement annotation there. My assumption is that, because javac + * generates a synthetic method for the body of the lambda, the actual method calls that Animal + * Sniffer is flagging don't appear inside toImmutableSortedMultiset but rather inside that + * synthetic method. By moving those calls to a named method, we're able to apply + * @IgnoreJRERequirement somewhere that it will help. + */ + private static void mapAndAdd( + T t, + Multiset multiset, + Function elementFunction, + ToIntFunction countFunction) { + multiset.add(checkNotNull(elementFunction.apply(t)), countFunction.applyAsInt(t)); + } + /** * Returns the empty immutable sorted multiset. * @@ -63,11 +133,11 @@ public static ImmutableSortedMultiset of() { } /** Returns an immutable sorted multiset containing a single element. */ - public static > ImmutableSortedMultiset of(E element) { + public static > ImmutableSortedMultiset of(E e1) { RegularImmutableSortedSet elementSet = - (RegularImmutableSortedSet) ImmutableSortedSet.of(element); + (RegularImmutableSortedSet) ImmutableSortedSet.of(e1); long[] cumulativeCounts = {0, 1}; - return new RegularImmutableSortedMultiset(elementSet, cumulativeCounts, 0, 1); + return new RegularImmutableSortedMultiset<>(elementSet, cumulativeCounts, 0, 1); } /** @@ -76,7 +146,6 @@ public static > ImmutableSortedMultiset of(E * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedMultiset of(E e1, E e2) { return copyOf(Ordering.natural(), Arrays.asList(e1, e2)); } @@ -87,7 +156,6 @@ public static > ImmutableSortedMultiset of(E * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedMultiset of(E e1, E e2, E e3) { return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3)); } @@ -98,7 +166,6 @@ public static > ImmutableSortedMultiset of(E * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedMultiset of( E e1, E e2, E e3, E e4) { return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3, e4)); @@ -110,7 +177,6 @@ public static > ImmutableSortedMultiset of( * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedMultiset of( E e1, E e2, E e3, E e4, E e5) { return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3, e4, e5)); @@ -122,7 +188,6 @@ public static > ImmutableSortedMultiset of( * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedMultiset of( E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { int size = remaining.length + 6; @@ -167,7 +232,7 @@ public static ImmutableSortedMultiset copyOf(Iterable elemen // Hack around E not being a subtype of Comparable. // Unsafe, see ImmutableSortedMultisetFauxverideShim. @SuppressWarnings("unchecked") - Ordering naturalOrder = (Ordering) Ordering.natural(); + Ordering naturalOrder = (Ordering) Ordering.>natural(); return copyOf(naturalOrder, elements); } @@ -185,7 +250,7 @@ public static ImmutableSortedMultiset copyOf(Iterator elemen // Hack around E not being a subtype of Comparable. // Unsafe, see ImmutableSortedMultisetFauxverideShim. @SuppressWarnings("unchecked") - Ordering naturalOrder = (Ordering) Ordering.natural(); + Ordering naturalOrder = (Ordering) Ordering.>natural(); return copyOf(naturalOrder, elements); } @@ -211,7 +276,6 @@ public static ImmutableSortedMultiset copyOf( * * @throws NullPointerException if {@code comparator} or any of {@code elements} is null */ - @SuppressWarnings("unchecked") public static ImmutableSortedMultiset copyOf( Comparator comparator, Iterable elements) { if (elements instanceof ImmutableSortedMultiset) { @@ -244,7 +308,7 @@ public static ImmutableSortedMultiset copyOf( */ public static ImmutableSortedMultiset copyOfSorted(SortedMultiset sortedMultiset) { return copyOfSortedEntries( - sortedMultiset.comparator(), Lists.newArrayList(sortedMultiset.entrySet())); + sortedMultiset.comparator(), new ArrayList<>(sortedMultiset.entrySet())); } private static ImmutableSortedMultiset copyOfSortedEntries( @@ -252,7 +316,7 @@ private static ImmutableSortedMultiset copyOfSortedEntries( if (entries.isEmpty()) { return emptyMultiset(comparator); } - ImmutableList.Builder elementsBuilder = new ImmutableList.Builder(entries.size()); + ImmutableList.Builder elementsBuilder = new ImmutableList.Builder<>(entries.size()); long[] cumulativeCounts = new long[entries.size() + 1]; int i = 0; for (Entry entry : entries) { @@ -260,7 +324,7 @@ private static ImmutableSortedMultiset copyOfSortedEntries( cumulativeCounts[i + 1] = cumulativeCounts[i] + entry.getCount(); i++; } - return new RegularImmutableSortedMultiset( + return new RegularImmutableSortedMultiset<>( new RegularImmutableSortedSet(elementsBuilder.build(), comparator), cumulativeCounts, 0, @@ -272,7 +336,7 @@ static ImmutableSortedMultiset emptyMultiset(Comparator compar if (Ordering.natural().equals(comparator)) { return (ImmutableSortedMultiset) RegularImmutableSortedMultiset.NATURAL_EMPTY_MULTISET; } else { - return new RegularImmutableSortedMultiset(comparator); + return new RegularImmutableSortedMultiset<>(comparator); } } @@ -286,7 +350,7 @@ public final Comparator comparator() { @Override public abstract ImmutableSortedSet elementSet(); - @LazyInit transient ImmutableSortedMultiset descendingMultiset; + @LazyInit transient @Nullable ImmutableSortedMultiset descendingMultiset; @Override public ImmutableSortedMultiset descendingMultiset() { @@ -312,7 +376,7 @@ public ImmutableSortedMultiset descendingMultiset() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final Entry pollFirstEntry() { + public final @Nullable Entry pollFirstEntry() { throw new UnsupportedOperationException(); } @@ -328,7 +392,7 @@ public final Entry pollFirstEntry() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final Entry pollLastEntry() { + public final @Nullable Entry pollLastEntry() { throw new UnsupportedOperationException(); } @@ -358,7 +422,7 @@ public ImmutableSortedMultiset subMultiset( * @throws NullPointerException if {@code comparator} is null */ public static Builder orderedBy(Comparator comparator) { - return new Builder(comparator); + return new Builder<>(comparator); } /** @@ -366,11 +430,11 @@ public static Builder orderedBy(Comparator comparator) { * reverse of their natural ordering. * *

    Note: the type parameter {@code E} extends {@code Comparable} rather than {@code - * Comparable} as a workaround for javac bug 6468354. + * Comparable} in order to accommodate users of obsolete javac versions affected by JDK-6468354. */ public static > Builder reverseOrder() { - return new Builder(Ordering.natural().reverse()); + return new Builder<>(Ordering.natural().reverse()); } /** @@ -380,18 +444,18 @@ public static > Builder reverseOrder() { * that implement {@link Comparable}. * *

    Note: the type parameter {@code E} extends {@code Comparable} rather than {@code - * Comparable} as a workaround for javac bug 6468354. + * Comparable} in order to accommodate users of obsolete javac versions affected by JDK-6468354. */ public static > Builder naturalOrder() { - return new Builder(Ordering.natural()); + return new Builder<>(Ordering.natural()); } /** * A builder for creating immutable multiset instances, especially {@code public static final} * multisets ("constant multisets"). Example: * - *

    {@code
    +   * {@snippet :
        * public static final ImmutableSortedMultiset BEANS =
        *     new ImmutableSortedMultiset.Builder(colorComparator())
        *         .addCopies(Bean.COCOA, 4)
    @@ -399,7 +463,7 @@ public static > Builder naturalOrder() {
        *         .addCopies(Bean.RED, 8)
        *         .addCopies(Bean.BLACK_EYED, 10)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple multisets in series. @@ -639,6 +703,7 @@ public ImmutableSortedMultiset build() { } } + @J2ktIncompatible // serialization private static final class SerializedForm implements Serializable { final Comparator comparator; final E[] elements; @@ -669,7 +734,173 @@ Object readResolve() { } @Override + @J2ktIncompatible // serialization Object writeReplace() { return new SerializedForm(this); } + + @J2ktIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + /** + * Not supported. Use {@link #toImmutableSortedMultiset} instead. This method exists only to hide + * {@link ImmutableMultiset#toImmutableMultiset} from consumers of {@code + * ImmutableSortedMultiset}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMultiset#toImmutableSortedMultiset}. + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @DoNotCall("Use toImmutableSortedMultiset.") + @Deprecated + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toImmutableMultiset() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link #toImmutableSortedMultiset} instead. This method exists only to hide + * {@link ImmutableMultiset#toImmutableMultiset} from consumers of {@code + * ImmutableSortedMultiset}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMultiset#toImmutableSortedMultiset}. + * @since 33.2.0 (available since 22.0 in guava-jre) + */ + @DoNotCall("Use toImmutableSortedMultiset.") + @Deprecated + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableMultiset( + Function elementFunction, + ToIntFunction countFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link #naturalOrder}, which offers better type-safety, instead. This method + * exists only to hide {@link ImmutableMultiset#builder} from consumers of {@code + * ImmutableSortedMultiset}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMultiset#naturalOrder}, which offers better type-safety. + */ + @DoNotCall("Use naturalOrder.") + @Deprecated + public static ImmutableSortedMultiset.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a parameter of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable)}. + */ + @DoNotCall("Elements must be Comparable. (Or, pass a Comparator to orderedBy or copyOf.)") + @Deprecated + public static ImmutableSortedMultiset of(E e1) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable)}. + */ + @DoNotCall("Elements must be Comparable. (Or, pass a Comparator to orderedBy or copyOf.)") + @Deprecated + public static ImmutableSortedMultiset of(E e1, E e2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable)}. + */ + @DoNotCall("Elements must be Comparable. (Or, pass a Comparator to orderedBy or copyOf.)") + @Deprecated + public static ImmutableSortedMultiset of(E e1, E e2, E e3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable)}. + */ + @DoNotCall("Elements must be Comparable. (Or, pass a Comparator to orderedBy or copyOf.)") + @Deprecated + public static ImmutableSortedMultiset of(E e1, E e2, E e3, E e4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable, Comparable)} . + * + */ + @DoNotCall("Elements must be Comparable. (Or, pass a Comparator to orderedBy or copyOf.)") + @Deprecated + public static ImmutableSortedMultiset of(E e1, E e2, E e3, E e4, E e5) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable, Comparable, + * Comparable, Comparable...)} . + */ + @DoNotCall("Elements must be Comparable. (Or, pass a Comparator to orderedBy or copyOf.)") + @Deprecated + public static ImmutableSortedMultiset of( + E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain non-{@code + * Comparable} elements. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#copyOf(Comparable[])}. + */ + @DoNotCall("Elements must be Comparable. (Or, pass a Comparator to orderedBy or copyOf.)") + @Deprecated + // The usage of "Z" here works around bugs in Javadoc (JDK-8318093) and JDiff. + public static ImmutableSortedMultiset copyOf(Z[] elements) { + throw new UnsupportedOperationException(); + } + + @J2ktIncompatible private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java b/android/guava/src/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java deleted file mode 100644 index 39d41ed26c5c..000000000000 --- a/android/guava/src/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2011 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import com.google.common.annotations.GwtIncompatible; - -/** - * "Overrides" the {@link ImmutableMultiset} static methods that lack {@link - * ImmutableSortedMultiset} equivalents with deprecated, exception-throwing versions. This prevents - * accidents like the following: - * - *

    {@code
    - * List objects = ...;
    - * // Sort them:
    - * Set sorted = ImmutableSortedMultiset.copyOf(objects);
    - * // BAD CODE! The returned multiset is actually an unsorted ImmutableMultiset!
    - * }
    - *
    - * 

    While we could put the overrides in {@link ImmutableSortedMultiset} itself, it seems clearer - * to separate these "do not call" methods from those intended for normal use. - * - * @author Louis Wasserman - */ -@GwtIncompatible -abstract class ImmutableSortedMultisetFauxverideShim extends ImmutableMultiset { - /** - * Not supported. Use {@link ImmutableSortedMultiset#naturalOrder}, which offers better - * type-safety, instead. This method exists only to hide {@link ImmutableMultiset#builder} from - * consumers of {@code ImmutableSortedMultiset}. - * - * @throws UnsupportedOperationException always - * @deprecated Use {@link ImmutableSortedMultiset#naturalOrder}, which offers better type-safety. - */ - @Deprecated - public static ImmutableSortedMultiset.Builder builder() { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a multiset that may contain a non-{@code - * Comparable} element. Proper calls will resolve to the version in {@code - * ImmutableSortedMultiset}, not this dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass a parameter of type {@code Comparable} to use {@link - * ImmutableSortedMultiset#of(Comparable)}. - */ - @Deprecated - public static ImmutableSortedMultiset of(E element) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a multiset that may contain a non-{@code - * Comparable} element. Proper calls will resolve to the version in {@code - * ImmutableSortedMultiset}, not this dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedMultiset#of(Comparable, Comparable)}. - */ - @Deprecated - public static ImmutableSortedMultiset of(E e1, E e2) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a multiset that may contain a non-{@code - * Comparable} element. Proper calls will resolve to the version in {@code - * ImmutableSortedMultiset}, not this dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable)}. - */ - @Deprecated - public static ImmutableSortedMultiset of(E e1, E e2, E e3) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a multiset that may contain a non-{@code - * Comparable} element. Proper calls will resolve to the version in {@code - * ImmutableSortedMultiset}, not this dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable)}. - */ - @Deprecated - public static ImmutableSortedMultiset of(E e1, E e2, E e3, E e4) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a multiset that may contain a non-{@code - * Comparable} element. Proper calls will resolve to the version in {@code - * ImmutableSortedMultiset}, not this dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable, Comparable)} . - * - */ - @Deprecated - public static ImmutableSortedMultiset of(E e1, E e2, E e3, E e4, E e5) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a multiset that may contain a non-{@code - * Comparable} element. Proper calls will resolve to the version in {@code - * ImmutableSortedMultiset}, not this dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable, Comparable, - * Comparable, Comparable...)} . - */ - @Deprecated - public static ImmutableSortedMultiset of( - E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a multiset that may contain non-{@code - * Comparable} elements. Proper calls will resolve to the version in {@code - * ImmutableSortedMultiset}, not this dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass parameters of type {@code Comparable} to use {@link - * ImmutableSortedMultiset#copyOf(Comparable[])}. - */ - @Deprecated - public static ImmutableSortedMultiset copyOf(E[] elements) { - throw new UnsupportedOperationException(); - } - - /* - * We would like to include an unsupported " copyOf(Iterable)" here, providing only the - * properly typed "> copyOf(Iterable)" in ImmutableSortedMultiset (and - * likewise for the Iterator equivalent). However, due to a change in Sun's interpretation of the - * JLS (as described at http://bugs.sun.com/view_bug.do?bug_id=6182950), the OpenJDK 7 compiler - * available as of this writing rejects our attempts. To maintain compatibility with that version - * and with any other compilers that interpret the JLS similarly, there is no definition of - * copyOf() here, and the definition in ImmutableSortedMultiset matches that in - * ImmutableMultiset. - * - * The result is that ImmutableSortedMultiset.copyOf() may be called on non-Comparable elements. - * We have not discovered a better solution. In retrospect, the static factory methods should - * have gone in a separate class so that ImmutableSortedMultiset wouldn't "inherit" - * too-permissive factory methods from ImmutableMultiset. - */ -} diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedSet.java b/android/guava/src/com/google/common/collect/ImmutableSortedSet.java index e10b4d7a72a6..069750cd36a8 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSortedSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableSortedSet.java @@ -18,10 +18,14 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.asImmutableList; import static com.google.common.collect.ObjectArrays.checkElementsNotNull; +import static java.lang.System.arraycopy; +import static java.util.Arrays.sort; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.concurrent.LazyInit; @@ -35,7 +39,8 @@ import java.util.Iterator; import java.util.NavigableSet; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A {@link NavigableSet} whose contents will never change, with many other important properties @@ -48,22 +53,40 @@ * collection will not correctly obey its specification. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Jared Levy * @author Louis Wasserman * @since 2.0 (implements {@code NavigableSet} since 12.0) */ // TODO(benyu): benchmark and optimize all creation paths, which are a mess now -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // we're overriding default serialization -public abstract class ImmutableSortedSet extends ImmutableSortedSetFauxverideShim +public abstract class ImmutableSortedSet extends ImmutableSet implements NavigableSet, SortedIterable { + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableSortedSet}, ordered by the specified comparator. + * + *

    If the elements contain duplicates (according to the comparator), only the first duplicate + * in encounter order will appear in the result. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toImmutableSortedSet( + Comparator comparator) { + return CollectCollectors.toImmutableSortedSet(comparator); + } + static RegularImmutableSortedSet emptySet(Comparator comparator) { if (Ordering.natural().equals(comparator)) { - return (RegularImmutableSortedSet) RegularImmutableSortedSet.NATURAL_EMPTY_SET; + @SuppressWarnings("unchecked") // The natural-ordered empty set supports all types. + RegularImmutableSortedSet result = + (RegularImmutableSortedSet) RegularImmutableSortedSet.NATURAL_EMPTY_SET; + return result; } else { - return new RegularImmutableSortedSet(ImmutableList.of(), comparator); + return new RegularImmutableSortedSet<>(ImmutableList.of(), comparator); } } @@ -72,13 +95,14 @@ static RegularImmutableSortedSet emptySet(Comparator comparato * *

    Performance note: the instance returned is a singleton. */ + @SuppressWarnings("unchecked") // The natural-ordered empty set supports all types. public static ImmutableSortedSet of() { return (ImmutableSortedSet) RegularImmutableSortedSet.NATURAL_EMPTY_SET; } /** Returns an immutable sorted set containing a single element. */ - public static > ImmutableSortedSet of(E element) { - return new RegularImmutableSortedSet(ImmutableList.of(element), Ordering.natural()); + public static > ImmutableSortedSet of(E e1) { + return new RegularImmutableSortedSet<>(ImmutableList.of(e1), Ordering.natural()); } /** @@ -88,7 +112,6 @@ public static > ImmutableSortedSet of(E eleme * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedSet of(E e1, E e2) { return construct(Ordering.natural(), 2, e1, e2); } @@ -100,7 +123,6 @@ public static > ImmutableSortedSet of(E e1, E * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedSet of(E e1, E e2, E e3) { return construct(Ordering.natural(), 3, e1, e2, e3); } @@ -112,7 +134,6 @@ public static > ImmutableSortedSet of(E e1, E * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedSet of(E e1, E e2, E e3, E e4) { return construct(Ordering.natural(), 4, e1, e2, e3, e4); } @@ -124,7 +145,6 @@ public static > ImmutableSortedSet of(E e1, E * * @throws NullPointerException if any element is null */ - @SuppressWarnings("unchecked") public static > ImmutableSortedSet of( E e1, E e2, E e3, E e4, E e5) { return construct(Ordering.natural(), 5, e1, e2, e3, e4, e5); @@ -141,14 +161,14 @@ public static > ImmutableSortedSet of( @SuppressWarnings("unchecked") public static > ImmutableSortedSet of( E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { - Comparable[] contents = new Comparable[6 + remaining.length]; + Comparable[] contents = new Comparable[6 + remaining.length]; contents[0] = e1; contents[1] = e2; contents[2] = e3; contents[3] = e4; contents[4] = e5; contents[5] = e6; - System.arraycopy(remaining, 0, contents, 6, remaining.length); + arraycopy(remaining, 0, contents, 6, remaining.length); return construct(Ordering.natural(), contents.length, (E[]) contents); } @@ -191,7 +211,7 @@ public static ImmutableSortedSet copyOf(Iterable elements) { // Hack around E not being a subtype of Comparable. // Unsafe, see ImmutableSortedSetFauxverideShim. @SuppressWarnings("unchecked") - Ordering naturalOrder = (Ordering) Ordering.natural(); + Ordering naturalOrder = (Ordering) Ordering.>natural(); return copyOf(naturalOrder, elements); } @@ -223,7 +243,7 @@ public static ImmutableSortedSet copyOf(Collection elements) // Hack around E not being a subtype of Comparable. // Unsafe, see ImmutableSortedSetFauxverideShim. @SuppressWarnings("unchecked") - Ordering naturalOrder = (Ordering) Ordering.natural(); + Ordering naturalOrder = (Ordering) Ordering.>natural(); return copyOf(naturalOrder, elements); } @@ -242,7 +262,7 @@ public static ImmutableSortedSet copyOf(Iterator elements) { // Hack around E not being a subtype of Comparable. // Unsafe, see ImmutableSortedSetFauxverideShim. @SuppressWarnings("unchecked") - Ordering naturalOrder = (Ordering) Ordering.natural(); + Ordering naturalOrder = (Ordering) Ordering.>natural(); return copyOf(naturalOrder, elements); } @@ -326,7 +346,7 @@ public static ImmutableSortedSet copyOfSorted(SortedSet sortedSet) { if (list.isEmpty()) { return emptySet(comparator); } else { - return new RegularImmutableSortedSet(list, comparator); + return new RegularImmutableSortedSet<>(list, comparator); } } @@ -347,7 +367,7 @@ static ImmutableSortedSet construct( return emptySet(comparator); } checkElementsNotNull(contents, n); - Arrays.sort(contents, 0, n, comparator); + sort(contents, 0, n, comparator); int uniques = 1; for (int i = 1; i < n; i++) { E cur = contents[i]; @@ -362,8 +382,7 @@ static ImmutableSortedSet construct( // large array relative to the number of elements, so we cap the ratio. contents = Arrays.copyOf(contents, uniques); } - return new RegularImmutableSortedSet( - ImmutableList.asImmutableList(contents, uniques), comparator); + return new RegularImmutableSortedSet<>(asImmutableList(contents, uniques), comparator); } /** @@ -375,7 +394,7 @@ static ImmutableSortedSet construct( * @throws NullPointerException if {@code comparator} is null */ public static Builder orderedBy(Comparator comparator) { - return new Builder(comparator); + return new Builder<>(comparator); } /** @@ -383,7 +402,7 @@ public static Builder orderedBy(Comparator comparator) { * of their natural ordering. */ public static > Builder reverseOrder() { - return new Builder(Collections.reverseOrder()); + return new Builder<>(Collections.reverseOrder()); } /** @@ -393,20 +412,20 @@ public static > Builder reverseOrder() { * implement {@link Comparable}. */ public static > Builder naturalOrder() { - return new Builder(Ordering.natural()); + return new Builder<>(Ordering.natural()); } /** * A builder for creating immutable sorted set instances, especially {@code public static final} * sets ("constant sets"), with a given comparator. Example: * - *

    {@code
    +   * {@snippet :
        * public static final ImmutableSortedSet LUCKY_NUMBERS =
        *     new ImmutableSortedSet.Builder(ODDS_FIRST_COMPARATOR)
        *         .addAll(SINGLE_DIGIT_PRIMES)
        *         .add(42)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple sets in series. Each set is a superset of the set created before it. @@ -420,10 +439,22 @@ public static final class Builder extends ImmutableSet.Builder { * Creates a new builder. The returned builder is equivalent to the builder generated by {@link * ImmutableSortedSet#orderedBy}. */ + /* + * TODO(cpovirk): use Object[] instead of E[] in the mainline? (The backport is different and + * doesn't need this suppression, but we keep it to minimize diffs.) Generally be more clear + * about when we have an Object[] vs. a Comparable[] or other array type in internalArray? If we + * used Object[], we might be able to optimize toArray() to use clone() sometimes. (See + * cl/592273615 and cl/592273683.) + */ public Builder(Comparator comparator) { this.comparator = checkNotNull(comparator); } + Builder(Comparator comparator, int expectedKeys) { + super(expectedKeys, false); + this.comparator = checkNotNull(comparator); + } + /** * Adds {@code element} to the {@code ImmutableSortedSet}. If the {@code ImmutableSortedSet} * already contains {@code element}, then {@code add} has no effect. (only the previously added @@ -507,16 +538,16 @@ public ImmutableSortedSet build() { } } - int unsafeCompare(Object a, Object b) { + int unsafeCompare(Object a, @Nullable Object b) { return unsafeCompare(comparator, a, b); } - static int unsafeCompare(Comparator comparator, Object a, Object b) { + static int unsafeCompare(Comparator comparator, Object a, @Nullable Object b) { // Pretend the comparator can compare anything. If it turns out it can't - // compare a and b, we should get a CCE on the subsequent line. Only methods - // that are spec'd to throw CCE should call this. - @SuppressWarnings("unchecked") - Comparator unsafeComparator = (Comparator) comparator; + // compare a and b, we should get a CCE or NPE on the subsequent line. Only methods + // that are spec'd to throw CCE and NPE should call this. + @SuppressWarnings({"unchecked", "nullness"}) + Comparator<@Nullable Object> unsafeComparator = (Comparator<@Nullable Object>) comparator; return unsafeComparator.compare(a, b); } @@ -554,7 +585,9 @@ public ImmutableSortedSet headSet(E toElement) { return headSet(toElement, false); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @Override public ImmutableSortedSet headSet(E toElement, boolean inclusive) { return headSetImpl(checkNotNull(toElement), inclusive); @@ -577,7 +610,9 @@ public ImmutableSortedSet subSet(E fromElement, E toElement) { return subSet(fromElement, true, toElement, false); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @GwtIncompatible // NavigableSet @Override public ImmutableSortedSet subSet( @@ -603,7 +638,9 @@ public ImmutableSortedSet tailSet(E fromElement) { return tailSet(fromElement, true); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @Override public ImmutableSortedSet tailSet(E fromElement, boolean inclusive) { return tailSetImpl(checkNotNull(fromElement), inclusive); @@ -620,30 +657,38 @@ abstract ImmutableSortedSet subSetImpl( abstract ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive); - /** @since 12.0 */ + /** + * @since 12.0 + */ @GwtIncompatible // NavigableSet @Override - public E lower(E e) { - return Iterators.getNext(headSet(e, false).descendingIterator(), null); + public @Nullable E lower(E e) { + return Iterators.<@Nullable E>getNext(headSet(e, false).descendingIterator(), null); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @Override - public E floor(E e) { - return Iterators.getNext(headSet(e, true).descendingIterator(), null); + public @Nullable E floor(E e) { + return Iterators.<@Nullable E>getNext(headSet(e, true).descendingIterator(), null); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @Override - public E ceiling(E e) { - return Iterables.getFirst(tailSet(e, true), null); + public @Nullable E ceiling(E e) { + return Iterables.<@Nullable E>getFirst(tailSet(e, true), null); } - /** @since 12.0 */ + /** + * @since 12.0 + */ @GwtIncompatible // NavigableSet @Override - public E higher(E e) { - return Iterables.getFirst(tailSet(e, false), null); + public @Nullable E higher(E e) { + return Iterables.<@Nullable E>getFirst(tailSet(e, false), null); } @Override @@ -668,7 +713,7 @@ public E last() { @GwtIncompatible // NavigableSet @Override @DoNotCall("Always throws UnsupportedOperationException") - public final E pollFirst() { + public final @Nullable E pollFirst() { throw new UnsupportedOperationException(); } @@ -684,15 +729,17 @@ public final E pollFirst() { @GwtIncompatible // NavigableSet @Override @DoNotCall("Always throws UnsupportedOperationException") - public final E pollLast() { + public final @Nullable E pollLast() { throw new UnsupportedOperationException(); } @GwtIncompatible // NavigableSet @LazyInit - transient ImmutableSortedSet descendingSet; + transient @Nullable ImmutableSortedSet descendingSet; - /** @since 12.0 */ + /** + * @since 12.0 + */ @GwtIncompatible // NavigableSet @Override public ImmutableSortedSet descendingSet() { @@ -711,13 +758,15 @@ public ImmutableSortedSet descendingSet() { @GwtIncompatible // NavigableSet abstract ImmutableSortedSet createDescendingSet(); - /** @since 12.0 */ + /** + * @since 12.0 + */ @GwtIncompatible // NavigableSet @Override public abstract UnmodifiableIterator descendingIterator(); /** Returns the position of an element within the set, or -1 if not present. */ - abstract int indexOf(@NullableDecl Object target); + abstract int indexOf(@Nullable Object target); /* * This class is used to serialize all ImmutableSortedSet instances, @@ -725,11 +774,12 @@ public ImmutableSortedSet descendingSet() { * only. This is necessary to ensure that the existence of a particular * implementation type is an implementation detail. */ - private static class SerializedForm implements Serializable { + @J2ktIncompatible // serialization + private static final class SerializedForm implements Serializable { final Comparator comparator; final Object[] elements; - public SerializedForm(Comparator comparator, Object[] elements) { + SerializedForm(Comparator comparator, Object[] elements) { this.comparator = comparator; this.elements = elements; } @@ -739,15 +789,167 @@ Object readResolve() { return new Builder(comparator).add((E[]) elements).build(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } + @J2ktIncompatible // serialization private void readObject(ObjectInputStream unused) throws InvalidObjectException { throw new InvalidObjectException("Use SerializedForm"); } @Override + @J2ktIncompatible // serialization Object writeReplace() { return new SerializedForm(comparator, toArray()); } + + /** + * Not supported. Use {@link #toImmutableSortedSet} instead. This method exists only to hide + * {@link ImmutableSet#toImmutableSet} from consumers of {@code ImmutableSortedSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedSet#toImmutableSortedSet}. + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @DoNotCall("Use toImmutableSortedSet") + @Deprecated + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toImmutableSet() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link #naturalOrder}, which offers better type-safety, instead. This method + * exists only to hide {@link ImmutableSet#builder} from consumers of {@code ImmutableSortedSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedSet#naturalOrder}, which offers better type-safety. + */ + @DoNotCall("Use naturalOrder") + @Deprecated + public static ImmutableSortedSet.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. This method exists only to hide {@link ImmutableSet#builderWithExpectedSize} + * from consumers of {@code ImmutableSortedSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Not supported by ImmutableSortedSet. + */ + @DoNotCall("Use naturalOrder (which does not accept an expected size)") + @Deprecated + public static ImmutableSortedSet.Builder builderWithExpectedSize(int expectedSize) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a parameter of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable)}. + */ + @DoNotCall("Pass a parameter of type Comparable") + @Deprecated + public static ImmutableSortedSet of(E e1) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable)}. + */ + @DoNotCall("Pass parameters of type Comparable") + @Deprecated + public static ImmutableSortedSet of(E e1, E e2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable, Comparable)}. + */ + @DoNotCall("Pass parameters of type Comparable") + @Deprecated + public static ImmutableSortedSet of(E e1, E e2, E e3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable)}. + */ + @DoNotCall("Pass parameters of type Comparable") + @Deprecated + public static ImmutableSortedSet of(E e1, E e2, E e3, E e4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of( Comparable, Comparable, Comparable, Comparable, Comparable)}. + */ + @DoNotCall("Pass parameters of type Comparable") + @Deprecated + public static ImmutableSortedSet of(E e1, E e2, E e3, E e4, E e5) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable, Comparable, + * Comparable, Comparable...)}. + */ + @DoNotCall("Pass parameters of type Comparable") + @Deprecated + public static ImmutableSortedSet of(E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain non-{@code Comparable} + * elements. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#copyOf(Comparable[])}. + */ + @DoNotCall("Pass parameters of type Comparable") + @Deprecated + // The usage of "Z" here works around bugs in Javadoc (JDK-8318093) and JDiff. + public static ImmutableSortedSet copyOf(Z[] elements) { + throw new UnsupportedOperationException(); + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java b/android/guava/src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java deleted file mode 100644 index 9d2af2c18106..000000000000 --- a/android/guava/src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2009 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import com.google.common.annotations.GwtIncompatible; - -/** - * "Overrides" the {@link ImmutableSet} static methods that lack {@link ImmutableSortedSet} - * equivalents with deprecated, exception-throwing versions. This prevents accidents like the - * following: - * - *
    {@code
    - * List objects = ...;
    - * // Sort them:
    - * Set sorted = ImmutableSortedSet.copyOf(objects);
    - * // BAD CODE! The returned set is actually an unsorted ImmutableSet!
    - * }
    - *
    - * 

    While we could put the overrides in {@link ImmutableSortedSet} itself, it seems clearer to - * separate these "do not call" methods from those intended for normal use. - * - * @author Chris Povirk - */ -@GwtIncompatible -abstract class ImmutableSortedSetFauxverideShim extends ImmutableSet { - /** - * Not supported. Use {@link ImmutableSortedSet#naturalOrder}, which offers better type-safety, - * instead. This method exists only to hide {@link ImmutableSet#builder} from consumers of {@code - * ImmutableSortedSet}. - * - * @throws UnsupportedOperationException always - * @deprecated Use {@link ImmutableSortedSet#naturalOrder}, which offers better type-safety. - */ - @Deprecated - public static ImmutableSortedSet.Builder builder() { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. This method exists only to hide {@link ImmutableSet#builderWithExpectedSize} - * from consumers of {@code ImmutableSortedSet}. - * - * @throws UnsupportedOperationException always - * @deprecated Not supported by ImmutableSortedSet. - */ - @Deprecated - public static ImmutableSortedSet.Builder builderWithExpectedSize(int expectedSize) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} - * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass a parameter of type {@code Comparable} to use {@link - * ImmutableSortedSet#of(Comparable)}. - */ - @Deprecated - public static ImmutableSortedSet of(E element) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} - * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedSet#of(Comparable, Comparable)}. - */ - @Deprecated - public static ImmutableSortedSet of(E e1, E e2) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} - * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedSet#of(Comparable, Comparable, Comparable)}. - */ - @Deprecated - public static ImmutableSortedSet of(E e1, E e2, E e3) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} - * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable)}. - */ - @Deprecated - public static ImmutableSortedSet of(E e1, E e2, E e3, E e4) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} - * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedSet#of( Comparable, Comparable, Comparable, Comparable, Comparable)}. - */ - @Deprecated - public static ImmutableSortedSet of(E e1, E e2, E e3, E e4, E e5) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} - * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass the parameters of type {@code Comparable} to use {@link - * ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable, Comparable, - * Comparable, Comparable...)}. - */ - @Deprecated - public static ImmutableSortedSet of(E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. You are attempting to create a set that may contain non-{@code Comparable} - * elements. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this - * dummy version. - * - * @throws UnsupportedOperationException always - * @deprecated Pass parameters of type {@code Comparable} to use {@link - * ImmutableSortedSet#copyOf(Comparable[])}. - */ - @Deprecated - public static ImmutableSortedSet copyOf(E[] elements) { - throw new UnsupportedOperationException(); - } - - /* - * We would like to include an unsupported " copyOf(Iterable)" here, - * providing only the properly typed - * "> copyOf(Iterable)" in ImmutableSortedSet (and - * likewise for the Iterator equivalent). However, due to a change in Sun's - * interpretation of the JLS (as described at - * http://bugs.sun.com/view_bug.do?bug_id=6182950), the OpenJDK 7 compiler - * available as of this writing rejects our attempts. To maintain - * compatibility with that version and with any other compilers that interpret - * the JLS similarly, there is no definition of copyOf() here, and the - * definition in ImmutableSortedSet matches that in ImmutableSet. - * - * The result is that ImmutableSortedSet.copyOf() may be called on - * non-Comparable elements. We have not discovered a better solution. In - * retrospect, the static factory methods should have gone in a separate class - * so that ImmutableSortedSet wouldn't "inherit" too-permissive factory - * methods from ImmutableSet. - */ -} diff --git a/android/guava/src/com/google/common/collect/ImmutableTable.java b/android/guava/src/com/google/common/collect/ImmutableTable.java index 1673ba7e77f4..9667c9639791 100644 --- a/android/guava/src/com/google/common/collect/ImmutableTable.java +++ b/android/guava/src/com/google/common/collect/ImmutableTable.java @@ -17,25 +17,35 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Tables.immutableCell; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.MoreObjects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.DoNotMock; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A {@link Table} whose contents will never change, with many other important properties detailed * at {@link ImmutableCollection}. * *

    See the Guava User Guide article on immutable collections. + * "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections. * * @author Gregory Kick * @since 11.0 @@ -44,6 +54,47 @@ public abstract class ImmutableTable extends AbstractTable implements Serializable { + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableTable}. Each + * input element is mapped to one cell in the returned table, with the rows, columns, and values + * generated by applying the specified functions. + * + *

    The returned {@code Collector} will throw a {@code NullPointerException} at collection time + * if the row, column, or value functions return null on any input. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction) { + return TableCollectors.toImmutableTable(rowFunction, columnFunction, valueFunction); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableTable}. Each + * input element is mapped to one cell in the returned table, with the rows, columns, and values + * generated by applying the specified functions. If multiple inputs are mapped to the same row + * and column pair, they will be combined with the specified merging function in encounter order. + * + *

    The returned {@code Collector} will throw a {@code NullPointerException} at collection time + * if the row, column, value, or merging functions return null on any input. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static + Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + return TableCollectors.toImmutableTable( + rowFunction, columnFunction, valueFunction, mergeFunction); + } + /** * Returns an empty immutable table. * @@ -89,7 +140,7 @@ static ImmutableTable copyOf( for (Cell cell : cells) { builder.put(cell); } - return builder.build(); + return builder.buildOrThrow(); } /** @@ -105,7 +156,7 @@ public static Builder builder() { * new entry with those values. */ static Cell cellOf(R rowKey, C columnKey, V value) { - return Tables.immutableCell( + return immutableCell( checkNotNull(rowKey, "rowKey"), checkNotNull(columnKey, "columnKey"), checkNotNull(value, "value")); @@ -115,14 +166,14 @@ static Cell cellOf(R rowKey, C columnKey, V value) { * A builder for creating immutable table instances, especially {@code public static final} tables * ("constant tables"). Example: * - *

    {@code
    +   * {@snippet :
        * static final ImmutableTable SPREADSHEET =
        *     new ImmutableTable.Builder()
        *         .put(1, 'A', "foo")
        *         .put(1, 'B', "bar")
        *         .put(2, 'A', "baz")
    -   *         .build();
    -   * }
    + * .buildOrThrow(); + * } * *

    By default, the order in which cells are added to the builder determines the iteration * ordering of all views in the returned table, with {@link #putAll} following the {@link @@ -132,16 +183,16 @@ static Cell cellOf(R rowKey, C columnKey, V value) { *

    For empty or single-cell immutable tables, {@link #of()} and {@link #of(Object, Object, * Object)} are even more convenient. * - *

    Builder instances can be reused - it is safe to call {@link #build} multiple times to build - * multiple tables in series. Each table is a superset of the tables created before it. + *

    Builder instances can be reused - it is safe to call {@link #buildOrThrow} multiple times to + * build multiple tables in series. Each table is a superset of the tables created before it. * * @since 11.0 */ @DoNotMock public static final class Builder { - private final List> cells = Lists.newArrayList(); - @NullableDecl private Comparator rowComparator; - @NullableDecl private Comparator columnComparator; + private final List> cells = new ArrayList<>(); + private @Nullable Comparator rowComparator; + private @Nullable Comparator columnComparator; /** * Creates a new builder. The returned builder is equivalent to the builder generated by {@link @@ -215,15 +266,30 @@ Builder combine(Builder other) { /** * Returns a newly-created immutable table. * + *

    Prefer the equivalent method {@link #buildOrThrow()} to make it explicit that the method + * will throw an exception if there are duplicate key pairs. The {@code build()} method will + * soon be deprecated. + * * @throws IllegalArgumentException if duplicate key pairs were added */ public ImmutableTable build() { + return buildOrThrow(); + } + + /** + * Returns a newly-created immutable table, or throws an exception if duplicate key pairs were + * added. + * + * @throws IllegalArgumentException if duplicate key pairs were added + * @since 31.0 + */ + public ImmutableTable buildOrThrow() { int size = cells.size(); switch (size) { case 0: return of(); case 1: - return new SingletonImmutableTable<>(Iterables.getOnlyElement(cells)); + return new SingletonImmutableTable<>(getOnlyElement(cells)); default: return RegularImmutableTable.forCells(cells, rowComparator, columnComparator); } @@ -267,7 +333,7 @@ final Iterator valuesIterator() { public ImmutableMap column(C columnKey) { checkNotNull(columnKey, "columnKey"); return MoreObjects.firstNonNull( - (ImmutableMap) columnMap().get(columnKey), ImmutableMap.of()); + (ImmutableMap) columnMap().get(columnKey), ImmutableMap.of()); } @Override @@ -292,8 +358,7 @@ public ImmutableSet columnKeySet() { @Override public ImmutableMap row(R rowKey) { checkNotNull(rowKey, "rowKey"); - return MoreObjects.firstNonNull( - (ImmutableMap) rowMap().get(rowKey), ImmutableMap.of()); + return MoreObjects.firstNonNull((ImmutableMap) rowMap().get(rowKey), ImmutableMap.of()); } @Override @@ -311,12 +376,12 @@ public ImmutableSet rowKeySet() { public abstract ImmutableMap> rowMap(); @Override - public boolean contains(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { return get(rowKey, columnKey) != null; } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return values().contains(value); } @@ -343,7 +408,7 @@ public final void clear() { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final V put(R rowKey, C columnKey, V value) { + public final @Nullable V put(R rowKey, C columnKey, V value) { throw new UnsupportedOperationException(); } @@ -370,13 +435,10 @@ public final void putAll(Table table) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final V remove(Object rowKey, Object columnKey) { + public final @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { throw new UnsupportedOperationException(); } - /** Creates the common serialized form for this table. */ - abstract SerializedForm createSerializedForm(); - /** * Serialized type for all ImmutableTable instances. It captures the logical contents and * preserves iteration order of all views. @@ -429,10 +491,18 @@ Object readResolve() { cellListBuilder.build(), ImmutableSet.copyOf(rowKeys), ImmutableSet.copyOf(columnKeys)); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - final Object writeReplace() { - return createSerializedForm(); + @J2ktIncompatible + @GwtIncompatible + abstract Object writeReplace(); + + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/IndexedImmutableSet.java b/android/guava/src/com/google/common/collect/IndexedImmutableSet.java index a31d3b28648d..cda89e43eb41 100644 --- a/android/guava/src/com/google/common/collect/IndexedImmutableSet.java +++ b/android/guava/src/com/google/common/collect/IndexedImmutableSet.java @@ -18,8 +18,10 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; -@GwtCompatible(emulated = true) +@GwtCompatible abstract class IndexedImmutableSet extends ImmutableSet { abstract E get(int index); @@ -30,7 +32,7 @@ public UnmodifiableIterator iterator() { @Override @GwtIncompatible - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { return asList().copyIntoArray(dst, offset); } @@ -51,6 +53,24 @@ boolean isPartialView() { public int size() { return IndexedImmutableSet.this.size(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } }; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/Internal.java b/android/guava/src/com/google/common/collect/Internal.java new file mode 100644 index 000000000000..4ec3c531f582 --- /dev/null +++ b/android/guava/src/com/google/common/collect/Internal.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.time.Duration; + +/** This class is for {@code com.google.common.collect} use only! */ +@J2ktIncompatible +@GwtIncompatible // java.time.Duration +@IgnoreJRERequirement // We use this method only from within APIs that require a Duration. +final class Internal { + + /** + * Returns the number of nanoseconds of the given duration without throwing or overflowing. + * + *

    Instead of throwing {@link ArithmeticException}, this method silently saturates to either + * {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE}. This behavior can be useful when decomposing + * a duration in order to call a legacy API which requires a {@code long, TimeUnit} pair. + */ + static long toNanosSaturated(Duration duration) { + // Using a try/catch seems lazy, but the catch block will rarely get invoked (except for + // durations longer than approximately +/- 292 years). + try { + return duration.toNanos(); + } catch (ArithmeticException tooBig) { + return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE; + } + } + + private Internal() {} +} diff --git a/android/guava/src/com/google/common/collect/Interner.java b/android/guava/src/com/google/common/collect/Interner.java index 310bfca6f0b3..e8a9002d5cd5 100644 --- a/android/guava/src/com/google/common/collect/Interner.java +++ b/android/guava/src/com/google/common/collect/Interner.java @@ -16,9 +16,8 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; -import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.DoNotMock; /** @@ -32,8 +31,8 @@ * @author Kevin Bourrillion * @since 3.0 */ -@Beta @DoNotMock("Use Interners.new*Interner") +@J2ktIncompatible @GwtIncompatible public interface Interner { /** @@ -48,6 +47,5 @@ public interface Interner { * * @throws NullPointerException if {@code sample} is null */ - @CanIgnoreReturnValue // TODO(cpovirk): Consider removing this? E intern(E sample); } diff --git a/android/guava/src/com/google/common/collect/Interners.java b/android/guava/src/com/google/common/collect/Interners.java index 061a1cfc7c73..2573eb1c52fd 100644 --- a/android/guava/src/com/google/common/collect/Interners.java +++ b/android/guava/src/com/google/common/collect/Interners.java @@ -16,13 +16,15 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Equivalence; import com.google.common.base.Function; import com.google.common.collect.MapMaker.Dummy; import com.google.common.collect.MapMakerInternalMap.InternalEntry; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.jspecify.annotations.Nullable; /** * Contains static methods pertaining to instances of {@link Interner}. @@ -30,7 +32,7 @@ * @author Kevin Bourrillion * @since 3.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class Interners { private Interners() {} @@ -51,6 +53,7 @@ private InternerBuilder() {} * * @see Interners#newStrongInterner() */ + @CanIgnoreReturnValue public InternerBuilder strong() { this.strong = true; return this; @@ -61,6 +64,7 @@ public InternerBuilder strong() { * * @see Interners#newWeakInterner() */ + @CanIgnoreReturnValue @GwtIncompatible("java.lang.ref.WeakReference") public InternerBuilder weak() { this.strong = false; @@ -72,6 +76,7 @@ public InternerBuilder weak() { * * @see MapMaker#concurrencyLevel(int) */ + @CanIgnoreReturnValue public InternerBuilder concurrencyLevel(int concurrencyLevel) { this.mapMaker.concurrencyLevel(concurrencyLevel); return this; @@ -81,7 +86,7 @@ public Interner build() { if (!strong) { mapMaker.weakKeys(); } - return new InternerImpl(mapMaker); + return new InternerImpl<>(mapMaker); } } @@ -124,11 +129,15 @@ private InternerImpl(MapMaker mapMaker) { public E intern(E sample) { while (true) { // trying to read the canonical... - InternalEntry entry = map.getEntry(sample); + @SuppressWarnings("rawtypes") // using raw types to avoid a bug in our nullness checker :( + InternalEntry entry = map.getEntry(sample); if (entry != null) { - E canonical = entry.getKey(); + Object canonical = entry.getKey(); if (canonical != null) { // only matters if weak/soft keys are used - return canonical; + // The compiler would know this is safe if not for our use of raw types (see above). + @SuppressWarnings("unchecked") + E result = (E) canonical; + return result; } } @@ -154,14 +163,14 @@ public E intern(E sample) { * @since 8.0 */ public static Function asFunction(Interner interner) { - return new InternerFunction(checkNotNull(interner)); + return new InternerFunction<>(checkNotNull(interner)); } - private static class InternerFunction implements Function { + private static final class InternerFunction implements Function { private final Interner interner; - public InternerFunction(Interner interner) { + InternerFunction(Interner interner) { this.interner = interner; } @@ -176,7 +185,7 @@ public int hashCode() { } @Override - public boolean equals(Object other) { + public boolean equals(@Nullable Object other) { if (other instanceof InternerFunction) { InternerFunction that = (InternerFunction) other; return interner.equals(that.interner); diff --git a/android/guava/src/com/google/common/collect/Iterables.java b/android/guava/src/com/google/common/collect/Iterables.java index 08df7f28d580..d99e4124f766 100644 --- a/android/guava/src/com/google/common/collect/Iterables.java +++ b/android/guava/src/com/google/common/collect/Iterables.java @@ -20,7 +20,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkRemove; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Function; @@ -28,6 +27,7 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; @@ -36,16 +36,19 @@ import java.util.Queue; import java.util.RandomAccess; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.SortedSet; +import java.util.stream.Stream; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * An assortment of mainly legacy static utility methods that operate on or return objects of type * {@code Iterable}. Except as noted, each method has a corresponding {@link Iterator}-based method * in the {@link Iterators} class. * - *

    Java 8 users: several common uses for this class are now more comprehensively addressed - * by the new {@link java.util.stream.Stream} library. Read the method documentation below for - * comparisons. This class is not being deprecated, but we gently encourage you to migrate to + *

    Java 8+ users: several common uses for this class are now more comprehensively + * addressed by the new {@link java.util.stream.Stream} library. Read the method documentation below + * for comparisons. This class is not being deprecated, but we gently encourage you to migrate to * streams. * *

    Performance notes: Unless otherwise noted, all of the iterables produced in this class @@ -53,19 +56,20 @@ * absolutely necessary. * *

    See the Guava User Guide article on {@code + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#iterables">{@code * Iterables}. * * @author Kevin Bourrillion * @author Jared Levy * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Iterables { private Iterables() {} /** Returns an unmodifiable view of {@code iterable}. */ - public static Iterable unmodifiableIterable(final Iterable iterable) { + public static Iterable unmodifiableIterable( + Iterable iterable) { checkNotNull(iterable); if (iterable instanceof UnmodifiableIterable || iterable instanceof ImmutableCollection) { @SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe @@ -81,12 +85,16 @@ public static Iterable unmodifiableIterable(final Iterable i * @deprecated no need to use this * @since 10.0 */ + @InlineMe( + replacement = "checkNotNull(iterable)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated public static Iterable unmodifiableIterable(ImmutableCollection iterable) { return checkNotNull(iterable); } - private static final class UnmodifiableIterable extends FluentIterable { + private static final class UnmodifiableIterable + extends FluentIterable { private final Iterable iterable; private UnmodifiableIterable(Iterable iterable) { @@ -118,7 +126,7 @@ public static int size(Iterable iterable) { * cases where {@link Collection#contains} might throw {@link NullPointerException} or {@link * ClassCastException}. */ - public static boolean contains(Iterable iterable, @NullableDecl Object element) { + public static boolean contains(Iterable iterable, @Nullable Object element) { if (iterable instanceof Collection) { Collection collection = (Collection) iterable; return Collections2.safeContains(collection, element); @@ -167,6 +175,9 @@ public static boolean retainAll(Iterable removeFrom, Collection elementsTo * The behavior of this method is not specified if {@code predicate} is dependent on {@code * removeFrom}. * + *

    Java 8+ users: if {@code removeFrom} is a {@link Collection}, use {@code + * removeFrom.removeIf(predicate)} instead. + * * @param removeFrom the iterable to (potentially) remove elements from * @param predicate a predicate that determines whether an element should be removed * @return {@code true} if any elements were removed from the iterable @@ -174,14 +185,15 @@ public static boolean retainAll(Iterable removeFrom, Collection elementsTo * @since 2.0 */ @CanIgnoreReturnValue - public static boolean removeIf(Iterable removeFrom, Predicate predicate) { + public static boolean removeIf( + Iterable removeFrom, Predicate predicate) { if (removeFrom instanceof RandomAccess && removeFrom instanceof List) { return removeIfFromRandomAccessList((List) removeFrom, checkNotNull(predicate)); } return Iterators.removeIf(removeFrom.iterator(), predicate); } - private static boolean removeIfFromRandomAccessList( + private static boolean removeIfFromRandomAccessList( List list, Predicate predicate) { // Note: Not all random access lists support set(). Additionally, it's possible // for a list to reject setting an element, such as when the list does not permit @@ -213,7 +225,7 @@ private static boolean removeIfFromRandomAccessList( return from != to; } - private static void slowRemoveIfForRemainingElements( + private static void slowRemoveIfForRemainingElements( List list, Predicate predicate, int to, int from) { // Here we know that: // * (to < from) and that both are valid indices. @@ -237,8 +249,8 @@ private static void slowRemoveIfForRemainingElements( } /** Removes and returns the first matching element, or returns {@code null} if there is none. */ - @NullableDecl - static T removeFirstMatching(Iterable removeFrom, Predicate predicate) { + static @Nullable T removeFirstMatching( + Iterable removeFrom, Predicate predicate) { checkNotNull(predicate); Iterator iterator = removeFrom.iterator(); while (iterator.hasNext()) { @@ -282,13 +294,14 @@ public static String toString(Iterable iterable) { /** * Returns the single element contained in {@code iterable}. * - *

    Java 8 users: the {@code Stream} equivalent to this method is {@code + *

    Java 8+ users: the {@code Stream} equivalent to this method is {@code * stream.collect(MoreCollectors.onlyElement())}. * * @throws NoSuchElementException if the iterable is empty * @throws IllegalArgumentException if the iterable contains multiple elements */ - public static T getOnlyElement(Iterable iterable) { + @ParametricNullness + public static T getOnlyElement(Iterable iterable) { return Iterators.getOnlyElement(iterable.iterator()); } @@ -296,13 +309,14 @@ public static T getOnlyElement(Iterable iterable) { * Returns the single element contained in {@code iterable}, or {@code defaultValue} if the * iterable is empty. * - *

    Java 8 users: the {@code Stream} equivalent to this method is {@code + *

    Java 8+ users: the {@code Stream} equivalent to this method is {@code * stream.collect(MoreCollectors.toOptional()).orElse(defaultValue)}. * * @throws IllegalArgumentException if the iterator contains multiple elements */ - @NullableDecl - public static T getOnlyElement(Iterable iterable, @NullableDecl T defaultValue) { + @ParametricNullness + public static T getOnlyElement( + Iterable iterable, @ParametricNullness T defaultValue) { return Iterators.getOnlyElement(iterable.iterator(), defaultValue); } @@ -314,11 +328,12 @@ public static T getOnlyElement(Iterable iterable, @NullableDecl * @return a newly-allocated array into which all the elements of the iterable have been copied */ @GwtIncompatible // Array.newInstance(Class, int) - public static T[] toArray(Iterable iterable, Class type) { + public static T[] toArray( + Iterable iterable, Class<@NonNull T> type) { return toArray(iterable, ObjectArrays.newArray(type, 0)); } - static T[] toArray(Iterable iterable, T[] array) { + static T[] toArray(Iterable iterable, T[] array) { Collection collection = castOrCopyToCollection(iterable); return collection.toArray(array); } @@ -329,7 +344,7 @@ static T[] toArray(Iterable iterable, T[] array) { * @param iterable the iterable to copy * @return a newly-allocated array into which all the elements of the iterable have been copied */ - static Object[] toArray(Iterable iterable) { + static @Nullable Object[] toArray(Iterable iterable) { return castOrCopyToCollection(iterable).toArray(); } @@ -338,7 +353,8 @@ static Object[] toArray(Iterable iterable) { * returned. Otherwise, an {@link java.util.ArrayList} is created with the contents of the * iterable in the same iteration order. */ - private static Collection castOrCopyToCollection(Iterable iterable) { + private static Collection castOrCopyToCollection( + Iterable iterable) { return (iterable instanceof Collection) ? (Collection) iterable : Lists.newArrayList(iterable.iterator()); @@ -350,7 +366,8 @@ private static Collection castOrCopyToCollection(Iterable iterable) { * @return {@code true} if {@code collection} was modified as a result of this operation. */ @CanIgnoreReturnValue - public static boolean addAll(Collection addTo, Iterable elementsToAdd) { + public static boolean addAll( + Collection addTo, Iterable elementsToAdd) { if (elementsToAdd instanceof Collection) { Collection c = (Collection) elementsToAdd; return addTo.addAll(c); @@ -362,14 +379,14 @@ public static boolean addAll(Collection addTo, Iterable elem * Returns the number of elements in the specified iterable that equal the specified object. This * implementation avoids a full iteration when the iterable is a {@link Multiset} or {@link Set}. * - *

    Java 8 users: In most cases, the {@code Stream} equivalent of this method is {@code + *

    Java 8+ users: In most cases, the {@code Stream} equivalent of this method is {@code * stream.filter(element::equals).count()}. If {@code element} might be null, use {@code * stream.filter(Predicate.isEqual(element)).count()} instead. * * @see java.util.Collections#frequency(Collection, Object) Collections.frequency(Collection, * Object) */ - public static int frequency(Iterable iterable, @NullableDecl Object element) { + public static int frequency(Iterable iterable, @Nullable Object element) { if ((iterable instanceof Multiset)) { return ((Multiset) iterable).count(element); } else if ((iterable instanceof Set)) { @@ -393,10 +410,10 @@ public static int frequency(Iterable iterable, @NullableDecl Object element) *

    To cycle over the iterable {@code n} times, use the following: {@code * Iterables.concat(Collections.nCopies(n, iterable))} * - *

    Java 8 users: The {@code Stream} equivalent of this method is {@code + *

    Java 8+ users: The {@code Stream} equivalent of this method is {@code * Stream.generate(() -> iterable).flatMap(Streams::stream)}. */ - public static Iterable cycle(final Iterable iterable) { + public static Iterable cycle(Iterable iterable) { checkNotNull(iterable); return new FluentIterable() { @Override @@ -427,12 +444,12 @@ public String toString() { *

    To cycle over the elements {@code n} times, use the following: {@code * Iterables.concat(Collections.nCopies(n, Arrays.asList(elements)))} * - *

    Java 8 users: If passing a single element {@code e}, the {@code Stream} equivalent of - * this method is {@code Stream.generate(() -> e)}. Otherwise, put the elements in a collection + *

    Java 8+ users: If passing a single element {@code e}, the {@code Stream} equivalent + * of this method is {@code Stream.generate(() -> e)}. Otherwise, put the elements in a collection * and use {@code Stream.generate(() -> collection).flatMap(Collection::stream)}. */ @SafeVarargs - public static Iterable cycle(T... elements) { + public static Iterable cycle(T... elements) { return cycle(Lists.newArrayList(elements)); } @@ -444,10 +461,11 @@ public static Iterable cycle(T... elements) { *

    The returned iterable's iterator supports {@code remove()} when the corresponding input * iterator supports it. * - *

    Java 8 users: The {@code Stream} equivalent of this method is {@code Stream.concat(a, - * b)}. + *

    Java 8+ users: The {@code Stream} equivalent of this method is {@code + * Stream.concat(a, b)}. */ - public static Iterable concat(Iterable a, Iterable b) { + public static Iterable concat( + Iterable a, Iterable b) { return FluentIterable.concat(a, b); } @@ -459,10 +477,10 @@ public static Iterable concat(Iterable a, IterableThe returned iterable's iterator supports {@code remove()} when the corresponding input * iterator supports it. * - *

    Java 8 users: The {@code Stream} equivalent of this method is {@code + *

    Java 8+ users: The {@code Stream} equivalent of this method is {@code * Streams.concat(a, b, c)}. */ - public static Iterable concat( + public static Iterable concat( Iterable a, Iterable b, Iterable c) { return FluentIterable.concat(a, b, c); } @@ -476,10 +494,10 @@ public static Iterable concat( *

    The returned iterable's iterator supports {@code remove()} when the corresponding input * iterator supports it. * - *

    Java 8 users: The {@code Stream} equivalent of this method is {@code + *

    Java 8+ users: The {@code Stream} equivalent of this method is {@code * Streams.concat(a, b, c, d)}. */ - public static Iterable concat( + public static Iterable concat( Iterable a, Iterable b, Iterable c, @@ -495,13 +513,13 @@ public static Iterable concat( *

    The returned iterable's iterator supports {@code remove()} when the corresponding input * iterator supports it. * - *

    Java 8 users: The {@code Stream} equivalent of this method is {@code + *

    Java 8+ users: The {@code Stream} equivalent of this method is {@code * Streams.concat(...)}. * * @throws NullPointerException if any of the provided iterables is null */ @SafeVarargs - public static Iterable concat(Iterable... inputs) { + public static Iterable concat(Iterable... inputs) { return FluentIterable.concat(inputs); } @@ -514,10 +532,11 @@ public static Iterable concat(Iterable... inputs) { * iterator supports it. The methods of the returned iterable may throw {@code * NullPointerException} if any of the input iterators is null. * - *

    Java 8 users: The {@code Stream} equivalent of this method is {@code + *

    Java 8+ users: The {@code Stream} equivalent of this method is {@code * streamOfStreams.flatMap(s -> s)}. */ - public static Iterable concat(Iterable> inputs) { + public static Iterable concat( + Iterable> inputs) { return FluentIterable.concat(inputs); } @@ -530,6 +549,10 @@ public static Iterable concat(Iterable> i *

    Iterators returned by the returned iterable do not support the {@link Iterator#remove()} * method. The returned lists implement {@link RandomAccess}, whether or not the input list does. * + *

    Note: The current implementation eagerly allocates storage for {@code size} elements. + * As a consequence, passing values like {@code Integer.MAX_VALUE} can lead to {@link + * OutOfMemoryError}. + * *

    Note: if {@code iterable} is a {@link List}, use {@link Lists#partition(List, int)} * instead. * @@ -539,7 +562,8 @@ public static Iterable concat(Iterable> i * into partitions * @throws IllegalArgumentException if {@code size} is nonpositive */ - public static Iterable> partition(final Iterable iterable, final int size) { + public static Iterable> partition( + Iterable iterable, int size) { checkNotNull(iterable); checkArgument(size > 0); return new FluentIterable>() { @@ -565,12 +589,13 @@ public Iterator> iterator() { * into partitions (the final iterable may have trailing null elements) * @throws IllegalArgumentException if {@code size} is nonpositive */ - public static Iterable> paddedPartition(final Iterable iterable, final int size) { + public static Iterable> paddedPartition( + Iterable iterable, int size) { checkNotNull(iterable); checkArgument(size > 0); - return new FluentIterable>() { + return new FluentIterable>() { @Override - public Iterator> iterator() { + public Iterator> iterator() { return Iterators.paddedPartition(iterable.iterator(), size); } }; @@ -582,8 +607,8 @@ public Iterator> iterator() { * *

    {@code Stream} equivalent: {@link Stream#filter}. */ - public static Iterable filter( - final Iterable unfiltered, final Predicate retainIfTrue) { + public static Iterable filter( + Iterable unfiltered, Predicate retainIfTrue) { checkNotNull(unfiltered); checkNotNull(retainIfTrue); return new FluentIterable() { @@ -602,15 +627,15 @@ public Iterator iterator() { * This does perform a little more work than necessary, so another option is to insert an * unchecked cast at some later point: * - *

    -   * {@code @SuppressWarnings("unchecked") // safe because of ::isInstance check
    +   * {@snippet :
    +   * @SuppressWarnings("unchecked") // safe because of ::isInstance check
        * ImmutableList result =
    -   *     (ImmutableList) stream.filter(NewType.class::isInstance).collect(toImmutableList());}
    -   * 
    + * (ImmutableList) stream.filter(NewType.class::isInstance).collect(toImmutableList()); + * } */ @SuppressWarnings("unchecked") @GwtIncompatible // Class.isInstance - public static Iterable filter(final Iterable unfiltered, final Class desiredType) { + public static Iterable filter(Iterable unfiltered, Class desiredType) { checkNotNull(unfiltered); checkNotNull(desiredType); return (Iterable) filter(unfiltered, Predicates.instanceOf(desiredType)); @@ -621,7 +646,8 @@ public static Iterable filter(final Iterable unfiltered, final Class{@code Stream} equivalent: {@link Stream#anyMatch}. */ - public static boolean any(Iterable iterable, Predicate predicate) { + public static boolean any( + Iterable iterable, Predicate predicate) { return Iterators.any(iterable.iterator(), predicate); } @@ -631,7 +657,8 @@ public static boolean any(Iterable iterable, Predicate predica * *

    {@code Stream} equivalent: {@link Stream#allMatch}. */ - public static boolean all(Iterable iterable, Predicate predicate) { + public static boolean all( + Iterable iterable, Predicate predicate) { return Iterators.all(iterable.iterator(), predicate); } @@ -644,7 +671,9 @@ public static boolean all(Iterable iterable, Predicate predica * * @throws NoSuchElementException if no element in {@code iterable} matches the given predicate */ - public static T find(Iterable iterable, Predicate predicate) { + @ParametricNullness + public static T find( + Iterable iterable, Predicate predicate) { return Iterators.find(iterable.iterator(), predicate); } @@ -658,12 +687,24 @@ public static T find(Iterable iterable, Predicate predicate) { * * @since 7.0 */ - @NullableDecl - public static T find( - Iterable iterable, - Predicate predicate, - @NullableDecl T defaultValue) { - return Iterators.find(iterable.iterator(), predicate, defaultValue); + // The signature we really want here is... + // + // @JointlyNullable T find( + // Iterable iterable, + // Predicate predicate, + // @JointlyNullable T defaultValue); + // + // ...where "@JointlyNullable" is similar to @PolyNull but slightly different: + // + // - @PolyNull means "@Nullable or @Nonnull" + // (That would be unsound for an input Iterable<@Nullable Foo>. So, if we wanted to use + // @PolyNull, we would have to restrict this method to non-null . But it has users who pass + // iterables with null elements.) + // + // - @JointlyNullable means "@Nullable or no annotation" + public static @Nullable T find( + Iterable iterable, Predicate predicate, @Nullable T defaultValue) { + return Iterators.find(iterable.iterator(), predicate, defaultValue); } /** @@ -691,7 +732,8 @@ public static Optional tryFind(Iterable iterable, Predicate * * @since 2.0 */ - public static int indexOf(Iterable iterable, Predicate predicate) { + public static int indexOf( + Iterable iterable, Predicate predicate) { return Iterators.indexOf(iterable.iterator(), predicate); } @@ -708,8 +750,8 @@ public static int indexOf(Iterable iterable, Predicate predica * *

    {@code Stream} equivalent: {@link Stream#map} */ - public static Iterable transform( - final Iterable fromIterable, final Function function) { + public static Iterable transform( + Iterable fromIterable, Function function) { checkNotNull(fromIterable); checkNotNull(function); return new FluentIterable() { @@ -731,7 +773,8 @@ public Iterator iterator() { * @throws IndexOutOfBoundsException if {@code position} is negative or greater than or equal to * the size of {@code iterable} */ - public static T get(Iterable iterable, int position) { + @ParametricNullness + public static T get(Iterable iterable, int position) { checkNotNull(iterable); return (iterable instanceof List) ? ((List) iterable).get(position) @@ -753,13 +796,13 @@ public static T get(Iterable iterable, int position) { * @throws IndexOutOfBoundsException if {@code position} is negative * @since 4.0 */ - @NullableDecl - public static T get( - Iterable iterable, int position, @NullableDecl T defaultValue) { + @ParametricNullness + public static T get( + Iterable iterable, int position, @ParametricNullness T defaultValue) { checkNotNull(iterable); Iterators.checkNonnegative(position); if (iterable instanceof List) { - List list = Lists.cast(iterable); + List list = (List) iterable; return (position < list.size()) ? list.get(position) : defaultValue; } else { Iterator iterator = iterable.iterator(); @@ -781,12 +824,18 @@ public static T get( * *

    {@code Stream} equivalent: {@code stream.findFirst().orElse(defaultValue)} * + *

    Java 21+ users: if {code iterable} is a {@code SequencedCollection} (e.g., any list), + * consider using {@code collection.getFirst()} instead. Note that if the collection is empty, + * {@code getFirst()} throws a {@code NoSuchElementException}, while this method returns the + * default value. + * * @param defaultValue the default value to return if the iterable is empty * @return the first element of {@code iterable} or the default value * @since 7.0 */ - @NullableDecl - public static T getFirst(Iterable iterable, @NullableDecl T defaultValue) { + @ParametricNullness + public static T getFirst( + Iterable iterable, @ParametricNullness T defaultValue) { return Iterators.getNext(iterable.iterator(), defaultValue); } @@ -796,10 +845,14 @@ public static T getFirst(Iterable iterable, @NullableDecl T def * *

    {@code Stream} equivalent: {@link Streams#findLast Streams.findLast(stream).get()} * + *

    Java 21+ users: if {code iterable} is a {@code SequencedCollection} (e.g., any list), + * consider using {@code collection.getLast()} instead. + * * @return the last element of {@code iterable} * @throws NoSuchElementException if the iterable is empty */ - public static T getLast(Iterable iterable) { + @ParametricNullness + public static T getLast(Iterable iterable) { // TODO(kevinb): Support a concurrently modified collection? if (iterable instanceof List) { List list = (List) iterable; @@ -807,6 +860,8 @@ public static T getLast(Iterable iterable) { throw new NoSuchElementException(); } return getLastInNonemptyList(list); + } else if (iterable instanceof SortedSet) { + return ((SortedSet) iterable).last(); } return Iterators.getLast(iterable.iterator()); @@ -819,25 +874,34 @@ public static T getLast(Iterable iterable) { * *

    {@code Stream} equivalent: {@code Streams.findLast(stream).orElse(defaultValue)} * + *

    Java 21+ users: if {code iterable} is a {@code SequencedCollection} (e.g., any list), + * consider using {@code collection.getLast()} instead. Note that if the collection is empty, + * {@code getLast()} throws a {@code NoSuchElementException}, while this method returns the + * default value. + * * @param defaultValue the value to return if {@code iterable} is empty * @return the last element of {@code iterable} or the default value * @since 3.0 */ - @NullableDecl - public static T getLast(Iterable iterable, @NullableDecl T defaultValue) { + @ParametricNullness + public static T getLast( + Iterable iterable, @ParametricNullness T defaultValue) { if (iterable instanceof Collection) { Collection c = (Collection) iterable; if (c.isEmpty()) { return defaultValue; } else if (iterable instanceof List) { - return getLastInNonemptyList(Lists.cast(iterable)); + return getLastInNonemptyList((List) iterable); + } else if (iterable instanceof SortedSet) { + return ((SortedSet) iterable).last(); } } return Iterators.getLast(iterable.iterator(), defaultValue); } - private static T getLastInNonemptyList(List list) { + @ParametricNullness + private static T getLastInNonemptyList(List list) { return list.get(list.size() - 1); } @@ -860,7 +924,8 @@ private static T getLastInNonemptyList(List list) { * * @since 3.0 */ - public static Iterable skip(final Iterable iterable, final int numberToSkip) { + public static Iterable skip( + Iterable iterable, int numberToSkip) { checkNotNull(iterable); checkArgument(numberToSkip >= 0, "number to skip cannot be negative"); @@ -868,11 +933,11 @@ public static Iterable skip(final Iterable iterable, final int numberT @Override public Iterator iterator() { if (iterable instanceof List) { - final List list = (List) iterable; + List list = (List) iterable; int toSkip = Math.min(list.size(), numberToSkip); return list.subList(toSkip, list.size()).iterator(); } - final Iterator iterator = iterable.iterator(); + Iterator iterator = iterable.iterator(); Iterators.advance(iterator, numberToSkip); @@ -890,6 +955,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { T result = iterator.next(); atStart = false; // not called if next() fails @@ -919,7 +985,8 @@ public void remove() { * @throws IllegalArgumentException if {@code limitSize} is negative * @since 3.0 */ - public static Iterable limit(final Iterable iterable, final int limitSize) { + public static Iterable limit( + Iterable iterable, int limitSize) { checkNotNull(iterable); checkArgument(limitSize >= 0, "limit is negative"); return new FluentIterable() { @@ -934,10 +1001,13 @@ public Iterator iterator() { * Returns a view of the supplied iterable that wraps each generated {@link Iterator} through * {@link Iterators#consumingIterator(Iterator)}. * - *

    Note: If {@code iterable} is a {@link Queue}, the returned iterable will get entries from - * {@link Queue#remove()} since {@link Queue}'s iteration order is undefined. Calling {@link - * Iterator#hasNext()} on a generated iterator from the returned iterable may cause an item to be - * immediately dequeued for return on a subsequent call to {@link Iterator#next()}. + *

    Note: If {@code iterable} is a {@link Queue}, the returned iterable will instead use {@link + * Queue#isEmpty} and {@link Queue#remove()}, since {@link Queue}'s iteration order is undefined. + * Calling {@link Iterator#hasNext()} on a generated iterator from the returned iterable may cause + * an item to be immediately dequeued for return on a subsequent call to {@link Iterator#next()}. + * + *

    Whether the input {@code iterable} is a {@link Queue} or not, the returned {@code Iterable} + * is not thread-safe. * * @param iterable the iterable to wrap * @return a view of the supplied iterable that wraps each generated iterator through {@link @@ -946,7 +1016,7 @@ public Iterator iterator() { * @see Iterators#consumingIterator(Iterator) * @since 2.0 */ - public static Iterable consumingIterable(final Iterable iterable) { + public static Iterable consumingIterable(Iterable iterable) { checkNotNull(iterable); return new FluentIterable() { @@ -991,15 +1061,14 @@ public static boolean isEmpty(Iterable iterable) { *

    Callers must ensure that the source {@code iterables} are in non-descending order as this * method does not sort its input. * - *

    For any equivalent elements across all {@code iterables}, it is undefined which element is - * returned first. + *

    For any equivalent elements across all {@code iterables}, elements are returned in the order + * of their source iterables. That is, if element A from iterable 1 and element B from iterable 2 + * compare as equal, A will be returned before B if iterable 1 was passed before iterable 2. * * @since 11.0 */ - @Beta - public static Iterable mergeSorted( - final Iterable> iterables, - final Comparator comparator) { + public static Iterable mergeSorted( + Iterable> iterables, Comparator comparator) { checkNotNull(iterables, "iterables"); checkNotNull(comparator, "comparator"); Iterable iterable = @@ -1007,20 +1076,9 @@ public static Iterable mergeSorted( @Override public Iterator iterator() { return Iterators.mergeSorted( - Iterables.transform(iterables, Iterables.toIterator()), comparator); + Iterables.transform(iterables, Iterable::iterator), comparator); } }; return new UnmodifiableIterable<>(iterable); } - - // TODO(user): Is this the best place for this? Move to fluent functions? - // Useful as a public method? - static Function, Iterator> toIterator() { - return new Function, Iterator>() { - @Override - public Iterator apply(Iterable iterable) { - return iterable.iterator(); - } - }; - } } diff --git a/android/guava/src/com/google/common/collect/Iterators.java b/android/guava/src/com/google/common/collect/Iterators.java index ac36b202c3ab..4beae8ba07b3 100644 --- a/android/guava/src/com/google/common/collect/Iterators.java +++ b/android/guava/src/com/google/common/collect/Iterators.java @@ -21,17 +21,20 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Predicates.instanceOf; import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Function; -import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; @@ -41,11 +44,12 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.PriorityQueue; import java.util.Queue; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * This class contains static utility methods that operate on or return objects of type {@link @@ -57,14 +61,14 @@ * necessary. * *

    See the Guava User Guide section on {@code + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#iterables">{@code * Iterators}. * * @author Kevin Bourrillion * @author Jared Levy * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Iterators { private Iterators() {} @@ -73,7 +77,7 @@ private Iterators() {} * *

    The {@link Iterable} equivalent of this method is {@link ImmutableSet#of()}. */ - static UnmodifiableIterator emptyIterator() { + static UnmodifiableIterator emptyIterator() { return emptyListIterator(); } @@ -84,7 +88,7 @@ static UnmodifiableIterator emptyIterator() { */ // Casting to any type is safe since there are no actual elements. @SuppressWarnings("unchecked") - static UnmodifiableListIterator emptyListIterator() { + static UnmodifiableListIterator emptyListIterator() { return (UnmodifiableListIterator) ArrayItr.EMPTY; } @@ -117,13 +121,13 @@ public void remove() { */ // Casting to any type is safe since there are no actual elements. @SuppressWarnings("unchecked") - static Iterator emptyModifiableIterator() { + static Iterator emptyModifiableIterator() { return (Iterator) EmptyModifiableIterator.INSTANCE; } /** Returns an unmodifiable view of {@code iterator}. */ - public static UnmodifiableIterator unmodifiableIterator( - final Iterator iterator) { + public static UnmodifiableIterator unmodifiableIterator( + Iterator iterator) { checkNotNull(iterator); if (iterator instanceof UnmodifiableIterator) { @SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe @@ -137,6 +141,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { return iterator.next(); } @@ -149,8 +154,12 @@ public T next() { * @deprecated no need to use this * @since 10.0 */ + @InlineMe( + replacement = "checkNotNull(iterator)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated - public static UnmodifiableIterator unmodifiableIterator(UnmodifiableIterator iterator) { + public static UnmodifiableIterator unmodifiableIterator( + UnmodifiableIterator iterator) { return checkNotNull(iterator); } @@ -168,7 +177,7 @@ public static int size(Iterator iterator) { } /** Returns {@code true} if {@code iterator} contains {@code element}. */ - public static boolean contains(Iterator iterator, @NullableDecl Object element) { + public static boolean contains(Iterator iterator, @Nullable Object element) { if (element == null) { while (iterator.hasNext()) { if (iterator.next() == null) { @@ -216,7 +225,8 @@ public static boolean removeAll(Iterator removeFrom, Collection elementsTo * @since 2.0 */ @CanIgnoreReturnValue - public static boolean removeIf(Iterator removeFrom, Predicate predicate) { + public static boolean removeIf( + Iterator removeFrom, Predicate predicate) { checkNotNull(predicate); boolean modified = false; while (removeFrom.hasNext()) { @@ -266,7 +276,7 @@ public static boolean elementsEqual(Iterator iterator1, Iterator iterator2 } Object o1 = iterator1.next(); Object o2 = iterator2.next(); - if (!Objects.equal(o1, o2)) { + if (!Objects.equals(o1, o2)) { return false; } } @@ -297,7 +307,8 @@ public static String toString(Iterator iterator) { * @throws IllegalArgumentException if the iterator contains multiple elements. The state of the * iterator is unspecified. */ - public static T getOnlyElement(Iterator iterator) { + @ParametricNullness + public static T getOnlyElement(Iterator iterator) { T first = iterator.next(); if (!iterator.hasNext()) { return first; @@ -322,8 +333,9 @@ public static T getOnlyElement(Iterator iterator) { * @throws IllegalArgumentException if the iterator contains multiple elements. The state of the * iterator is unspecified. */ - @NullableDecl - public static T getOnlyElement(Iterator iterator, @NullableDecl T defaultValue) { + @ParametricNullness + public static T getOnlyElement( + Iterator iterator, @ParametricNullness T defaultValue) { return iterator.hasNext() ? getOnlyElement(iterator) : defaultValue; } @@ -336,9 +348,10 @@ public static T getOnlyElement(Iterator iterator, @NullableDecl * @return a newly-allocated array into which all the elements of the iterator have been copied */ @GwtIncompatible // Array.newInstance(Class, int) - public static T[] toArray(Iterator iterator, Class type) { + public static T[] toArray( + Iterator iterator, Class<@NonNull T> type) { List list = Lists.newArrayList(iterator); - return Iterables.toArray(list, type); + return Iterables.toArray(list, type); } /** @@ -348,7 +361,8 @@ public static T[] toArray(Iterator iterator, Class type) { * @return {@code true} if {@code collection} was modified as a result of this operation */ @CanIgnoreReturnValue - public static boolean addAll(Collection addTo, Iterator iterator) { + public static boolean addAll( + Collection addTo, Iterator iterator) { checkNotNull(addTo); checkNotNull(iterator); boolean wasModified = false; @@ -364,7 +378,7 @@ public static boolean addAll(Collection addTo, Iterator iter * * @see Collections#frequency */ - public static int frequency(Iterator iterator, @NullableDecl Object element) { + public static int frequency(Iterator iterator, @Nullable Object element) { int count = 0; while (contains(iterator, element)) { // Since it lives in the same class, we know contains gets to the element and then stops, @@ -386,7 +400,7 @@ public static int frequency(Iterator iterator, @NullableDecl Object element) * should use an explicit {@code break} or be certain that you will eventually remove all the * elements. */ - public static Iterator cycle(final Iterable iterable) { + public static Iterator cycle(Iterable iterable) { checkNotNull(iterable); return new Iterator() { Iterator iterator = emptyModifiableIterator(); @@ -406,6 +420,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { if (!iterator.hasNext()) { iterator = iterable.iterator(); @@ -436,7 +451,7 @@ public void remove() { * elements. */ @SafeVarargs - public static Iterator cycle(T... elements) { + public static Iterator cycle(T... elements) { return cycle(Lists.newArrayList(elements)); } @@ -444,10 +459,14 @@ public static Iterator cycle(T... elements) { * Returns an Iterator that walks the specified array, nulling out elements behind it. This can * avoid memory leaks when an element is no longer necessary. * + *

    This method accepts an array with element type {@code @Nullable T}, but callers must pass an + * array whose contents are initially non-null. The {@code @Nullable} annotation indicates that + * this method will write nulls into the array during iteration. + * *

    This is mainly just to avoid the intermediate ArrayDeque in ConsumingQueueIterator. */ - private static Iterator consumingForArray(final T... elements) { - return new UnmodifiableIterator() { + private static > Iterator consumingForArray(@Nullable I... elements) { + return new UnmodifiableIterator() { int index = 0; @Override @@ -456,11 +475,15 @@ public boolean hasNext() { } @Override - public T next() { + public I next() { if (!hasNext()) { throw new NoSuchElementException(); } - T result = elements[index]; + /* + * requireNonNull is safe because our callers always pass non-null arguments. Each element + * of the array becomes null only when we iterate past it and then clear it. + */ + I result = requireNonNull(elements[index]); elements[index] = null; index++; return result; @@ -476,7 +499,8 @@ public T next() { *

    The returned iterator supports {@code remove()} when the corresponding input iterator * supports it. */ - public static Iterator concat(Iterator a, Iterator b) { + public static Iterator concat( + Iterator a, Iterator b) { checkNotNull(a); checkNotNull(b); return concat(consumingForArray(a, b)); @@ -490,7 +514,7 @@ public static Iterator concat(Iterator a, IteratorThe returned iterator supports {@code remove()} when the corresponding input iterator * supports it. */ - public static Iterator concat( + public static Iterator concat( Iterator a, Iterator b, Iterator c) { checkNotNull(a); checkNotNull(b); @@ -507,7 +531,7 @@ public static Iterator concat( *

    The returned iterator supports {@code remove()} when the corresponding input iterator * supports it. */ - public static Iterator concat( + public static Iterator concat( Iterator a, Iterator b, Iterator c, @@ -529,7 +553,8 @@ public static Iterator concat( * * @throws NullPointerException if any of the provided iterators is null */ - public static Iterator concat(Iterator... inputs) { + @SafeVarargs + public static Iterator concat(Iterator... inputs) { return concatNoDefensiveCopy(Arrays.copyOf(inputs, inputs.length)); } @@ -542,12 +567,14 @@ public static Iterator concat(Iterator... inputs) { * supports it. The methods of the returned iterator may throw {@code NullPointerException} if any * of the input iterators is null. */ - public static Iterator concat(Iterator> inputs) { - return new ConcatenatedIterator(inputs); + public static Iterator concat( + Iterator> inputs) { + return new ConcatenatedIterator<>(inputs); } /** Concats a varargs array of iterators without making a defensive copy of the array. */ - static Iterator concatNoDefensiveCopy(Iterator... inputs) { + static Iterator concatNoDefensiveCopy( + Iterator... inputs) { for (Iterator input : checkNotNull(inputs)) { checkNotNull(input); } @@ -562,13 +589,18 @@ static Iterator concatNoDefensiveCopy(Iterator... inputs) { * *

    The returned lists implement {@link java.util.RandomAccess}. * + *

    Note: The current implementation eagerly allocates storage for {@code size} elements. + * As a consequence, passing values like {@code Integer.MAX_VALUE} can lead to {@link + * OutOfMemoryError}. + * * @param iterator the iterator to return a partitioned view of * @param size the desired size of each partition (the last may be smaller) * @return an iterator of immutable lists containing the elements of {@code iterator} divided into * partitions * @throws IllegalArgumentException if {@code size} is nonpositive */ - public static UnmodifiableIterator> partition(Iterator iterator, int size) { + public static UnmodifiableIterator> partition( + Iterator iterator, int size) { return partitionImpl(iterator, size, false); } @@ -586,26 +618,28 @@ public static UnmodifiableIterator> partition(Iterator iterator, * partitions (the final iterable may have trailing null elements) * @throws IllegalArgumentException if {@code size} is nonpositive */ - public static UnmodifiableIterator> paddedPartition(Iterator iterator, int size) { + public static + UnmodifiableIterator> paddedPartition(Iterator iterator, int size) { return partitionImpl(iterator, size, true); } - private static UnmodifiableIterator> partitionImpl( - final Iterator iterator, final int size, final boolean pad) { + private static UnmodifiableIterator> partitionImpl( + Iterator iterator, int size, boolean pad) { checkNotNull(iterator); checkArgument(size > 0); - return new UnmodifiableIterator>() { + return new UnmodifiableIterator>() { @Override public boolean hasNext() { return iterator.hasNext(); } @Override - public List next() { + public List<@Nullable T> next() { if (!hasNext()) { throw new NoSuchElementException(); } - Object[] array = new Object[size]; + @SuppressWarnings("unchecked") // we only put Ts in it + @Nullable T[] array = (@Nullable T[]) new Object[size]; int count = 0; for (; count < size && iterator.hasNext(); count++) { array[count] = iterator.next(); @@ -614,9 +648,13 @@ public List next() { array[i] = null; // for GWT } - @SuppressWarnings("unchecked") // we only put Ts in it - List list = Collections.unmodifiableList((List) Arrays.asList(array)); - return (pad || count == size) ? list : list.subList(0, count); + List<@Nullable T> list = unmodifiableList(asList(array)); + // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker. + if (pad || count == size) { + return list; + } else { + return list.subList(0, count); + } } }; } @@ -625,13 +663,13 @@ public List next() { * Returns a view of {@code unfiltered} containing all elements that satisfy the input predicate * {@code retainIfTrue}. */ - public static UnmodifiableIterator filter( - final Iterator unfiltered, final Predicate retainIfTrue) { + public static UnmodifiableIterator filter( + Iterator unfiltered, Predicate retainIfTrue) { checkNotNull(unfiltered); checkNotNull(retainIfTrue); return new AbstractIterator() { @Override - protected T computeNext() { + protected @Nullable T computeNext() { while (unfiltered.hasNext()) { T element = unfiltered.next(); if (retainIfTrue.apply(element)) { @@ -657,7 +695,8 @@ public static UnmodifiableIterator filter(Iterator unfiltered, Class boolean any(Iterator iterator, Predicate predicate) { + public static boolean any( + Iterator iterator, Predicate predicate) { return indexOf(iterator, predicate) != -1; } @@ -665,7 +704,8 @@ public static boolean any(Iterator iterator, Predicate predica * Returns {@code true} if every element returned by {@code iterator} satisfies the given * predicate. If {@code iterator} is empty, {@code true} is returned. */ - public static boolean all(Iterator iterator, Predicate predicate) { + public static boolean all( + Iterator iterator, Predicate predicate) { checkNotNull(predicate); while (iterator.hasNext()) { T element = iterator.next(); @@ -685,7 +725,9 @@ public static boolean all(Iterator iterator, Predicate predica * * @throws NoSuchElementException if no element in {@code iterator} matches the given predicate */ - public static T find(Iterator iterator, Predicate predicate) { + @ParametricNullness + public static T find( + Iterator iterator, Predicate predicate) { checkNotNull(iterator); checkNotNull(predicate); while (iterator.hasNext()) { @@ -705,11 +747,9 @@ public static T find(Iterator iterator, Predicate predicate) { * * @since 7.0 */ - @NullableDecl - public static T find( - Iterator iterator, - Predicate predicate, - @NullableDecl T defaultValue) { + // For discussion of this signature, see the corresponding overload of *Iterables*.find. + public static @Nullable T find( + Iterator iterator, Predicate predicate, @Nullable T defaultValue) { checkNotNull(iterator); checkNotNull(predicate); while (iterator.hasNext()) { @@ -758,7 +798,8 @@ public static Optional tryFind(Iterator iterator, Predicate * * @since 2.0 */ - public static int indexOf(Iterator iterator, Predicate predicate) { + public static int indexOf( + Iterator iterator, Predicate predicate) { checkNotNull(predicate, "predicate"); for (int i = 0; iterator.hasNext(); i++) { T current = iterator.next(); @@ -777,12 +818,13 @@ public static int indexOf(Iterator iterator, Predicate predica * successful {@code remove()} call, {@code fromIterator} no longer contains the corresponding * element. */ - public static Iterator transform( - final Iterator fromIterator, final Function function) { + public static Iterator transform( + Iterator fromIterator, Function function) { checkNotNull(function); return new TransformedIterator(fromIterator) { + @ParametricNullness @Override - T transform(F from) { + T transform(@ParametricNullness F from) { return function.apply(from); } }; @@ -797,7 +839,8 @@ T transform(F from) { * @throws IndexOutOfBoundsException if {@code position} is negative or greater than or equal to * the number of elements remaining in {@code iterator} */ - public static T get(Iterator iterator, int position) { + @ParametricNullness + public static T get(Iterator iterator, int position) { checkNonnegative(position); int skipped = advance(iterator, position); if (!iterator.hasNext()) { @@ -823,9 +866,9 @@ public static T get(Iterator iterator, int position) { * @throws IndexOutOfBoundsException if {@code position} is negative * @since 4.0 */ - @NullableDecl - public static T get( - Iterator iterator, int position, @NullableDecl T defaultValue) { + @ParametricNullness + public static T get( + Iterator iterator, int position, @ParametricNullness T defaultValue) { checkNonnegative(position); advance(iterator, position); return getNext(iterator, defaultValue); @@ -845,8 +888,9 @@ static void checkNonnegative(int position) { * @return the next element of {@code iterator} or the default value * @since 7.0 */ - @NullableDecl - public static T getNext(Iterator iterator, @NullableDecl T defaultValue) { + @ParametricNullness + public static T getNext( + Iterator iterator, @ParametricNullness T defaultValue) { return iterator.hasNext() ? iterator.next() : defaultValue; } @@ -856,7 +900,8 @@ public static T getNext(Iterator iterator, @NullableDecl T defa * @return the last element of {@code iterator} * @throws NoSuchElementException if the iterator is empty */ - public static T getLast(Iterator iterator) { + @ParametricNullness + public static T getLast(Iterator iterator) { while (true) { T current = iterator.next(); if (!iterator.hasNext()) { @@ -873,8 +918,9 @@ public static T getLast(Iterator iterator) { * @return the last element of {@code iterator} * @since 3.0 */ - @NullableDecl - public static T getLast(Iterator iterator, @NullableDecl T defaultValue) { + @ParametricNullness + public static T getLast( + Iterator iterator, @ParametricNullness T defaultValue) { return iterator.hasNext() ? getLast(iterator) : defaultValue; } @@ -907,7 +953,8 @@ public static int advance(Iterator iterator, int numberToAdvance) { * @throws IllegalArgumentException if {@code limitSize} is negative * @since 3.0 */ - public static Iterator limit(final Iterator iterator, final int limitSize) { + public static Iterator limit( + Iterator iterator, int limitSize) { checkNotNull(iterator); checkArgument(limitSize >= 0, "limit is negative"); return new Iterator() { @@ -919,6 +966,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -939,13 +987,14 @@ public void remove() { * {@code iterator} as it is returned. * *

    The provided iterator must support {@link Iterator#remove()} or else the returned iterator - * will fail on the first call to {@code next}. + * will fail on the first call to {@code next}. The returned {@link Iterator} is also not + * thread-safe. * * @param iterator the iterator to remove and return elements from * @return an iterator that removes and returns elements from the supplied iterator * @since 2.0 */ - public static Iterator consumingIterator(final Iterator iterator) { + public static Iterator consumingIterator(Iterator iterator) { checkNotNull(iterator); return new UnmodifiableIterator() { @Override @@ -954,6 +1003,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { T next = iterator.next(); iterator.remove(); @@ -971,8 +1021,7 @@ public String toString() { * Deletes and returns the next value from the iterator, or returns {@code null} if there is no * such value. */ - @NullableDecl - static T pollNext(Iterator iterator) { + static @Nullable T pollNext(Iterator iterator) { if (iterator.hasNext()) { T result = iterator.next(); iterator.remove(); @@ -1004,46 +1053,41 @@ static void clear(Iterator iterator) { * {@link ImmutableList#copyOf(Object[])}}, or {@link ImmutableList#of}. */ @SafeVarargs - public static UnmodifiableIterator forArray(final T... array) { - return forArray(array, 0, array.length, 0); + public static UnmodifiableIterator forArray(T... array) { + return forArrayWithPosition(array, 0); } /** - * Returns a list iterator containing the elements in the specified range of {@code array} in - * order, starting at the specified index. + * Returns a list iterator containing the elements in the specified {@code array} in order, + * starting at the specified {@code position}. * *

    The {@code Iterable} equivalent of this method is {@code - * Arrays.asList(array).subList(offset, offset + length).listIterator(index)}. + * Arrays.asList(array).listIterator(position)}. */ - static UnmodifiableListIterator forArray( - final T[] array, final int offset, int length, int index) { - checkArgument(length >= 0); - int end = offset + length; - - // Technically we should give a slightly more descriptive error on overflow - Preconditions.checkPositionIndexes(offset, end, array.length); - Preconditions.checkPositionIndex(index, length); - if (length == 0) { + static UnmodifiableListIterator forArrayWithPosition( + T[] array, int position) { + if (array.length == 0) { + Preconditions.checkPositionIndex(position, array.length); // otherwise checked in ArrayItr return emptyListIterator(); } - return new ArrayItr(array, offset, length, index); + return new ArrayItr<>(array, position); } - private static final class ArrayItr extends AbstractIndexedListIterator { - static final UnmodifiableListIterator EMPTY = new ArrayItr<>(new Object[0], 0, 0, 0); + private static final class ArrayItr + extends AbstractIndexedListIterator { + static final UnmodifiableListIterator EMPTY = new ArrayItr<>(new Object[0], 0); private final T[] array; - private final int offset; - ArrayItr(T[] array, int offset, int length, int index) { - super(length, index); + ArrayItr(T[] array, int position) { + super(array.length, position); this.array = array; - this.offset = offset; } @Override + @ParametricNullness protected T get(int index) { - return array[offset + index]; + return array[index]; } } @@ -1052,24 +1096,34 @@ protected T get(int index) { * *

    The {@link Iterable} equivalent of this method is {@link Collections#singleton}. */ - public static UnmodifiableIterator singletonIterator(@NullableDecl final T value) { - return new UnmodifiableIterator() { - boolean done; + public static UnmodifiableIterator singletonIterator( + @ParametricNullness T value) { + return new SingletonIterator<>(value); + } - @Override - public boolean hasNext() { - return !done; - } + private static final class SingletonIterator + extends UnmodifiableIterator { + private final T value; + private boolean done; - @Override - public T next() { - if (done) { - throw new NoSuchElementException(); - } - done = true; - return value; + SingletonIterator(T value) { + this.value = value; + } + + @Override + public boolean hasNext() { + return !done; + } + + @Override + @ParametricNullness + public T next() { + if (done) { + throw new NoSuchElementException(); } - }; + done = true; + return value; + } } /** @@ -1082,7 +1136,8 @@ public T next() { *

    Java 9 users: use {@code enumeration.asIterator()} instead, unless it is important to * return an {@code UnmodifiableIterator} instead of a plain {@code Iterator}. */ - public static UnmodifiableIterator forEnumeration(final Enumeration enumeration) { + public static UnmodifiableIterator forEnumeration( + Enumeration enumeration) { checkNotNull(enumeration); return new UnmodifiableIterator() { @Override @@ -1091,6 +1146,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { return enumeration.nextElement(); } @@ -1103,7 +1159,9 @@ public T next() { *

    The {@code Iterable} equivalent of this method is either {@link Collections#enumeration} (if * you have a {@link Collection}), or {@code Iterators.asEnumeration(collection.iterator())}. */ - public static Enumeration asEnumeration(final Iterator iterator) { + // This is an adapter for cases in which users do need an Enumeration for whatever reason. + @SuppressWarnings("JdkObsolete") + public static Enumeration asEnumeration(Iterator iterator) { checkNotNull(iterator); return new Enumeration() { @Override @@ -1112,6 +1170,7 @@ public boolean hasMoreElements() { } @Override + @ParametricNullness public T nextElement() { return iterator.next(); } @@ -1119,13 +1178,13 @@ public T nextElement() { } /** Implementation of PeekingIterator that avoids peeking unless necessary. */ - private static class PeekingImpl implements PeekingIterator { + private static final class PeekingImpl implements PeekingIterator { private final Iterator iterator; private boolean hasPeeked; - @NullableDecl private E peekedElement; + private @Nullable E peekedElement; - public PeekingImpl(Iterator iterator) { + PeekingImpl(Iterator iterator) { this.iterator = checkNotNull(iterator); } @@ -1135,11 +1194,13 @@ public boolean hasNext() { } @Override + @ParametricNullness public E next() { if (!hasPeeked) { return iterator.next(); } - E result = peekedElement; + // The cast is safe because of the hasPeeked check. + E result = uncheckedCastNullableTToT(peekedElement); hasPeeked = false; peekedElement = null; return result; @@ -1152,12 +1213,14 @@ public void remove() { } @Override + @ParametricNullness public E peek() { if (!hasPeeked) { peekedElement = iterator.next(); hasPeeked = true; } - return peekedElement; + // The cast is safe because of the hasPeeked check. + return uncheckedCastNullableTToT(peekedElement); } } @@ -1168,13 +1231,13 @@ public E peek() { * iteration, and hence return the same object each time. A subsequent call to {@code next} is * guaranteed to return the same object again. For example: * - *

    {@code
    +   * {@snippet :
        * PeekingIterator peekingIterator =
        *     Iterators.peekingIterator(Iterators.forArray("a", "b"));
        * String a1 = peekingIterator.peek(); // returns "a"
        * String a2 = peekingIterator.peek(); // also returns "a"
        * String a3 = peekingIterator.next(); // also returns "a"
    -   * }
    + * } * *

    Any structural changes to the underlying iteration (aside from those performed by the * iterator's own {@link PeekingIterator#remove()} method) will leave the iterator in an undefined @@ -1197,7 +1260,8 @@ public E peek() { * @return a peeking iterator backed by that iterator. Apart from the additional {@link * PeekingIterator#peek()} method, this iterator behaves exactly the same as {@code iterator}. */ - public static PeekingIterator peekingIterator(Iterator iterator) { + public static PeekingIterator peekingIterator( + Iterator iterator) { if (iterator instanceof PeekingImpl) { // Safe to cast to because PeekingImpl only uses T // covariantly (and cannot be subclassed to add non-covariant uses). @@ -1205,7 +1269,7 @@ public static PeekingIterator peekingIterator(Iterator itera PeekingImpl peeking = (PeekingImpl) iterator; return peeking; } - return new PeekingImpl(iterator); + return new PeekingImpl<>(iterator); } /** @@ -1214,8 +1278,12 @@ public static PeekingIterator peekingIterator(Iterator itera * @deprecated no need to use this * @since 10.0 */ + @InlineMe( + replacement = "checkNotNull(iterator)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated - public static PeekingIterator peekingIterator(PeekingIterator iterator) { + public static PeekingIterator peekingIterator( + PeekingIterator iterator) { return checkNotNull(iterator); } @@ -1226,18 +1294,18 @@ public static PeekingIterator peekingIterator(PeekingIterator iterator *

    Callers must ensure that the source {@code iterators} are in non-descending order as this * method does not sort its input. * - *

    For any equivalent elements across all {@code iterators}, it is undefined which element is - * returned first. + *

    For any equivalent elements across all {@code iterators}, elements are returned in the order + * of their source iterators. That is, if element A from iterator 1 and element B from iterator 2 + * compare as equal, A will be returned before B if iterator 1 was passed before iterator 2. * * @since 11.0 */ - @Beta - public static UnmodifiableIterator mergeSorted( + public static UnmodifiableIterator mergeSorted( Iterable> iterators, Comparator comparator) { checkNotNull(iterators, "iterators"); checkNotNull(comparator, "comparator"); - return new MergingIterator(iterators, comparator); + return new MergingIterator<>(iterators, comparator); } /** @@ -1249,27 +1317,40 @@ public static UnmodifiableIterator mergeSorted( * iterators. (Retrieving all elements takes approximately O(N*log(M)) time, where N is the total * number of elements.) */ - private static class MergingIterator extends UnmodifiableIterator { - final Queue> queue; + private static final class MergingIterator + extends UnmodifiableIterator { - public MergingIterator( - Iterable> iterators, - final Comparator itemComparator) { + // Wrapper class to track insertion order for stable sorting + private static class IndexedIterator { + final PeekingIterator iterator; + final int index; + + IndexedIterator(PeekingIterator iterator, int index) { + this.iterator = iterator; + this.index = index; + } + } + + final Queue> queue; + + MergingIterator( + Iterable> iterators, Comparator itemComparator) { // A comparator that's used by the heap, allowing the heap - // to be sorted based on the top of each iterator. - Comparator> heapComparator = - new Comparator>() { - @Override - public int compare(PeekingIterator o1, PeekingIterator o2) { - return itemComparator.compare(o1.peek(), o2.peek()); - } - }; + // to be sorted based on the top of each iterator, with insertion order as tiebreaker + Comparator> heapComparator = + (o1, o2) -> + ComparisonChain.start() + .compare(o1.iterator.peek(), o2.iterator.peek(), itemComparator) + // When elements are equal, use insertion order to maintain stability + .compare(o1.index, o2.index) + .result(); queue = new PriorityQueue<>(2, heapComparator); + int index = 0; for (Iterator iterator : iterators) { if (iterator.hasNext()) { - queue.add(Iterators.peekingIterator(iterator)); + queue.add(new IndexedIterator<>(peekingIterator(iterator), index++)); } } } @@ -1280,19 +1361,22 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { - PeekingIterator nextIter = queue.remove(); + IndexedIterator nextIndexed = queue.remove(); + PeekingIterator nextIter = nextIndexed.iterator; T next = nextIter.next(); if (nextIter.hasNext()) { - queue.add(nextIter); + queue.add(nextIndexed); } return next; } } - private static class ConcatenatedIterator implements Iterator { + private static final class ConcatenatedIterator + implements Iterator { /* The last iterator to return an element. Calls to remove() go to this iterator. */ - @NullableDecl private Iterator toRemove; + private @Nullable Iterator toRemove; /* The iterator currently returning elements. */ private Iterator iterator; @@ -1304,10 +1388,10 @@ private static class ConcatenatedIterator implements Iterator { * operation O(1). */ - private Iterator> topMetaIterator; + private @Nullable Iterator> topMetaIterator; // Only becomes nonnull if we encounter nested concatenations. - @NullableDecl private Deque>> metaIterators; + private @Nullable Deque>> metaIterators; ConcatenatedIterator(Iterator> metaIterator) { iterator = emptyIterator(); @@ -1315,8 +1399,7 @@ private static class ConcatenatedIterator implements Iterator { } // Returns a nonempty meta-iterator or, if all meta-iterators are empty, null. - @NullableDecl - private Iterator> getTopMetaIterator() { + private @Nullable Iterator> getTopMetaIterator() { while (topMetaIterator == null || !topMetaIterator.hasNext()) { if (metaIterators != null && !metaIterators.isEmpty()) { topMetaIterator = metaIterators.removeFirst(); @@ -1366,6 +1449,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public T next() { if (hasNext()) { toRemove = iterator; @@ -1377,14 +1461,11 @@ public T next() { @Override public void remove() { - CollectPreconditions.checkRemove(toRemove != null); + if (toRemove == null) { + throw new IllegalStateException("no calls to next() since the last call to remove()"); + } toRemove.remove(); toRemove = null; } } - - /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ - static ListIterator cast(Iterator iterator) { - return (ListIterator) iterator; - } } diff --git a/android/guava/src/com/google/common/collect/LexicographicalOrdering.java b/android/guava/src/com/google/common/collect/LexicographicalOrdering.java index 0e6c19652abf..6d96e982b6be 100644 --- a/android/guava/src/com/google/common/collect/LexicographicalOrdering.java +++ b/android/guava/src/com/google/common/collect/LexicographicalOrdering.java @@ -17,14 +17,17 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.Comparator; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** An ordering which sorts iterables by comparing corresponding elements pairwise. */ -@GwtCompatible(serializable = true) -final class LexicographicalOrdering extends Ordering> implements Serializable { +@GwtCompatible +final class LexicographicalOrdering extends Ordering> + implements Serializable { final Comparator elementOrder; LexicographicalOrdering(Comparator elementOrder) { @@ -51,7 +54,7 @@ public int compare(Iterable leftIterable, Iterable rightIterable) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -72,5 +75,5 @@ public String toString() { return elementOrder + ".lexicographical()"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/LinkedHashMultimap.java b/android/guava/src/com/google/common/collect/LinkedHashMultimap.java index 361661705d55..a55bd41febd0 100644 --- a/android/guava/src/com/google/common/collect/LinkedHashMultimap.java +++ b/android/guava/src/com/google/common/collect/LinkedHashMultimap.java @@ -16,18 +16,20 @@ package com.google.common.collect; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.CollectPreconditions.checkNonnegative; -import static com.google.common.collect.CollectPreconditions.checkRemove; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Objects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.j2objc.annotations.WeakOuter; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Arrays; import java.util.Collection; import java.util.ConcurrentModificationException; @@ -35,8 +37,9 @@ import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@code Multimap} that does not allow duplicate key-value entries and that @@ -65,20 +68,24 @@ * read operations will work correctly. To allow concurrent update operations, wrap your multimap * with a call to {@link Multimaps#synchronizedSetMultimap}. * + *

    Warning: Do not modify either a key or a value of a {@code LinkedHashMultimap} + * in a way that affects its {@link Object#equals} behavior. Undefined behavior and bugs will + * result. + * *

    See the Guava User Guide article on {@code - * Multimap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}. * * @author Jared Levy * @author Louis Wasserman * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) -public final class LinkedHashMultimap - extends LinkedHashMultimapGwtSerializationDependencies { +@GwtCompatible +public final class LinkedHashMultimap + extends AbstractSetMultimap { /** Creates a new, empty {@code LinkedHashMultimap} with the default initial capacities. */ - public static LinkedHashMultimap create() { + public static + LinkedHashMultimap create() { return new LinkedHashMultimap<>(DEFAULT_KEY_CAPACITY, DEFAULT_VALUE_SET_CAPACITY); } @@ -91,7 +98,8 @@ public static LinkedHashMultimap create() { * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is * negative */ - public static LinkedHashMultimap create(int expectedKeys, int expectedValuesPerKey) { + public static + LinkedHashMultimap create(int expectedKeys, int expectedValuesPerKey) { return new LinkedHashMultimap<>( Maps.capacity(expectedKeys), Maps.capacity(expectedValuesPerKey)); } @@ -104,41 +112,13 @@ public static LinkedHashMultimap create(int expectedKeys, int expec * * @param multimap the multimap whose contents are copied to this multimap */ - public static LinkedHashMultimap create( - Multimap multimap) { + public static + LinkedHashMultimap create(Multimap multimap) { LinkedHashMultimap result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY); result.putAll(multimap); return result; } - private interface ValueSetLink { - ValueSetLink getPredecessorInValueSet(); - - ValueSetLink getSuccessorInValueSet(); - - void setPredecessorInValueSet(ValueSetLink entry); - - void setSuccessorInValueSet(ValueSetLink entry); - } - - private static void succeedsInValueSet(ValueSetLink pred, ValueSetLink succ) { - pred.setSuccessorInValueSet(succ); - succ.setPredecessorInValueSet(pred); - } - - private static void succeedsInMultimap(ValueEntry pred, ValueEntry succ) { - pred.setSuccessorInMultimap(succ); - succ.setPredecessorInMultimap(pred); - } - - private static void deleteFromValueSet(ValueSetLink entry) { - succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet()); - } - - private static void deleteFromMultimap(ValueEntry entry) { - succeedsInMultimap(entry.getPredecessorInMultimap(), entry.getSuccessorInMultimap()); - } - /** * LinkedHashMultimap entries are in no less than three coexisting linked lists: a bucket in the * hash table for a {@code Set} associated with a key, the linked list of insertion-ordered @@ -146,65 +126,30 @@ private static void deleteFromMultimap(ValueEntry entry) { * whole. */ @VisibleForTesting - static final class ValueEntry extends ImmutableEntry implements ValueSetLink { + static final class ValueEntry + extends SimpleImmutableEntry { final int smearedValueHash; - @NullableDecl ValueEntry nextInValueBucket; + @Nullable ValueEntry nextInValueBucket; - @NullableDecl ValueSetLink predecessorInValueSet; - @NullableDecl ValueSetLink successorInValueSet; + private @Nullable ValueEntry predecessorInValueSet; + private @Nullable ValueEntry successorInValueSet; - @NullableDecl ValueEntry predecessorInMultimap; - @NullableDecl ValueEntry successorInMultimap; + private @Nullable ValueEntry predecessorInMultimap; + private @Nullable ValueEntry successorInMultimap; ValueEntry( - @NullableDecl K key, - @NullableDecl V value, + @ParametricNullness K key, + @ParametricNullness V value, int smearedValueHash, - @NullableDecl ValueEntry nextInValueBucket) { + @Nullable ValueEntry nextInValueBucket) { super(key, value); this.smearedValueHash = smearedValueHash; this.nextInValueBucket = nextInValueBucket; } - boolean matchesValue(@NullableDecl Object v, int smearedVHash) { - return smearedValueHash == smearedVHash && Objects.equal(getValue(), v); - } - - @Override - public ValueSetLink getPredecessorInValueSet() { - return predecessorInValueSet; - } - - @Override - public ValueSetLink getSuccessorInValueSet() { - return successorInValueSet; - } - - @Override - public void setPredecessorInValueSet(ValueSetLink entry) { - predecessorInValueSet = entry; - } - - @Override - public void setSuccessorInValueSet(ValueSetLink entry) { - successorInValueSet = entry; - } - - public ValueEntry getPredecessorInMultimap() { - return predecessorInMultimap; - } - - public ValueEntry getSuccessorInMultimap() { - return successorInMultimap; - } - - public void setSuccessorInMultimap(ValueEntry multimapSuccessor) { - this.successorInMultimap = multimapSuccessor; - } - - public void setPredecessorInMultimap(ValueEntry multimapPredecessor) { - this.predecessorInMultimap = multimapPredecessor; + boolean matchesValue(@Nullable Object v, int smearedVHash) { + return smearedValueHash == smearedVHash && Objects.equals(getValue(), v); } } @@ -212,16 +157,15 @@ public void setPredecessorInMultimap(ValueEntry multimapPredecessor) { private static final int DEFAULT_VALUE_SET_CAPACITY = 2; @VisibleForTesting static final double VALUE_SET_LOAD_FACTOR = 1.0; - @VisibleForTesting transient int valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; - private transient ValueEntry multimapHeaderEntry; + @VisibleForTesting transient int valueSetCapacity; + private transient MultimapIterationChain multimapIterationChain = + new MultimapIterationChain<>(); private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) { - super(Platform.>newLinkedHashMapWithExpectedSize(keyCapacity)); + super(Platform.newLinkedHashMapWithExpectedSize(keyCapacity)); checkNonnegative(valueSetCapacity, "expectedValuesPerKey"); this.valueSetCapacity = valueSetCapacity; - this.multimapHeaderEntry = new ValueEntry<>(null, null, 0, null); - succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); } /** @@ -246,7 +190,7 @@ Set createCollection() { * @return a new decorated set containing a collection of values for one key */ @Override - Collection createCollection(K key) { + Collection createCollection(@ParametricNullness K key) { return new ValueSet(key, valueSetCapacity); } @@ -259,7 +203,7 @@ Collection createCollection(K key) { */ @CanIgnoreReturnValue @Override - public Set replaceValues(@NullableDecl K key, Iterable values) { + public Set replaceValues(@ParametricNullness K key, Iterable values) { return super.replaceValues(key, values); } @@ -308,63 +252,62 @@ public Collection values() { @VisibleForTesting @WeakOuter - final class ValueSet extends Sets.ImprovedAbstractSet implements ValueSetLink { + final class ValueSet extends Sets.ImprovedAbstractSet { /* * We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory * consumption. */ - private final K key; - @VisibleForTesting ValueEntry[] hashTable; + @ParametricNullness private final K key; + @VisibleForTesting @Nullable ValueEntry[] hashTable; private int size = 0; private int modCount = 0; - // We use the set object itself as the end of the linked list, avoiding an unnecessary - // entry object per key. - private ValueSetLink firstEntry; - private ValueSetLink lastEntry; + private @Nullable ValueEntry firstEntry; + private @Nullable ValueEntry lastEntry; - ValueSet(K key, int expectedValues) { + ValueSet(@ParametricNullness K key, int expectedValues) { this.key = key; - this.firstEntry = this; - this.lastEntry = this; // Round expected values up to a power of 2 to get the table size. int tableSize = Hashing.closedTableSize(expectedValues, VALUE_SET_LOAD_FACTOR); - @SuppressWarnings("unchecked") - ValueEntry[] hashTable = new ValueEntry[tableSize]; + @SuppressWarnings({"rawtypes", "unchecked"}) + @Nullable ValueEntry[] hashTable = new @Nullable ValueEntry[tableSize]; this.hashTable = hashTable; } - private int mask() { - return hashTable.length - 1; - } - - @Override - public ValueSetLink getPredecessorInValueSet() { - return lastEntry; + private void succeedsInValueSet( + @Nullable ValueEntry pred, @Nullable ValueEntry succ) { + if (pred == null) { + firstEntry = succ; + } else { + pred.successorInValueSet = succ; + } + if (succ == null) { + lastEntry = pred; + } else { + succ.predecessorInValueSet = pred; + } } - @Override - public ValueSetLink getSuccessorInValueSet() { - return firstEntry; + private void deleteFromValueSet(ValueEntry entry) { + succeedsInValueSet(entry.predecessorInValueSet, entry.successorInValueSet); } - @Override - public void setPredecessorInValueSet(ValueSetLink entry) { - lastEntry = entry; + private void appendToValueSet(ValueEntry newEntry) { + succeedsInValueSet(lastEntry, newEntry); + lastEntry = newEntry; } - @Override - public void setSuccessorInValueSet(ValueSetLink entry) { - firstEntry = entry; + private int mask() { + return hashTable.length - 1; } @Override public Iterator iterator() { return new Iterator() { - ValueSetLink nextEntry = firstEntry; - @NullableDecl ValueEntry toRemove; + @Nullable ValueEntry nextEntry = firstEntry; + @Nullable ValueEntry toRemove; int expectedModCount = modCount; private void checkForComodification() { @@ -376,25 +319,27 @@ private void checkForComodification() { @Override public boolean hasNext() { checkForComodification(); - return nextEntry != ValueSet.this; + return nextEntry != null; } @Override + @ParametricNullness public V next() { - if (!hasNext()) { + checkForComodification(); + ValueEntry entry = nextEntry; + if (entry == null) { throw new NoSuchElementException(); } - ValueEntry entry = (ValueEntry) nextEntry; V result = entry.getValue(); toRemove = entry; - nextEntry = entry.getSuccessorInValueSet(); + nextEntry = entry.successorInValueSet; return result; } @Override public void remove() { checkForComodification(); - checkRemove(toRemove != null); + checkState(toRemove != null, "no calls to next() since the last call to remove()"); ValueSet.this.remove(toRemove.getValue()); expectedModCount = modCount; toRemove = null; @@ -408,7 +353,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { int smearedHash = Hashing.smearedHash(o); for (ValueEntry entry = hashTable[smearedHash & mask()]; entry != null; @@ -421,7 +366,7 @@ public boolean contains(@NullableDecl Object o) { } @Override - public boolean add(@NullableDecl V value) { + public boolean add(@ParametricNullness V value) { int smearedHash = Hashing.smearedHash(value); int bucket = smearedHash & mask(); ValueEntry rowHead = hashTable[bucket]; @@ -432,10 +377,8 @@ public boolean add(@NullableDecl V value) { } ValueEntry newEntry = new ValueEntry<>(key, value, smearedHash, rowHead); - succeedsInValueSet(lastEntry, newEntry); - succeedsInValueSet(newEntry, this); - succeedsInMultimap(multimapHeaderEntry.getPredecessorInMultimap(), newEntry); - succeedsInMultimap(newEntry, multimapHeaderEntry); + appendToValueSet(newEntry); + multimapIterationChain.append(newEntry); hashTable[bucket] = newEntry; size++; modCount++; @@ -446,23 +389,23 @@ public boolean add(@NullableDecl V value) { private void rehashIfNecessary() { if (Hashing.needsResizing(size, hashTable.length, VALUE_SET_LOAD_FACTOR)) { @SuppressWarnings("unchecked") - ValueEntry[] hashTable = new ValueEntry[this.hashTable.length * 2]; + ValueEntry[] hashTable = + (ValueEntry[]) new ValueEntry[this.hashTable.length * 2]; this.hashTable = hashTable; int mask = hashTable.length - 1; - for (ValueSetLink entry = firstEntry; - entry != this; - entry = entry.getSuccessorInValueSet()) { - ValueEntry valueEntry = (ValueEntry) entry; - int bucket = valueEntry.smearedValueHash & mask; - valueEntry.nextInValueBucket = hashTable[bucket]; - hashTable[bucket] = valueEntry; + for (ValueEntry entry = firstEntry; + entry != null; + entry = entry.successorInValueSet) { + int bucket = entry.smearedValueHash & mask; + entry.nextInValueBucket = hashTable[bucket]; + hashTable[bucket] = entry; } } } @CanIgnoreReturnValue @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { int smearedHash = Hashing.smearedHash(o); int bucket = smearedHash & mask(); ValueEntry prev = null; @@ -477,7 +420,7 @@ public boolean remove(@NullableDecl Object o) { prev.nextInValueBucket = entry.nextInValueBucket; } deleteFromValueSet(entry); - deleteFromMultimap(entry); + multimapIterationChain.delete(entry); size--; modCount++; return true; @@ -490,13 +433,12 @@ public boolean remove(@NullableDecl Object o) { public void clear() { Arrays.fill(hashTable, null); size = 0; - for (ValueSetLink entry = firstEntry; - entry != this; - entry = entry.getSuccessorInValueSet()) { - ValueEntry valueEntry = (ValueEntry) entry; - deleteFromMultimap(valueEntry); + for (ValueEntry entry = firstEntry; entry != null; entry = entry.successorInValueSet) { + multimapIterationChain.delete(entry); + // TODO(cpovirk): Also clear *InValueSet (after reading next) and nextInValueBucket? } - succeedsInValueSet(this, this); + firstEntry = null; + lastEntry = null; modCount++; } } @@ -504,28 +446,28 @@ public void clear() { @Override Iterator> entryIterator() { return new Iterator>() { - ValueEntry nextEntry = multimapHeaderEntry.successorInMultimap; - @NullableDecl ValueEntry toRemove; + @Nullable ValueEntry nextEntry = multimapIterationChain.firstEntry; + @Nullable ValueEntry toRemove; @Override public boolean hasNext() { - return nextEntry != multimapHeaderEntry; + return nextEntry != null; } @Override public Entry next() { - if (!hasNext()) { + ValueEntry entry = nextEntry; + if (entry == null) { throw new NoSuchElementException(); } - ValueEntry result = nextEntry; - toRemove = result; - nextEntry = nextEntry.successorInMultimap; - return result; + toRemove = entry; + nextEntry = entry.successorInMultimap; + return entry; } @Override public void remove() { - checkRemove(toRemove != null); + checkState(toRemove != null, "no calls to next() since the last call to remove()"); LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue()); toRemove = null; } @@ -537,18 +479,13 @@ Iterator valueIterator() { return Maps.valueIterator(entryIterator()); } - @Override - public void clear() { - super.clear(); - succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); - } - /** * @serialData the expected values per key, the number of distinct keys, the number of entries, * and the entries in order */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(keySet().size()); for (K key : keySet()) { @@ -561,11 +498,11 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } } - @GwtIncompatible // java.io.ObjectInputStream - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - multimapHeaderEntry = new ValueEntry<>(null, null, 0, null); - succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + multimapIterationChain = new MultimapIterationChain<>(); valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; int distinctKeys = stream.readInt(); Map> map = Platform.newLinkedHashMapWithExpectedSize(12); @@ -580,11 +517,42 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo K key = (K) stream.readObject(); @SuppressWarnings("unchecked") V value = (V) stream.readObject(); - map.get(key).add(value); + /* + * requireNonNull is safe for a properly serialized multimap: We've already inserted a + * collection for each key that we expect. + */ + requireNonNull(map.get(key)).add(value); } setMap(map); } - @GwtIncompatible // java serialization not supported - private static final long serialVersionUID = 1; + private static final class MultimapIterationChain< + K extends @Nullable Object, V extends @Nullable Object> { + @Nullable ValueEntry firstEntry; + @Nullable ValueEntry lastEntry; + + void succeeds(@Nullable ValueEntry pred, @Nullable ValueEntry succ) { + if (pred == null) { + firstEntry = succ; + } else { + pred.successorInMultimap = succ; + } + if (succ == null) { + lastEntry = pred; + } else { + succ.predecessorInMultimap = pred; + } + } + + void delete(ValueEntry entry) { + succeeds(entry.predecessorInMultimap, entry.successorInMultimap); + } + + void append(ValueEntry newEntry) { + succeeds(lastEntry, newEntry); + lastEntry = newEntry; + } + } + + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } diff --git a/android/guava/src/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java b/android/guava/src/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java deleted file mode 100644 index bb4a2e490e45..000000000000 --- a/android/guava/src/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2016 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import com.google.common.annotations.GwtCompatible; -import java.util.Collection; -import java.util.Map; - -/** - * A dummy superclass to support GWT serialization of the element types of a {@link - * LinkedHashMultimap}. The GWT supersource for this class contains a field for each type. - * - *

    For details about this hack, see {@code GwtSerializationDependencies}, which takes the same - * approach but with a subclass rather than a superclass. - * - *

    TODO(cpovirk): Consider applying this subclass approach to our other types. - */ -@GwtCompatible(emulated = true) -abstract class LinkedHashMultimapGwtSerializationDependencies - extends AbstractSetMultimap { - LinkedHashMultimapGwtSerializationDependencies(Map> map) { - super(map); - } -} diff --git a/android/guava/src/com/google/common/collect/LinkedHashMultiset.java b/android/guava/src/com/google/common/collect/LinkedHashMultiset.java index 6f95d4d2dc4f..6b5240b42c7b 100644 --- a/android/guava/src/com/google/common/collect/LinkedHashMultiset.java +++ b/android/guava/src/com/google/common/collect/LinkedHashMultiset.java @@ -17,6 +17,7 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.Nullable; /** * A {@code Multiset} implementation with predictable iteration order. Its iterator orders elements @@ -26,18 +27,18 @@ * element will appear at the end of the iteration. * *

    See the Guava User Guide article on {@code - * Multiset}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}. * * @author Kevin Bourrillion * @author Jared Levy * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) -public final class LinkedHashMultiset extends AbstractMapBasedMultiset { +@GwtCompatible +public final class LinkedHashMultiset + extends AbstractMapBasedMultiset { /** Creates a new, empty {@code LinkedHashMultiset} using the default initial capacity. */ - public static LinkedHashMultiset create() { + public static LinkedHashMultiset create() { return create(ObjectCountHashMap.DEFAULT_SIZE); } @@ -48,8 +49,8 @@ public static LinkedHashMultiset create() { * @param distinctElements the expected number of distinct elements * @throws IllegalArgumentException if {@code distinctElements} is negative */ - public static LinkedHashMultiset create(int distinctElements) { - return new LinkedHashMultiset(distinctElements); + public static LinkedHashMultiset create(int distinctElements) { + return new LinkedHashMultiset<>(distinctElements); } /** @@ -59,7 +60,8 @@ public static LinkedHashMultiset create(int distinctElements) { * * @param elements the elements that the multiset should contain */ - public static LinkedHashMultiset create(Iterable elements) { + public static LinkedHashMultiset create( + Iterable elements) { LinkedHashMultiset multiset = create(Multisets.inferDistinctElements(elements)); Iterables.addAll(multiset, elements); return multiset; @@ -70,7 +72,9 @@ public static LinkedHashMultiset create(Iterable elements) { } @Override - void init(int distinctElements) { - backingMap = new ObjectCountLinkedHashMap<>(distinctElements); + ObjectCountHashMap newBackingMap(int distinctElements) { + return new ObjectCountLinkedHashMap<>(distinctElements); } + + // TODO(cpovirk): Should we have a serialVersionUID here? } diff --git a/android/guava/src/com/google/common/collect/LinkedListMultimap.java b/android/guava/src/com/google/common/collect/LinkedListMultimap.java index 3aa1efdabe36..af35da14a011 100644 --- a/android/guava/src/com/google/common/collect/LinkedListMultimap.java +++ b/android/guava/src/com/google/common/collect/LinkedListMultimap.java @@ -18,17 +18,20 @@ import static com.google.common.base.Preconditions.checkPositionIndex; import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.CollectPreconditions.checkRemove; import static java.util.Collections.unmodifiableList; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.j2objc.annotations.Weak; import com.google.j2objc.annotations.WeakOuter; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.AbstractMap.SimpleEntry; import java.util.AbstractSequentialList; import java.util.Collection; import java.util.ConcurrentModificationException; @@ -39,33 +42,33 @@ import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An implementation of {@code ListMultimap} that supports deterministic iteration order for both * keys and values. The iteration order is preserved across non-distinct key values. For example, * for the following multimap definition: * - *

    {@code
    + * {@snippet :
      * Multimap multimap = LinkedListMultimap.create();
      * multimap.put(key1, foo);
      * multimap.put(key2, bar);
      * multimap.put(key1, baz);
    - * }
    + * } * * ... the iteration order for {@link #keys()} is {@code [key1, key2, key1]}, and similarly for * {@link #entries()}. Unlike {@link LinkedHashMultimap}, the iteration order is kept consistent * between keys, entries and values. For example, calling: * - *
    {@code
    + * {@snippet :
      * multimap.remove(key1, foo);
    - * }
    + * } * *

    changes the entries iteration order to {@code [key2=bar, key1=baz]} and the key iteration * order to {@code [key2, key1]}. The {@link #entries()} iterator returns mutable map entries, and * {@link #replaceValues} attempts to preserve iteration order as much as possible. * - *

    The collections returned by {@link #keySet()} and {@link #asMap} iterate through the keys in + *

    The collections returned by {@link #keySet()} and {@link #asMap()} iterate through the keys in * the order they were first added to the multimap. Similarly, {@link #get}, {@link #removeAll}, and * {@link #replaceValues} return collections that iterate through the values in the order they were * added. The collections generated by {@link #entries()}, {@link #keys()}, and {@link #values} @@ -74,8 +77,8 @@ *

    The {@link #values()} and {@link #entries()} methods both return a {@code List}, instead of * the {@code Collection} specified by the {@link ListMultimap} interface. * - *

    The methods {@link #get}, {@link #keySet()}, {@link #keys()}, {@link #values}, {@link - * #entries()}, and {@link #asMap} return collections that are views of the multimap. If the + *

    The methods {@link #get}, {@link #keySet()}, {@link #keys()}, {@link #values()}, {@link + * #entries()}, and {@link #asMap()} return collections that are views of the multimap. If the * multimap is modified while an iteration over any of those collections is in progress, except * through the iterator's methods, the results of the iteration are undefined. * @@ -87,15 +90,15 @@ * with a call to {@link Multimaps#synchronizedListMultimap}. * *

    See the Guava User Guide article on {@code - * Multimap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}. * * @author Mike Bostock * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) -public class LinkedListMultimap extends AbstractMultimap - implements ListMultimap, Serializable { +@GwtCompatible +@SuppressWarnings("WrongCommentType") // false positive +public class LinkedListMultimap + extends AbstractMultimap implements ListMultimap, Serializable { /* * Order is maintained using a linked list containing all key-value pairs. In * addition, a series of disjoint linked lists of "siblings", each containing @@ -103,38 +106,19 @@ public class LinkedListMultimap extends AbstractMultimap * ValueForKeyIterator} in constant time. */ - private static final class Node extends AbstractMapEntry { - @NullableDecl final K key; - @NullableDecl V value; - @NullableDecl Node next; // the next node (with any key) - @NullableDecl Node previous; // the previous node (with any key) - @NullableDecl Node nextSibling; // the next node with the same key - @NullableDecl Node previousSibling; // the previous node with the same key + static final class Node + extends SimpleEntry { + @Nullable Node next; // the next node (with any key) + @Weak @Nullable Node previous; // the previous node (with any key) + @Nullable Node nextSibling; // the next node with the same key + @Weak @Nullable Node previousSibling; // the previous node with the same key - Node(@NullableDecl K key, @NullableDecl V value) { - this.key = key; - this.value = value; - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public V setValue(@NullableDecl V newValue) { - V result = value; - this.value = newValue; - return result; + Node(@ParametricNullness K key, @ParametricNullness V value) { + super(key, value); } } - private static class KeyList { + private static final class KeyList { Node head; Node tail; int count; @@ -148,8 +132,8 @@ private static class KeyList { } } - @NullableDecl private transient Node head; // the head for all keys - @NullableDecl private transient Node tail; // the tail for all keys + private transient @Nullable Node head; // the head for all keys + private transient @Nullable Node tail; // the tail for all keys private transient Map> keyToKeyList; private transient int size; @@ -161,7 +145,8 @@ private static class KeyList { private transient int modCount; /** Creates a new, empty {@code LinkedListMultimap} with the default initial capacity. */ - public static LinkedListMultimap create() { + public static + LinkedListMultimap create() { return new LinkedListMultimap<>(); } @@ -172,7 +157,8 @@ public static LinkedListMultimap create() { * @param expectedKeys the expected number of distinct keys * @throws IllegalArgumentException if {@code expectedKeys} is negative */ - public static LinkedListMultimap create(int expectedKeys) { + public static + LinkedListMultimap create(int expectedKeys) { return new LinkedListMultimap<>(expectedKeys); } @@ -183,8 +169,8 @@ public static LinkedListMultimap create(int expectedKeys) { * * @param multimap the multimap whose contents are copied to this multimap */ - public static LinkedListMultimap create( - Multimap multimap) { + public static + LinkedListMultimap create(Multimap multimap) { return new LinkedListMultimap<>(multimap); } @@ -204,18 +190,19 @@ private LinkedListMultimap(Multimap multimap) { /** * Adds a new node for the specified key-value pair before the specified {@code nextSibling} * element, or at the end of the list if {@code nextSibling} is null. Note: if {@code nextSibling} - * is specified, it MUST be for an node for the same {@code key}! + * is specified, it MUST be for a node for the same {@code key}! */ @CanIgnoreReturnValue private Node addNode( - @NullableDecl K key, @NullableDecl V value, @NullableDecl Node nextSibling) { + @ParametricNullness K key, @ParametricNullness V value, @Nullable Node nextSibling) { Node node = new Node<>(key, value); if (head == null) { // empty list head = tail = node; keyToKeyList.put(key, new KeyList(node)); modCount++; } else if (nextSibling == null) { // non-empty list, add to tail - tail.next = node; + // requireNonNull is safe because the list is non-empty. + requireNonNull(tail).next = node; node.previous = tail; tail = node; KeyList keyList = keyToKeyList.get(key); @@ -230,14 +217,19 @@ private Node addNode( keyList.tail = node; } } else { // non-empty list, insert before nextSibling - KeyList keyList = keyToKeyList.get(key); + /* + * requireNonNull is safe as long as callers pass a nextSibling that (a) has the same key and + * (b) is present in the multimap. (And they do, except maybe in case of concurrent + * modification, in which case all bets are off.) + */ + KeyList keyList = requireNonNull(keyToKeyList.get(key)); keyList.count++; node.previous = nextSibling.previous; node.previousSibling = nextSibling.previousSibling; node.next = nextSibling; node.nextSibling = nextSibling; if (nextSibling.previousSibling == null) { // nextSibling was key head - keyToKeyList.get(key).head = node; + keyList.head = node; } else { nextSibling.previousSibling.nextSibling = node; } @@ -269,21 +261,29 @@ private void removeNode(Node node) { tail = node.previous; } if (node.previousSibling == null && node.nextSibling == null) { - KeyList keyList = keyToKeyList.remove(node.key); + /* + * requireNonNull is safe as long as we call removeNode only for nodes that are still in the + * Multimap. This should be the case (except in case of concurrent modification, when all bets + * are off). + */ + KeyList keyList = requireNonNull(keyToKeyList.remove(node.getKey())); keyList.count = 0; modCount++; } else { - KeyList keyList = keyToKeyList.get(node.key); + // requireNonNull is safe (under the conditions listed in the comment in the branch above). + KeyList keyList = requireNonNull(keyToKeyList.get(node.getKey())); keyList.count--; if (node.previousSibling == null) { - keyList.head = node.nextSibling; + // requireNonNull is safe because we checked that not *both* siblings were null. + keyList.head = requireNonNull(node.nextSibling); } else { node.previousSibling.nextSibling = node.nextSibling; } if (node.nextSibling == null) { - keyList.tail = node.previousSibling; + // requireNonNull is safe because we checked that not *both* siblings were null. + keyList.tail = requireNonNull(node.previousSibling); } else { node.nextSibling.previousSibling = node.previousSibling; } @@ -292,23 +292,16 @@ private void removeNode(Node node) { } /** Removes all nodes for the specified key. */ - private void removeAllNodes(@NullableDecl Object key) { + private void removeAllNodes(@ParametricNullness K key) { Iterators.clear(new ValueForKeyIterator(key)); } - /** Helper method for verifying that an iterator element is present. */ - private static void checkElement(@NullableDecl Object node) { - if (node == null) { - throw new NoSuchElementException(); - } - } - /** An {@code Iterator} over all nodes. */ - private class NodeIterator implements ListIterator> { + private final class NodeIterator implements ListIterator> { int nextIndex; - @NullableDecl Node next; - @NullableDecl Node current; - @NullableDecl Node previous; + @Nullable Node next; + @Nullable Node current; + @Nullable Node previous; int expectedModCount = modCount; NodeIterator(int index) { @@ -345,7 +338,9 @@ public boolean hasNext() { @Override public Node next() { checkForConcurrentModification(); - checkElement(next); + if (next == null) { + throw new NoSuchElementException(); + } previous = current = next; next = next.next; nextIndex++; @@ -355,7 +350,7 @@ public Node next() { @Override public void remove() { checkForConcurrentModification(); - checkRemove(current != null); + checkState(current != null, "no calls to next() since the last call to remove()"); if (current != next) { // after call to next() previous = current.previous; nextIndex--; @@ -377,7 +372,9 @@ public boolean hasPrevious() { @Override public Node previous() { checkForConcurrentModification(); - checkElement(previous); + if (previous == null) { + throw new NoSuchElementException(); + } next = current = previous; previous = previous.previous; nextIndex--; @@ -404,17 +401,17 @@ public void add(Entry e) { throw new UnsupportedOperationException(); } - void setValue(V value) { + void setValue(@ParametricNullness V value) { checkState(current != null); - current.value = value; + current.setValue(value); } } /** An {@code Iterator} over distinct keys in key head order. */ - private class DistinctKeyIterator implements Iterator { - final Set seenKeys = Sets.newHashSetWithExpectedSize(keySet().size()); - Node next = head; - @NullableDecl Node current; + private final class DistinctKeyIterator implements Iterator { + final Set seenKeys = Sets.newHashSetWithExpectedSize(keySet().size()); + @Nullable Node next = head; + @Nullable Node current; int expectedModCount = modCount; private void checkForConcurrentModification() { @@ -430,37 +427,40 @@ public boolean hasNext() { } @Override + @ParametricNullness public K next() { checkForConcurrentModification(); - checkElement(next); + if (next == null) { + throw new NoSuchElementException(); + } current = next; - seenKeys.add(current.key); + seenKeys.add(current.getKey()); do { // skip ahead to next unseen key next = next.next; - } while ((next != null) && !seenKeys.add(next.key)); - return current.key; + } while ((next != null) && !seenKeys.add(next.getKey())); + return current.getKey(); } @Override public void remove() { checkForConcurrentModification(); - checkRemove(current != null); - removeAllNodes(current.key); + checkState(current != null, "no calls to next() since the last call to remove()"); + removeAllNodes(current.getKey()); current = null; expectedModCount = modCount; } } /** A {@code ListIterator} over values for a specified key. */ - private class ValueForKeyIterator implements ListIterator { - @NullableDecl final Object key; + private final class ValueForKeyIterator implements ListIterator { + @ParametricNullness final K key; int nextIndex; - @NullableDecl Node next; - @NullableDecl Node current; - @NullableDecl Node previous; + @Nullable Node next; + @Nullable Node current; + @Nullable Node previous; /** Constructs a new iterator over all values for the specified key. */ - ValueForKeyIterator(@NullableDecl Object key) { + ValueForKeyIterator(@ParametricNullness K key) { this.key = key; KeyList keyList = keyToKeyList.get(key); next = (keyList == null) ? null : keyList.head; @@ -474,7 +474,7 @@ private class ValueForKeyIterator implements ListIterator { * * @throws IndexOutOfBoundsException if index is invalid */ - public ValueForKeyIterator(@NullableDecl Object key, int index) { + ValueForKeyIterator(@ParametricNullness K key, int index) { KeyList keyList = keyToKeyList.get(key); int size = (keyList == null) ? 0 : keyList.count; checkPositionIndex(index, size); @@ -501,12 +501,15 @@ public boolean hasNext() { @CanIgnoreReturnValue @Override + @ParametricNullness public V next() { - checkElement(next); + if (next == null) { + throw new NoSuchElementException(); + } previous = current = next; next = next.nextSibling; nextIndex++; - return current.value; + return current.getValue(); } @Override @@ -516,12 +519,15 @@ public boolean hasPrevious() { @CanIgnoreReturnValue @Override + @ParametricNullness public V previous() { - checkElement(previous); + if (previous == null) { + throw new NoSuchElementException(); + } next = current = previous; previous = previous.previousSibling; nextIndex--; - return current.value; + return current.getValue(); } @Override @@ -536,7 +542,7 @@ public int previousIndex() { @Override public void remove() { - checkRemove(current != null); + checkState(current != null, "no calls to next() since the last call to remove()"); if (current != next) { // after call to next() previous = current.previousSibling; nextIndex--; @@ -548,15 +554,14 @@ public void remove() { } @Override - public void set(V value) { + public void set(@ParametricNullness V value) { checkState(current != null); - current.value = value; + current.setValue(value); } @Override - @SuppressWarnings("unchecked") - public void add(V value) { - previous = addNode((K) key, value, next); + public void add(@ParametricNullness V value) { + previous = addNode(key, value, next); nextIndex++; current = null; } @@ -575,12 +580,12 @@ public boolean isEmpty() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return keyToKeyList.containsKey(key); } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return values().contains(value); } @@ -595,7 +600,7 @@ public boolean containsValue(@NullableDecl Object value) { */ @CanIgnoreReturnValue @Override - public boolean put(@NullableDecl K key, @NullableDecl V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { addNode(key, value, null); return true; } @@ -612,7 +617,7 @@ public boolean put(@NullableDecl K key, @NullableDecl V value) { */ @CanIgnoreReturnValue @Override - public List replaceValues(@NullableDecl K key, Iterable values) { + public List replaceValues(@ParametricNullness K key, Iterable values) { List oldValues = getCopy(key); ListIterator keyValues = new ValueForKeyIterator(key); Iterator newValues = values.iterator(); @@ -637,7 +642,7 @@ public List replaceValues(@NullableDecl K key, Iterable values) return oldValues; } - private List getCopy(@NullableDecl Object key) { + private List getCopy(@ParametricNullness K key) { return unmodifiableList(Lists.newArrayList(new ValueForKeyIterator(key))); } @@ -648,9 +653,16 @@ private List getCopy(@NullableDecl Object key) { */ @CanIgnoreReturnValue @Override - public List removeAll(@NullableDecl Object key) { - List oldValues = getCopy(key); - removeAllNodes(key); + public List removeAll(@Nullable Object key) { + /* + * Safe because all we do is remove values for the key, not add them. (If we wanted to make sure + * to call getCopy and removeAllNodes only with a true K, then we could check containsKey first. + * But that check wouldn't eliminate the warnings.) + */ + @SuppressWarnings({"unchecked", "nullness"}) + K castKey = (K) key; + List oldValues = getCopy(castKey); + removeAllNodes(castKey); return oldValues; } @@ -675,7 +687,7 @@ public void clear() { *

    The returned list is not serializable and does not have random access. */ @Override - public List get(@NullableDecl final K key) { + public List get(@ParametricNullness K key) { return new AbstractSequentialList() { @Override public int size() { @@ -693,7 +705,7 @@ public ListIterator listIterator(int index) { @Override Set createKeySet() { @WeakOuter - class KeySetImpl extends Sets.ImprovedAbstractSet { + final class KeySetImpl extends Sets.ImprovedAbstractSet { @Override public int size() { return keyToKeyList.size(); @@ -705,12 +717,12 @@ public Iterator iterator() { } @Override - public boolean contains(Object key) { // for performance + public boolean contains(@Nullable Object key) { // for performance return containsKey(key); } @Override - public boolean remove(Object o) { // for performance + public boolean remove(@Nullable Object o) { // for performance return !LinkedListMultimap.this.removeAll(o).isEmpty(); } } @@ -738,7 +750,7 @@ public List values() { @Override List createValues() { @WeakOuter - class ValuesImpl extends AbstractSequentialList { + final class ValuesImpl extends AbstractSequentialList { @Override public int size() { return size; @@ -746,15 +758,16 @@ public int size() { @Override public ListIterator listIterator(int index) { - final NodeIterator nodeItr = new NodeIterator(index); + NodeIterator nodeItr = new NodeIterator(index); return new TransformedListIterator, V>(nodeItr) { @Override + @ParametricNullness V transform(Entry entry) { return entry.getValue(); } @Override - public void set(V value) { + public void set(@ParametricNullness V value) { nodeItr.setValue(value); } }; @@ -787,7 +800,7 @@ public List> entries() { @Override List> createEntries() { @WeakOuter - class EntriesImpl extends AbstractSequentialList> { + final class EntriesImpl extends AbstractSequentialList> { @Override public int size() { return size; @@ -816,8 +829,9 @@ Map> createAsMap() { * number of values for that key, and the key's values, followed by successive keys and values * from the entries() ordering */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(size()); for (Entry entry : entries()) { @@ -826,8 +840,9 @@ private void writeObject(ObjectOutputStream stream) throws IOException { } } - @GwtIncompatible // java.io.ObjectInputStream - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); keyToKeyList = CompactLinkedHashMap.create(); int size = stream.readInt(); @@ -840,6 +855,5 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo } } - @GwtIncompatible // java serialization not supported - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ListMultimap.java b/android/guava/src/com/google/common/collect/ListMultimap.java index 46c18ac38614..a530833267f4 100644 --- a/android/guava/src/com/google/common/collect/ListMultimap.java +++ b/android/guava/src/com/google/common/collect/ListMultimap.java @@ -21,7 +21,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@code Multimap} that can hold duplicate key-value pairs and that maintains the insertion @@ -33,14 +33,14 @@ * {@link #asMap} has {@code List} values. * *

    See the Guava User Guide article on {@code - * Multimap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}. * * @author Jared Levy * @since 2.0 */ @GwtCompatible -public interface ListMultimap extends Multimap { +public interface ListMultimap + extends Multimap { /** * {@inheritDoc} * @@ -49,7 +49,7 @@ public interface ListMultimap extends Multimap { * the {@link Multimap} interface. */ @Override - List get(@NullableDecl K key); + List get(@ParametricNullness K key); /** * {@inheritDoc} @@ -60,7 +60,7 @@ public interface ListMultimap extends Multimap { */ @CanIgnoreReturnValue @Override - List removeAll(@NullableDecl Object key); + List removeAll(@Nullable Object key); /** * {@inheritDoc} @@ -71,7 +71,7 @@ public interface ListMultimap extends Multimap { */ @CanIgnoreReturnValue @Override - List replaceValues(K key, Iterable values); + List replaceValues(@ParametricNullness K key, Iterable values); /** * {@inheritDoc} @@ -93,5 +93,5 @@ public interface ListMultimap extends Multimap { * empty {@code SetMultimap}. */ @Override - boolean equals(@NullableDecl Object obj); + boolean equals(@Nullable Object obj); } diff --git a/android/guava/src/com/google/common/collect/Lists.java b/android/guava/src/com/google/common/collect/Lists.java index 33f65339bff4..d20ae7ab266f 100644 --- a/android/guava/src/com/google/common/collect/Lists.java +++ b/android/guava/src/com/google/common/collect/Lists.java @@ -24,15 +24,17 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.CollectPreconditions.checkNonnegative; import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.Iterators.elementsEqual; +import static java.lang.Math.min; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; -import com.google.common.base.Objects; import com.google.common.math.IntMath; import com.google.common.primitives.Ints; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.math.RoundingMode; import java.util.AbstractList; @@ -46,39 +48,42 @@ import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.RandomAccess; import java.util.concurrent.CopyOnWriteArrayList; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@link List} instances. Also see this class's counterparts * {@link Sets}, {@link Maps} and {@link Queues}. * *

    See the Guava User Guide article on {@code Lists}. + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#lists">{@code Lists}. * * @author Kevin Bourrillion * @author Mike Bostock * @author Louis Wasserman * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Lists { private Lists() {} // ArrayList /** - * Creates a mutable, empty {@code ArrayList} instance (for Java 6 and earlier). + * Creates a mutable, empty {@code ArrayList} instance. * *

    Note: if mutability is not required, use {@link ImmutableList#of()} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code ArrayList} {@linkplain ArrayList#ArrayList() constructor} - * directly, taking advantage of the new "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code ArrayList} {@linkplain ArrayList#ArrayList() constructor} directly, taking + * advantage of "diamond" + * syntax. */ - @GwtCompatible(serializable = true) - public static ArrayList newArrayList() { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static ArrayList newArrayList() { return new ArrayList<>(); } @@ -92,13 +97,13 @@ public static ArrayList newArrayList() { * Arrays#asList}. * *

    Note that even when you do need the ability to add or remove, this method provides only a - * tiny bit of syntactic sugar for {@code newArrayList(}{@link Arrays#asList asList}{@code + * tiny bit of syntactic sugar for {@code new ArrayList<>(}{@link Arrays#asList asList}{@code * (...))}, or for creating an empty list then calling {@link Collections#addAll}. This method is - * not actually very useful and will likely be deprecated in the future. + * not actually very useful. */ @SafeVarargs - @GwtCompatible(serializable = true) - public static ArrayList newArrayList(E... elements) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static ArrayList newArrayList(E... elements) { checkNotNull(elements); // for GWT // Avoid integer overflow when a large array is passed in int capacity = computeArrayListCapacity(elements.length); @@ -115,13 +120,15 @@ public static ArrayList newArrayList(E... elements) { * ImmutableList#copyOf(Iterable)} instead. (Or, change {@code elements} to be a {@link * FluentIterable} and call {@code elements.toList()}.) * - *

    Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't - * need this method. Use the {@code ArrayList} {@linkplain ArrayList#ArrayList(Collection) - * constructor} directly, taking advantage of the new "diamond" + *

    Note: if {@code elements} is a {@link Collection}, you don't need this method. Use + * the {@code ArrayList} {@linkplain ArrayList#ArrayList(Collection) constructor} directly, taking + * advantage of "diamond" * syntax. */ - @GwtCompatible(serializable = true) - public static ArrayList newArrayList(Iterable elements) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static ArrayList newArrayList( + Iterable elements) { checkNotNull(elements); // for GWT // Let ArrayList's sizing logic work, if possible return (elements instanceof Collection) @@ -136,9 +143,10 @@ public static ArrayList newArrayList(Iterable elements) { *

    Note: if mutability is not required and the elements are non-null, use {@link * ImmutableList#copyOf(Iterator)} instead. */ - @GwtCompatible(serializable = true) - public static ArrayList newArrayList(Iterator elements) { - ArrayList list = newArrayList(); + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static ArrayList newArrayList( + Iterator elements) { + ArrayList list = new ArrayList<>(); Iterators.addAll(list, elements); return list; } @@ -155,11 +163,12 @@ static int computeArrayListCapacity(int arraySize) { * Creates an {@code ArrayList} instance backed by an array with the specified initial size; * simply delegates to {@link ArrayList#ArrayList(int)}. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use {@code new }{@link ArrayList#ArrayList(int) ArrayList}{@code <>(int)} - * directly, taking advantage of the new "diamond" syntax. - * (Unlike here, there is no risk of overload ambiguity, since the {@code ArrayList} constructors - * very wisely did not accept varargs.) + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use {@code new }{@link ArrayList#ArrayList(int) ArrayList}{@code <>(int)} directly, taking + * advantage of "diamond" + * syntax. (Unlike here, there is no risk of overload ambiguity, since the {@code ArrayList} + * constructors very wisely did not accept varargs.) * * @param initialArraySize the exact size of the initial backing array for the returned array list * ({@code ArrayList} documentation calls this value the "capacity") @@ -167,34 +176,34 @@ static int computeArrayListCapacity(int arraySize) { * reaches {@code initialArraySize + 1} * @throws IllegalArgumentException if {@code initialArraySize} is negative */ - @GwtCompatible(serializable = true) - public static ArrayList newArrayListWithCapacity(int initialArraySize) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static ArrayList newArrayListWithCapacity( + int initialArraySize) { checkNonnegative(initialArraySize, "initialArraySize"); // for GWT. return new ArrayList<>(initialArraySize); } /** * Creates an {@code ArrayList} instance to hold {@code estimatedSize} elements, plus an - * unspecified amount of padding; you almost certainly mean to call {@link - * #newArrayListWithCapacity} (see that method for further advice on usage). - * - *

    Note: This method will soon be deprecated. Even in the rare case that you do want - * some amount of padding, it's best if you choose your desired amount explicitly. + * unspecified amount of padding; **don't do this**. Instead, use {@code new }{@link + * ArrayList#ArrayList(int) ArrayList}{@code <>(int)} directly and choose an explicit padding + * amount. * * @param estimatedSize an estimate of the eventual {@link List#size()} of the new list * @return a new, empty {@code ArrayList}, sized appropriately to hold the estimated number of * elements * @throws IllegalArgumentException if {@code estimatedSize} is negative */ - @GwtCompatible(serializable = true) - public static ArrayList newArrayListWithExpectedSize(int estimatedSize) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static ArrayList newArrayListWithExpectedSize( + int estimatedSize) { return new ArrayList<>(computeArrayListCapacity(estimatedSize)); } // LinkedList /** - * Creates a mutable, empty {@code LinkedList} instance (for Java 6 and earlier). + * Creates a mutable, empty {@code LinkedList} instance. * *

    Note: if you won't be adding any elements to the list, use {@link ImmutableList#of()} * instead. @@ -203,13 +212,17 @@ public static ArrayList newArrayListWithExpectedSize(int estimatedSize) { * outperform {@code LinkedList} except in certain rare and specific situations. Unless you have * spent a lot of time benchmarking your specific needs, use one of those instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code LinkedList} {@linkplain LinkedList#LinkedList() - * constructor} directly, taking advantage of the new "diamond" + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code LinkedList} {@linkplain LinkedList#LinkedList() constructor} directly, taking + * advantage of "diamond" * syntax. */ - @GwtCompatible(serializable = true) - public static LinkedList newLinkedList() { + @SuppressWarnings({ + "NonApiType", // acts as a direct substitute for a constructor call + "JdkObsolete", // We recommend against this method but need to keep it for compatibility. + }) + public static LinkedList newLinkedList() { return new LinkedList<>(); } @@ -225,14 +238,16 @@ public static LinkedList newLinkedList() { * outperform {@code LinkedList} except in certain rare and specific situations. Unless you have * spent a lot of time benchmarking your specific needs, use one of those instead. * - *

    Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't - * need this method. Use the {@code LinkedList} {@linkplain LinkedList#LinkedList(Collection) - * constructor} directly, taking advantage of the new "diamond" + *

    Note: if {@code elements} is a {@link Collection}, you don't need this method. Use + * the {@code LinkedList} {@linkplain LinkedList#LinkedList(Collection) constructor} directly, + * taking advantage of "diamond" * syntax. */ - @GwtCompatible(serializable = true) - public static LinkedList newLinkedList(Iterable elements) { - LinkedList list = newLinkedList(); + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static LinkedList newLinkedList( + Iterable elements) { + LinkedList list = new LinkedList<>(); Iterables.addAll(list, elements); return list; } @@ -246,8 +261,12 @@ public static LinkedList newLinkedList(Iterable elements) { * @return a new, empty {@code CopyOnWriteArrayList} * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArrayList - public static CopyOnWriteArrayList newCopyOnWriteArrayList() { + @InlineMe( + replacement = "new CopyOnWriteArrayList<>()", + imports = {"java.util.concurrent.CopyOnWriteArrayList"}) + public static CopyOnWriteArrayList newCopyOnWriteArrayList() { return new CopyOnWriteArrayList<>(); } @@ -258,8 +277,9 @@ public static CopyOnWriteArrayList newCopyOnWriteArrayList() { * @return a new {@code CopyOnWriteArrayList} containing those elements * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArrayList - public static CopyOnWriteArrayList newCopyOnWriteArrayList( + public static CopyOnWriteArrayList newCopyOnWriteArrayList( Iterable elements) { // We copy elements to an ArrayList first, rather than incurring the // quadratic cost of adding them to the COWAL directly. @@ -284,7 +304,7 @@ public static CopyOnWriteArrayList newCopyOnWriteArrayList( * @param rest an array of additional elements, possibly empty * @return an unmodifiable list containing the specified elements */ - public static List asList(@NullableDecl E first, E[] rest) { + public static List asList(@ParametricNullness E first, E[] rest) { return new OnePlusArrayList<>(first, rest); } @@ -304,17 +324,20 @@ public static List asList(@NullableDecl E first, E[] rest) { * @param rest an array of additional elements, possibly empty * @return an unmodifiable list containing the specified elements */ - public static List asList(@NullableDecl E first, @NullableDecl E second, E[] rest) { + public static List asList( + @ParametricNullness E first, @ParametricNullness E second, E[] rest) { return new TwoPlusArrayList<>(first, second, rest); } - /** @see Lists#asList(Object, Object[]) */ - private static class OnePlusArrayList extends AbstractList + /** + * @see Lists#asList(Object, Object[]) + */ + private static final class OnePlusArrayList extends AbstractList implements Serializable, RandomAccess { - @NullableDecl final E first; + @ParametricNullness final E first; final E[] rest; - OnePlusArrayList(@NullableDecl E first, E[] rest) { + OnePlusArrayList(@ParametricNullness E first, E[] rest) { this.first = first; this.rest = checkNotNull(rest); } @@ -325,23 +348,26 @@ public int size() { } @Override + @ParametricNullness public E get(int index) { // check explicitly so the IOOBE will have the right message checkElementIndex(index, size()); return (index == 0) ? first : rest[index - 1]; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - /** @see Lists#asList(Object, Object, Object[]) */ - private static class TwoPlusArrayList extends AbstractList + /** + * @see Lists#asList(Object, Object, Object[]) + */ + private static final class TwoPlusArrayList extends AbstractList implements Serializable, RandomAccess { - @NullableDecl final E first; - @NullableDecl final E second; + @ParametricNullness final E first; + @ParametricNullness final E second; final E[] rest; - TwoPlusArrayList(@NullableDecl E first, @NullableDecl E second, E[] rest) { + TwoPlusArrayList(@ParametricNullness E first, @ParametricNullness E second, E[] rest) { this.first = first; this.second = second; this.rest = checkNotNull(rest); @@ -353,6 +379,7 @@ public int size() { } @Override + @ParametricNullness public E get(int index) { switch (index) { case 0: @@ -366,7 +393,7 @@ public E get(int index) { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -374,11 +401,11 @@ public E get(int index) { * lists in order; the "n-ary Cartesian * product" of the lists. For example: * - *

    {@code
    +   * {@snippet :
        * Lists.cartesianProduct(ImmutableList.of(
        *     ImmutableList.of(1, 2),
        *     ImmutableList.of("A", "B", "C")))
    -   * }
    + * } * *

    returns a list containing six lists in the following order: * @@ -394,7 +421,7 @@ public E get(int index) { *

    The result is guaranteed to be in the "traditional", lexicographical order for Cartesian * products that you would get from nesting for loops: * - *

    {@code
    +   * {@snippet :
        * for (B b0 : lists.get(0)) {
        *   for (B b1 : lists.get(1)) {
        *     ...
    @@ -402,7 +429,7 @@ public E get(int index) {
        *     // operate on tuple
        *   }
        * }
    -   * }
    + * } * *

    Note that if any input list is empty, the Cartesian product will also be empty. If no lists * at all are provided (an empty list), the resulting Cartesian product has one element, an empty @@ -432,11 +459,11 @@ public static List> cartesianProduct(ListCartesian * product" of the lists. For example: * - *

    {@code
    +   * {@snippet :
        * Lists.cartesianProduct(ImmutableList.of(
        *     ImmutableList.of(1, 2),
        *     ImmutableList.of("A", "B", "C")))
    -   * }
    + * } * *

    returns a list containing six lists in the following order: * @@ -452,7 +479,7 @@ public static List> cartesianProduct(ListThe result is guaranteed to be in the "traditional", lexicographical order for Cartesian * products that you would get from nesting for loops: * - *

    {@code
    +   * {@snippet :
        * for (B b0 : lists.get(0)) {
        *   for (B b1 : lists.get(1)) {
        *     ...
    @@ -460,7 +487,7 @@ public static  List> cartesianProduct(List
    +   * }
        *
        * 

    Note that if any input list is empty, the Cartesian product will also be empty. If no lists * at all are provided (an empty list), the resulting Cartesian product has one element, an empty @@ -514,11 +541,11 @@ public static List> cartesianProduct(List... lists) { * serialize the copy. Other methods similar to this do not implement serialization at all for * this reason. * - *

    Java 8 users: many use cases for this method are better addressed by {@link + *

    Java 8+ users: many use cases for this method are better addressed by {@link * java.util.stream.Stream#map}. This method is not being deprecated, but we gently encourage you * to migrate to streams. */ - public static List transform( + public static List transform( List fromList, Function function) { return (fromList instanceof RandomAccess) ? new TransformingRandomAccessList<>(fromList, function) @@ -530,8 +557,9 @@ public static List transform( * * @see Lists#transform */ - private static class TransformingSequentialList extends AbstractSequentialList - implements Serializable { + private static final class TransformingSequentialList< + F extends @Nullable Object, T extends @Nullable Object> + extends AbstractSequentialList implements Serializable { final List fromList; final Function function; @@ -545,8 +573,8 @@ private static class TransformingSequentialList extends AbstractSequential * can be overkill. That's why we forward this call directly to the backing list. */ @Override - public void clear() { - fromList.clear(); + protected void removeRange(int fromIndex, int toIndex) { + fromList.subList(fromIndex, toIndex).clear(); } @Override @@ -555,16 +583,22 @@ public int size() { } @Override - public ListIterator listIterator(final int index) { + public boolean isEmpty() { + return fromList.isEmpty(); + } + + @Override + public ListIterator listIterator(int index) { return new TransformedListIterator(fromList.listIterator(index)) { @Override - T transform(F from) { + @ParametricNullness + T transform(@ParametricNullness F from) { return function.apply(from); } }; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -574,8 +608,9 @@ T transform(F from) { * * @see Lists#transform */ - private static class TransformingRandomAccessList extends AbstractList - implements RandomAccess, Serializable { + private static final class TransformingRandomAccessList< + F extends @Nullable Object, T extends @Nullable Object> + extends AbstractList implements RandomAccess, Serializable { final List fromList; final Function function; @@ -584,12 +619,17 @@ private static class TransformingRandomAccessList extends AbstractList this.function = checkNotNull(function); } + /** + * The default implementation inherited is based on iteration and removal of each element which + * can be overkill. That's why we forward this call directly to the backing list. + */ @Override - public void clear() { - fromList.clear(); + protected void removeRange(int fromIndex, int toIndex) { + fromList.subList(fromIndex, toIndex).clear(); } @Override + @ParametricNullness public T get(int index) { return function.apply(fromList.get(index)); } @@ -624,7 +664,7 @@ public int size() { return fromList.size(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -642,7 +682,7 @@ public int size() { * @return a list of consecutive sublists * @throws IllegalArgumentException if {@code partitionSize} is nonpositive */ - public static List> partition(List list, int size) { + public static List> partition(List list, int size) { checkNotNull(list); checkArgument(size > 0); return (list instanceof RandomAccess) @@ -650,7 +690,7 @@ public static List> partition(List list, int size) { : new Partition<>(list, size); } - private static class Partition extends AbstractList> { + private static class Partition extends AbstractList> { final List list; final int size; @@ -663,7 +703,7 @@ private static class Partition extends AbstractList> { public List get(int index) { checkElementIndex(index, size()); int start = index * size; - int end = Math.min(start + size, list.size()); + int end = min(start + size, list.size()); return list.subList(start, end); } @@ -678,7 +718,8 @@ public boolean isEmpty() { } } - private static class RandomAccessPartition extends Partition implements RandomAccess { + private static final class RandomAccessPartition extends Partition + implements RandomAccess { RandomAccessPartition(List list, int size) { super(list, size); } @@ -702,7 +743,6 @@ public static ImmutableList charactersOf(String string) { * @return an {@code List} view of the character sequence * @since 7.0 */ - @Beta public static List charactersOf(CharSequence sequence) { return new CharSequenceAsList(checkNotNull(sequence)); } @@ -717,12 +757,12 @@ private static final class StringAsImmutableList extends ImmutableList { @@ -779,9 +828,13 @@ public int size() { * * @since 7.0 */ - public static List reverse(List list) { + public static List reverse(List list) { if (list instanceof ImmutableList) { - return ((ImmutableList) list).reverse(); + // Avoid nullness warnings. + List reversed = ((ImmutableList) list).reverse(); + @SuppressWarnings("unchecked") + List result = (List) reversed; + return result; } else if (list instanceof ReverseList) { return ((ReverseList) list).getForwardList(); } else if (list instanceof RandomAccess) { @@ -791,7 +844,7 @@ public static List reverse(List list) { } } - private static class ReverseList extends AbstractList { + private static class ReverseList extends AbstractList { private final List forwardList; ReverseList(List forwardList) { @@ -815,7 +868,7 @@ private int reversePosition(int index) { } @Override - public void add(int index, @NullableDecl T element) { + public void add(int index, @ParametricNullness T element) { forwardList.add(reversePosition(index), element); } @@ -825,6 +878,7 @@ public void clear() { } @Override + @ParametricNullness public T remove(int index) { return forwardList.remove(reverseIndex(index)); } @@ -835,11 +889,13 @@ protected void removeRange(int fromIndex, int toIndex) { } @Override - public T set(int index, @NullableDecl T element) { + @ParametricNullness + public T set(int index, @ParametricNullness T element) { return forwardList.set(reverseIndex(index), element); } @Override + @ParametricNullness public T get(int index) { return forwardList.get(reverseIndex(index)); } @@ -863,13 +919,13 @@ public Iterator iterator() { @Override public ListIterator listIterator(int index) { int start = reversePosition(index); - final ListIterator forwardIterator = forwardList.listIterator(start); + ListIterator forwardIterator = forwardList.listIterator(start); return new ListIterator() { boolean canRemoveOrSet; @Override - public void add(T e) { + public void add(@ParametricNullness T e) { forwardIterator.add(e); forwardIterator.previous(); canRemoveOrSet = false; @@ -886,6 +942,7 @@ public boolean hasPrevious() { } @Override + @ParametricNullness public T next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -900,6 +957,7 @@ public int nextIndex() { } @Override + @ParametricNullness public T previous() { if (!hasPrevious()) { throw new NoSuchElementException(); @@ -921,7 +979,7 @@ public void remove() { } @Override - public void set(T e) { + public void set(@ParametricNullness T e) { checkState(canRemoveOrSet); forwardIterator.set(e); } @@ -929,7 +987,8 @@ public void set(T e) { } } - private static class RandomAccessReverseList extends ReverseList implements RandomAccess { + private static final class RandomAccessReverseList + extends ReverseList implements RandomAccess { RandomAccessReverseList(List forwardList) { super(forwardList); } @@ -949,7 +1008,7 @@ static int hashCodeImpl(List list) { } /** An implementation of {@link List#equals(Object)}. */ - static boolean equalsImpl(List thisList, @NullableDecl Object other) { + static boolean equalsImpl(List thisList, @Nullable Object other) { if (other == checkNotNull(thisList)) { return true; } @@ -964,18 +1023,19 @@ static boolean equalsImpl(List thisList, @NullableDecl Object other) { if (thisList instanceof RandomAccess && otherList instanceof RandomAccess) { // avoid allocation and use the faster loop for (int i = 0; i < size; i++) { - if (!Objects.equal(thisList.get(i), otherList.get(i))) { + if (!Objects.equals(thisList.get(i), otherList.get(i))) { return false; } } return true; } else { - return Iterators.elementsEqual(thisList.iterator(), otherList.iterator()); + return elementsEqual(thisList.iterator(), otherList.iterator()); } } /** An implementation of {@link List#addAll(int, Collection)}. */ - static boolean addAllImpl(List list, int index, Iterable elements) { + static boolean addAllImpl( + List list, int index, Iterable elements) { boolean changed = false; ListIterator listIterator = list.listIterator(index); for (E e : elements) { @@ -986,13 +1046,13 @@ static boolean addAllImpl(List list, int index, Iterable ele } /** An implementation of {@link List#indexOf(Object)}. */ - static int indexOfImpl(List list, @NullableDecl Object element) { + static int indexOfImpl(List list, @Nullable Object element) { if (list instanceof RandomAccess) { return indexOfRandomAccess(list, element); } else { ListIterator listIterator = list.listIterator(); while (listIterator.hasNext()) { - if (Objects.equal(element, listIterator.next())) { + if (Objects.equals(element, listIterator.next())) { return listIterator.previousIndex(); } } @@ -1000,7 +1060,7 @@ static int indexOfImpl(List list, @NullableDecl Object element) { } } - private static int indexOfRandomAccess(List list, @NullableDecl Object element) { + private static int indexOfRandomAccess(List list, @Nullable Object element) { int size = list.size(); if (element == null) { for (int i = 0; i < size; i++) { @@ -1019,13 +1079,13 @@ private static int indexOfRandomAccess(List list, @NullableDecl Object elemen } /** An implementation of {@link List#lastIndexOf(Object)}. */ - static int lastIndexOfImpl(List list, @NullableDecl Object element) { + static int lastIndexOfImpl(List list, @Nullable Object element) { if (list instanceof RandomAccess) { return lastIndexOfRandomAccess(list, element); } else { ListIterator listIterator = list.listIterator(list.size()); while (listIterator.hasPrevious()) { - if (Objects.equal(element, listIterator.previous())) { + if (Objects.equals(element, listIterator.previous())) { return listIterator.nextIndex(); } } @@ -1033,7 +1093,7 @@ static int lastIndexOfImpl(List list, @NullableDecl Object element) { } } - private static int lastIndexOfRandomAccess(List list, @NullableDecl Object element) { + private static int lastIndexOfRandomAccess(List list, @Nullable Object element) { if (element == null) { for (int i = list.size() - 1; i >= 0; i--) { if (list.get(i) == null) { @@ -1051,12 +1111,13 @@ private static int lastIndexOfRandomAccess(List list, @NullableDecl Object el } /** Returns an implementation of {@link List#listIterator(int)}. */ - static ListIterator listIteratorImpl(List list, int index) { + static ListIterator listIteratorImpl(List list, int index) { return new AbstractListWrapper<>(list).listIterator(index); } /** An implementation of {@link List#subList(int, int)}. */ - static List subListImpl(final List list, int fromIndex, int toIndex) { + static List subListImpl( + List list, int fromIndex, int toIndex) { List wrapper; if (list instanceof RandomAccess) { wrapper = @@ -1066,7 +1127,7 @@ public ListIterator listIterator(int index) { return backingList.listIterator(index); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; }; } else { wrapper = @@ -1076,13 +1137,13 @@ public ListIterator listIterator(int index) { return backingList.listIterator(index); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; }; } return wrapper.subList(fromIndex, toIndex); } - private static class AbstractListWrapper extends AbstractList { + private static class AbstractListWrapper extends AbstractList { final List backingList; AbstractListWrapper(List backingList) { @@ -1090,7 +1151,7 @@ private static class AbstractListWrapper extends AbstractList { } @Override - public void add(int index, E element) { + public void add(int index, @ParametricNullness E element) { backingList.add(index, element); } @@ -1100,22 +1161,25 @@ public boolean addAll(int index, Collection c) { } @Override + @ParametricNullness public E get(int index) { return backingList.get(index); } @Override + @ParametricNullness public E remove(int index) { return backingList.remove(index); } @Override - public E set(int index, E element) { + @ParametricNullness + public E set(int index, @ParametricNullness E element) { return backingList.set(index, element); } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { return backingList.contains(o); } @@ -1125,15 +1189,10 @@ public int size() { } } - private static class RandomAccessListWrapper extends AbstractListWrapper - implements RandomAccess { + private static class RandomAccessListWrapper + extends AbstractListWrapper implements RandomAccess { RandomAccessListWrapper(List backingList) { super(backingList); } } - - /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ - static List cast(Iterable iterable) { - return (List) iterable; - } } diff --git a/android/guava/src/com/google/common/collect/MapDifference.java b/android/guava/src/com/google/common/collect/MapDifference.java index 9933770cb4e4..831d6c654c6e 100644 --- a/android/guava/src/com/google/common/collect/MapDifference.java +++ b/android/guava/src/com/google/common/collect/MapDifference.java @@ -19,7 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.DoNotMock; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An object representing the differences between two maps. @@ -29,7 +29,7 @@ */ @DoNotMock("Use Maps.difference") @GwtCompatible -public interface MapDifference { +public interface MapDifference { /** * Returns {@code true} if there are no differences between the two maps; that is, if the maps are * equal. @@ -67,15 +67,15 @@ public interface MapDifference { * #entriesDiffering()} of the two instances are equal. */ @Override - boolean equals(@NullableDecl Object object); + boolean equals(@Nullable Object object); /** * Returns the hash code for this instance. This is defined as the hash code of * - *

    {@code
    +   * {@snippet :
        * Arrays.asList(entriesOnlyOnLeft(), entriesOnlyOnRight(),
        *     entriesInCommon(), entriesDiffering())
    -   * }
    + * } */ @Override int hashCode(); @@ -87,11 +87,13 @@ public interface MapDifference { * @since 2.0 */ @DoNotMock("Use Maps.difference") - interface ValueDifference { + interface ValueDifference { /** Returns the value from the left map (possibly null). */ + @ParametricNullness V leftValue(); /** Returns the value from the right map (possibly null). */ + @ParametricNullness V rightValue(); /** @@ -99,7 +101,7 @@ interface ValueDifference { * {@link #rightValue()} values are also equal. */ @Override - boolean equals(@NullableDecl Object other); + boolean equals(@Nullable Object other); /** * The hash code equals the value {@code Arrays.asList(leftValue(), rightValue()).hashCode()}. diff --git a/android/guava/src/com/google/common/collect/MapMaker.java b/android/guava/src/com/google/common/collect/MapMaker.java index 4f837a0d3306..fd06656c4015 100644 --- a/android/guava/src/com/google/common/collect/MapMaker.java +++ b/android/guava/src/com/google/common/collect/MapMaker.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Ascii; import com.google.common.base.Equivalence; import com.google.common.base.MoreObjects; @@ -30,7 +31,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A builder of {@link ConcurrentMap} instances that can have keys or values automatically wrapped @@ -38,12 +39,12 @@ * *

    Usage example: * - *

    {@code
    + * {@snippet :
      * ConcurrentMap timers = new MapMaker()
      *     .concurrencyLevel(4)
      *     .weakKeys()
      *     .makeMap();
    - * }
    + * } * *

    These features are all optional; {@code new MapMaker().makeMap()} returns a valid concurrent * map that behaves similarly to a {@link ConcurrentHashMap}. @@ -85,7 +86,8 @@ * @author Kevin Bourrillion * @since 2.0 */ -@GwtCompatible(emulated = true) +@J2ktIncompatible +@GwtCompatible public final class MapMaker { private static final int DEFAULT_INITIAL_CAPACITY = 16; private static final int DEFAULT_CONCURRENCY_LEVEL = 4; @@ -98,10 +100,10 @@ public final class MapMaker { int initialCapacity = UNSET_INT; int concurrencyLevel = UNSET_INT; - @NullableDecl Strength keyStrength; - @NullableDecl Strength valueStrength; + @Nullable Strength keyStrength; + @Nullable Strength valueStrength; - @NullableDecl Equivalence keyEquivalence; + @Nullable Equivalence keyEquivalence; /** * Constructs a new {@code MapMaker} instance with default settings, including strong keys, strong @@ -205,6 +207,7 @@ public MapMaker weakKeys() { return setKeyStrength(Strength.WEAK); } + @CanIgnoreReturnValue MapMaker setKeyStrength(Strength strength) { checkState(keyStrength == null, "Key strength was already set to %s", keyStrength); keyStrength = checkNotNull(strength); @@ -251,6 +254,7 @@ enum Dummy { VALUE } + @CanIgnoreReturnValue MapMaker setValueStrength(Strength strength) { checkState(valueStrength == null, "Value strength was already set to %s", valueStrength); valueStrength = checkNotNull(strength); diff --git a/android/guava/src/com/google/common/collect/MapMakerInternalMap.java b/android/guava/src/com/google/common/collect/MapMakerInternalMap.java index b72d0ea54371..490d47449843 100644 --- a/android/guava/src/com/google/common/collect/MapMakerInternalMap.java +++ b/android/guava/src/com/google/common/collect/MapMakerInternalMap.java @@ -16,17 +16,21 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkRemove; +import static java.lang.Math.min; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Equivalence; import com.google.common.collect.MapMaker.Dummy; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.concurrent.GuardedBy; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.Weak; import com.google.j2objc.annotations.WeakOuter; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; @@ -35,8 +39,8 @@ import java.lang.ref.WeakReference; import java.util.AbstractCollection; import java.util.AbstractMap; +import java.util.AbstractMap.SimpleEntry; import java.util.AbstractSet; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.Map; @@ -47,7 +51,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.locks.ReentrantLock; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; /** * The concurrent hash map implementation built by {@link MapMaker}. @@ -64,9 +69,14 @@ * @author Doug Lea ({@code ConcurrentHashMap}) */ // TODO(kak): Consider removing @CanIgnoreReturnValue from this class. +@J2ktIncompatible @GwtIncompatible -@SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress. -class MapMakerInternalMap< +@SuppressWarnings({ + "GuardedBy", // TODO(b/35466881): Fix or suppress. + "nullness", // too much trouble for the payoff +}) +@NullUnmarked // TODO(cpovirk): Annotate for nullness. +final class MapMakerInternalMap< K, V, E extends MapMakerInternalMap.InternalEntry, @@ -126,8 +136,6 @@ class MapMakerInternalMap< // TODO(fry): empirically optimize this static final int DRAIN_MAX = 16; - static final long CLEANUP_EXECUTOR_DELAY_SECS = 60; - // Fields /** @@ -158,12 +166,12 @@ class MapMakerInternalMap< * Creates a new, empty map with the specified strategy, initial capacity and concurrency level. */ private MapMakerInternalMap(MapMaker builder, InternalEntryHelper entryHelper) { - concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); + concurrencyLevel = min(builder.getConcurrencyLevel(), MAX_SEGMENTS); keyEquivalence = builder.getKeyEquivalence(); this.entryHelper = entryHelper; - int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); + int initialCapacity = min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); // Find power-of-two sizes best matching arguments. Constraints: // (segmentCount > concurrencyLevel) @@ -189,7 +197,7 @@ private MapMakerInternalMap(MapMaker builder, InternalEntryHelper en } for (int i = 0; i < this.segments.length; ++i) { - this.segments[i] = createSegment(segmentSize, MapMaker.UNSET_INT); + this.segments[i] = createSegment(segmentSize); } } @@ -198,18 +206,18 @@ private MapMakerInternalMap(MapMaker builder, InternalEntryHelper en MapMaker builder) { if (builder.getKeyStrength() == Strength.STRONG && builder.getValueStrength() == Strength.STRONG) { - return new MapMakerInternalMap<>(builder, StrongKeyStrongValueEntry.Helper.instance()); + return new MapMakerInternalMap<>(builder, StrongKeyStrongValueEntry.Helper.instance()); } if (builder.getKeyStrength() == Strength.STRONG && builder.getValueStrength() == Strength.WEAK) { - return new MapMakerInternalMap<>(builder, StrongKeyWeakValueEntry.Helper.instance()); + return new MapMakerInternalMap<>(builder, StrongKeyWeakValueEntry.Helper.instance()); } if (builder.getKeyStrength() == Strength.WEAK && builder.getValueStrength() == Strength.STRONG) { - return new MapMakerInternalMap<>(builder, WeakKeyStrongValueEntry.Helper.instance()); + return new MapMakerInternalMap<>(builder, WeakKeyStrongValueEntry.Helper.instance()); } if (builder.getKeyStrength() == Strength.WEAK && builder.getValueStrength() == Strength.WEAK) { - return new MapMakerInternalMap<>(builder, WeakKeyWeakValueEntry.Helper.instance()); + return new MapMakerInternalMap<>(builder, WeakKeyWeakValueEntry.Helper.instance()); } throw new AssertionError(); } @@ -229,11 +237,11 @@ private MapMakerInternalMap(MapMaker builder, InternalEntryHelper en MapMaker builder) { if (builder.getKeyStrength() == Strength.STRONG && builder.getValueStrength() == Strength.STRONG) { - return new MapMakerInternalMap<>(builder, StrongKeyDummyValueEntry.Helper.instance()); + return new MapMakerInternalMap<>(builder, StrongKeyDummyValueEntry.Helper.instance()); } if (builder.getKeyStrength() == Strength.WEAK && builder.getValueStrength() == Strength.STRONG) { - return new MapMakerInternalMap<>(builder, WeakKeyDummyValueEntry.Helper.instance()); + return new MapMakerInternalMap<>(builder, WeakKeyDummyValueEntry.Helper.instance()); } if (builder.getValueStrength() == Strength.WEAK) { throw new IllegalArgumentException("Map cannot have both weak and dummy values"); @@ -286,18 +294,18 @@ interface InternalEntryHelper< Strength valueStrength(); /** Returns a freshly created segment, typed at the {@code S} type. */ - S newSegment(MapMakerInternalMap map, int initialCapacity, int maxSegmentSize); + S newSegment(MapMakerInternalMap map, int initialCapacity); /** * Returns a freshly created entry, typed at the {@code E} type, for the given {@code segment}. */ - E newEntry(S segment, K key, int hash, @NullableDecl E next); + E newEntry(S segment, K key, int hash, @Nullable E next); /** * Returns a freshly created entry, typed at the {@code E} type, for the given {@code segment}, * that is a copy of the given {@code entry}. */ - E copy(S segment, E entry, @NullableDecl E newNext); + E copy(S segment, E entry, @Nullable E newNext); /** * Sets the value of the given {@code entry} in the given {@code segment} to be the given {@code @@ -339,27 +347,25 @@ abstract static class AbstractStrongKeyEntry { final K key; final int hash; - @NullableDecl final E next; - AbstractStrongKeyEntry(K key, int hash, @NullableDecl E next) { + AbstractStrongKeyEntry(K key, int hash) { this.key = key; this.hash = hash; - this.next = next; } @Override - public K getKey() { - return this.key; + public final K getKey() { + return key; } @Override - public int getHash() { + public final int getHash() { return hash; } @Override - public E getNext() { - return next; + public @Nullable E getNext() { + return null; } } @@ -371,12 +377,6 @@ interface StrongValueEntry> interface WeakValueEntry> extends InternalEntry { /** Gets the weak value reference held by entry. */ WeakValueReference getValueReference(); - - /** - * Clears the weak value reference held by the entry. Should be used when the entry's value is - * overwritten. - */ - void clearValue(); } @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value @@ -386,30 +386,33 @@ WeakValueReference unsetWeakValueReference() { } /** Concrete implementation of {@link InternalEntry} for strong keys and strong values. */ - static final class StrongKeyStrongValueEntry + static class StrongKeyStrongValueEntry extends AbstractStrongKeyEntry> implements StrongValueEntry> { - @NullableDecl private volatile V value = null; + private volatile @Nullable V value = null; - StrongKeyStrongValueEntry(K key, int hash, @NullableDecl StrongKeyStrongValueEntry next) { - super(key, hash, next); + private StrongKeyStrongValueEntry(K key, int hash) { + super(key, hash); } @Override - @NullableDecl - public V getValue() { + public final @Nullable V getValue() { return value; } - void setValue(V value) { - this.value = value; - } + private static final class LinkedStrongKeyStrongValueEntry + extends StrongKeyStrongValueEntry { + private final StrongKeyStrongValueEntry next; + + LinkedStrongKeyStrongValueEntry(K key, int hash, StrongKeyStrongValueEntry next) { + super(key, hash); + this.next = next; + } - StrongKeyStrongValueEntry copy(StrongKeyStrongValueEntry newNext) { - StrongKeyStrongValueEntry newEntry = - new StrongKeyStrongValueEntry<>(this.key, this.hash, newNext); - newEntry.value = this.value; - return newEntry; + @Override + public StrongKeyStrongValueEntry getNext() { + return next; + } } /** Concrete implementation of {@link InternalEntryHelper} for strong keys and strong values. */ @@ -438,17 +441,19 @@ public StrongKeyStrongValueSegment newSegment( MapMakerInternalMap< K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - return new StrongKeyStrongValueSegment<>(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + return new StrongKeyStrongValueSegment<>(map, initialCapacity); } @Override public StrongKeyStrongValueEntry copy( StrongKeyStrongValueSegment segment, StrongKeyStrongValueEntry entry, - @NullableDecl StrongKeyStrongValueEntry newNext) { - return entry.copy(newNext); + @Nullable StrongKeyStrongValueEntry newNext) { + StrongKeyStrongValueEntry newEntry = + newEntry(segment, entry.key, entry.hash, newNext); + newEntry.value = entry.value; + return newEntry; } @Override @@ -456,7 +461,7 @@ public void setValue( StrongKeyStrongValueSegment segment, StrongKeyStrongValueEntry entry, V value) { - entry.setValue(value); + entry.value = value; } @Override @@ -464,49 +469,48 @@ public StrongKeyStrongValueEntry newEntry( StrongKeyStrongValueSegment segment, K key, int hash, - @NullableDecl StrongKeyStrongValueEntry next) { - return new StrongKeyStrongValueEntry<>(key, hash, next); + @Nullable StrongKeyStrongValueEntry next) { + return next == null + ? new StrongKeyStrongValueEntry<>(key, hash) + : new LinkedStrongKeyStrongValueEntry<>(key, hash, next); } } } /** Concrete implementation of {@link InternalEntry} for strong keys and weak values. */ - static final class StrongKeyWeakValueEntry + static class StrongKeyWeakValueEntry extends AbstractStrongKeyEntry> implements WeakValueEntry> { private volatile WeakValueReference> valueReference = unsetWeakValueReference(); - StrongKeyWeakValueEntry(K key, int hash, @NullableDecl StrongKeyWeakValueEntry next) { - super(key, hash, next); + private StrongKeyWeakValueEntry(K key, int hash) { + super(key, hash); } @Override - public V getValue() { + public final @Nullable V getValue() { return valueReference.get(); } @Override - public void clearValue() { - valueReference.clear(); + public final WeakValueReference> getValueReference() { + return valueReference; } - void setValue(V value, ReferenceQueue queueForValues) { - WeakValueReference> previous = this.valueReference; - this.valueReference = new WeakValueReferenceImpl<>(queueForValues, value, this); - previous.clear(); - } + private static final class LinkedStrongKeyWeakValueEntry + extends StrongKeyWeakValueEntry { + private final StrongKeyWeakValueEntry next; - StrongKeyWeakValueEntry copy( - ReferenceQueue queueForValues, StrongKeyWeakValueEntry newNext) { - StrongKeyWeakValueEntry newEntry = new StrongKeyWeakValueEntry<>(key, hash, newNext); - newEntry.valueReference = valueReference.copyFor(queueForValues, newEntry); - return newEntry; - } + LinkedStrongKeyWeakValueEntry(K key, int hash, StrongKeyWeakValueEntry next) { + super(key, hash); + this.next = next; + } - @Override - public WeakValueReference> getValueReference() { - return valueReference; + @Override + public StrongKeyWeakValueEntry getNext() { + return next; + } } /** Concrete implementation of {@link InternalEntryHelper} for strong keys and weak values. */ @@ -534,26 +538,29 @@ public Strength valueStrength() { public StrongKeyWeakValueSegment newSegment( MapMakerInternalMap, StrongKeyWeakValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - return new StrongKeyWeakValueSegment<>(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + return new StrongKeyWeakValueSegment<>(map, initialCapacity); } @Override - public StrongKeyWeakValueEntry copy( + public @Nullable StrongKeyWeakValueEntry copy( StrongKeyWeakValueSegment segment, StrongKeyWeakValueEntry entry, - @NullableDecl StrongKeyWeakValueEntry newNext) { + @Nullable StrongKeyWeakValueEntry newNext) { if (Segment.isCollected(entry)) { return null; } - return entry.copy(segment.queueForValues, newNext); + StrongKeyWeakValueEntry newEntry = newEntry(segment, entry.key, entry.hash, newNext); + newEntry.valueReference = entry.valueReference.copyFor(segment.queueForValues, newEntry); + return newEntry; } @Override public void setValue( StrongKeyWeakValueSegment segment, StrongKeyWeakValueEntry entry, V value) { - entry.setValue(value, segment.queueForValues); + WeakValueReference> previous = entry.valueReference; + entry.valueReference = new WeakValueReferenceImpl<>(segment.queueForValues, value, entry); + previous.clear(); } @Override @@ -561,29 +568,41 @@ public StrongKeyWeakValueEntry newEntry( StrongKeyWeakValueSegment segment, K key, int hash, - @NullableDecl StrongKeyWeakValueEntry next) { - return new StrongKeyWeakValueEntry<>(key, hash, next); + @Nullable StrongKeyWeakValueEntry next) { + return next == null + ? new StrongKeyWeakValueEntry<>(key, hash) + : new LinkedStrongKeyWeakValueEntry<>(key, hash, next); } } } /** Concrete implementation of {@link InternalEntry} for strong keys and {@link Dummy} values. */ - static final class StrongKeyDummyValueEntry + private static class StrongKeyDummyValueEntry extends AbstractStrongKeyEntry> implements StrongValueEntry> { - StrongKeyDummyValueEntry(K key, int hash, @NullableDecl StrongKeyDummyValueEntry next) { - super(key, hash, next); + + private StrongKeyDummyValueEntry(K key, int hash) { + super(key, hash); } @Override - public Dummy getValue() { + public final Dummy getValue() { return Dummy.VALUE; } - void setValue(Dummy value) {} + private static final class LinkedStrongKeyDummyValueEntry + extends StrongKeyDummyValueEntry { + private final StrongKeyDummyValueEntry next; - StrongKeyDummyValueEntry copy(StrongKeyDummyValueEntry newNext) { - return new StrongKeyDummyValueEntry(this.key, this.hash, newNext); + LinkedStrongKeyDummyValueEntry(K key, int hash, StrongKeyDummyValueEntry next) { + super(key, hash); + this.next = next; + } + + @Override + public StrongKeyDummyValueEntry getNext() { + return next; + } } /** @@ -614,17 +633,16 @@ public Strength valueStrength() { public StrongKeyDummyValueSegment newSegment( MapMakerInternalMap, StrongKeyDummyValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - return new StrongKeyDummyValueSegment(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + return new StrongKeyDummyValueSegment<>(map, initialCapacity); } @Override public StrongKeyDummyValueEntry copy( StrongKeyDummyValueSegment segment, StrongKeyDummyValueEntry entry, - @NullableDecl StrongKeyDummyValueEntry newNext) { - return entry.copy(newNext); + @Nullable StrongKeyDummyValueEntry newNext) { + return newEntry(segment, entry.key, entry.hash, newNext); } @Override @@ -636,8 +654,10 @@ public StrongKeyDummyValueEntry newEntry( StrongKeyDummyValueSegment segment, K key, int hash, - @NullableDecl StrongKeyDummyValueEntry next) { - return new StrongKeyDummyValueEntry(key, hash, next); + @Nullable StrongKeyDummyValueEntry next) { + return next == null + ? new StrongKeyDummyValueEntry(key, hash) + : new LinkedStrongKeyDummyValueEntry<>(key, hash, next); } } } @@ -646,49 +666,55 @@ public StrongKeyDummyValueEntry newEntry( abstract static class AbstractWeakKeyEntry> extends WeakReference implements InternalEntry { final int hash; - @NullableDecl final E next; - AbstractWeakKeyEntry(ReferenceQueue queue, K key, int hash, @NullableDecl E next) { + AbstractWeakKeyEntry(ReferenceQueue queue, K key, int hash) { super(key, queue); this.hash = hash; - this.next = next; } @Override - public K getKey() { + public final K getKey() { return get(); } @Override - public int getHash() { + public final int getHash() { return hash; } @Override - public E getNext() { - return next; + public @Nullable E getNext() { + return null; } } /** Concrete implementation of {@link InternalEntry} for weak keys and {@link Dummy} values. */ - static final class WeakKeyDummyValueEntry + private static class WeakKeyDummyValueEntry extends AbstractWeakKeyEntry> implements StrongValueEntry> { - WeakKeyDummyValueEntry( - ReferenceQueue queue, K key, int hash, @NullableDecl WeakKeyDummyValueEntry next) { - super(queue, key, hash, next); + + private WeakKeyDummyValueEntry(ReferenceQueue queue, K key, int hash) { + super(queue, key, hash); } @Override - public Dummy getValue() { + public final Dummy getValue() { return Dummy.VALUE; } - void setValue(Dummy value) {} + private static final class LinkedWeakKeyDummyValueEntry extends WeakKeyDummyValueEntry { + private final WeakKeyDummyValueEntry next; + + private LinkedWeakKeyDummyValueEntry( + ReferenceQueue queue, K key, int hash, WeakKeyDummyValueEntry next) { + super(queue, key, hash); + this.next = next; + } - WeakKeyDummyValueEntry copy( - ReferenceQueue queueForKeys, WeakKeyDummyValueEntry newNext) { - return new WeakKeyDummyValueEntry(queueForKeys, getKey(), this.hash, newNext); + @Override + public WeakKeyDummyValueEntry getNext() { + return next; + } } /** @@ -718,21 +744,21 @@ public Strength valueStrength() { @Override public WeakKeyDummyValueSegment newSegment( MapMakerInternalMap, WeakKeyDummyValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - return new WeakKeyDummyValueSegment(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + return new WeakKeyDummyValueSegment<>(map, initialCapacity); } @Override - public WeakKeyDummyValueEntry copy( + public @Nullable WeakKeyDummyValueEntry copy( WeakKeyDummyValueSegment segment, WeakKeyDummyValueEntry entry, - @NullableDecl WeakKeyDummyValueEntry newNext) { - if (entry.getKey() == null) { + @Nullable WeakKeyDummyValueEntry newNext) { + K key = entry.getKey(); + if (key == null) { // key collected return null; } - return entry.copy(segment.queueForKeys, newNext); + return newEntry(segment, key, entry.hash, newNext); } @Override @@ -744,42 +770,43 @@ public WeakKeyDummyValueEntry newEntry( WeakKeyDummyValueSegment segment, K key, int hash, - @NullableDecl WeakKeyDummyValueEntry next) { - return new WeakKeyDummyValueEntry(segment.queueForKeys, key, hash, next); + @Nullable WeakKeyDummyValueEntry next) { + return next == null + ? new WeakKeyDummyValueEntry<>(segment.queueForKeys, key, hash) + : new LinkedWeakKeyDummyValueEntry<>(segment.queueForKeys, key, hash, next); } } } /** Concrete implementation of {@link InternalEntry} for weak keys and strong values. */ - static final class WeakKeyStrongValueEntry + static class WeakKeyStrongValueEntry extends AbstractWeakKeyEntry> implements StrongValueEntry> { - @NullableDecl private volatile V value = null; + private volatile @Nullable V value = null; - WeakKeyStrongValueEntry( - ReferenceQueue queue, - K key, - int hash, - @NullableDecl WeakKeyStrongValueEntry next) { - super(queue, key, hash, next); + private WeakKeyStrongValueEntry(ReferenceQueue queue, K key, int hash) { + super(queue, key, hash); } @Override - @NullableDecl - public V getValue() { + public final @Nullable V getValue() { return value; } - void setValue(V value) { - this.value = value; - } + private static final class LinkedWeakKeyStrongValueEntry + extends WeakKeyStrongValueEntry { + private final WeakKeyStrongValueEntry next; + + private LinkedWeakKeyStrongValueEntry( + ReferenceQueue queue, K key, int hash, WeakKeyStrongValueEntry next) { + super(queue, key, hash); + this.next = next; + } - WeakKeyStrongValueEntry copy( - ReferenceQueue queueForKeys, WeakKeyStrongValueEntry newNext) { - WeakKeyStrongValueEntry newEntry = - new WeakKeyStrongValueEntry<>(queueForKeys, getKey(), this.hash, newNext); - newEntry.setValue(value); - return newEntry; + @Override + public WeakKeyStrongValueEntry getNext() { + return next; + } } /** Concrete implementation of {@link InternalEntryHelper} for weak keys and strong values. */ @@ -807,27 +834,29 @@ public Strength valueStrength() { public WeakKeyStrongValueSegment newSegment( MapMakerInternalMap, WeakKeyStrongValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - return new WeakKeyStrongValueSegment<>(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + return new WeakKeyStrongValueSegment<>(map, initialCapacity); } @Override - public WeakKeyStrongValueEntry copy( + public @Nullable WeakKeyStrongValueEntry copy( WeakKeyStrongValueSegment segment, WeakKeyStrongValueEntry entry, - @NullableDecl WeakKeyStrongValueEntry newNext) { - if (entry.getKey() == null) { + @Nullable WeakKeyStrongValueEntry newNext) { + K key = entry.getKey(); + if (key == null) { // key collected return null; } - return entry.copy(segment.queueForKeys, newNext); + WeakKeyStrongValueEntry newEntry = newEntry(segment, key, entry.hash, newNext); + newEntry.value = entry.value; + return newEntry; } @Override public void setValue( WeakKeyStrongValueSegment segment, WeakKeyStrongValueEntry entry, V value) { - entry.setValue(value); + entry.value = value; } @Override @@ -835,53 +864,49 @@ public WeakKeyStrongValueEntry newEntry( WeakKeyStrongValueSegment segment, K key, int hash, - @NullableDecl WeakKeyStrongValueEntry next) { - return new WeakKeyStrongValueEntry<>(segment.queueForKeys, key, hash, next); + @Nullable WeakKeyStrongValueEntry next) { + return next == null + ? new WeakKeyStrongValueEntry<>(segment.queueForKeys, key, hash) + : new LinkedWeakKeyStrongValueEntry<>(segment.queueForKeys, key, hash, next); } } } /** Concrete implementation of {@link InternalEntry} for weak keys and weak values. */ - static final class WeakKeyWeakValueEntry + private static class WeakKeyWeakValueEntry extends AbstractWeakKeyEntry> implements WeakValueEntry> { private volatile WeakValueReference> valueReference = unsetWeakValueReference(); - WeakKeyWeakValueEntry( - ReferenceQueue queue, K key, int hash, @NullableDecl WeakKeyWeakValueEntry next) { - super(queue, key, hash, next); + WeakKeyWeakValueEntry(ReferenceQueue queue, K key, int hash) { + super(queue, key, hash); } @Override - public V getValue() { + public final V getValue() { return valueReference.get(); } - WeakKeyWeakValueEntry copy( - ReferenceQueue queueForKeys, - ReferenceQueue queueForValues, - WeakKeyWeakValueEntry newNext) { - WeakKeyWeakValueEntry newEntry = - new WeakKeyWeakValueEntry<>(queueForKeys, getKey(), this.hash, newNext); - newEntry.valueReference = valueReference.copyFor(queueForValues, newEntry); - return newEntry; - } - @Override - public void clearValue() { - valueReference.clear(); + public final WeakValueReference> getValueReference() { + return valueReference; } - void setValue(V value, ReferenceQueue queueForValues) { - WeakValueReference> previous = this.valueReference; - this.valueReference = new WeakValueReferenceImpl<>(queueForValues, value, this); - previous.clear(); - } + private static final class LinkedWeakKeyWeakValueEntry + extends WeakKeyWeakValueEntry { + private final WeakKeyWeakValueEntry next; - @Override - public WeakValueReference> getValueReference() { - return valueReference; + LinkedWeakKeyWeakValueEntry( + ReferenceQueue queue, K key, int hash, WeakKeyWeakValueEntry next) { + super(queue, key, hash); + this.next = next; + } + + @Override + public WeakKeyWeakValueEntry getNext() { + return next; + } } /** Concrete implementation of {@link InternalEntryHelper} for weak keys and weak values. */ @@ -908,30 +933,34 @@ public Strength valueStrength() { @Override public WeakKeyWeakValueSegment newSegment( MapMakerInternalMap, WeakKeyWeakValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - return new WeakKeyWeakValueSegment<>(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + return new WeakKeyWeakValueSegment<>(map, initialCapacity); } @Override - public WeakKeyWeakValueEntry copy( + public @Nullable WeakKeyWeakValueEntry copy( WeakKeyWeakValueSegment segment, WeakKeyWeakValueEntry entry, - @NullableDecl WeakKeyWeakValueEntry newNext) { - if (entry.getKey() == null) { + @Nullable WeakKeyWeakValueEntry newNext) { + K key = entry.getKey(); + if (key == null) { // key collected return null; } if (Segment.isCollected(entry)) { return null; } - return entry.copy(segment.queueForKeys, segment.queueForValues, newNext); + WeakKeyWeakValueEntry newEntry = newEntry(segment, key, entry.hash, newNext); + newEntry.valueReference = entry.valueReference.copyFor(segment.queueForValues, newEntry); + return newEntry; } @Override public void setValue( WeakKeyWeakValueSegment segment, WeakKeyWeakValueEntry entry, V value) { - entry.setValue(value, segment.queueForValues); + WeakValueReference> previous = entry.valueReference; + entry.valueReference = new WeakValueReferenceImpl<>(segment.queueForValues, value, entry); + previous.clear(); } @Override @@ -939,8 +968,10 @@ public WeakKeyWeakValueEntry newEntry( WeakKeyWeakValueSegment segment, K key, int hash, - @NullableDecl WeakKeyWeakValueEntry next) { - return new WeakKeyWeakValueEntry<>(segment.queueForKeys, key, hash, next); + @Nullable WeakKeyWeakValueEntry next) { + return next == null + ? new WeakKeyWeakValueEntry<>(segment.queueForKeys, key, hash) + : new LinkedWeakKeyWeakValueEntry<>(segment.queueForKeys, key, hash, next); } } } @@ -951,8 +982,7 @@ interface WeakValueReference> { * Returns the current value being referenced, or {@code null} if there is none (e.g. because * either it got collected, or {@link #clear} was called, or it wasn't set in the first place). */ - @NullableDecl - V get(); + @Nullable V get(); /** Returns the entry which contains this {@link WeakValueReference}. */ E getEntry(); @@ -962,7 +992,7 @@ interface WeakValueReference> { /** * Returns a freshly created {@link WeakValueReference} for the given {@code entry} (and on the - * given {@code queue} with the same value as this {@link WeakValueReference}. + * given {@code queue}) with the same value as this {@link WeakValueReference}. */ WeakValueReference copyFor(ReferenceQueue queue, E entry); } @@ -999,13 +1029,13 @@ public Object getValue() { } /** - * A singleton {@link WeakValueReference} used to denote an unset value in a entry with weak + * A singleton {@link WeakValueReference} used to denote an unset value in an entry with weak * values. */ static final WeakValueReference UNSET_WEAK_VALUE_REFERENCE = new WeakValueReference() { @Override - public DummyInternalEntry getEntry() { + public @Nullable DummyInternalEntry getEntry() { return null; } @@ -1013,7 +1043,7 @@ public DummyInternalEntry getEntry() { public void clear() {} @Override - public Object get() { + public @Nullable Object get() { return null; } @@ -1058,9 +1088,9 @@ static int rehash(int h) { // using variant of single-word Wang/Jenkins hash. // TODO(kevinb): use Hashing/move this to Hashing? h += (h << 15) ^ 0xffffcd7d; - h ^= (h >>> 10); - h += (h << 3); - h ^= (h >>> 6); + h ^= h >>> 10; + h += h << 3; + h ^= h >>> 6; h += (h << 2) + (h << 14); return h ^ (h >>> 16); } @@ -1111,15 +1141,15 @@ Segment segmentFor(int hash) { return segments[(hash >>> segmentShift) & segmentMask]; } - Segment createSegment(int initialCapacity, int maxSegmentSize) { - return entryHelper.newSegment(this, initialCapacity, maxSegmentSize); + Segment createSegment(int initialCapacity) { + return entryHelper.newSegment(this, initialCapacity); } /** * Gets the value from an entry. Returns {@code null} if the entry is invalid, partially-collected * or computing. */ - V getLiveValue(E entry) { + @Nullable V getLiveValue(E entry) { if (entry.getKey() == null) { return null; } @@ -1128,7 +1158,7 @@ V getLiveValue(E entry) { @SuppressWarnings("unchecked") final Segment[] newSegmentArray(int ssize) { - return new Segment[ssize]; + return (Segment[]) new Segment[ssize]; } // Inner Classes @@ -1193,10 +1223,7 @@ abstract static class Segment< int threshold; /** The per-segment table. */ - @NullableDecl volatile AtomicReferenceArray table; - - /** The maximum size of this map. MapMaker.UNSET_INT if there is no maximum. */ - final int maxSegmentSize; + volatile @Nullable AtomicReferenceArray table; /** * A counter of the number of reads since the last write, used to drain queues on a small @@ -1204,9 +1231,8 @@ abstract static class Segment< */ final AtomicInteger readCount = new AtomicInteger(); - Segment(MapMakerInternalMap map, int initialCapacity, int maxSegmentSize) { + Segment(MapMakerInternalMap map, int initialCapacity) { this.map = map; - this.maxSegmentSize = maxSegmentSize; initTable(newEntryArray(initialCapacity)); } @@ -1231,20 +1257,16 @@ void setValue(E entry, V value) { } /** Returns a copy of the given {@code entry}. */ - E copyEntry(E original, E newNext) { + @Nullable E copyEntry(E original, E newNext) { return this.map.entryHelper.copy(self(), original, newNext); } AtomicReferenceArray newEntryArray(int size) { - return new AtomicReferenceArray(size); + return new AtomicReferenceArray<>(size); } void initTable(AtomicReferenceArray newTable) { this.threshold = newTable.length() * 3 / 4; // 0.75 - if (this.threshold == maxSegmentSize) { - // prevent spurious expansion before eviction - this.threshold++; - } this.table = newTable; } @@ -1255,7 +1277,7 @@ void initTable(AtomicReferenceArray newTable) { * implementation type. * *

    This method is provided as a convenience for tests. Otherwise they'd need to be - * knowledgable about all the implementation details of our type system trickery. + * knowledgeable about all the implementation details of our type system trickery. */ abstract E castForTesting(InternalEntry entry); @@ -1301,7 +1323,7 @@ void setTableEntryForTesting(int i, InternalEntry entry) { } /** Unsafely returns a copy of the given entry. */ - E copyForTesting(InternalEntry entry, @NullableDecl InternalEntry newNext) { + E copyForTesting(InternalEntry entry, @Nullable InternalEntry newNext) { return this.map.entryHelper.copy(self(), castForTesting(entry), castForTesting(newNext)); } @@ -1311,7 +1333,7 @@ void setValueForTesting(InternalEntry entry, V value) { } /** Unsafely returns a fresh entry. */ - E newEntryForTesting(K key, int hash, @NullableDecl InternalEntry next) { + E newEntryForTesting(K key, int hash, @Nullable InternalEntry next) { return this.map.entryHelper.newEntry(self(), key, hash, castForTesting(next)); } @@ -1322,15 +1344,15 @@ boolean removeTableEntryForTesting(InternalEntry entry) { } /** Unsafely removes the given entry from the given chain in this segment's hash table. */ - E removeFromChainForTesting(InternalEntry first, InternalEntry entry) { + @Nullable E removeFromChainForTesting( + InternalEntry first, InternalEntry entry) { return removeFromChain(castForTesting(first), castForTesting(entry)); } /** * Unsafely returns the value of the given entry if it's still live, or {@code null} otherwise. */ - @NullableDecl - V getLiveValueForTesting(InternalEntry entry) { + @Nullable V getLiveValueForTesting(InternalEntry entry) { return getLiveValue(castForTesting(entry)); } @@ -1380,7 +1402,7 @@ void clearReferenceQueue(ReferenceQueue referenceQueue) { } /** Returns first entry of bin for given hash. */ - E getFirst(int hash) { + @Nullable E getFirst(int hash) { // read this volatile field only once AtomicReferenceArray table = this.table; return table.get(hash & (table.length() - 1)); @@ -1388,7 +1410,7 @@ E getFirst(int hash) { // Specialized implementations of map methods - E getEntry(Object key, int hash) { + @Nullable E getEntry(Object key, int hash) { if (count != 0) { // read-volatile for (E e = getFirst(hash); e != null; e = e.getNext()) { if (e.getHash() != hash) { @@ -1410,11 +1432,11 @@ E getEntry(Object key, int hash) { return null; } - E getLiveEntry(Object key, int hash) { + @Nullable E getLiveEntry(Object key, int hash) { return getEntry(key, hash); } - V get(Object key, int hash) { + @Nullable V get(Object key, int hash) { try { E e = getLiveEntry(key, hash); if (e == null) { @@ -1473,7 +1495,7 @@ boolean containsValue(Object value) { } } - V put(K key, int hash, V value, boolean onlyIfAbsent) { + @Nullable V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); try { preWriteCleanup(); @@ -1646,7 +1668,7 @@ boolean replace(K key, int hash, V oldValue, V newValue) { } } - V replace(K key, int hash, V newValue) { + @Nullable V replace(K key, int hash, V newValue) { lock(); try { preWriteCleanup(); @@ -1688,7 +1710,7 @@ V replace(K key, int hash, V newValue) { } @CanIgnoreReturnValue - V remove(Object key, int hash) { + @Nullable V remove(Object key, int hash) { lock(); try { preWriteCleanup(); @@ -1801,7 +1823,7 @@ void clear() { * @return the new first entry for the table */ @GuardedBy("this") - E removeFromChain(E first, E entry) { + @Nullable E removeFromChain(E first, E entry) { int newCount = count; E newFirst = entry.getNext(); for (E e = first; e != entry; e = e.getNext()) { @@ -1944,8 +1966,7 @@ static > boolean isCollected(E entry) { * Gets the value from an entry. Returns {@code null} if the entry is invalid or * partially-collected. */ - @NullableDecl - V getLiveValue(E entry) { + @Nullable V getLiveValue(E entry) { if (entry.getKey() == null) { tryDrainReferenceQueues(); return null; @@ -2002,9 +2023,8 @@ static final class StrongKeyStrongValueSegment MapMakerInternalMap< K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - super(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + super(map, initialCapacity); } @Override @@ -2014,7 +2034,8 @@ StrongKeyStrongValueSegment self() { @SuppressWarnings("unchecked") @Override - public StrongKeyStrongValueEntry castForTesting(InternalEntry entry) { + public @Nullable StrongKeyStrongValueEntry castForTesting( + @Nullable InternalEntry entry) { return (StrongKeyStrongValueEntry) entry; } } @@ -2022,14 +2043,13 @@ public StrongKeyStrongValueEntry castForTesting(InternalEntry ent /** Concrete implementation of {@link Segment} for strong keys and weak values. */ static final class StrongKeyWeakValueSegment extends Segment, StrongKeyWeakValueSegment> { - private final ReferenceQueue queueForValues = new ReferenceQueue(); + private final ReferenceQueue queueForValues = new ReferenceQueue<>(); StrongKeyWeakValueSegment( MapMakerInternalMap, StrongKeyWeakValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - super(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + super(map, initialCapacity); } @Override @@ -2044,7 +2064,8 @@ ReferenceQueue getValueReferenceQueueForTesting() { @SuppressWarnings("unchecked") @Override - public StrongKeyWeakValueEntry castForTesting(InternalEntry entry) { + public @Nullable StrongKeyWeakValueEntry castForTesting( + @Nullable InternalEntry entry) { return (StrongKeyWeakValueEntry) entry; } @@ -2090,9 +2111,8 @@ static final class StrongKeyDummyValueSegment StrongKeyDummyValueSegment( MapMakerInternalMap, StrongKeyDummyValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - super(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + super(map, initialCapacity); } @Override @@ -2110,14 +2130,13 @@ public StrongKeyDummyValueEntry castForTesting(InternalEntry ent /** Concrete implementation of {@link Segment} for weak keys and strong values. */ static final class WeakKeyStrongValueSegment extends Segment, WeakKeyStrongValueSegment> { - private final ReferenceQueue queueForKeys = new ReferenceQueue(); + private final ReferenceQueue queueForKeys = new ReferenceQueue<>(); WeakKeyStrongValueSegment( MapMakerInternalMap, WeakKeyStrongValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - super(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + super(map, initialCapacity); } @Override @@ -2150,14 +2169,13 @@ void maybeClearReferenceQueues() { /** Concrete implementation of {@link Segment} for weak keys and weak values. */ static final class WeakKeyWeakValueSegment extends Segment, WeakKeyWeakValueSegment> { - private final ReferenceQueue queueForKeys = new ReferenceQueue(); - private final ReferenceQueue queueForValues = new ReferenceQueue(); + private final ReferenceQueue queueForKeys = new ReferenceQueue<>(); + private final ReferenceQueue queueForValues = new ReferenceQueue<>(); WeakKeyWeakValueSegment( MapMakerInternalMap, WeakKeyWeakValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - super(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + super(map, initialCapacity); } @Override @@ -2177,7 +2195,8 @@ ReferenceQueue getValueReferenceQueueForTesting() { @SuppressWarnings("unchecked") @Override - public WeakKeyWeakValueEntry castForTesting(InternalEntry entry) { + public @Nullable WeakKeyWeakValueEntry castForTesting( + @Nullable InternalEntry entry) { return (WeakKeyWeakValueEntry) entry; } @@ -2221,13 +2240,12 @@ void maybeClearReferenceQueues() { /** Concrete implementation of {@link Segment} for weak keys and {@link Dummy} values. */ static final class WeakKeyDummyValueSegment extends Segment, WeakKeyDummyValueSegment> { - private final ReferenceQueue queueForKeys = new ReferenceQueue(); + private final ReferenceQueue queueForKeys = new ReferenceQueue<>(); WeakKeyDummyValueSegment( MapMakerInternalMap, WeakKeyDummyValueSegment> map, - int initialCapacity, - int maxSegmentSize) { - super(map, initialCapacity, maxSegmentSize); + int initialCapacity) { + super(map, initialCapacity); } @Override @@ -2261,7 +2279,7 @@ static final class CleanupMapTask implements Runnable { final WeakReference> mapReference; public CleanupMapTask(MapMakerInternalMap map) { - this.mapReference = new WeakReference>(map); + this.mapReference = new WeakReference<>(map); } @Override @@ -2335,7 +2353,7 @@ public int size() { } @Override - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { if (key == null) { return null; } @@ -2347,7 +2365,7 @@ public V get(@NullableDecl Object key) { * Returns the internal entry for the specified key. The entry may be computing or partially * collected. Does not impact recency ordering. */ - E getEntry(@NullableDecl Object key) { + @Nullable E getEntry(@Nullable Object key) { if (key == null) { return null; } @@ -2356,7 +2374,7 @@ E getEntry(@NullableDecl Object key) { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { if (key == null) { return false; } @@ -2365,7 +2383,7 @@ public boolean containsKey(@NullableDecl Object key) { } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { if (value == null) { return false; } @@ -2375,7 +2393,7 @@ public boolean containsValue(@NullableDecl Object value) { // such that none of the subsequent iterations observed it, despite the fact that at every point // in time it was present somewhere int the map. This becomes increasingly unlikely as // CONTAINS_VALUE_RETRIES increases, though without locking it is theoretically possible. - final Segment[] segments = this.segments; + Segment[] segments = this.segments; long last = -1L; for (int i = 0; i < CONTAINS_VALUE_RETRIES; i++) { long sum = 0L; @@ -2404,7 +2422,7 @@ public boolean containsValue(@NullableDecl Object value) { @CanIgnoreReturnValue @Override - public V put(K key, V value) { + public @Nullable V put(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); @@ -2413,7 +2431,7 @@ public V put(K key, V value) { @CanIgnoreReturnValue @Override - public V putIfAbsent(K key, V value) { + public @Nullable V putIfAbsent(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); @@ -2429,7 +2447,7 @@ public void putAll(Map m) { @CanIgnoreReturnValue @Override - public V remove(@NullableDecl Object key) { + public @Nullable V remove(@Nullable Object key) { if (key == null) { return null; } @@ -2439,7 +2457,7 @@ public V remove(@NullableDecl Object key) { @CanIgnoreReturnValue @Override - public boolean remove(@NullableDecl Object key, @NullableDecl Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { if (key == null || value == null) { return false; } @@ -2449,7 +2467,7 @@ public boolean remove(@NullableDecl Object key, @NullableDecl Object value) { @CanIgnoreReturnValue @Override - public boolean replace(K key, @NullableDecl V oldValue, V newValue) { + public boolean replace(K key, @Nullable V oldValue, V newValue) { checkNotNull(key); checkNotNull(newValue); if (oldValue == null) { @@ -2461,7 +2479,7 @@ public boolean replace(K key, @NullableDecl V oldValue, V newValue) { @CanIgnoreReturnValue @Override - public V replace(K key, V value) { + public @Nullable V replace(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); @@ -2475,7 +2493,7 @@ public void clear() { } } - @NullableDecl transient Set keySet; + @LazyInit transient @Nullable Set keySet; @Override public Set keySet() { @@ -2483,7 +2501,7 @@ public Set keySet() { return (ks != null) ? ks : (keySet = new KeySet()); } - @NullableDecl transient Collection values; + @LazyInit transient @Nullable Collection values; @Override public Collection values() { @@ -2491,7 +2509,7 @@ public Collection values() { return (vs != null) ? vs : (values = new Values()); } - @NullableDecl transient Set> entrySet; + @LazyInit transient @Nullable Set> entrySet; @Override public Set> entrySet() { @@ -2505,11 +2523,11 @@ abstract class HashIterator implements Iterator { int nextSegmentIndex; int nextTableIndex; - @NullableDecl Segment currentSegment; - @NullableDecl AtomicReferenceArray currentTable; - @NullableDecl E nextEntry; - @NullableDecl WriteThroughEntry nextExternal; - @NullableDecl WriteThroughEntry lastReturned; + @Nullable Segment currentSegment; + @Nullable AtomicReferenceArray currentTable; + @Nullable E nextEntry; + @Nullable WriteThroughEntry nextExternal; + @Nullable WriteThroughEntry lastReturned; HashIterator() { nextSegmentIndex = segments.length - 1; @@ -2629,46 +2647,20 @@ public V next() { * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the underlying * map. */ - final class WriteThroughEntry extends AbstractMapEntry { - final K key; // non-null - V value; // non-null - + final class WriteThroughEntry extends SimpleEntry { WriteThroughEntry(K key, V value) { - this.key = key; - this.value = value; + super(key, value); } - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public boolean equals(@NullableDecl Object object) { - // Cannot use key and value equivalence - if (object instanceof Entry) { - Entry that = (Entry) object; - return key.equals(that.getKey()) && value.equals(that.getValue()); - } - return false; - } - - @Override - public int hashCode() { - // Cannot use key and value equivalence - return key.hashCode() ^ value.hashCode(); - } + /* + * We inherit equals() and hashCode() instead of overriding them to use keyEquivalence and + * valueEquivalence. + */ @Override public V setValue(V newValue) { - V oldValue = put(key, newValue); - value = newValue; // only if put succeeds - return oldValue; + put(getKey(), newValue); + return super.setValue(newValue); // done after put() so that it happens only if put() succeeds } } @@ -2681,7 +2673,7 @@ public Entry next() { } @WeakOuter - final class KeySet extends SafeToArraySet { + final class KeySet extends AbstractSet { @Override public Iterator iterator() { @@ -2741,23 +2733,10 @@ public boolean contains(Object o) { public void clear() { MapMakerInternalMap.this.clear(); } - - // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. - // https://code.google.com/p/android/issues/detail?id=36519 / http://r.android.com/47508 - - @Override - public Object[] toArray() { - return toArrayList(this).toArray(); - } - - @Override - public T[] toArray(T[] a) { - return toArrayList(this).toArray(a); - } } @WeakOuter - final class EntrySet extends SafeToArraySet> { + final class EntrySet extends AbstractSet> { @Override public Iterator> iterator() { @@ -2805,28 +2784,6 @@ public void clear() { } } - private abstract static class SafeToArraySet extends AbstractSet { - // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. - // https://code.google.com/p/android/issues/detail?id=36519 / http://r.android.com/47508 - - @Override - public Object[] toArray() { - return toArrayList(this).toArray(); - } - - @Override - public T[] toArray(T[] a) { - return toArrayList(this).toArray(a); - } - } - - private static ArrayList toArrayList(Collection c) { - // Avoid calling ArrayList(Collection), which may call back into toArray. - ArrayList result = new ArrayList<>(c.size()); - Iterators.addAll(result, c.iterator()); - return result; - } - // Serialization Support private static final long serialVersionUID = 5; @@ -2841,6 +2798,11 @@ Object writeReplace() { this); } + @J2ktIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream in) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializationProxy"); + } + /** * The actual object that gets serialized. Unfortunately, readResolve() doesn't get called when a * circular dependency is present, so the proxy must be able to behave as the map itself. @@ -2886,7 +2848,7 @@ void writeMapTo(ObjectOutputStream out) throws IOException { out.writeObject(null); // terminate entries } - @SuppressWarnings("deprecation") // serialization of deprecated feature + @J2ktIncompatible // java.io.ObjectInputStream MapMaker readMapMaker(ObjectInputStream in) throws IOException { int size = in.readInt(); return new MapMaker() @@ -2898,6 +2860,7 @@ MapMaker readMapMaker(ObjectInputStream in) throws IOException { } @SuppressWarnings("unchecked") + @J2ktIncompatible // java.io.ObjectInputStream void readEntries(ObjectInputStream in) throws IOException, ClassNotFoundException { while (true) { K key = (K) in.readObject(); @@ -2933,6 +2896,7 @@ private void writeObject(ObjectOutputStream out) throws IOException { writeMapTo(out); } + @J2ktIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); MapMaker mapMaker = readMapMaker(in); diff --git a/android/guava/src/com/google/common/collect/Maps.java b/android/guava/src/com/google/common/collect/Maps.java index f0bac2ad0196..27ad271e917a 100644 --- a/android/guava/src/com/google/common/collect/Maps.java +++ b/android/guava/src/com/google/common/collect/Maps.java @@ -21,32 +21,38 @@ import static com.google.common.base.Predicates.compose; import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.lang.Math.ceil; +import static java.util.Collections.singletonMap; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; import com.google.common.base.Equivalence; import com.google.common.base.Function; -import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.MapDifference.ValueDifference; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedWith; import com.google.j2objc.annotations.Weak; import com.google.j2objc.annotations.WeakOuter; import java.io.Serializable; import java.util.AbstractCollection; import java.util.AbstractMap; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EnumMap; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -54,6 +60,7 @@ import java.util.Map.Entry; import java.util.NavigableMap; import java.util.NavigableSet; +import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.SortedMap; @@ -61,7 +68,10 @@ import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.BinaryOperator; +import java.util.stream.Collector; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@link Map} instances (including instances of {@link @@ -69,7 +79,7 @@ * and {@link Queues}. * *

    See the Guava User Guide article on {@code Maps}. + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#maps">{@code Maps}. * * @author Kevin Bourrillion * @author Mike Bostock @@ -77,49 +87,26 @@ * @author Louis Wasserman * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Maps { private Maps() {} - private enum EntryFunction implements Function, Object> { - KEY { - @Override - @NullableDecl - public Object apply(Entry entry) { - return entry.getKey(); - } - }, - VALUE { - @Override - @NullableDecl - public Object apply(Entry entry) { - return entry.getValue(); - } - }; - } - - @SuppressWarnings("unchecked") - static Function, K> keyFunction() { - return (Function) EntryFunction.KEY; - } - - @SuppressWarnings("unchecked") - static Function, V> valueFunction() { - return (Function) EntryFunction.VALUE; - } - - static Iterator keyIterator(Iterator> entryIterator) { + static Iterator keyIterator( + Iterator> entryIterator) { return new TransformedIterator, K>(entryIterator) { @Override + @ParametricNullness K transform(Entry entry) { return entry.getKey(); } }; } - static Iterator valueIterator(Iterator> entryIterator) { + static Iterator valueIterator( + Iterator> entryIterator) { return new TransformedIterator, V>(entryIterator) { @Override + @ParametricNullness V transform(Entry entry) { return entry.getValue(); } @@ -137,7 +124,6 @@ V transform(Entry entry) { * @return an immutable map containing those entries * @since 14.0 */ - @GwtCompatible(serializable = true) public static , V> ImmutableMap immutableEnumMap( Map map) { if (map instanceof ImmutableEnumMap) { @@ -153,9 +139,8 @@ public static , V> ImmutableMap immutableEnumMap( K key1 = entry1.getKey(); V value1 = entry1.getValue(); checkEntryNotNull(key1, value1); - Class clazz = key1.getDeclaringClass(); - EnumMap enumMap = new EnumMap<>(clazz); - enumMap.put(key1, value1); + // Do something that works for j2cl, where we can't call getDeclaredClass(): + EnumMap enumMap = new EnumMap<>(singletonMap(key1, value1)); while (entryItr.hasNext()) { Entry entry = entryItr.next(); K key = entry.getKey(); @@ -166,6 +151,48 @@ public static , V> ImmutableMap immutableEnumMap( return ImmutableEnumMap.asImmutable(enumMap); } + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. The + * resulting implementation is specialized for enum key types. The returned map and its views will + * iterate over keys in their enum definition order, not encounter order. + * + *

    If the mapped keys contain duplicates, an {@code IllegalArgumentException} is thrown when + * the collection operation is performed. (This differs from the {@code Collector} returned by + * {@link java.util.stream.Collectors#toMap(java.util.function.Function, + * java.util.function.Function) Collectors.toMap(Function, Function)}, which throws an {@code + * IllegalStateException}.) + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static , V> + Collector> toImmutableEnumMap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction) { + return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. The + * resulting implementation is specialized for enum key types. The returned map and its views will + * iterate over keys in their enum definition order, not encounter order. + * + *

    If the mapped keys contain duplicates, the values are merged using the specified merging + * function. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static , V> + Collector> toImmutableEnumMap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction, + BinaryOperator mergeFunction) { + return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction, mergeFunction); + } + /** * Creates a mutable, empty {@code HashMap} instance. * @@ -173,13 +200,16 @@ public static , V> ImmutableMap immutableEnumMap( * *

    Note: if {@code K} is an {@code enum} type, use {@link #newEnumMap} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code HashMap} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code HashMap} constructor directly, taking advantage of "diamond" + * syntax. * * @return a new, empty {@code HashMap} */ - public static HashMap newHashMap() { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static + HashMap newHashMap() { return new HashMap<>(); } @@ -190,14 +220,17 @@ public static HashMap newHashMap() { * *

    Note: if {@code K} is an {@link Enum} type, use {@link #newEnumMap} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code HashMap} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code HashMap} constructor directly, taking advantage of "diamond" + * syntax. * * @param map the mappings to be placed in the new map * @return a new {@code HashMap} initialized with the mappings from {@code map} */ - public static HashMap newHashMap(Map map) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static HashMap newHashMap( + Map map) { return new HashMap<>(map); } @@ -212,7 +245,9 @@ public static HashMap newHashMap(Map map) * without resizing * @throws IllegalArgumentException if {@code expectedSize} is negative */ - public static HashMap newHashMapWithExpectedSize(int expectedSize) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static + HashMap newHashMapWithExpectedSize(int expectedSize) { return new HashMap<>(capacity(expectedSize)); } @@ -226,10 +261,19 @@ static int capacity(int expectedSize) { return expectedSize + 1; } if (expectedSize < Ints.MAX_POWER_OF_TWO) { - // This is the calculation used in JDK8 to resize when a putAll - // happens; it seems to be the most conservative calculation we - // can make. 0.75 is the default load factor. - return (int) ((float) expectedSize / 0.75F + 1.0F); + // This seems to be consistent across JDKs. The capacity argument to HashMap and LinkedHashMap + // ends up being used to compute a "threshold" size, beyond which the internal table + // will be resized. That threshold is ceilingPowerOfTwo(capacity*loadFactor), where + // loadFactor is 0.75 by default. So with the calculation here we ensure that the + // threshold is equal to ceilingPowerOfTwo(expectedSize). There is a separate code + // path when the first operation on the new map is putAll(otherMap). There, prior to + // https://github.com/openjdk/jdk/commit/3e393047e12147a81e2899784b943923fc34da8e, a bug + // meant that sometimes a too-large threshold is calculated. However, this new threshold is + // independent of the initial capacity, except that it won't be lower than the threshold + // computed from that capacity. Because the internal table is only allocated on the first + // write, we won't see copying because of the new threshold. So it is always OK to use the + // calculation here. + return (int) ceil(expectedSize / 0.75); } return Integer.MAX_VALUE; // any large value } @@ -239,13 +283,16 @@ static int capacity(int expectedSize) { * *

    Note: if mutability is not required, use {@link ImmutableMap#of()} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code LinkedHashMap} constructor directly, taking advantage of - * the new "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code LinkedHashMap} constructor directly, taking advantage of "diamond" + * syntax. * * @return a new, empty {@code LinkedHashMap} */ - public static LinkedHashMap newLinkedHashMap() { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static + LinkedHashMap newLinkedHashMap() { return new LinkedHashMap<>(); } @@ -255,14 +302,17 @@ public static LinkedHashMap newLinkedHashMap() { * *

    Note: if mutability is not required, use {@link ImmutableMap#copyOf(Map)} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code LinkedHashMap} constructor directly, taking advantage of - * the new "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code LinkedHashMap} constructor directly, taking advantage of "diamond" + * syntax. * * @param map the mappings to be placed in the new map * @return a new, {@code LinkedHashMap} initialized with the mappings from {@code map} */ - public static LinkedHashMap newLinkedHashMap(Map map) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static + LinkedHashMap newLinkedHashMap(Map map) { return new LinkedHashMap<>(map); } @@ -278,7 +328,9 @@ public static LinkedHashMap newLinkedHashMap(Map LinkedHashMap newLinkedHashMapWithExpectedSize(int expectedSize) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static + LinkedHashMap newLinkedHashMapWithExpectedSize(int expectedSize) { return new LinkedHashMap<>(capacity(expectedSize)); } @@ -297,13 +349,18 @@ public static ConcurrentMap newConcurrentMap() { * *

    Note: if mutability is not required, use {@link ImmutableSortedMap#of()} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code TreeMap} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code TreeMap} constructor directly, taking advantage of "diamond" + * syntax. * * @return a new, empty {@code TreeMap} */ - public static TreeMap newTreeMap() { + @SuppressWarnings({ + "rawtypes", // https://github.com/google/guava/issues/989 + "NonApiType", // acts as a direct substitute for a constructor call + }) + public static TreeMap newTreeMap() { return new TreeMap<>(); } @@ -314,16 +371,19 @@ public static TreeMap newTreeMap() { *

    Note: if mutability is not required, use {@link * ImmutableSortedMap#copyOfSorted(SortedMap)} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code TreeMap} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code TreeMap} constructor directly, taking advantage of "diamond" + * syntax. * * @param map the sorted map whose mappings are to be placed in the new map and whose comparator * is to be used to sort the new map * @return a new {@code TreeMap} initialized with the mappings from {@code map} and using the * comparator of {@code map} */ - public static TreeMap newTreeMap(SortedMap map) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static TreeMap newTreeMap( + SortedMap map) { return new TreeMap<>(map); } @@ -333,15 +393,17 @@ public static TreeMap newTreeMap(SortedMap map) { *

    Note: if mutability is not required, use {@code * ImmutableSortedMap.orderedBy(comparator).build()} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code TreeMap} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code TreeMap} constructor directly, taking advantage of "diamond" + * syntax. * * @param comparator the comparator to sort the keys with * @return a new, empty {@code TreeMap} */ - public static TreeMap newTreeMap( - @NullableDecl Comparator comparator) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static + TreeMap newTreeMap(@Nullable Comparator comparator) { // Ideally, the extra type parameter "C" shouldn't be necessary. It is a // work-around of a compiler type inference quirk that prevents the // following code from being compiled: @@ -356,36 +418,41 @@ public static TreeMap newTreeMap( * @param type the key type for this map * @return a new, empty {@code EnumMap} */ - public static , V> EnumMap newEnumMap(Class type) { + public static , V extends @Nullable Object> EnumMap newEnumMap( + Class type) { return new EnumMap<>(checkNotNull(type)); } /** * Creates an {@code EnumMap} with the same mappings as the specified map. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code EnumMap} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code EnumMap} constructor directly, taking advantage of "diamond" + * syntax. * * @param map the map from which to initialize this {@code EnumMap} * @return a new {@code EnumMap} initialized with the mappings from {@code map} * @throws IllegalArgumentException if {@code m} is not an {@code EnumMap} instance and contains * no mappings */ - public static , V> EnumMap newEnumMap(Map map) { + public static , V extends @Nullable Object> EnumMap newEnumMap( + Map map) { return new EnumMap<>(map); } /** * Creates an {@code IdentityHashMap} instance. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code IdentityHashMap} constructor directly, taking advantage of - * the new "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code IdentityHashMap} constructor directly, taking advantage of "diamond" + * syntax. * * @return a new, empty {@code IdentityHashMap} */ - public static IdentityHashMap newIdentityHashMap() { + public static + IdentityHashMap newIdentityHashMap() { return new IdentityHashMap<>(); } @@ -404,10 +471,11 @@ public static IdentityHashMap newIdentityHashMap() { * @param right the map to treat as the "right" map for purposes of comparison * @return the difference between the two maps */ - @SuppressWarnings("unchecked") - public static MapDifference difference( - Map left, Map right) { + public static + MapDifference difference( + Map left, Map right) { if (left instanceof SortedMap) { + @SuppressWarnings("unchecked") SortedMap sortedLeft = (SortedMap) left; return difference(sortedLeft, right); } @@ -428,16 +496,17 @@ public static MapDifference difference( * @return the difference between the two maps * @since 10.0 */ - public static MapDifference difference( - Map left, - Map right, - Equivalence valueEquivalence) { + public static + MapDifference difference( + Map left, + Map right, + Equivalence valueEquivalence) { Preconditions.checkNotNull(valueEquivalence); - Map onlyOnLeft = newLinkedHashMap(); + Map onlyOnLeft = new LinkedHashMap<>(); Map onlyOnRight = new LinkedHashMap<>(right); // will whittle it down - Map onBoth = newLinkedHashMap(); - Map> differences = newLinkedHashMap(); + Map onBoth = new LinkedHashMap<>(); + Map> differences = new LinkedHashMap<>(); doDifference(left, right, valueEquivalence, onlyOnLeft, onlyOnRight, onBoth, differences); return new MapDifferenceImpl<>(onlyOnLeft, onlyOnRight, onBoth, differences); } @@ -459,33 +528,45 @@ public static MapDifference difference( * @return the difference between the two maps * @since 11.0 */ - public static SortedMapDifference difference( - SortedMap left, Map right) { + public static + SortedMapDifference difference( + SortedMap left, Map right) { checkNotNull(left); checkNotNull(right); Comparator comparator = orNaturalOrder(left.comparator()); - SortedMap onlyOnLeft = Maps.newTreeMap(comparator); - SortedMap onlyOnRight = Maps.newTreeMap(comparator); + SortedMap onlyOnLeft = newTreeMap(comparator); + SortedMap onlyOnRight = newTreeMap(comparator); onlyOnRight.putAll(right); // will whittle it down - SortedMap onBoth = Maps.newTreeMap(comparator); - SortedMap> differences = Maps.newTreeMap(comparator); + SortedMap onBoth = newTreeMap(comparator); + SortedMap> differences = newTreeMap(comparator); + doDifference(left, right, Equivalence.equals(), onlyOnLeft, onlyOnRight, onBoth, differences); return new SortedMapDifferenceImpl<>(onlyOnLeft, onlyOnRight, onBoth, differences); } - private static void doDifference( + private static void doDifference( Map left, Map right, - Equivalence valueEquivalence, + Equivalence valueEquivalence, Map onlyOnLeft, Map onlyOnRight, Map onBoth, - Map> differences) { + Map> differences) { for (Entry entry : left.entrySet()) { K leftKey = entry.getKey(); V leftValue = entry.getValue(); if (right.containsKey(leftKey)) { - V rightValue = onlyOnRight.remove(leftKey); + /* + * The cast is safe because onlyOnRight contains all the keys of right. + * + * TODO(cpovirk): Consider checking onlyOnRight.containsKey instead of right.containsKey. + * That could change behavior if the input maps use different equivalence relations (and so + * a key that appears once in `right` might appear multiple times in `left`). We don't + * guarantee behavior in that case, anyway, and the current behavior is likely undesirable. + * So that's either a reason to feel free to change it or a reason to not bother thinking + * further about this. + */ + V rightValue = uncheckedCastNullableTToT(onlyOnRight.remove(leftKey)); if (valueEquivalence.equivalent(leftValue, rightValue)) { onBoth.put(leftKey, leftValue); } else { @@ -497,7 +578,8 @@ private static void doDifference( } } - private static Map unmodifiableMap(Map map) { + private static Map unmodifiableMap( + Map map) { if (map instanceof SortedMap) { return Collections.unmodifiableSortedMap((SortedMap) map); } else { @@ -505,7 +587,8 @@ private static Map unmodifiableMap(Map map) { } } - static class MapDifferenceImpl implements MapDifference { + private static class MapDifferenceImpl + implements MapDifference { final Map onlyOnLeft; final Map onlyOnRight; final Map onBoth; @@ -548,7 +631,7 @@ public Map> entriesDiffering() { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -564,7 +647,7 @@ && entriesInCommon().equals(other.entriesInCommon()) @Override public int hashCode() { - return Objects.hashCode( + return Objects.hash( entriesOnlyOnLeft(), entriesOnlyOnRight(), entriesInCommon(), entriesDiffering()); } @@ -588,42 +671,45 @@ public String toString() { } } - static class ValueDifferenceImpl implements MapDifference.ValueDifference { - @NullableDecl private final V left; - @NullableDecl private final V right; + static final class ValueDifferenceImpl implements ValueDifference { + @ParametricNullness private final V left; + @ParametricNullness private final V right; - static ValueDifference create(@NullableDecl V left, @NullableDecl V right) { - return new ValueDifferenceImpl(left, right); + static ValueDifference create( + @ParametricNullness V left, @ParametricNullness V right) { + return new ValueDifferenceImpl<>(left, right); } - private ValueDifferenceImpl(@NullableDecl V left, @NullableDecl V right) { + private ValueDifferenceImpl(@ParametricNullness V left, @ParametricNullness V right) { this.left = left; this.right = right; } @Override + @ParametricNullness public V leftValue() { return left; } @Override + @ParametricNullness public V rightValue() { return right; } @Override - public boolean equals(@NullableDecl Object object) { - if (object instanceof MapDifference.ValueDifference) { - MapDifference.ValueDifference that = (MapDifference.ValueDifference) object; - return Objects.equal(this.left, that.leftValue()) - && Objects.equal(this.right, that.rightValue()); + public boolean equals(@Nullable Object object) { + if (object instanceof ValueDifference) { + ValueDifference that = (ValueDifference) object; + return Objects.equals(this.left, that.leftValue()) + && Objects.equals(this.right, that.rightValue()); } return false; } @Override public int hashCode() { - return Objects.hashCode(left, right); + return Objects.hash(left, right); } @Override @@ -632,8 +718,9 @@ public String toString() { } } - static class SortedMapDifferenceImpl extends MapDifferenceImpl - implements SortedMapDifference { + private static final class SortedMapDifferenceImpl< + K extends @Nullable Object, V extends @Nullable Object> + extends MapDifferenceImpl implements SortedMapDifference { SortedMapDifferenceImpl( SortedMap onlyOnLeft, SortedMap onlyOnRight, @@ -669,7 +756,8 @@ public SortedMap entriesOnlyOnRight() { * ugly type-casting in one place. */ @SuppressWarnings("unchecked") - static Comparator orNaturalOrder(@NullableDecl Comparator comparator) { + static Comparator orNaturalOrder( + @Nullable Comparator comparator) { if (comparator != null) { // can't use ? : because of javac bug 5080917 return comparator; } @@ -700,7 +788,8 @@ static Comparator orNaturalOrder(@NullableDecl Comparator Map asMap(Set set, Function function) { + public static Map asMap( + Set set, Function function) { return new AsMapView<>(set, function); } @@ -727,7 +816,8 @@ public static Map asMap(Set set, Function function * * @since 14.0 */ - public static SortedMap asMap(SortedSet set, Function function) { + public static SortedMap asMap( + SortedSet set, Function function) { return new SortedAsMapView<>(set, function); } @@ -755,12 +845,13 @@ public static SortedMap asMap(SortedSet set, Function NavigableMap asMap( + public static NavigableMap asMap( NavigableSet set, Function function) { return new NavigableAsMapView<>(set, function); } - private static class AsMapView extends ViewCachingAbstractMap { + private static class AsMapView + extends ViewCachingAbstractMap { private final Set set; final Function function; @@ -790,12 +881,12 @@ public int size() { } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return backingSet().contains(key); } @Override - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { if (Collections2.safeContains(backingSet(), key)) { @SuppressWarnings("unchecked") // unsafe, but Javadoc warns about it K k = (K) key; @@ -806,7 +897,7 @@ public V get(@NullableDecl Object key) { } @Override - public V remove(@NullableDecl Object key) { + public @Nullable V remove(@Nullable Object key) { if (backingSet().remove(key)) { @SuppressWarnings("unchecked") // unsafe, but Javadoc warns about it K k = (K) key; @@ -824,7 +915,7 @@ public void clear() { @Override protected Set> createEntrySet() { @WeakOuter - class EntrySetImpl extends EntrySet { + final class EntrySetImpl extends EntrySet { @Override Map map() { return AsMapView.this; @@ -839,17 +930,18 @@ public Iterator> iterator() { } } - static Iterator> asMapEntryIterator( - Set set, final Function function) { + static + Iterator> asMapEntryIterator(Set set, Function function) { return new TransformedIterator>(set.iterator()) { @Override - Entry transform(final K key) { + Entry transform(@ParametricNullness K key) { return immutableEntry(key, function.apply(key)); } }; } - private static class SortedAsMapView extends AsMapView implements SortedMap { + private static final class SortedAsMapView + extends AsMapView implements SortedMap { SortedAsMapView(SortedSet set, Function function) { super(set, function); @@ -861,7 +953,7 @@ SortedSet backingSet() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return backingSet().comparator(); } @@ -871,33 +963,37 @@ public Set keySet() { } @Override - public SortedMap subMap(K fromKey, K toKey) { + public SortedMap subMap(@ParametricNullness K fromKey, @ParametricNullness K toKey) { return asMap(backingSet().subSet(fromKey, toKey), function); } @Override - public SortedMap headMap(K toKey) { + public SortedMap headMap(@ParametricNullness K toKey) { return asMap(backingSet().headSet(toKey), function); } @Override - public SortedMap tailMap(K fromKey) { + public SortedMap tailMap(@ParametricNullness K fromKey) { return asMap(backingSet().tailSet(fromKey), function); } @Override + @ParametricNullness public K firstKey() { return backingSet().first(); } @Override + @ParametricNullness public K lastKey() { return backingSet().last(); } } @GwtIncompatible // NavigableMap - private static final class NavigableAsMapView extends AbstractNavigableMap { + private static final class NavigableAsMapView< + K extends @Nullable Object, V extends @Nullable Object> + extends AbstractNavigableMap { /* * Using AbstractNavigableMap is simpler than extending SortedAsMapView and rewriting all the * NavigableMap methods. @@ -913,28 +1009,30 @@ private static final class NavigableAsMapView extends AbstractNavigableMap @Override public NavigableMap subMap( - K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + @ParametricNullness K fromKey, + boolean fromInclusive, + @ParametricNullness K toKey, + boolean toInclusive) { return asMap(set.subSet(fromKey, fromInclusive, toKey, toInclusive), function); } @Override - public NavigableMap headMap(K toKey, boolean inclusive) { + public NavigableMap headMap(@ParametricNullness K toKey, boolean inclusive) { return asMap(set.headSet(toKey, inclusive), function); } @Override - public NavigableMap tailMap(K fromKey, boolean inclusive) { + public NavigableMap tailMap(@ParametricNullness K fromKey, boolean inclusive) { return asMap(set.tailSet(fromKey, inclusive), function); } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return set.comparator(); } @Override - @NullableDecl - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { if (Collections2.safeContains(set, key)) { @SuppressWarnings("unchecked") // unsafe, but Javadoc warns about it K k = (K) key; @@ -975,7 +1073,7 @@ public NavigableMap descendingMap() { } } - private static Set removeOnlySet(final Set set) { + private static Set removeOnlySet(Set set) { return new ForwardingSet() { @Override protected Set delegate() { @@ -983,7 +1081,7 @@ protected Set delegate() { } @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { throw new UnsupportedOperationException(); } @@ -994,7 +1092,7 @@ public boolean addAll(Collection es) { }; } - private static SortedSet removeOnlySortedSet(final SortedSet set) { + private static SortedSet removeOnlySortedSet(SortedSet set) { return new ForwardingSortedSet() { @Override protected SortedSet delegate() { @@ -1002,7 +1100,7 @@ protected SortedSet delegate() { } @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { throw new UnsupportedOperationException(); } @@ -1012,24 +1110,26 @@ public boolean addAll(Collection es) { } @Override - public SortedSet headSet(E toElement) { + public SortedSet headSet(@ParametricNullness E toElement) { return removeOnlySortedSet(super.headSet(toElement)); } @Override - public SortedSet subSet(E fromElement, E toElement) { + public SortedSet subSet( + @ParametricNullness E fromElement, @ParametricNullness E toElement) { return removeOnlySortedSet(super.subSet(fromElement, toElement)); } @Override - public SortedSet tailSet(E fromElement) { + public SortedSet tailSet(@ParametricNullness E fromElement) { return removeOnlySortedSet(super.tailSet(fromElement)); } }; } @GwtIncompatible // NavigableSet - private static NavigableSet removeOnlyNavigableSet(final NavigableSet set) { + private static NavigableSet removeOnlyNavigableSet( + NavigableSet set) { return new ForwardingNavigableSet() { @Override protected NavigableSet delegate() { @@ -1037,7 +1137,7 @@ protected NavigableSet delegate() { } @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { throw new UnsupportedOperationException(); } @@ -1047,34 +1147,38 @@ public boolean addAll(Collection es) { } @Override - public SortedSet headSet(E toElement) { + public SortedSet headSet(@ParametricNullness E toElement) { return removeOnlySortedSet(super.headSet(toElement)); } @Override - public NavigableSet headSet(E toElement, boolean inclusive) { + public NavigableSet headSet(@ParametricNullness E toElement, boolean inclusive) { return removeOnlyNavigableSet(super.headSet(toElement, inclusive)); } @Override - public SortedSet subSet(E fromElement, E toElement) { + public SortedSet subSet( + @ParametricNullness E fromElement, @ParametricNullness E toElement) { return removeOnlySortedSet(super.subSet(fromElement, toElement)); } @Override public NavigableSet subSet( - E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + @ParametricNullness E fromElement, + boolean fromInclusive, + @ParametricNullness E toElement, + boolean toInclusive) { return removeOnlyNavigableSet( super.subSet(fromElement, fromInclusive, toElement, toInclusive)); } @Override - public SortedSet tailSet(E fromElement) { + public SortedSet tailSet(@ParametricNullness E fromElement) { return removeOnlySortedSet(super.tailSet(fromElement)); } @Override - public NavigableSet tailSet(E fromElement, boolean inclusive) { + public NavigableSet tailSet(@ParametricNullness E fromElement, boolean inclusive) { return removeOnlyNavigableSet(super.tailSet(fromElement, inclusive)); } @@ -1097,6 +1201,18 @@ public NavigableSet descendingSet() { *

    If {@code keys} is a {@link Set}, a live view can be obtained instead of a copy using {@link * Maps#asMap(Set, Function)}. * + *

    Note: on Java 8+, it is usually better to use streams. For example: + * + * {@snippet : + * import static com.google.common.collect.ImmutableMap.toImmutableMap; + * ... + * ImmutableMap colorNames = + * allColors.stream().collect(toImmutableMap(c -> c, c -> c.toString())); + * } + * + *

    Streams provide a more standard and flexible API and the lambdas make it clear what the keys + * and values in the map are. + * * @throws NullPointerException if any element of {@code keys} is {@code null}, or if {@code * valueFunction} produces {@code null} for any key * @since 14.0 @@ -1122,13 +1238,13 @@ public static ImmutableMap toMap( public static ImmutableMap toMap( Iterator keys, Function valueFunction) { checkNotNull(valueFunction); - // Using LHM instead of a builder so as not to fail on duplicate keys - Map builder = newLinkedHashMap(); + ImmutableMap.Builder builder = ImmutableMap.builder(); while (keys.hasNext()) { K key = keys.next(); builder.put(key, valueFunction.apply(key)); } - return ImmutableMap.copyOf(builder); + // Using buildKeepingLast() so as not to fail on duplicate keys + return builder.buildKeepingLast(); } /** @@ -1137,19 +1253,31 @@ public static ImmutableMap toMap( * {@code keyFunction} to that value. These entries appear in the same order as the input values. * Example usage: * - *

    {@code
    +   * {@snippet :
        * Color red = new Color("red", 255, 0, 0);
        * ...
        * ImmutableSet allColors = ImmutableSet.of(red, green, blue);
        *
    -   * Map colorForName =
    -   *     uniqueIndex(allColors, toStringFunction());
    +   * ImmutableMap colorForName =
    +   *     uniqueIndex(allColors, c -> c.toString());
        * assertThat(colorForName).containsEntry("red", red);
    -   * }
    + * } * *

    If your index may associate multiple values with each key, use {@link * Multimaps#index(Iterable, Function) Multimaps.index}. * + *

    Note: on Java 8+, it is usually better to use streams. For example: + * + * {@snippet : + * import static com.google.common.collect.ImmutableMap.toImmutableMap; + * ... + * ImmutableMap colorForName = + * allColors.stream().collect(toImmutableMap(c -> c.toString(), c -> c)); + * } + * + *

    Streams provide a more standard and flexible API and the lambdas make it clear what the keys + * and values in the map are. + * * @param values the values to use when constructing the {@code Map} * @param keyFunction the function used to produce the key for each value * @return a map mapping the result of evaluating the function {@code keyFunction} on each value @@ -1162,7 +1290,12 @@ public static ImmutableMap toMap( @CanIgnoreReturnValue public static ImmutableMap uniqueIndex( Iterable values, Function keyFunction) { - // TODO(lowasser): consider presizing the builder if values is a Collection + if (values instanceof Collection) { + return uniqueIndex( + values.iterator(), + keyFunction, + ImmutableMap.builderWithExpectedSize(((Collection) values).size())); + } return uniqueIndex(values.iterator(), keyFunction); } @@ -1172,7 +1305,7 @@ public static ImmutableMap uniqueIndex( * {@code keyFunction} to that value. These entries appear in the same order as the input values. * Example usage: * - *

    {@code
    +   * {@snippet :
        * Color red = new Color("red", 255, 0, 0);
        * ...
        * Iterator allColors = ImmutableSet.of(red, green, blue).iterator();
    @@ -1180,7 +1313,7 @@ public static  ImmutableMap uniqueIndex(
        * Map colorForName =
        *     uniqueIndex(allColors, toStringFunction());
        * assertThat(colorForName).containsEntry("red", red);
    -   * }
    + * } * *

    If your index may associate multiple values with each key, use {@link * Multimaps#index(Iterator, Function) Multimaps.index}. @@ -1198,14 +1331,18 @@ public static ImmutableMap uniqueIndex( @CanIgnoreReturnValue public static ImmutableMap uniqueIndex( Iterator values, Function keyFunction) { + return uniqueIndex(values, keyFunction, ImmutableMap.builder()); + } + + private static ImmutableMap uniqueIndex( + Iterator values, Function keyFunction, ImmutableMap.Builder builder) { checkNotNull(keyFunction); - ImmutableMap.Builder builder = ImmutableMap.builder(); while (values.hasNext()) { V value = values.next(); builder.put(keyFunction.apply(value), value); } try { - return builder.build(); + return builder.buildOrThrow(); } catch (IllegalArgumentException duplicateKeys) { throw new IllegalArgumentException( duplicateKeys.getMessage() @@ -1220,19 +1357,46 @@ public static ImmutableMap uniqueIndex( * * @param properties a {@code Properties} object to be converted * @return an immutable map containing all the entries in {@code properties} - * @throws ClassCastException if any key in {@code Properties} is not a {@code String} - * @throws NullPointerException if any key or value in {@code Properties} is null + * @throws ClassCastException if any key in {@code properties} is not a {@code String} + * @throws NullPointerException if any key or value in {@code properties} is null */ + @J2ktIncompatible @GwtIncompatible // java.util.Properties public static ImmutableMap fromProperties(Properties properties) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) { - String key = (String) e.nextElement(); - builder.put(key, properties.getProperty(key)); - } - - return builder.build(); + /* + * requireNonNull is safe because propertyNames contains only non-null elements. + * + * Accordingly, we have it annotated as returning `Enumeration` in our + * prototype checker's JDK. However, the checker still sees the return type as plain + * `Enumeration`, probably because of one of the following two bugs (and maybe those two + * bugs are themselves just symptoms of the same underlying problem): + * + * https://github.com/typetools/checker-framework/issues/3030 + * + * https://github.com/typetools/checker-framework/issues/3236 + */ + String key = (String) requireNonNull(e.nextElement()); + /* + * requireNonNull is safe because the key came from propertyNames... + * + * ...except that it's possible for users to insert a string key with a non-string value, and + * in that case, getProperty *will* return null. + * + * TODO(b/192002623): Handle that case: Either: + * + * - Skip non-string keys and values entirely, as proposed in the linked bug. + * + * - Throw ClassCastException instead of NullPointerException, as documented in the current + * Javadoc. (Note that we can't necessarily "just" change our call to `getProperty` to `get` + * because `get` does not consult the default properties.) + */ + builder.put(key, requireNonNull(properties.getProperty(key))); + } + + return builder.buildOrThrow(); } /** @@ -1247,9 +1411,9 @@ public static ImmutableMap fromProperties(Properties properties) * @param key the key to be associated with the returned entry * @param value the value to be associated with the returned entry */ - @GwtCompatible(serializable = true) - public static Entry immutableEntry(@NullableDecl K key, @NullableDecl V value) { - return new ImmutableEntry<>(key, value); + public static Entry immutableEntry( + @ParametricNullness K key, @ParametricNullness V value) { + return new SimpleImmutableEntry<>(key, value); } /** @@ -1260,36 +1424,41 @@ public static Entry immutableEntry(@NullableDecl K key, @NullableDe * @param entrySet the entries for which to return an unmodifiable view * @return an unmodifiable view of the entries */ - static Set> unmodifiableEntrySet(Set> entrySet) { + static + Set> unmodifiableEntrySet(Set> entrySet) { return new UnmodifiableEntrySet<>(Collections.unmodifiableSet(entrySet)); } /** * Returns an unmodifiable view of the specified map entry. The {@link Entry#setValue} operation - * throws an {@link UnsupportedOperationException}. This also has the side-effect of redefining + * throws an {@link UnsupportedOperationException}. This also has the side effect of redefining * {@code equals} to comply with the Entry contract, to avoid a possible nefarious implementation * of equals. * * @param entry the entry for which to return an unmodifiable view * @return an unmodifiable view of the entry */ - static Entry unmodifiableEntry(final Entry entry) { + static Entry unmodifiableEntry( + Entry entry) { checkNotNull(entry); return new AbstractMapEntry() { @Override + @ParametricNullness public K getKey() { return entry.getKey(); } @Override + @ParametricNullness public V getValue() { return entry.getValue(); } }; } - static UnmodifiableIterator> unmodifiableEntryIterator( - final Iterator> entryIterator) { + static + UnmodifiableIterator> unmodifiableEntryIterator( + Iterator> entryIterator) { return new UnmodifiableIterator>() { @Override public boolean hasNext() { @@ -1303,8 +1472,9 @@ public Entry next() { }; } - /** @see Multimaps#unmodifiableEntries */ - static class UnmodifiableEntries extends ForwardingCollection> { + /** The implementation of {@link Multimaps#unmodifiableEntries}. */ + static class UnmodifiableEntries + extends ForwardingCollection> { private final Collection> entries; UnmodifiableEntries(Collection> entries) { @@ -1324,19 +1494,27 @@ public Iterator> iterator() { // See java.util.Collections.UnmodifiableEntrySet for details on attacks. @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { + /* + * standardToArray returns `@Nullable Object[]` rather than `Object[]` but because it can + * be used with collections that may contain null. This collection never contains nulls, so we + * could return `Object[]`. But this class is private and J2KT cannot change return types in + * overrides, so we declare `@Nullable Object[]` as the return type. + */ return standardToArray(); } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return standardToArray(array); } } - /** @see Maps#unmodifiableEntrySet(Set) */ - static class UnmodifiableEntrySet extends UnmodifiableEntries - implements Set> { + /** The implementation of {@link Maps#unmodifiableEntrySet(Set)}. */ + private static final class UnmodifiableEntrySet< + K extends @Nullable Object, V extends @Nullable Object> + extends UnmodifiableEntries implements Set> { UnmodifiableEntrySet(Set> entries) { super(entries); } @@ -1344,7 +1522,7 @@ static class UnmodifiableEntrySet extends UnmodifiableEntries // See java.util.Collections.UnmodifiableEntrySet for details on attacks. @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return Sets.equalsImpl(this, object); } @@ -1364,7 +1542,7 @@ public int hashCode() { * * @since 16.0 */ - public static Converter asConverter(final BiMap bimap) { + public static Converter asConverter(BiMap bimap) { return new BiMapConverter<>(bimap); } @@ -1392,7 +1570,7 @@ private static Y convert(BiMap bimap, X input) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof BiMapConverter) { BiMapConverter that = (BiMapConverter) object; return this.bimap.equals(that.bimap); @@ -1411,7 +1589,7 @@ public String toString() { return "Maps.asConverter(" + bimap + ")"; } - private static final long serialVersionUID = 0L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0L; } /** @@ -1422,9 +1600,8 @@ public String toString() { *

    It is imperative that the user manually synchronize on the returned map when accessing any * of its collection views: * - *

    {@code
    -   * BiMap map = Maps.synchronizedBiMap(
    -   *     HashBiMap.create());
    +   * {@snippet :
    +   * BiMap map = Maps.synchronizedBiMap(HashBiMap.create());
        * ...
        * Set set = map.keySet();  // Needn't be in synchronized block
        * ...
    @@ -1434,7 +1611,7 @@ public String toString() {
        *     foo(it.next());
        *   }
        * }
    -   * }
    + * } * *

    Failure to follow this advice may result in non-deterministic behavior. * @@ -1443,7 +1620,9 @@ public String toString() { * @param bimap the bimap to be wrapped in a synchronized view * @return a synchronized view of the specified bimap */ - public static BiMap synchronizedBiMap(BiMap bimap) { + @J2ktIncompatible // Synchronized + public static + BiMap synchronizedBiMap(BiMap bimap) { return Synchronized.biMap(bimap, null); } @@ -1458,19 +1637,23 @@ public static BiMap synchronizedBiMap(BiMap bimap) { * @param bimap the bimap for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified bimap */ - public static BiMap unmodifiableBiMap(BiMap bimap) { + public static + BiMap unmodifiableBiMap(BiMap bimap) { return new UnmodifiableBiMap<>(bimap, null); } - /** @see Maps#unmodifiableBiMap(BiMap) */ - private static class UnmodifiableBiMap extends ForwardingMap - implements BiMap, Serializable { + /** + * @see Maps#unmodifiableBiMap(BiMap) + */ + private static final class UnmodifiableBiMap< + K extends @Nullable Object, V extends @Nullable Object> + extends ForwardingMap implements BiMap, Serializable { final Map unmodifiableMap; final BiMap delegate; - @RetainedWith @NullableDecl BiMap inverse; - @NullableDecl transient Set values; + @LazyInit @RetainedWith @Nullable BiMap inverse; + @LazyInit transient @Nullable Set values; - UnmodifiableBiMap(BiMap delegate, @NullableDecl BiMap inverse) { + UnmodifiableBiMap(BiMap delegate, @Nullable BiMap inverse) { unmodifiableMap = Collections.unmodifiableMap(delegate); this.delegate = delegate; this.inverse = inverse; @@ -1482,7 +1665,7 @@ protected Map delegate() { } @Override - public V forcePut(K key, V value) { + public @Nullable V forcePut(@ParametricNullness K key, @ParametricNullness V value) { throw new UnsupportedOperationException(); } @@ -1500,24 +1683,19 @@ public Set values() { return (result == null) ? values = Collections.unmodifiableSet(delegate.values()) : result; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Returns a view of a map where each value is transformed by a function. All other properties of * the map, such as iteration order, are left intact. For example, the code: * - *

    {@code
    +   * {@snippet :
        * Map map = ImmutableMap.of("a", 4, "b", 9);
    -   * Function sqrt =
    -   *     new Function() {
    -   *       public Double apply(Integer in) {
    -   *         return Math.sqrt((int) in);
    -   *       }
    -   *     };
    +   * Function sqrt = (Integer in) -> Math.sqrt((int) in);
        * Map transformed = Maps.transformValues(map, sqrt);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {a=2.0, b=3.0}}. * @@ -1536,27 +1714,24 @@ public Set values() { * function} should be fast. To avoid lazy evaluation when the returned map doesn't need to be a * view, copy the returned map into a new map of your choosing. */ - public static Map transformValues( - Map fromMap, Function function) { - return transformEntries(fromMap, asEntryTransformer(function)); + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + Map transformValues(Map fromMap, Function function) { + checkNotNull(function); + return transformEntries(fromMap, (key, value) -> function.apply(value)); } /** * Returns a view of a sorted map where each value is transformed by a function. All other * properties of the map, such as iteration order, are left intact. For example, the code: * - *
    {@code
    +   * {@snippet :
        * SortedMap map = ImmutableSortedMap.of("a", 4, "b", 9);
    -   * Function sqrt =
    -   *     new Function() {
    -   *       public Double apply(Integer in) {
    -   *         return Math.sqrt((int) in);
    -   *       }
    -   *     };
    +   * Function sqrt = (Integer in) -> Math.sqrt((int) in);
        * SortedMap transformed =
        *      Maps.transformValues(map, sqrt);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {a=2.0, b=3.0}}. * @@ -1577,29 +1752,27 @@ public static Map transformValues( * * @since 11.0 */ - public static SortedMap transformValues( - SortedMap fromMap, Function function) { - return transformEntries(fromMap, asEntryTransformer(function)); + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + SortedMap transformValues( + SortedMap fromMap, Function function) { + checkNotNull(function); + return transformEntries(fromMap, (key, value) -> function.apply(value)); } /** * Returns a view of a navigable map where each value is transformed by a function. All other * properties of the map, such as iteration order, are left intact. For example, the code: * - *
    {@code
    +   * {@snippet :
        * NavigableMap map = Maps.newTreeMap();
        * map.put("a", 4);
        * map.put("b", 9);
    -   * Function sqrt =
    -   *     new Function() {
    -   *       public Double apply(Integer in) {
    -   *         return Math.sqrt((int) in);
    -   *       }
    -   *     };
    +   * Function sqrt = (Integer in) -> Math.sqrt((int) in);
        * NavigableMap transformed =
        *      Maps.transformNavigableValues(map, sqrt);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {a=2.0, b=3.0}}. * @@ -1621,9 +1794,12 @@ public static SortedMap transformValues( * @since 13.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap transformValues( - NavigableMap fromMap, Function function) { - return transformEntries(fromMap, asEntryTransformer(function)); + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + NavigableMap transformValues( + NavigableMap fromMap, Function function) { + checkNotNull(function); + return transformEntries(fromMap, (key, value) -> function.apply(value)); } /** @@ -1634,7 +1810,7 @@ public static NavigableMap transformValues( *

    All other properties of the transformed map, such as iteration order, are left intact. For * example, the code: * - *

    {@code
    +   * {@snippet :
        * Map options =
        *     ImmutableMap.of("verbose", true, "sort", false);
        * EntryTransformer flagPrefixer =
    @@ -1646,7 +1822,7 @@ public static  NavigableMap transformValues(
        * Map transformed =
        *     Maps.transformEntries(options, flagPrefixer);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {verbose=verbose, sort=nosort}}. * @@ -1673,8 +1849,10 @@ public static NavigableMap transformValues( * * @since 7.0 */ - public static Map transformEntries( - Map fromMap, EntryTransformer transformer) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + Map transformEntries( + Map fromMap, EntryTransformer transformer) { return new TransformedEntriesMap<>(fromMap, transformer); } @@ -1686,7 +1864,7 @@ public static Map transformEntries( *

    All other properties of the transformed map, such as iteration order, are left intact. For * example, the code: * - *

    {@code
    +   * {@snippet :
        * Map options =
        *     ImmutableSortedMap.of("verbose", true, "sort", false);
        * EntryTransformer flagPrefixer =
    @@ -1698,7 +1876,7 @@ public static  Map transformEntries(
        * SortedMap transformed =
        *     Maps.transformEntries(options, flagPrefixer);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {sort=yessort, verbose=verbose}}. * @@ -1725,8 +1903,10 @@ public static Map transformEntries( * * @since 11.0 */ - public static SortedMap transformEntries( - SortedMap fromMap, EntryTransformer transformer) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + SortedMap transformEntries( + SortedMap fromMap, EntryTransformer transformer) { return new TransformedEntriesSortedMap<>(fromMap, transformer); } @@ -1738,7 +1918,7 @@ public static SortedMap transformEntries( *

    All other properties of the transformed map, such as iteration order, are left intact. For * example, the code: * - *

    {@code
    +   * {@snippet :
        * NavigableMap options = Maps.newTreeMap();
        * options.put("verbose", false);
        * options.put("sort", true);
    @@ -1751,7 +1931,7 @@ public static  SortedMap transformEntries(
        * NavigableMap transformed =
        *     LabsMaps.transformNavigableEntries(options, flagPrefixer);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {sort=yessort, verbose=verbose}}. * @@ -1779,8 +1959,10 @@ public static SortedMap transformEntries( * @since 13.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap transformEntries( - NavigableMap fromMap, EntryTransformer transformer) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + NavigableMap transformEntries( + NavigableMap fromMap, EntryTransformer transformer) { return new TransformedEntriesNavigableMap<>(fromMap, transformer); } @@ -1793,71 +1975,42 @@ public static NavigableMap transformEntries( * @param the value type of the output entry * @since 7.0 */ - public interface EntryTransformer { + public interface EntryTransformer< + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> { /** * Determines an output value based on a key-value pair. This method is generally * expected, but not absolutely required, to have the following properties: * *
      *
    • Its execution does not cause any observable side effects. - *
    • The computation is consistent with equals; that is, {@link Objects#equal - * Objects.equal}{@code (k1, k2) &&} {@link Objects#equal}{@code (v1, v2)} implies that - * {@code Objects.equal(transformer.transform(k1, v1), transformer.transform(k2, v2))}. + *
    • The computation is consistent with equals; that is, {@link Objects#equals + * Objects.equals}{@code (k1, k2) &&} {@link Objects#equals Objects.equals}{@code (v1, + * v2)} implies that {@code Objects.equals(transformer.transform(k1, v1), + * transformer.transform(k2, v2))}. *
    * * @throws NullPointerException if the key or value is null and this transformer does not accept * null arguments */ - V2 transformEntry(@NullableDecl K key, @NullableDecl V1 value); - } - - /** Views a function as an entry transformer that ignores the entry key. */ - static EntryTransformer asEntryTransformer( - final Function function) { - checkNotNull(function); - return new EntryTransformer() { - @Override - public V2 transformEntry(K key, V1 value) { - return function.apply(value); - } - }; - } - - static Function asValueToValueFunction( - final EntryTransformer transformer, final K key) { - checkNotNull(transformer); - return new Function() { - @Override - public V2 apply(@NullableDecl V1 v1) { - return transformer.transformEntry(key, v1); - } - }; - } - - /** Views an entry transformer as a function from {@code Entry} to values. */ - static Function, V2> asEntryToValueFunction( - final EntryTransformer transformer) { - checkNotNull(transformer); - return new Function, V2>() { - @Override - public V2 apply(Entry entry) { - return transformer.transformEntry(entry.getKey(), entry.getValue()); - } - }; + @ParametricNullness + V2 transformEntry(@ParametricNullness K key, @ParametricNullness V1 value); } /** Returns a view of an entry transformed by the specified transformer. */ - static Entry transformEntry( - final EntryTransformer transformer, final Entry entry) { + static + Entry transformEntry( + EntryTransformer transformer, Entry entry) { checkNotNull(transformer); checkNotNull(entry); return new AbstractMapEntry() { @Override + @ParametricNullness public K getKey() { return entry.getKey(); } @Override + @ParametricNullness public V2 getValue() { return transformer.transformEntry(entry.getKey(), entry.getValue()); } @@ -1865,18 +2018,16 @@ public V2 getValue() { } /** Views an entry transformer as a function from entries to entries. */ - static Function, Entry> asEntryToEntryFunction( - final EntryTransformer transformer) { + static + Function, Entry> asEntryToEntryFunction( + EntryTransformer transformer) { checkNotNull(transformer); - return new Function, Entry>() { - @Override - public Entry apply(final Entry entry) { - return transformEntry(transformer, entry); - } - }; + return entry -> transformEntry(transformer, entry); } - static class TransformedEntriesMap extends IteratorBasedAbstractMap { + private static class TransformedEntriesMap< + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + extends IteratorBasedAbstractMap { final Map fromMap; final EntryTransformer transformer; @@ -1892,26 +2043,29 @@ public int size() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return fromMap.containsKey(key); } // safe as long as the user followed the Warning in the javadoc @SuppressWarnings("unchecked") @Override - public V2 get(Object key) { + public @Nullable V2 get(@Nullable Object key) { V1 value = fromMap.get(key); - return (value != null || fromMap.containsKey(key)) - ? transformer.transformEntry((K) key, value) - : null; + if (value != null || fromMap.containsKey(key)) { + // The cast is safe because of the containsKey check. + return transformer.transformEntry((K) key, uncheckedCastNullableTToT(value)); + } + return null; } // safe as long as the user followed the Warning in the javadoc @SuppressWarnings("unchecked") @Override - public V2 remove(Object key) { + public @Nullable V2 remove(@Nullable Object key) { return fromMap.containsKey(key) - ? transformer.transformEntry((K) key, fromMap.remove(key)) + // The cast is safe because of the containsKey check. + ? transformer.transformEntry((K) key, uncheckedCastNullableTToT(fromMap.remove(key))) : null; } @@ -1928,7 +2082,7 @@ public Set keySet() { @Override Iterator> entryIterator() { return Iterators.transform( - fromMap.entrySet().iterator(), Maps.asEntryToEntryFunction(transformer)); + fromMap.entrySet().iterator(), asEntryToEntryFunction(transformer)); } @Override @@ -1937,8 +2091,9 @@ public Collection values() { } } - static class TransformedEntriesSortedMap extends TransformedEntriesMap - implements SortedMap { + private static class TransformedEntriesSortedMap< + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + extends TransformedEntriesMap implements SortedMap { protected SortedMap fromMap() { return (SortedMap) fromMap; @@ -1950,38 +2105,41 @@ protected SortedMap fromMap() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return fromMap().comparator(); } @Override + @ParametricNullness public K firstKey() { return fromMap().firstKey(); } @Override - public SortedMap headMap(K toKey) { + public SortedMap headMap(@ParametricNullness K toKey) { return transformEntries(fromMap().headMap(toKey), transformer); } @Override + @ParametricNullness public K lastKey() { return fromMap().lastKey(); } @Override - public SortedMap subMap(K fromKey, K toKey) { + public SortedMap subMap(@ParametricNullness K fromKey, @ParametricNullness K toKey) { return transformEntries(fromMap().subMap(fromKey, toKey), transformer); } @Override - public SortedMap tailMap(K fromKey) { + public SortedMap tailMap(@ParametricNullness K fromKey) { return transformEntries(fromMap().tailMap(fromKey), transformer); } } @GwtIncompatible // NavigableMap - private static class TransformedEntriesNavigableMap + private static final class TransformedEntriesNavigableMap< + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> extends TransformedEntriesSortedMap implements NavigableMap { TransformedEntriesNavigableMap( @@ -1990,12 +2148,12 @@ private static class TransformedEntriesNavigableMap } @Override - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(@ParametricNullness K key) { return transformEntry(fromMap().ceilingEntry(key)); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(@ParametricNullness K key) { return fromMap().ceilingKey(key); } @@ -2010,52 +2168,52 @@ public NavigableMap descendingMap() { } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return transformEntry(fromMap().firstEntry()); } @Override - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(@ParametricNullness K key) { return transformEntry(fromMap().floorEntry(key)); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(@ParametricNullness K key) { return fromMap().floorKey(key); } @Override - public NavigableMap headMap(K toKey) { + public NavigableMap headMap(@ParametricNullness K toKey) { return headMap(toKey, false); } @Override - public NavigableMap headMap(K toKey, boolean inclusive) { + public NavigableMap headMap(@ParametricNullness K toKey, boolean inclusive) { return transformEntries(fromMap().headMap(toKey, inclusive), transformer); } @Override - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(@ParametricNullness K key) { return transformEntry(fromMap().higherEntry(key)); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(@ParametricNullness K key) { return fromMap().higherKey(key); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return transformEntry(fromMap().lastEntry()); } @Override - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(@ParametricNullness K key) { return transformEntry(fromMap().lowerEntry(key)); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(@ParametricNullness K key) { return fromMap().lowerKey(key); } @@ -2065,39 +2223,41 @@ public NavigableSet navigableKeySet() { } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return transformEntry(fromMap().pollFirstEntry()); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return transformEntry(fromMap().pollLastEntry()); } @Override public NavigableMap subMap( - K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + @ParametricNullness K fromKey, + boolean fromInclusive, + @ParametricNullness K toKey, + boolean toInclusive) { return transformEntries( fromMap().subMap(fromKey, fromInclusive, toKey, toInclusive), transformer); } @Override - public NavigableMap subMap(K fromKey, K toKey) { + public NavigableMap subMap(@ParametricNullness K fromKey, @ParametricNullness K toKey) { return subMap(fromKey, true, toKey, false); } @Override - public NavigableMap tailMap(K fromKey) { + public NavigableMap tailMap(@ParametricNullness K fromKey) { return tailMap(fromKey, true); } @Override - public NavigableMap tailMap(K fromKey, boolean inclusive) { + public NavigableMap tailMap(@ParametricNullness K fromKey, boolean inclusive) { return transformEntries(fromMap().tailMap(fromKey, inclusive), transformer); } - @NullableDecl - private Entry transformEntry(@NullableDecl Entry entry) { + private @Nullable Entry transformEntry(@Nullable Entry entry) { return (entry == null) ? null : Maps.transformEntry(transformer, entry); } @@ -2107,12 +2267,14 @@ protected NavigableMap fromMap() { } } - static Predicate> keyPredicateOnEntries(Predicate keyPredicate) { - return compose(keyPredicate, Maps.keyFunction()); + static Predicate> keyPredicateOnEntries( + Predicate keyPredicate) { + return compose(keyPredicate, Entry::getKey); } - static Predicate> valuePredicateOnEntries(Predicate valuePredicate) { - return compose(valuePredicate, Maps.valueFunction()); + static Predicate> valuePredicateOnEntries( + Predicate valuePredicate) { + return compose(valuePredicate, Entry::getValue); } /** @@ -2138,8 +2300,8 @@ static Predicate> valuePredicateOnEntries(Predicate v * {@link Predicate#apply}. Do not provide a predicate such as {@code * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. */ - public static Map filterKeys( - Map unfiltered, final Predicate keyPredicate) { + public static Map filterKeys( + Map unfiltered, Predicate keyPredicate) { checkNotNull(keyPredicate); Predicate> entryPredicate = keyPredicateOnEntries(keyPredicate); return (unfiltered instanceof AbstractFilteredMap) @@ -2173,11 +2335,10 @@ public static Map filterKeys( * * @since 11.0 */ - public static SortedMap filterKeys( - SortedMap unfiltered, final Predicate keyPredicate) { - // TODO(lowasser): Return a subclass of Maps.FilteredKeyMap for slightly better - // performance. - return filterEntries(unfiltered, Maps.keyPredicateOnEntries(keyPredicate)); + public static SortedMap filterKeys( + SortedMap unfiltered, Predicate keyPredicate) { + // TODO(lowasser): Return a subclass of FilteredKeyMap for slightly better performance. + return filterEntries(unfiltered, keyPredicateOnEntries(keyPredicate)); } /** @@ -2207,11 +2368,11 @@ public static SortedMap filterKeys( * @since 14.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap filterKeys( - NavigableMap unfiltered, final Predicate keyPredicate) { - // TODO(lowasser): Return a subclass of Maps.FilteredKeyMap for slightly better - // performance. - return filterEntries(unfiltered, Maps.keyPredicateOnEntries(keyPredicate)); + public static + NavigableMap filterKeys( + NavigableMap unfiltered, Predicate keyPredicate) { + // TODO(lowasser): Return a subclass of FilteredKeyMap for slightly better performance. + return filterEntries(unfiltered, keyPredicateOnEntries(keyPredicate)); } /** @@ -2238,10 +2399,10 @@ public static NavigableMap filterKeys( * * @since 14.0 */ - public static BiMap filterKeys( - BiMap unfiltered, final Predicate keyPredicate) { + public static BiMap filterKeys( + BiMap unfiltered, Predicate keyPredicate) { checkNotNull(keyPredicate); - return filterEntries(unfiltered, Maps.keyPredicateOnEntries(keyPredicate)); + return filterEntries(unfiltered, keyPredicateOnEntries(keyPredicate)); } /** @@ -2267,9 +2428,9 @@ public static BiMap filterKeys( * at {@link Predicate#apply}. Do not provide a predicate such as {@code * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. */ - public static Map filterValues( - Map unfiltered, final Predicate valuePredicate) { - return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + public static Map filterValues( + Map unfiltered, Predicate valuePredicate) { + return filterEntries(unfiltered, valuePredicateOnEntries(valuePredicate)); } /** @@ -2298,9 +2459,10 @@ public static Map filterValues( * * @since 11.0 */ - public static SortedMap filterValues( - SortedMap unfiltered, final Predicate valuePredicate) { - return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + public static + SortedMap filterValues( + SortedMap unfiltered, Predicate valuePredicate) { + return filterEntries(unfiltered, valuePredicateOnEntries(valuePredicate)); } /** @@ -2330,9 +2492,10 @@ public static SortedMap filterValues( * @since 14.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap filterValues( - NavigableMap unfiltered, final Predicate valuePredicate) { - return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + public static + NavigableMap filterValues( + NavigableMap unfiltered, Predicate valuePredicate) { + return filterEntries(unfiltered, valuePredicateOnEntries(valuePredicate)); } /** @@ -2362,9 +2525,9 @@ public static NavigableMap filterValues( * * @since 14.0 */ - public static BiMap filterValues( - BiMap unfiltered, final Predicate valuePredicate) { - return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + public static BiMap filterValues( + BiMap unfiltered, Predicate valuePredicate) { + return filterEntries(unfiltered, valuePredicateOnEntries(valuePredicate)); } /** @@ -2391,7 +2554,7 @@ public static BiMap filterValues( *

    Warning: {@code entryPredicate} must be consistent with equals, as documented * at {@link Predicate#apply}. */ - public static Map filterEntries( + public static Map filterEntries( Map unfiltered, Predicate> entryPredicate) { checkNotNull(entryPredicate); return (unfiltered instanceof AbstractFilteredMap) @@ -2425,8 +2588,9 @@ public static Map filterEntries( * * @since 11.0 */ - public static SortedMap filterEntries( - SortedMap unfiltered, Predicate> entryPredicate) { + public static + SortedMap filterEntries( + SortedMap unfiltered, Predicate> entryPredicate) { checkNotNull(entryPredicate); return (unfiltered instanceof FilteredEntrySortedMap) ? filterFiltered((FilteredEntrySortedMap) unfiltered, entryPredicate) @@ -2460,8 +2624,9 @@ public static SortedMap filterEntries( * @since 14.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap filterEntries( - NavigableMap unfiltered, Predicate> entryPredicate) { + public static + NavigableMap filterEntries( + NavigableMap unfiltered, Predicate> entryPredicate) { checkNotNull(entryPredicate); return (unfiltered instanceof FilteredEntryNavigableMap) ? filterFiltered((FilteredEntryNavigableMap) unfiltered, entryPredicate) @@ -2495,7 +2660,7 @@ public static NavigableMap filterEntries( * * @since 14.0 */ - public static BiMap filterEntries( + public static BiMap filterEntries( BiMap unfiltered, Predicate> entryPredicate) { checkNotNull(unfiltered); checkNotNull(entryPredicate); @@ -2508,19 +2673,19 @@ public static BiMap filterEntries( * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when filtering a filtered * map. */ - private static Map filterFiltered( + private static Map filterFiltered( AbstractFilteredMap map, Predicate> entryPredicate) { - return new FilteredEntryMap<>( - map.unfiltered, Predicates.>and(map.predicate, entryPredicate)); + return new FilteredEntryMap<>(map.unfiltered, Predicates.and(map.predicate, entryPredicate)); } /** * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when filtering a filtered * sorted map. */ - private static SortedMap filterFiltered( - FilteredEntrySortedMap map, Predicate> entryPredicate) { - Predicate> predicate = Predicates.>and(map.predicate, entryPredicate); + private static + SortedMap filterFiltered( + FilteredEntrySortedMap map, Predicate> entryPredicate) { + Predicate> predicate = Predicates.and(map.predicate, entryPredicate); return new FilteredEntrySortedMap<>(map.sortedMap(), predicate); } @@ -2529,10 +2694,10 @@ private static SortedMap filterFiltered( * navigable map. */ @GwtIncompatible // NavigableMap - private static NavigableMap filterFiltered( - FilteredEntryNavigableMap map, Predicate> entryPredicate) { - Predicate> predicate = - Predicates.>and(map.entryPredicate, entryPredicate); + private static + NavigableMap filterFiltered( + FilteredEntryNavigableMap map, Predicate> entryPredicate) { + Predicate> predicate = Predicates.and(map.entryPredicate, entryPredicate); return new FilteredEntryNavigableMap<>(map.unfiltered, predicate); } @@ -2540,13 +2705,16 @@ private static NavigableMap filterFiltered( * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when filtering a filtered * map. */ - private static BiMap filterFiltered( - FilteredEntryBiMap map, Predicate> entryPredicate) { - Predicate> predicate = Predicates.>and(map.predicate, entryPredicate); + private static + BiMap filterFiltered( + FilteredEntryBiMap map, Predicate> entryPredicate) { + Predicate> predicate = Predicates.and(map.predicate, entryPredicate); return new FilteredEntryBiMap<>(map.unfiltered(), predicate); } - private abstract static class AbstractFilteredMap extends ViewCachingAbstractMap { + private abstract static class AbstractFilteredMap< + K extends @Nullable Object, V extends @Nullable Object> + extends ViewCachingAbstractMap { final Map unfiltered; final Predicate> predicate; @@ -2555,16 +2723,16 @@ private abstract static class AbstractFilteredMap extends ViewCachingAbstr this.predicate = predicate; } - boolean apply(@NullableDecl Object key, @NullableDecl V value) { - // This method is called only when the key is in the map, implying that - // key is a K. - @SuppressWarnings("unchecked") + boolean apply(@Nullable Object key, @ParametricNullness V value) { + // This method is called only when the key is in the map (or about to be added to the map), + // implying that key is a K. + @SuppressWarnings({"unchecked", "nullness"}) K k = (K) key; - return predicate.apply(Maps.immutableEntry(k, value)); + return predicate.apply(immutableEntry(k, value)); } @Override - public V put(K key, V value) { + public @Nullable V put(@ParametricNullness K key, @ParametricNullness V value) { checkArgument(apply(key, value)); return unfiltered.put(key, value); } @@ -2578,12 +2746,12 @@ public void putAll(Map map) { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return unfiltered.containsKey(key) && apply(key, unfiltered.get(key)); } @Override - public V get(Object key) { + public @Nullable V get(@Nullable Object key) { V value = unfiltered.get(key); return ((value != null) && apply(key, value)) ? value : null; } @@ -2594,7 +2762,7 @@ public boolean isEmpty() { } @Override - public V remove(Object key) { + public @Nullable V remove(@Nullable Object key) { return containsKey(key) ? unfiltered.remove(key) : null; } @@ -2604,7 +2772,9 @@ Collection createValues() { } } - private static final class FilteredMapValues extends Maps.Values { + private static final class FilteredMapValues< + K extends @Nullable Object, V extends @Nullable Object> + extends Values { final Map unfiltered; final Predicate> predicate; @@ -2616,11 +2786,11 @@ private static final class FilteredMapValues extends Maps.Values { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { Iterator> entryItr = unfiltered.entrySet().iterator(); while (entryItr.hasNext()) { Entry entry = entryItr.next(); - if (predicate.apply(entry) && Objects.equal(entry.getValue(), o)) { + if (predicate.apply(entry) && Objects.equals(entry.getValue(), o)) { entryItr.remove(); return true; } @@ -2657,18 +2827,20 @@ public boolean retainAll(Collection collection) { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { // creating an ArrayList so filtering happens once return Lists.newArrayList(iterator()).toArray(); } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return Lists.newArrayList(iterator()).toArray(array); } } - private static class FilteredKeyMap extends AbstractFilteredMap { + private static final class FilteredKeyMap + extends AbstractFilteredMap { final Predicate keyPredicate; FilteredKeyMap( @@ -2693,12 +2865,13 @@ Set createKeySet() { // that key is a K. @Override @SuppressWarnings("unchecked") - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return unfiltered.containsKey(key) && keyPredicate.apply((K) key); } } - static class FilteredEntryMap extends AbstractFilteredMap { + private static class FilteredEntryMap + extends AbstractFilteredMap { /** * Entries in this set satisfy the predicate, but they don't validate the input to {@code * Entry.setValue()}. @@ -2716,7 +2889,7 @@ protected Set> createEntrySet() { } @WeakOuter - private class EntrySet extends ForwardingSet> { + private final class EntrySet extends ForwardingSet> { @Override protected Set> delegate() { return filteredEntrySet; @@ -2726,7 +2899,7 @@ protected Set> delegate() { public Iterator> iterator() { return new TransformedIterator, Entry>(filteredEntrySet.iterator()) { @Override - Entry transform(final Entry entry) { + Entry transform(Entry entry) { return new ForwardingMapEntry() { @Override protected Entry delegate() { @@ -2734,7 +2907,8 @@ protected Entry delegate() { } @Override - public V setValue(V newValue) { + @ParametricNullness + public V setValue(@ParametricNullness V newValue) { checkArgument(apply(getKey(), newValue)); return super.setValue(newValue); } @@ -2749,7 +2923,7 @@ Set createKeySet() { return new KeySet(); } - static boolean removeAllKeys( + static boolean removeAllKeys( Map map, Predicate> entryPredicate, Collection keyCollection) { Iterator> entryItr = map.entrySet().iterator(); boolean result = false; @@ -2763,7 +2937,7 @@ static boolean removeAllKeys( return result; } - static boolean retainAllKeys( + static boolean retainAllKeys( Map map, Predicate> entryPredicate, Collection keyCollection) { Iterator> entryItr = map.entrySet().iterator(); boolean result = false; @@ -2784,7 +2958,7 @@ class KeySet extends Maps.KeySet { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { if (containsKey(o)) { unfiltered.remove(o); return true; @@ -2803,20 +2977,22 @@ public boolean retainAll(Collection collection) { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { // creating an ArrayList so filtering happens once return Lists.newArrayList(iterator()).toArray(); } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return Lists.newArrayList(iterator()).toArray(array); } } } - private static class FilteredEntrySortedMap extends FilteredEntryMap - implements SortedMap { + private static final class FilteredEntrySortedMap< + K extends @Nullable Object, V extends @Nullable Object> + extends FilteredEntryMap implements SortedMap { FilteredEntrySortedMap( SortedMap unfiltered, Predicate> entryPredicate) { @@ -2838,56 +3014,62 @@ SortedSet createKeySet() { } @WeakOuter - class SortedKeySet extends KeySet implements SortedSet { + final class SortedKeySet extends KeySet implements SortedSet { @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return sortedMap().comparator(); } @Override - public SortedSet subSet(K fromElement, K toElement) { + public SortedSet subSet( + @ParametricNullness K fromElement, @ParametricNullness K toElement) { return (SortedSet) subMap(fromElement, toElement).keySet(); } @Override - public SortedSet headSet(K toElement) { + public SortedSet headSet(@ParametricNullness K toElement) { return (SortedSet) headMap(toElement).keySet(); } @Override - public SortedSet tailSet(K fromElement) { + public SortedSet tailSet(@ParametricNullness K fromElement) { return (SortedSet) tailMap(fromElement).keySet(); } @Override + @ParametricNullness public K first() { return firstKey(); } @Override + @ParametricNullness public K last() { return lastKey(); } } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return sortedMap().comparator(); } @Override + @ParametricNullness public K firstKey() { // correctly throws NoSuchElementException when filtered map is empty. return keySet().iterator().next(); } @Override + @ParametricNullness public K lastKey() { SortedMap headMap = sortedMap(); while (true) { // correctly throws NoSuchElementException when filtered map is empty. K key = headMap.lastKey(); - if (apply(key, unfiltered.get(key))) { + // The cast is safe because the key is taken from the map. + if (apply(key, uncheckedCastNullableTToT(unfiltered.get(key)))) { return key; } headMap = sortedMap().headMap(key); @@ -2895,23 +3077,25 @@ public K lastKey() { } @Override - public SortedMap headMap(K toKey) { + public SortedMap headMap(@ParametricNullness K toKey) { return new FilteredEntrySortedMap<>(sortedMap().headMap(toKey), predicate); } @Override - public SortedMap subMap(K fromKey, K toKey) { + public SortedMap subMap(@ParametricNullness K fromKey, @ParametricNullness K toKey) { return new FilteredEntrySortedMap<>(sortedMap().subMap(fromKey, toKey), predicate); } @Override - public SortedMap tailMap(K fromKey) { + public SortedMap tailMap(@ParametricNullness K fromKey) { return new FilteredEntrySortedMap<>(sortedMap().tailMap(fromKey), predicate); } } @GwtIncompatible // NavigableMap - private static class FilteredEntryNavigableMap extends AbstractNavigableMap { + private static final class FilteredEntryNavigableMap< + K extends @Nullable Object, V extends @Nullable Object> + extends AbstractNavigableMap { /* * It's less code to extend AbstractNavigableMap and forward the filtering logic to * FilteredEntryMap than to extend FilteredEntrySortedMap and reimplement all the NavigableMap @@ -2930,13 +3114,13 @@ private static class FilteredEntryNavigableMap extends AbstractNavigableMa } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return unfiltered.comparator(); } @Override public NavigableSet navigableKeySet() { - return new Maps.NavigableKeySet(this) { + return new NavigableKeySet(this) { @Override public boolean removeAll(Collection collection) { return FilteredEntryMap.removeAllKeys(unfiltered, entryPredicate, collection); @@ -2975,23 +3159,22 @@ public boolean isEmpty() { } @Override - @NullableDecl - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { return filteredDelegate.get(key); } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return filteredDelegate.containsKey(key); } @Override - public V put(K key, V value) { + public @Nullable V put(@ParametricNullness K key, @ParametricNullness V value) { return filteredDelegate.put(key, value); } @Override - public V remove(@NullableDecl Object key) { + public @Nullable V remove(@Nullable Object key) { return filteredDelegate.remove(key); } @@ -3011,12 +3194,12 @@ public Set> entrySet() { } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return Iterables.removeFirstMatching(unfiltered.entrySet(), entryPredicate); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return Iterables.removeFirstMatching(unfiltered.descendingMap().entrySet(), entryPredicate); } @@ -3027,34 +3210,33 @@ public NavigableMap descendingMap() { @Override public NavigableMap subMap( - K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + @ParametricNullness K fromKey, + boolean fromInclusive, + @ParametricNullness K toKey, + boolean toInclusive) { return filterEntries( unfiltered.subMap(fromKey, fromInclusive, toKey, toInclusive), entryPredicate); } @Override - public NavigableMap headMap(K toKey, boolean inclusive) { + public NavigableMap headMap(@ParametricNullness K toKey, boolean inclusive) { return filterEntries(unfiltered.headMap(toKey, inclusive), entryPredicate); } @Override - public NavigableMap tailMap(K fromKey, boolean inclusive) { + public NavigableMap tailMap(@ParametricNullness K fromKey, boolean inclusive) { return filterEntries(unfiltered.tailMap(fromKey, inclusive), entryPredicate); } } - static final class FilteredEntryBiMap extends FilteredEntryMap - implements BiMap { + static final class FilteredEntryBiMap + extends FilteredEntryMap implements BiMap { @RetainedWith private final BiMap inverse; - private static Predicate> inversePredicate( - final Predicate> forwardPredicate) { - return new Predicate>() { - @Override - public boolean apply(Entry input) { - return forwardPredicate.apply(Maps.immutableEntry(input.getValue(), input.getKey())); - } - }; + @SuppressWarnings("nullness") // TODO: b/423853632 - Remove after checker is fixed. + private static + Predicate> inversePredicate(Predicate> forwardPredicate) { + return input -> forwardPredicate.apply(immutableEntry(input.getValue(), input.getKey())); } FilteredEntryBiMap(BiMap delegate, Predicate> predicate) { @@ -3074,7 +3256,7 @@ BiMap unfiltered() { } @Override - public V forcePut(@NullableDecl K key, @NullableDecl V value) { + public @Nullable V forcePut(@ParametricNullness K key, @ParametricNullness V value) { checkArgument(apply(key, value)); return unfiltered().forcePut(key, value); } @@ -3109,8 +3291,8 @@ public Set values() { * @since 12.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap unmodifiableNavigableMap( - NavigableMap map) { + public static + NavigableMap unmodifiableNavigableMap(NavigableMap map) { checkNotNull(map); if (map instanceof UnmodifiableNavigableMap) { @SuppressWarnings("unchecked") // covariant @@ -3121,14 +3303,15 @@ public static NavigableMap unmodifiableNavigableMap( } } - @NullableDecl - private static Entry unmodifiableOrNull(@NullableDecl Entry entry) { - return (entry == null) ? null : Maps.unmodifiableEntry(entry); + private static + @Nullable Entry unmodifiableOrNull(@Nullable Entry entry) { + return (entry == null) ? null : unmodifiableEntry(entry); } @GwtIncompatible // NavigableMap - static class UnmodifiableNavigableMap extends ForwardingSortedMap - implements NavigableMap, Serializable { + private static final class UnmodifiableNavigableMap< + K extends @Nullable Object, V extends @Nullable Object> + extends ForwardingSortedMap implements NavigableMap, Serializable { private final NavigableMap delegate; UnmodifiableNavigableMap(NavigableMap delegate) { @@ -3147,66 +3330,66 @@ protected SortedMap delegate() { } @Override - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(@ParametricNullness K key) { return unmodifiableOrNull(delegate.lowerEntry(key)); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(@ParametricNullness K key) { return delegate.lowerKey(key); } @Override - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(@ParametricNullness K key) { return unmodifiableOrNull(delegate.floorEntry(key)); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(@ParametricNullness K key) { return delegate.floorKey(key); } @Override - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(@ParametricNullness K key) { return unmodifiableOrNull(delegate.ceilingEntry(key)); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(@ParametricNullness K key) { return delegate.ceilingKey(key); } @Override - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(@ParametricNullness K key) { return unmodifiableOrNull(delegate.higherEntry(key)); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(@ParametricNullness K key) { return delegate.higherKey(key); } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return unmodifiableOrNull(delegate.firstEntry()); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return unmodifiableOrNull(delegate.lastEntry()); } @Override - public final Entry pollFirstEntry() { + public final @Nullable Entry pollFirstEntry() { throw new UnsupportedOperationException(); } @Override - public final Entry pollLastEntry() { + public final @Nullable Entry pollLastEntry() { throw new UnsupportedOperationException(); } - @NullableDecl private transient UnmodifiableNavigableMap descendingMap; + @LazyInit private transient @Nullable UnmodifiableNavigableMap descendingMap; @Override public NavigableMap descendingMap() { @@ -3232,35 +3415,37 @@ public NavigableSet descendingKeySet() { } @Override - public SortedMap subMap(K fromKey, K toKey) { + public SortedMap subMap(@ParametricNullness K fromKey, @ParametricNullness K toKey) { return subMap(fromKey, true, toKey, false); } @Override public NavigableMap subMap( - K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { - return Maps.unmodifiableNavigableMap( - delegate.subMap(fromKey, fromInclusive, toKey, toInclusive)); + @ParametricNullness K fromKey, + boolean fromInclusive, + @ParametricNullness K toKey, + boolean toInclusive) { + return unmodifiableNavigableMap(delegate.subMap(fromKey, fromInclusive, toKey, toInclusive)); } @Override - public SortedMap headMap(K toKey) { + public SortedMap headMap(@ParametricNullness K toKey) { return headMap(toKey, false); } @Override - public NavigableMap headMap(K toKey, boolean inclusive) { - return Maps.unmodifiableNavigableMap(delegate.headMap(toKey, inclusive)); + public NavigableMap headMap(@ParametricNullness K toKey, boolean inclusive) { + return unmodifiableNavigableMap(delegate.headMap(toKey, inclusive)); } @Override - public SortedMap tailMap(K fromKey) { + public SortedMap tailMap(@ParametricNullness K fromKey) { return tailMap(fromKey, true); } @Override - public NavigableMap tailMap(K fromKey, boolean inclusive) { - return Maps.unmodifiableNavigableMap(delegate.tailMap(fromKey, inclusive)); + public NavigableMap tailMap(@ParametricNullness K fromKey, boolean inclusive) { + return unmodifiableNavigableMap(delegate.tailMap(fromKey, inclusive)); } } @@ -3273,7 +3458,7 @@ public NavigableMap tailMap(K fromKey, boolean inclusive) { * iterating over any of its collection views, or the collections views of any of its {@code * descendingMap}, {@code subMap}, {@code headMap} or {@code tailMap} views. * - *

    {@code
    +   * {@snippet :
        * NavigableMap map = synchronizedNavigableMap(new TreeMap());
        *
        * // Needn't be in synchronized block
    @@ -3285,11 +3470,11 @@ public NavigableMap tailMap(K fromKey, boolean inclusive) {
        *     foo(it.next());
        *   }
        * }
    -   * }
    + * } * *

    or: * - *

    {@code
    +   * {@snippet :
        * NavigableMap map = synchronizedNavigableMap(new TreeMap());
        * NavigableMap map2 = map.subMap(foo, false, bar, true);
        *
    @@ -3302,7 +3487,7 @@ public NavigableMap tailMap(K fromKey, boolean inclusive) {
        *     foo(it.next());
        *   }
        * }
    -   * }
    + * } * *

    Failure to follow this advice may result in non-deterministic behavior. * @@ -3314,8 +3499,9 @@ public NavigableMap tailMap(K fromKey, boolean inclusive) { * @since 13.0 */ @GwtIncompatible // NavigableMap - public static NavigableMap synchronizedNavigableMap( - NavigableMap navigableMap) { + @J2ktIncompatible // Synchronized + public static + NavigableMap synchronizedNavigableMap(NavigableMap navigableMap) { return Synchronized.navigableMap(navigableMap); } @@ -3323,15 +3509,16 @@ public static NavigableMap synchronizedNavigableMap( * {@code AbstractMap} extension that makes it easy to cache customized keySet, values, and * entrySet views. */ - @GwtCompatible - abstract static class ViewCachingAbstractMap extends AbstractMap { + abstract static class ViewCachingAbstractMap< + K extends @Nullable Object, V extends @Nullable Object> + extends AbstractMap { /** * Creates the entry set to be returned by {@link #entrySet()}. This method is invoked at most * once on a given map, at the time when {@code entrySet} is first called. */ abstract Set> createEntrySet(); - @NullableDecl private transient Set> entrySet; + @LazyInit private transient @Nullable Set> entrySet; @Override public Set> entrySet() { @@ -3339,7 +3526,7 @@ public Set> entrySet() { return (result == null) ? entrySet = createEntrySet() : result; } - @NullableDecl private transient Set keySet; + @LazyInit private transient @Nullable Set keySet; @Override public Set keySet() { @@ -3351,7 +3538,7 @@ Set createKeySet() { return new KeySet<>(this); } - @NullableDecl private transient Collection values; + @LazyInit private transient @Nullable Collection values; @Override public Collection values() { @@ -3364,7 +3551,9 @@ Collection createValues() { } } - abstract static class IteratorBasedAbstractMap extends AbstractMap { + abstract static class IteratorBasedAbstractMap< + K extends @Nullable Object, V extends @Nullable Object> + extends AbstractMap { @Override public abstract int size(); @@ -3395,7 +3584,7 @@ public void clear() { * Delegates to {@link Map#get}. Returns {@code null} on {@code ClassCastException} and {@code * NullPointerException}. */ - static V safeGet(Map map, @NullableDecl Object key) { + static @Nullable V safeGet(Map map, @Nullable Object key) { checkNotNull(map); try { return map.get(key); @@ -3408,7 +3597,7 @@ static V safeGet(Map map, @NullableDecl Object key) { * Delegates to {@link Map#containsKey}. Returns {@code false} on {@code ClassCastException} and * {@code NullPointerException}. */ - static boolean safeContainsKey(Map map, Object key) { + static boolean safeContainsKey(Map map, @Nullable Object key) { checkNotNull(map); try { return map.containsKey(key); @@ -3421,7 +3610,7 @@ static boolean safeContainsKey(Map map, Object key) { * Delegates to {@link Map#remove}. Returns {@code null} on {@code ClassCastException} and {@code * NullPointerException}. */ - static V safeRemove(Map map, Object key) { + static @Nullable V safeRemove(Map map, @Nullable Object key) { checkNotNull(map); try { return map.remove(key); @@ -3431,12 +3620,12 @@ static V safeRemove(Map map, Object key) { } /** An admittedly inefficient implementation of {@link Map#containsKey}. */ - static boolean containsKeyImpl(Map map, @NullableDecl Object key) { + static boolean containsKeyImpl(Map map, @Nullable Object key) { return Iterators.contains(keyIterator(map.entrySet().iterator()), key); } /** An implementation of {@link Map#containsValue}. */ - static boolean containsValueImpl(Map map, @NullableDecl Object value) { + static boolean containsValueImpl(Map map, @Nullable Object value) { return Iterators.contains(valueIterator(map.entrySet().iterator()), value); } @@ -3452,7 +3641,8 @@ static boolean containsValueImpl(Map map, @NullableDecl Object value) { * @param o the object that might be contained in {@code c} * @return {@code true} if {@code c} contains {@code o} */ - static boolean containsEntryImpl(Collection> c, Object o) { + static boolean containsEntryImpl( + Collection> c, @Nullable Object o) { if (!(o instanceof Entry)) { return false; } @@ -3470,7 +3660,8 @@ static boolean containsEntryImpl(Collection> c, Object o) { * @param o the object to remove from {@code c} * @return {@code true} if {@code c} was changed */ - static boolean removeEntryImpl(Collection> c, Object o) { + static boolean removeEntryImpl( + Collection> c, @Nullable Object o) { if (!(o instanceof Entry)) { return false; } @@ -3478,7 +3669,7 @@ static boolean removeEntryImpl(Collection> c, Object o) { } /** An implementation of {@link Map#equals}. */ - static boolean equalsImpl(Map map, Object object) { + static boolean equalsImpl(Map map, @Nullable Object object) { if (map == object) { return true; } else if (object instanceof Map) { @@ -3503,13 +3694,15 @@ static String toStringImpl(Map map) { } /** An implementation of {@link Map#putAll}. */ - static void putAllImpl(Map self, Map map) { + static void putAllImpl( + Map self, Map map) { for (Entry entry : map.entrySet()) { self.put(entry.getKey(), entry.getValue()); } } - static class KeySet extends Sets.ImprovedAbstractSet { + static class KeySet + extends Sets.ImprovedAbstractSet { @Weak final Map map; KeySet(Map map) { @@ -3536,12 +3729,12 @@ public boolean isEmpty() { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { return map().containsKey(o); } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { if (contains(o)) { map().remove(o); return true; @@ -3555,17 +3748,16 @@ public void clear() { } } - @NullableDecl - static K keyOrNull(@NullableDecl Entry entry) { + static @Nullable K keyOrNull(@Nullable Entry entry) { return (entry == null) ? null : entry.getKey(); } - @NullableDecl - static V valueOrNull(@NullableDecl Entry entry) { + static @Nullable V valueOrNull(@Nullable Entry entry) { return (entry == null) ? null : entry.getValue(); } - static class SortedKeySet extends KeySet implements SortedSet { + static class SortedKeySet + extends KeySet implements SortedSet { SortedKeySet(SortedMap map) { super(map); } @@ -3576,38 +3768,41 @@ SortedMap map() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return map().comparator(); } @Override - public SortedSet subSet(K fromElement, K toElement) { + public SortedSet subSet(@ParametricNullness K fromElement, @ParametricNullness K toElement) { return new SortedKeySet<>(map().subMap(fromElement, toElement)); } @Override - public SortedSet headSet(K toElement) { + public SortedSet headSet(@ParametricNullness K toElement) { return new SortedKeySet<>(map().headMap(toElement)); } @Override - public SortedSet tailSet(K fromElement) { + public SortedSet tailSet(@ParametricNullness K fromElement) { return new SortedKeySet<>(map().tailMap(fromElement)); } @Override + @ParametricNullness public K first() { return map().firstKey(); } @Override + @ParametricNullness public K last() { return map().lastKey(); } } @GwtIncompatible // NavigableMap - static class NavigableKeySet extends SortedKeySet implements NavigableSet { + static class NavigableKeySet + extends SortedKeySet implements NavigableSet { NavigableKeySet(NavigableMap map) { super(map); } @@ -3618,32 +3813,32 @@ NavigableMap map() { } @Override - public K lower(K e) { + public @Nullable K lower(@ParametricNullness K e) { return map().lowerKey(e); } @Override - public K floor(K e) { + public @Nullable K floor(@ParametricNullness K e) { return map().floorKey(e); } @Override - public K ceiling(K e) { + public @Nullable K ceiling(@ParametricNullness K e) { return map().ceilingKey(e); } @Override - public K higher(K e) { + public @Nullable K higher(@ParametricNullness K e) { return map().higherKey(e); } @Override - public K pollFirst() { + public @Nullable K pollFirst() { return keyOrNull(map().pollFirstEntry()); } @Override - public K pollLast() { + public @Nullable K pollLast() { return keyOrNull(map().pollLastEntry()); } @@ -3659,37 +3854,41 @@ public Iterator descendingIterator() { @Override public NavigableSet subSet( - K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { + @ParametricNullness K fromElement, + boolean fromInclusive, + @ParametricNullness K toElement, + boolean toInclusive) { return map().subMap(fromElement, fromInclusive, toElement, toInclusive).navigableKeySet(); } @Override - public SortedSet subSet(K fromElement, K toElement) { + public SortedSet subSet(@ParametricNullness K fromElement, @ParametricNullness K toElement) { return subSet(fromElement, true, toElement, false); } @Override - public NavigableSet headSet(K toElement, boolean inclusive) { + public NavigableSet headSet(@ParametricNullness K toElement, boolean inclusive) { return map().headMap(toElement, inclusive).navigableKeySet(); } @Override - public SortedSet headSet(K toElement) { + public SortedSet headSet(@ParametricNullness K toElement) { return headSet(toElement, false); } @Override - public NavigableSet tailSet(K fromElement, boolean inclusive) { + public NavigableSet tailSet(@ParametricNullness K fromElement, boolean inclusive) { return map().tailMap(fromElement, inclusive).navigableKeySet(); } @Override - public SortedSet tailSet(K fromElement) { + public SortedSet tailSet(@ParametricNullness K fromElement) { return tailSet(fromElement, true); } } - static class Values extends AbstractCollection { + static class Values + extends AbstractCollection { @Weak final Map map; Values(Map map) { @@ -3706,12 +3905,12 @@ public Iterator iterator() { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { try { return super.remove(o); } catch (UnsupportedOperationException e) { for (Entry entry : map().entrySet()) { - if (Objects.equal(o, entry.getValue())) { + if (Objects.equals(o, entry.getValue())) { map().remove(entry.getKey()); return true; } @@ -3725,7 +3924,7 @@ public boolean removeAll(Collection c) { try { return super.removeAll(checkNotNull(c)); } catch (UnsupportedOperationException e) { - Set toRemove = Sets.newHashSet(); + Set toRemove = new HashSet<>(); for (Entry entry : map().entrySet()) { if (c.contains(entry.getValue())) { toRemove.add(entry.getKey()); @@ -3740,7 +3939,7 @@ public boolean retainAll(Collection c) { try { return super.retainAll(checkNotNull(c)); } catch (UnsupportedOperationException e) { - Set toRetain = Sets.newHashSet(); + Set toRetain = new HashSet<>(); for (Entry entry : map().entrySet()) { if (c.contains(entry.getValue())) { toRetain.add(entry.getKey()); @@ -3761,7 +3960,7 @@ public boolean isEmpty() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { return map().containsValue(o); } @@ -3771,7 +3970,8 @@ public void clear() { } } - abstract static class EntrySet extends Sets.ImprovedAbstractSet> { + abstract static class EntrySet + extends Sets.ImprovedAbstractSet> { abstract Map map(); @Override @@ -3785,12 +3985,12 @@ public void clear() { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Entry) { Entry entry = (Entry) o; Object key = entry.getKey(); - V value = Maps.safeGet(map(), key); - return Objects.equal(value, entry.getValue()) && (value != null || map().containsKey(key)); + V value = safeGet(map(), key); + return Objects.equals(value, entry.getValue()) && (value != null || map().containsKey(key)); } return false; } @@ -3801,8 +4001,12 @@ public boolean isEmpty() { } @Override - public boolean remove(Object o) { - if (contains(o)) { + public boolean remove(@Nullable Object o) { + /* + * `o instanceof Entry` is guaranteed by `contains`, but we check it here to satisfy our + * nullness checker. + */ + if (contains(o) && o instanceof Entry) { Entry entry = (Entry) o; return map().keySet().remove(entry.getKey()); } @@ -3825,9 +4029,13 @@ public boolean retainAll(Collection c) { return super.retainAll(checkNotNull(c)); } catch (UnsupportedOperationException e) { // if the iterators don't support remove - Set keys = Sets.newHashSetWithExpectedSize(c.size()); + Set<@Nullable Object> keys = Sets.newHashSetWithExpectedSize(c.size()); for (Object o : c) { - if (contains(o)) { + /* + * `o instanceof Entry` is guaranteed by `contains`, but we check it here to satisfy our + * nullness checker. + */ + if (contains(o) && o instanceof Entry) { Entry entry = (Entry) o; keys.add(entry.getKey()); } @@ -3838,8 +4046,8 @@ public boolean retainAll(Collection c) { } @GwtIncompatible // NavigableMap - abstract static class DescendingMap extends ForwardingMap - implements NavigableMap { + abstract static class DescendingMap + extends ForwardingMap implements NavigableMap { abstract NavigableMap forward(); @@ -3848,7 +4056,7 @@ protected final Map delegate() { return forward(); } - @NullableDecl private transient Comparator comparator; + @LazyInit private transient @Nullable Comparator comparator; @SuppressWarnings("unchecked") @Override @@ -3865,77 +4073,79 @@ public Comparator comparator() { } // If we inline this, we get a javac error. - private static Ordering reverse(Comparator forward) { + private static Ordering reverse(Comparator forward) { return Ordering.from(forward).reverse(); } @Override + @ParametricNullness public K firstKey() { return forward().lastKey(); } @Override + @ParametricNullness public K lastKey() { return forward().firstKey(); } @Override - public Entry lowerEntry(K key) { + public @Nullable Entry lowerEntry(@ParametricNullness K key) { return forward().higherEntry(key); } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(@ParametricNullness K key) { return forward().higherKey(key); } @Override - public Entry floorEntry(K key) { + public @Nullable Entry floorEntry(@ParametricNullness K key) { return forward().ceilingEntry(key); } @Override - public K floorKey(K key) { + public @Nullable K floorKey(@ParametricNullness K key) { return forward().ceilingKey(key); } @Override - public Entry ceilingEntry(K key) { + public @Nullable Entry ceilingEntry(@ParametricNullness K key) { return forward().floorEntry(key); } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(@ParametricNullness K key) { return forward().floorKey(key); } @Override - public Entry higherEntry(K key) { + public @Nullable Entry higherEntry(@ParametricNullness K key) { return forward().lowerEntry(key); } @Override - public K higherKey(K key) { + public @Nullable K higherKey(@ParametricNullness K key) { return forward().lowerKey(key); } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return forward().lastEntry(); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return forward().firstEntry(); } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { return forward().pollLastEntry(); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { return forward().pollFirstEntry(); } @@ -3944,7 +4154,7 @@ public NavigableMap descendingMap() { return forward(); } - @NullableDecl private transient Set> entrySet; + @LazyInit private transient @Nullable Set> entrySet; @Override public Set> entrySet() { @@ -3956,7 +4166,7 @@ public Set> entrySet() { Set> createEntrySet() { @WeakOuter - class EntrySetImpl extends EntrySet { + final class EntrySetImpl extends EntrySet { @Override Map map() { return DescendingMap.this; @@ -3975,7 +4185,7 @@ public Set keySet() { return navigableKeySet(); } - @NullableDecl private transient NavigableSet navigableKeySet; + @LazyInit private transient @Nullable NavigableSet navigableKeySet; @Override public NavigableSet navigableKeySet() { @@ -3990,32 +4200,35 @@ public NavigableSet descendingKeySet() { @Override public NavigableMap subMap( - K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + @ParametricNullness K fromKey, + boolean fromInclusive, + @ParametricNullness K toKey, + boolean toInclusive) { return forward().subMap(toKey, toInclusive, fromKey, fromInclusive).descendingMap(); } @Override - public SortedMap subMap(K fromKey, K toKey) { + public SortedMap subMap(@ParametricNullness K fromKey, @ParametricNullness K toKey) { return subMap(fromKey, true, toKey, false); } @Override - public NavigableMap headMap(K toKey, boolean inclusive) { + public NavigableMap headMap(@ParametricNullness K toKey, boolean inclusive) { return forward().tailMap(toKey, inclusive).descendingMap(); } @Override - public SortedMap headMap(K toKey) { + public SortedMap headMap(@ParametricNullness K toKey) { return headMap(toKey, false); } @Override - public NavigableMap tailMap(K fromKey, boolean inclusive) { + public NavigableMap tailMap(@ParametricNullness K fromKey, boolean inclusive) { return forward().headMap(fromKey, inclusive).descendingMap(); } @Override - public SortedMap tailMap(K fromKey) { + public SortedMap tailMap(@ParametricNullness K fromKey) { return tailMap(fromKey, true); } @@ -4037,7 +4250,7 @@ static ImmutableMap indexMap(Collection list) { for (E e : list) { builder.put(e, i++); } - return builder.build(); + return builder.buildOrThrow(); } /** @@ -4056,10 +4269,9 @@ static ImmutableMap indexMap(Collection list) { * * @since 20.0 */ - @Beta @GwtIncompatible // NavigableMap - public static , V> NavigableMap subMap( - NavigableMap map, Range range) { + public static , V extends @Nullable Object> + NavigableMap subMap(NavigableMap map, Range range) { if (map.comparator() != null && map.comparator() != Ordering.natural() && range.hasLowerBound() diff --git a/android/guava/src/com/google/common/collect/MinMaxPriorityQueue.java b/android/guava/src/com/google/common/collect/MinMaxPriorityQueue.java index 639775c5bedf..9d6772c0e92b 100644 --- a/android/guava/src/com/google/common/collect/MinMaxPriorityQueue.java +++ b/android/guava/src/com/google/common/collect/MinMaxPriorityQueue.java @@ -21,11 +21,14 @@ import static com.google.common.base.Preconditions.checkPositionIndex; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.CollectPreconditions.checkRemove; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.System.arraycopy; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.math.IntMath; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.j2objc.annotations.Weak; import com.google.j2objc.annotations.WeakOuter; @@ -41,7 +44,7 @@ import java.util.NoSuchElementException; import java.util.PriorityQueue; import java.util.Queue; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A double-ended priority queue, which provides constant-time access to both its least element and @@ -51,11 +54,11 @@ * *

    Usage example: * - *

    {@code
    + * {@snippet :
      * MinMaxPriorityQueue users = MinMaxPriorityQueue.orderedBy(userComparator)
      *     .maximumSize(1000)
      *     .create();
    - * }
    + * } * *

    As a {@link Queue} it functions exactly as a {@link PriorityQueue}: its head element -- the * implicit target of the methods {@link #peek()}, {@link #poll()} and {@link #remove()} -- is @@ -96,7 +99,6 @@ * @author Torbjorn Gannholm * @since 8.0 */ -@Beta @GwtCompatible public final class MinMaxPriorityQueue extends AbstractQueue { @@ -105,7 +107,7 @@ public final class MinMaxPriorityQueue extends AbstractQueue { * initial contents, and an initial expected size of 11. */ public static > MinMaxPriorityQueue create() { - return new Builder(Ordering.natural()).create(); + return new Builder>(Ordering.natural()).create(); } /** @@ -114,21 +116,28 @@ public static > MinMaxPriorityQueue create() { */ public static > MinMaxPriorityQueue create( Iterable initialContents) { - return new Builder(Ordering.natural()).create(initialContents); + return new Builder(Ordering.natural()).create(initialContents); } /** * Creates and returns a new builder, configured to build {@code MinMaxPriorityQueue} instances * that use {@code comparator} to determine the least and greatest elements. */ + /* + * TODO(cpovirk): Change to Comparator to permit Comparator<@Nullable ...> and + * Comparator? What we have here matches the immutable collections, but those also + * expose a public Builder constructor that accepts "? super." So maybe we should do *that* + * instead. + */ public static Builder orderedBy(Comparator comparator) { - return new Builder(comparator); + return new Builder<>(comparator); } /** * Creates and returns a new builder, configured to build {@code MinMaxPriorityQueue} instances * sized appropriately to hold {@code expectedSize} elements. */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static Builder expectedSize(int expectedSize) { return new Builder(Ordering.natural()).expectedSize(expectedSize); } @@ -139,6 +148,7 @@ public static Builder expectedSize(int expectedSize) { * immediately removes its greatest element (according to its comparator), which might be the * element that was just added. */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static Builder maximumSize(int maximumSize) { return new Builder(Ordering.natural()).maximumSize(maximumSize); } @@ -153,7 +163,6 @@ public static Builder maximumSize(int maximumSize) { * Queue} but not a {@code Queue}). * @since 8.0 */ - @Beta public static final class Builder { /* * TODO(kevinb): when the dust settles, see if we still need this or can @@ -198,7 +207,7 @@ public Builder maximumSize(int maximumSize) { * initial contents. */ public MinMaxPriorityQueue create() { - return create(Collections.emptySet()); + return create(Collections.emptySet()); } /** @@ -207,7 +216,7 @@ public MinMaxPriorityQueue create() { */ public MinMaxPriorityQueue create(Iterable initialContents) { MinMaxPriorityQueue queue = - new MinMaxPriorityQueue( + new MinMaxPriorityQueue<>( this, initialQueueSize(expectedSize, maximumSize, initialContents)); for (T element : initialContents) { queue.offer(element); @@ -224,7 +233,7 @@ private Ordering ordering() { private final Heap minHeap; private final Heap maxHeap; @VisibleForTesting final int maximumSize; - private Object[] queue; + private @Nullable Object[] queue; private int size; private int modCount; @@ -292,17 +301,21 @@ public boolean offer(E element) { @CanIgnoreReturnValue @Override - public E poll() { + public @Nullable E poll() { return isEmpty() ? null : removeAndGet(0); } @SuppressWarnings("unchecked") // we must carefully only allow Es to get in E elementData(int index) { - return (E) queue[index]; + /* + * requireNonNull is safe as long as we're careful to call this method only with populated + * indexes. + */ + return (E) requireNonNull(queue[index]); } @Override - public E peek() { + public @Nullable E peek() { return isEmpty() ? null : elementData(0); } @@ -325,7 +338,7 @@ private int getMaxElementIndex() { * empty. */ @CanIgnoreReturnValue - public E pollFirst() { + public @Nullable E pollFirst() { return poll(); } @@ -343,7 +356,7 @@ public E removeFirst() { * Retrieves, but does not remove, the least element of this queue, or returns {@code null} if the * queue is empty. */ - public E peekFirst() { + public @Nullable E peekFirst() { return peek(); } @@ -352,7 +365,7 @@ public E peekFirst() { * empty. */ @CanIgnoreReturnValue - public E pollLast() { + public @Nullable E pollLast() { return isEmpty() ? null : removeAndGet(getMaxElementIndex()); } @@ -373,7 +386,7 @@ public E removeLast() { * Retrieves, but does not remove, the greatest element of this queue, or returns {@code null} if * the queue is empty. */ - public E peekLast() { + public @Nullable E peekLast() { return isEmpty() ? null : elementData(getMaxElementIndex()); } @@ -392,7 +405,7 @@ public E peekLast() { */ @VisibleForTesting @CanIgnoreReturnValue - MoveDesc removeAt(int index) { + @Nullable MoveDesc removeAt(int index) { checkPositionIndex(index, size); modCount++; size--; @@ -416,18 +429,18 @@ MoveDesc removeAt(int index) { // Last element is moved to before index, swapped with trickled element. if (changes == null) { // The trickled element is still after index. - return new MoveDesc(actualLastElement, toTrickle); + return new MoveDesc<>(actualLastElement, toTrickle); } else { // The trickled element is back before index, but the replaced element // has now been moved after index. - return new MoveDesc(actualLastElement, changes.replaced); + return new MoveDesc<>(actualLastElement, changes.replaced); } } // Trickled element was after index to begin with, no adjustment needed. return changes; } - private MoveDesc fillHole(int index, E toTrickle) { + private @Nullable MoveDesc fillHole(int index, E toTrickle) { Heap heap = heapForIndex(index); // We consider elementData(index) a "hole", and we want to fill it // with the last element of the heap, toTrickle. @@ -450,7 +463,7 @@ private MoveDesc fillHole(int index, E toTrickle) { } // Returned from removeAt() to iterator.remove() - static class MoveDesc { + private static final class MoveDesc { final E toTrickle; final E replaced; @@ -497,14 +510,17 @@ boolean isIntact() { } /** - * Each instance of MinMaxPriortyQueue encapsulates two instances of Heap: a min-heap and a + * Each instance of MinMaxPriorityQueue encapsulates two instances of Heap: a min-heap and a * max-heap. Conceptually, these might each have their own array for storage, but for efficiency's * sake they are stored interleaved on alternate heap levels in the same array (MMPQ.queue). */ @WeakOuter - private class Heap { + private final class Heap { final Ordering ordering; - @Weak @NullableDecl Heap otherHeap; + + @SuppressWarnings("nullness:initialization.field.uninitialized") + @Weak + Heap otherHeap; // always initialized immediately after construction Heap(Ordering ordering) { this.ordering = ordering; @@ -518,7 +534,7 @@ int compareElements(int a, int b) { * Tries to move {@code toTrickle} from a min to a max level and bubble up there. If it moved * before {@code removeIndex} this method returns a pair as described in {@link #removeAt}. */ - MoveDesc tryCrossOverAndBubbleUp(int removeIndex, int vacated, E toTrickle) { + @Nullable MoveDesc tryCrossOverAndBubbleUp(int removeIndex, int vacated, E toTrickle) { int crossOver = crossOver(vacated, toTrickle); if (crossOver == vacated) { return null; @@ -538,7 +554,7 @@ MoveDesc tryCrossOverAndBubbleUp(int removeIndex, int vacated, E toTrickle) { } // bubble it up the opposite heap if (otherHeap.bubbleUpAlternatingLevels(crossOver, toTrickle) < removeIndex) { - return new MoveDesc(toTrickle, parent); + return new MoveDesc<>(toTrickle, parent); } else { return null; } @@ -586,7 +602,7 @@ int findMin(int index, int len) { return -1; } checkState(index > 0); - int limit = Math.min(index, size - len) + len; + int limit = min(index, size - len) + len; int minIndex = index; for (int i = index + 1; i < limit; i++) { if (compareElements(i, minIndex) < 0) { @@ -622,17 +638,18 @@ int crossOverUp(int index, E x) { int parentIndex = getParentIndex(index); E parentElement = elementData(parentIndex); if (parentIndex != 0) { - // This is a guard for the case of the childless uncle. - // Since the end of the array is actually the middle of the heap, - // a smaller childless uncle can become a child of x when we - // bubble up alternate levels, violating the invariant. + /* + * This is a guard for the case of the childless aunt node. Since the end of the array is + * actually the middle of the heap, a smaller childless aunt node can become a child of x + * when we bubble up alternate levels, violating the invariant. + */ int grandparentIndex = getParentIndex(parentIndex); - int uncleIndex = getRightChildIndex(grandparentIndex); - if (uncleIndex != parentIndex && getLeftChildIndex(uncleIndex) >= size) { - E uncleElement = elementData(uncleIndex); - if (ordering.compare(uncleElement, parentElement) < 0) { - parentIndex = uncleIndex; - parentElement = uncleElement; + int auntIndex = getRightChildIndex(grandparentIndex); + if (auntIndex != parentIndex && getLeftChildIndex(auntIndex) >= size) { + E auntElement = elementData(auntIndex); + if (ordering.compare(auntElement, parentElement) < 0) { + parentIndex = auntIndex; + parentElement = auntElement; } } } @@ -645,26 +662,30 @@ int crossOverUp(int index, E x) { return index; } + // About the term "aunt node": it's better to leave gender out of it, but for this the English + // language has nothing for us. Except for the whimsical neologism "pibling" (!) which we + // obviously could not expect to increase anyone's understanding of the code. + /** * Swap {@code actualLastElement} with the conceptually correct last element of the heap. * Returns the index that {@code actualLastElement} now resides in. * *

    Since the last element of the array is actually in the middle of the sorted structure, a - * childless uncle node could be smaller, which would corrupt the invariant if this element - * becomes the new parent of the uncle. In that case, we first switch the last element with its - * uncle, before returning. + * childless aunt node could be smaller, which would corrupt the invariant if this element + * becomes the new parent of the aunt node. In that case, we first switch the last element with + * its aunt node, before returning. */ int swapWithConceptuallyLastElement(E actualLastElement) { int parentIndex = getParentIndex(size); if (parentIndex != 0) { int grandparentIndex = getParentIndex(parentIndex); - int uncleIndex = getRightChildIndex(grandparentIndex); - if (uncleIndex != parentIndex && getLeftChildIndex(uncleIndex) >= size) { - E uncleElement = elementData(uncleIndex); - if (ordering.compare(uncleElement, actualLastElement) < 0) { - queue[uncleIndex] = actualLastElement; - queue[size] = uncleElement; - return uncleIndex; + int auntIndex = getRightChildIndex(grandparentIndex); + if (auntIndex != parentIndex && getLeftChildIndex(auntIndex) >= size) { + E auntElement = elementData(auntIndex); + if (ordering.compare(auntElement, actualLastElement) < 0) { + queue[auntIndex] = actualLastElement; + queue[size] = auntElement; + return auntIndex; } } } @@ -745,15 +766,15 @@ private int getGrandparentIndex(int i) { * *

    If the underlying queue is modified during iteration an exception will be thrown. */ - private class QueueIterator implements Iterator { + private final class QueueIterator implements Iterator { private int cursor = -1; private int nextCursor = -1; private int expectedModCount = modCount; // The same element is not allowed in both forgetMeNot and skipMe, but duplicates are allowed in // either of them, up to the same multiplicity as the queue. - @NullableDecl private Queue forgetMeNot; - @NullableDecl private List skipMe; - @NullableDecl private E lastFromForgetMeNot; + private @Nullable Queue forgetMeNot; + private @Nullable List skipMe; + private @Nullable E lastFromForgetMeNot; private boolean canRemove; @Override @@ -791,9 +812,10 @@ public void remove() { if (cursor < size()) { MoveDesc moved = removeAt(cursor); if (moved != null) { - if (forgetMeNot == null) { - forgetMeNot = new ArrayDeque(); - skipMe = new ArrayList(3); + // Either both are null or neither is, but we check both to satisfy the nullness checker. + if (forgetMeNot == null || skipMe == null) { + forgetMeNot = new ArrayDeque<>(); + skipMe = new ArrayList<>(3); } if (!foundAndRemovedExactReference(skipMe, moved.toTrickle)) { forgetMeNot.add(moved.toTrickle); @@ -805,7 +827,7 @@ public void remove() { cursor--; nextCursor--; } else { // we must have set lastFromForgetMeNot in next() - checkState(removeExact(lastFromForgetMeNot)); + checkState(removeExact(requireNonNull(lastFromForgetMeNot))); lastFromForgetMeNot = null; } } @@ -888,9 +910,10 @@ public void clear() { } @Override + @J2ktIncompatible // Incompatible return type change. Use inherited (unoptimized) implementation public Object[] toArray() { Object[] copyTo = new Object[size]; - System.arraycopy(queue, 0, copyTo, 0, size); + arraycopy(queue, 0, copyTo, 0, size); return copyTo; } @@ -924,7 +947,7 @@ static int initialQueueSize( // Enlarge to contain initial contents if (initialContents instanceof Collection) { int initialSize = ((Collection) initialContents).size(); - result = Math.max(result, initialSize); + result = max(result, initialSize); } // Now cap it at maxSize + 1 @@ -935,7 +958,7 @@ private void growIfNeeded() { if (size > queue.length) { int newCapacity = calculateNewCapacity(); Object[] newQueue = new Object[newCapacity]; - System.arraycopy(queue, 0, newQueue, 0, queue.length); + arraycopy(queue, 0, newQueue, 0, queue.length); queue = newQueue; } } @@ -944,12 +967,12 @@ private void growIfNeeded() { private int calculateNewCapacity() { int oldCapacity = queue.length; int newCapacity = - (oldCapacity < 64) ? (oldCapacity + 1) * 2 : IntMath.checkedMultiply(oldCapacity / 2, 3); + (oldCapacity < 64) ? (oldCapacity + 1) * 2 : Math.multiplyExact(oldCapacity / 2, 3); return capAtMaximumSize(newCapacity, maximumSize); } /** There's no reason for the queueSize to ever be more than maxSize + 1 */ private static int capAtMaximumSize(int queueSize, int maximumSize) { - return Math.min(queueSize - 1, maximumSize) + 1; // don't overflow + return min(queueSize - 1, maximumSize) + 1; // don't overflow } } diff --git a/android/guava/src/com/google/common/collect/MoreCollectors.java b/android/guava/src/com/google/common/collect/MoreCollectors.java new file mode 100644 index 000000000000..467aca395eed --- /dev/null +++ b/android/guava/src/com/google/common/collect/MoreCollectors.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Collections.emptyList; + +import com.google.common.annotations.GwtCompatible; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; + +/** + * Collectors not present in {@code java.util.stream.Collectors} that are not otherwise associated + * with a {@code com.google.common} type. + * + * @author Louis Wasserman + * @since 33.2.0 (available since 21.0 in guava-jre) + */ +@GwtCompatible +@IgnoreJRERequirement // Users will use this only if they're already using streams. +public final class MoreCollectors { + + /* + * TODO(lowasser): figure out if we can convert this to a concurrent AtomicReference-based + * collector without breaking j2cl? + */ + private static final Collector> TO_OPTIONAL = + Collector.of( + ToOptionalState::new, + ToOptionalState::add, + ToOptionalState::combine, + ToOptionalState::getOptional, + Collector.Characteristics.UNORDERED); + + /** + * A collector that converts a stream of zero or one elements to an {@code Optional}. + * + * @throws IllegalArgumentException if the stream consists of two or more elements. + * @throws NullPointerException if any element in the stream is {@code null}. + * @return {@code Optional.of(onlyElement)} if the stream has exactly one element (must not be + * {@code null}) and returns {@code Optional.empty()} if it has none. + */ + @SuppressWarnings("unchecked") + public static Collector> toOptional() { + return (Collector) TO_OPTIONAL; + } + + private static final Object NULL_PLACEHOLDER = new Object(); + + private static final Collector<@Nullable Object, ?, @Nullable Object> ONLY_ELEMENT = + Collector.<@Nullable Object, ToOptionalState, @Nullable Object>of( + ToOptionalState::new, + (state, o) -> state.add((o == null) ? NULL_PLACEHOLDER : o), + ToOptionalState::combine, + state -> { + Object result = state.getElement(); + return (result == NULL_PLACEHOLDER) ? null : result; + }, + Collector.Characteristics.UNORDERED); + + /** + * A collector that takes a stream containing exactly one element and returns that element. The + * returned collector throws an {@code IllegalArgumentException} if the stream consists of two or + * more elements, and a {@code NoSuchElementException} if the stream is empty. + */ + @SuppressWarnings("unchecked") + public static Collector onlyElement() { + return (Collector) ONLY_ELEMENT; + } + + /** + * This atrocity is here to let us report several of the elements in the stream if there were more + * than one, not just two. + */ + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types + private static final class ToOptionalState { + static final int MAX_EXTRAS = 4; + + @Nullable Object element; + List extras; + + ToOptionalState() { + element = null; + extras = emptyList(); + } + + IllegalArgumentException multiples(boolean overflow) { + StringBuilder sb = + new StringBuilder().append("expected one element but was: <").append(element); + for (Object o : extras) { + sb.append(", ").append(o); + } + if (overflow) { + sb.append(", ..."); + } + sb.append('>'); + throw new IllegalArgumentException(sb.toString()); + } + + void add(Object o) { + checkNotNull(o); + if (element == null) { + this.element = o; + } else if (extras.isEmpty()) { + // Replace immutable empty list with mutable list. + extras = new ArrayList<>(MAX_EXTRAS); + extras.add(o); + } else if (extras.size() < MAX_EXTRAS) { + extras.add(o); + } else { + throw multiples(true); + } + } + + ToOptionalState combine(ToOptionalState other) { + if (element == null) { + return other; + } else if (other.element == null) { + return this; + } else { + if (extras.isEmpty()) { + // Replace immutable empty list with mutable list. + extras = new ArrayList<>(); + } + extras.add(other.element); + extras.addAll(other.extras); + if (extras.size() > MAX_EXTRAS) { + extras.subList(MAX_EXTRAS, extras.size()).clear(); + throw multiples(true); + } + return this; + } + } + + @IgnoreJRERequirement // see enclosing class (whose annotation Animal Sniffer ignores here...) + Optional getOptional() { + if (extras.isEmpty()) { + return Optional.ofNullable(element); + } else { + throw multiples(false); + } + } + + Object getElement() { + if (element == null) { + throw new NoSuchElementException(); + } else if (extras.isEmpty()) { + return element; + } else { + throw multiples(false); + } + } + } + + private MoreCollectors() {} +} diff --git a/android/guava/src/com/google/common/collect/Multimap.java b/android/guava/src/com/google/common/collect/Multimap.java index cdfa0f6906dd..870c3182721e 100644 --- a/android/guava/src/com/google/common/collect/Multimap.java +++ b/android/guava/src/com/google/common/collect/Multimap.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A collection that maps keys to values, similar to {@link Map}, but in which each key may be @@ -56,7 +56,7 @@ * *

    The following code: * - *

    {@code
    + * {@snippet :
      * ListMultimap multimap = ArrayListMultimap.create();
      * for (President pres : US_PRESIDENTS_IN_ORDER) {
      *   multimap.put(pres.firstName(), pres.lastName());
    @@ -65,17 +65,17 @@
      *   List lastNames = multimap.get(firstName);
      *   out.println(firstName + ": " + lastNames);
      * }
    - * }
    + * } * * ... produces output such as: * - *
    {@code
    + * {@snippet :
      * Zachary: [Taylor]
      * John: [Adams, Adams, Tyler, Kennedy]  // Remember, Quincy!
      * George: [Washington, Bush, Bush]
      * Grover: [Cleveland, Cleveland]        // Two, non-consecutive terms, rep'ing NJ!
      * ...
    - * }
    + * } * *

    Views

    * @@ -131,13 +131,16 @@ * *

    Implementations

    * - *

    As always, prefer the immutable implementations, {@link ImmutableListMultimap} and {@link - * ImmutableSetMultimap}. General-purpose mutable implementations are listed above under "All Known - * Implementing Classes". You can also create a custom multimap, backed by any {@code Map} - * and {@link Collection} types, using the {@link Multimaps#newMultimap Multimaps.newMultimap} - * family of methods. Finally, another popular way to obtain a multimap is using {@link - * Multimaps#index Multimaps.index}. See the {@link Multimaps} class for these and other static - * utilities related to multimaps. + *

      + *
    • {@link ImmutableListMultimap} + *
    • {@link ImmutableSetMultimap} + *
    • Configure your own mutable multimap with {@link MultimapBuilder} + *
    • {@link LinkedListMultimap} (for one unusual kind of mutable {@code Multimap}) + *
    + * + * Guava contains a number of other multimap implementations, such as {@link ArrayListMultimap}. In + * new code, we recommend using {@link MultimapBuilder} instead: It provides better control of how + * keys and values are stored. * *

    Other Notes

    * @@ -150,15 +153,14 @@ * {@link UnsupportedOperationException}. * *

    See the Guava User Guide article on {@code - * Multimap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}. * * @author Jared Levy * @since 2.0 */ @DoNotMock("Use ImmutableMultimap, HashMultimap, or another implementation") @GwtCompatible -public interface Multimap { +public interface Multimap { // Query Operations /** @@ -180,21 +182,20 @@ public interface Multimap { * Returns {@code true} if this multimap contains at least one key-value pair with the key {@code * key}. */ - boolean containsKey(@CompatibleWith("K") @NullableDecl Object key); + boolean containsKey(@CompatibleWith("K") @Nullable Object key); /** * Returns {@code true} if this multimap contains at least one key-value pair with the value * {@code value}. */ - boolean containsValue(@CompatibleWith("V") @NullableDecl Object value); + boolean containsValue(@CompatibleWith("V") @Nullable Object value); /** * Returns {@code true} if this multimap contains at least one key-value pair with the key {@code * key} and the value {@code value}. */ boolean containsEntry( - @CompatibleWith("K") @NullableDecl Object key, - @CompatibleWith("V") @NullableDecl Object value); + @CompatibleWith("K") @Nullable Object key, @CompatibleWith("V") @Nullable Object value); // Modification Operations @@ -209,7 +210,7 @@ boolean containsEntry( * multimap already contained the key-value pair and doesn't allow duplicates */ @CanIgnoreReturnValue - boolean put(@NullableDecl K key, @NullableDecl V value); + boolean put(@ParametricNullness K key, @ParametricNullness V value); /** * Removes a single key-value pair with the key {@code key} and the value {@code value} from this @@ -220,8 +221,7 @@ boolean containsEntry( */ @CanIgnoreReturnValue boolean remove( - @CompatibleWith("K") @NullableDecl Object key, - @CompatibleWith("V") @NullableDecl Object value); + @CompatibleWith("K") @Nullable Object key, @CompatibleWith("V") @Nullable Object value); // Bulk Operations @@ -229,18 +229,18 @@ boolean remove( * Stores a key-value pair in this multimap for each of {@code values}, all using the same key, * {@code key}. Equivalent to (but expected to be more efficient than): * - *

    {@code
    +   * {@snippet :
        * for (V value : values) {
        *   put(key, value);
        * }
    -   * }
    + * } * *

    In particular, this is a no-op if {@code values} is empty. * * @return {@code true} if the multimap changed */ @CanIgnoreReturnValue - boolean putAll(@NullableDecl K key, Iterable values); + boolean putAll(@ParametricNullness K key, Iterable values); /** * Stores all key-value pairs of {@code multimap} in this multimap, in the order returned by @@ -261,7 +261,7 @@ boolean remove( * no effect on the multimap. */ @CanIgnoreReturnValue - Collection replaceValues(@NullableDecl K key, Iterable values); + Collection replaceValues(@ParametricNullness K key, Iterable values); /** * Removes all values associated with the key {@code key}. @@ -273,7 +273,7 @@ boolean remove( * modifiable, but updating it will have no effect on the multimap. */ @CanIgnoreReturnValue - Collection removeAll(@CompatibleWith("K") @NullableDecl Object key); + Collection removeAll(@CompatibleWith("K") @Nullable Object key); /** Removes all key-value pairs from the multimap, leaving it {@linkplain #isEmpty empty}. */ void clear(); @@ -287,7 +287,7 @@ boolean remove( * *

    Changes to the returned collection will update the underlying multimap, and vice versa. */ - Collection get(@NullableDecl K key); + Collection get(@ParametricNullness K key); /** * Returns a view collection of all distinct keys contained in this multimap. Note that the @@ -354,7 +354,7 @@ boolean remove( * multimaps are equal, because they both have empty {@link #asMap} views. */ @Override - boolean equals(@NullableDecl Object obj); + boolean equals(@Nullable Object obj); /** * Returns the hash code for this multimap. diff --git a/android/guava/src/com/google/common/collect/MultimapBuilder.java b/android/guava/src/com/google/common/collect/MultimapBuilder.java index 161c29d170ee..37910765558c 100644 --- a/android/guava/src/com/google/common/collect/MultimapBuilder.java +++ b/android/guava/src/com/google/common/collect/MultimapBuilder.java @@ -34,20 +34,18 @@ import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; +import org.jspecify.annotations.Nullable; /** - * A builder for a multimap implementation that allows customization of the backing map and value - * collection implementations used in a particular multimap. + * An immutable builder for {@link Multimap} instances, letting you independently select the desired + * behaviors (for example, ordering) of the backing map and value-collections. Example: * - *

    This can be used to easily configure multimap data structure implementations not provided - * explicitly in {@code com.google.common.collect}, for example: - * - *

    {@code
    - * ListMultimap treeListMultimap =
    - *     MultimapBuilder.treeKeys().arrayListValues().build();
    - * SetMultimap hashEnumMultimap =
    - *     MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();
    - * }
    + * {@snippet : + * ListMultimap errorsByUser = + * MultimapBuilder.linkedHashKeys().arrayListValues().build(); + * SortedSetMultimap methodsForName = + * MultimapBuilder.treeKeys().treeSetValues(this::compareMethods).build(); + * } * *

    {@code MultimapBuilder} instances are immutable. Invoking a configuration method has no effect * on the receiving instance; you must store and use the new builder instance it returns instead. @@ -61,7 +59,7 @@ * @since 16.0 */ @GwtCompatible -public abstract class MultimapBuilder { +public abstract class MultimapBuilder { /* * Leaving K and V as upper bounds rather than the actual key and value types allows type * parameters to be left implicit more often. CacheBuilder uses the same technique. @@ -72,7 +70,7 @@ private MultimapBuilder() {} private static final int DEFAULT_EXPECTED_KEYS = 8; /** Uses a hash table to map keys to value collections. */ - public static MultimapBuilderWithKeys hashKeys() { + public static MultimapBuilderWithKeys<@Nullable Object> hashKeys() { return hashKeys(DEFAULT_EXPECTED_KEYS); } @@ -82,11 +80,11 @@ public static MultimapBuilderWithKeys hashKeys() { * * @throws IllegalArgumentException if {@code expectedKeys < 0} */ - public static MultimapBuilderWithKeys hashKeys(final int expectedKeys) { + public static MultimapBuilderWithKeys<@Nullable Object> hashKeys(int expectedKeys) { checkNonnegative(expectedKeys, "expectedKeys"); - return new MultimapBuilderWithKeys() { + return new MultimapBuilderWithKeys<@Nullable Object>() { @Override - Map> createMap() { + Map> createMap() { return Platform.newHashMapWithExpectedSize(expectedKeys); } }; @@ -100,7 +98,7 @@ Map> createMap() { * multimap, save that if all values associated with a key are removed and then the key is added * back into the multimap, that key will come last in the key iteration order. */ - public static MultimapBuilderWithKeys linkedHashKeys() { + public static MultimapBuilderWithKeys<@Nullable Object> linkedHashKeys() { return linkedHashKeys(DEFAULT_EXPECTED_KEYS); } @@ -113,11 +111,11 @@ public static MultimapBuilderWithKeys linkedHashKeys() { * multimap, save that if all values associated with a key are removed and then the key is added * back into the multimap, that key will come last in the key iteration order. */ - public static MultimapBuilderWithKeys linkedHashKeys(final int expectedKeys) { + public static MultimapBuilderWithKeys<@Nullable Object> linkedHashKeys(int expectedKeys) { checkNonnegative(expectedKeys, "expectedKeys"); - return new MultimapBuilderWithKeys() { + return new MultimapBuilderWithKeys<@Nullable Object>() { @Override - Map> createMap() { + Map> createMap() { return Platform.newLinkedHashMapWithExpectedSize(expectedKeys); } }; @@ -151,11 +149,12 @@ public static MultimapBuilderWithKeys treeKeys() { *

    Multimaps generated by the resulting builder will not be serializable if {@code comparator} * is not serializable. */ - public static MultimapBuilderWithKeys treeKeys(final Comparator comparator) { + public static MultimapBuilderWithKeys treeKeys( + Comparator comparator) { checkNotNull(comparator); return new MultimapBuilderWithKeys() { @Override - Map> createMap() { + Map> createMap() { return new TreeMap<>(comparator); } }; @@ -166,13 +165,12 @@ Map> createMap() { * * @since 16.0 */ - public static > MultimapBuilderWithKeys enumKeys( - final Class keyClass) { + public static > MultimapBuilderWithKeys enumKeys(Class keyClass) { checkNotNull(keyClass); return new MultimapBuilderWithKeys() { @SuppressWarnings("unchecked") @Override - Map> createMap() { + Map> createMap() { // K must actually be K0, since enums are effectively final // (their subclasses are inaccessible) return (Map>) new EnumMap>(keyClass); @@ -180,7 +178,8 @@ Map> createMap() { }; } - private static final class ArrayListSupplier implements Supplier>, Serializable { + private static final class ArrayListSupplier + implements Supplier>, Serializable { private final int expectedValuesPerKey; ArrayListSupplier(int expectedValuesPerKey) { @@ -189,14 +188,14 @@ private static final class ArrayListSupplier implements Supplier>, Se @Override public List get() { - return new ArrayList(expectedValuesPerKey); + return new ArrayList<>(expectedValuesPerKey); } } - private enum LinkedListSupplier implements Supplier> { + private enum LinkedListSupplier implements Supplier> { INSTANCE; - public static Supplier> instance() { + static Supplier> instance() { // Each call generates a fresh LinkedList, which can serve as a List for any V. @SuppressWarnings({"rawtypes", "unchecked"}) Supplier> result = (Supplier) INSTANCE; @@ -204,12 +203,15 @@ public static Supplier> instance() { } @Override - public List get() { + // We recommend against linkedListValues but need to keep it for compatibility. + @SuppressWarnings("JdkObsolete") + public List get() { return new LinkedList<>(); } } - private static final class HashSetSupplier implements Supplier>, Serializable { + private static final class HashSetSupplier + implements Supplier>, Serializable { private final int expectedValuesPerKey; HashSetSupplier(int expectedValuesPerKey) { @@ -222,7 +224,8 @@ public Set get() { } } - private static final class LinkedHashSetSupplier implements Supplier>, Serializable { + private static final class LinkedHashSetSupplier + implements Supplier>, Serializable { private final int expectedValuesPerKey; LinkedHashSetSupplier(int expectedValuesPerKey) { @@ -235,7 +238,8 @@ public Set get() { } } - private static final class TreeSetSupplier implements Supplier>, Serializable { + private static final class TreeSetSupplier + implements Supplier>, Serializable { private final Comparator comparator; TreeSetSupplier(Comparator comparator) { @@ -244,7 +248,7 @@ private static final class TreeSetSupplier implements Supplier>, @Override public SortedSet get() { - return new TreeSet(comparator); + return new TreeSet<>(comparator); } } @@ -269,16 +273,16 @@ public Set get() { * @param The upper bound on the key type of the generated multimap. * @since 16.0 */ - public abstract static class MultimapBuilderWithKeys { + public abstract static class MultimapBuilderWithKeys { private static final int DEFAULT_EXPECTED_VALUES_PER_KEY = 2; MultimapBuilderWithKeys() {} - abstract Map> createMap(); + abstract Map> createMap(); /** Uses an {@link ArrayList} to store value collections. */ - public ListMultimapBuilder arrayListValues() { + public ListMultimapBuilder arrayListValues() { return arrayListValues(DEFAULT_EXPECTED_VALUES_PER_KEY); } @@ -288,31 +292,39 @@ public ListMultimapBuilder arrayListValues() { * * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} */ - public ListMultimapBuilder arrayListValues(final int expectedValuesPerKey) { + public ListMultimapBuilder arrayListValues(int expectedValuesPerKey) { checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); - return new ListMultimapBuilder() { + return new ListMultimapBuilder() { @Override - public ListMultimap build() { + public ListMultimap build() { return Multimaps.newListMultimap( - MultimapBuilderWithKeys.this.createMap(), + MultimapBuilderWithKeys.this.createMap(), new ArrayListSupplier(expectedValuesPerKey)); } }; } - /** Uses a {@link LinkedList} to store value collections. */ - public ListMultimapBuilder linkedListValues() { - return new ListMultimapBuilder() { + /** + * Uses a {@link LinkedList} to store value collections. + * + *

    Performance note: {@link ArrayList} and {@link java.util.ArrayDeque} consistently + * outperform {@code LinkedList} except in certain rare and specific situations. Unless you have + * spent a lot of time benchmarking your specific needs, use one of those instead. (However, we + * do not currently offer a {@link Multimap} implementation based on {@link + * java.util.ArrayDeque}.) + */ + public ListMultimapBuilder linkedListValues() { + return new ListMultimapBuilder() { @Override - public ListMultimap build() { + public ListMultimap build() { return Multimaps.newListMultimap( - MultimapBuilderWithKeys.this.createMap(), LinkedListSupplier.instance()); + MultimapBuilderWithKeys.this.createMap(), LinkedListSupplier.instance()); } }; } /** Uses a hash-based {@code Set} to store value collections. */ - public SetMultimapBuilder hashSetValues() { + public SetMultimapBuilder hashSetValues() { return hashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); } @@ -322,20 +334,20 @@ public SetMultimapBuilder hashSetValues() { * * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} */ - public SetMultimapBuilder hashSetValues(final int expectedValuesPerKey) { + public SetMultimapBuilder hashSetValues(int expectedValuesPerKey) { checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); - return new SetMultimapBuilder() { + return new SetMultimapBuilder() { @Override - public SetMultimap build() { + public SetMultimap build() { return Multimaps.newSetMultimap( - MultimapBuilderWithKeys.this.createMap(), + MultimapBuilderWithKeys.this.createMap(), new HashSetSupplier(expectedValuesPerKey)); } }; } /** Uses an insertion-ordered hash-based {@code Set} to store value collections. */ - public SetMultimapBuilder linkedHashSetValues() { + public SetMultimapBuilder linkedHashSetValues() { return linkedHashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); } @@ -345,13 +357,13 @@ public SetMultimapBuilder linkedHashSetValues() { * * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} */ - public SetMultimapBuilder linkedHashSetValues(final int expectedValuesPerKey) { + public SetMultimapBuilder linkedHashSetValues(int expectedValuesPerKey) { checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); - return new SetMultimapBuilder() { + return new SetMultimapBuilder() { @Override - public SetMultimap build() { + public SetMultimap build() { return Multimaps.newSetMultimap( - MultimapBuilderWithKeys.this.createMap(), + MultimapBuilderWithKeys.this.createMap(), new LinkedHashSetSupplier(expectedValuesPerKey)); } }; @@ -369,20 +381,20 @@ public SortedSetMultimapBuilder treeSetValues() { *

    Multimaps generated by the resulting builder will not be serializable if {@code * comparator} is not serializable. */ - public SortedSetMultimapBuilder treeSetValues(final Comparator comparator) { + public SortedSetMultimapBuilder treeSetValues( + Comparator comparator) { checkNotNull(comparator, "comparator"); return new SortedSetMultimapBuilder() { @Override public SortedSetMultimap build() { return Multimaps.newSortedSetMultimap( - MultimapBuilderWithKeys.this.createMap(), new TreeSetSupplier(comparator)); + MultimapBuilderWithKeys.this.createMap(), new TreeSetSupplier(comparator)); } }; } /** Uses an {@link EnumSet} to store value collections. */ - public > SetMultimapBuilder enumSetValues( - final Class valueClass) { + public > SetMultimapBuilder enumSetValues(Class valueClass) { checkNotNull(valueClass, "valueClass"); return new SetMultimapBuilder() { @Override @@ -391,7 +403,7 @@ public SetMultimap build() { // (their subclasses are inaccessible) @SuppressWarnings({"unchecked", "rawtypes"}) Supplier> factory = (Supplier) new EnumSetSupplier(valueClass); - return Multimaps.newSetMultimap(MultimapBuilderWithKeys.this.createMap(), factory); + return Multimaps.newSetMultimap(MultimapBuilderWithKeys.this.createMap(), factory); } }; } @@ -416,7 +428,9 @@ public Multimap build( * * @since 16.0 */ - public abstract static class ListMultimapBuilder extends MultimapBuilder { + public abstract static class ListMultimapBuilder< + K0 extends @Nullable Object, V0 extends @Nullable Object> + extends MultimapBuilder { ListMultimapBuilder() {} @Override @@ -425,7 +439,7 @@ public abstract static class ListMultimapBuilder extends MultimapBuilder @Override public ListMultimap build( Multimap multimap) { - return (ListMultimap) super.build(multimap); + return (ListMultimap) super.build(multimap); } } @@ -434,7 +448,9 @@ public ListMultimap build( * * @since 16.0 */ - public abstract static class SetMultimapBuilder extends MultimapBuilder { + public abstract static class SetMultimapBuilder< + K0 extends @Nullable Object, V0 extends @Nullable Object> + extends MultimapBuilder { SetMultimapBuilder() {} @Override @@ -443,7 +459,7 @@ public abstract static class SetMultimapBuilder extends MultimapBuilder< @Override public SetMultimap build( Multimap multimap) { - return (SetMultimap) super.build(multimap); + return (SetMultimap) super.build(multimap); } } @@ -452,7 +468,9 @@ public SetMultimap build( * * @since 16.0 */ - public abstract static class SortedSetMultimapBuilder extends SetMultimapBuilder { + public abstract static class SortedSetMultimapBuilder< + K0 extends @Nullable Object, V0 extends @Nullable Object> + extends SetMultimapBuilder { SortedSetMultimapBuilder() {} @Override @@ -461,7 +479,7 @@ public abstract static class SortedSetMultimapBuilder extends SetMultima @Override public SortedSetMultimap build( Multimap multimap) { - return (SortedSetMultimap) super.build(multimap); + return (SortedSetMultimap) super.build(multimap); } } } diff --git a/android/guava/src/com/google/common/collect/Multimaps.java b/android/guava/src/com/google/common/collect/Multimaps.java index b3b09ef73595..298199cc4160 100644 --- a/android/guava/src/com/google/common/collect/Multimaps.java +++ b/android/guava/src/com/google/common/collect/Multimaps.java @@ -19,16 +19,19 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.collect.Maps.EntryTransformer; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.Weak; import com.google.j2objc.annotations.WeakOuter; @@ -49,13 +52,15 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.stream.Collector; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; /** * Provides static methods acting on or generating a {@code Multimap}. * *

    See the Guava User Guide article on {@code + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#multimaps">{@code * Multimaps}. * * @author Jared Levy @@ -64,13 +69,111 @@ * @author Louis Wasserman * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Multimaps { private Multimaps() {} + /** + * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the + * specified supplier. The keys and values of the entries are the result of applying the provided + * mapping functions to the input elements, accumulated in the encounter order of the stream. + * + *

    Example: + * + * {@snippet : + * static final ListMultimap FIRST_LETTER_MULTIMAP = + * Stream.of("banana", "apple", "carrot", "asparagus", "cherry") + * .collect( + * toMultimap( + * str -> str.charAt(0), + * str -> str.substring(1), + * MultimapBuilder.treeKeys().arrayListValues()::build)); + * + * // is equivalent to + * + * static final ListMultimap FIRST_LETTER_MULTIMAP; + * + * static { + * FIRST_LETTER_MULTIMAP = MultimapBuilder.treeKeys().arrayListValues().build(); + * FIRST_LETTER_MULTIMAP.put('b', "anana"); + * FIRST_LETTER_MULTIMAP.put('a', "pple"); + * FIRST_LETTER_MULTIMAP.put('a', "sparagus"); + * FIRST_LETTER_MULTIMAP.put('c', "arrot"); + * FIRST_LETTER_MULTIMAP.put('c', "herry"); + * } + * } + * + *

    To collect to an {@link ImmutableMultimap}, use either {@link + * ImmutableSetMultimap#toImmutableSetMultimap} or {@link + * ImmutableListMultimap#toImmutableListMultimap}. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static < + T extends @Nullable Object, + K extends @Nullable Object, + V extends @Nullable Object, + M extends Multimap> + Collector toMultimap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction, + java.util.function.Supplier multimapSupplier) { + return CollectCollectors.toMultimap(keyFunction, valueFunction, multimapSupplier); + } + + /** + * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the + * specified supplier. Each input element is mapped to a key and a stream of values, each of which + * are put into the resulting {@code Multimap}, in the encounter order of the stream and the + * encounter order of the streams of values. + * + *

    Example: + * + * {@snippet : + * static final ListMultimap FIRST_LETTER_MULTIMAP = + * Stream.of("banana", "apple", "carrot", "asparagus", "cherry") + * .collect( + * flatteningToMultimap( + * str -> str.charAt(0), + * str -> str.substring(1).chars().mapToObj(c -> (char) c), + * MultimapBuilder.linkedHashKeys().arrayListValues()::build)); + * + * // is equivalent to + * + * static final ListMultimap FIRST_LETTER_MULTIMAP; + * + * static { + * FIRST_LETTER_MULTIMAP = MultimapBuilder.linkedHashKeys().arrayListValues().build(); + * FIRST_LETTER_MULTIMAP.putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a')); + * FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('p', 'p', 'l', 'e')); + * FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't')); + * FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's')); + * FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y')); + * } + * } + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static < + T extends @Nullable Object, + K extends @Nullable Object, + V extends @Nullable Object, + M extends Multimap> + Collector flatteningToMultimap( + java.util.function.Function keyFunction, + java.util.function.Function> valueFunction, + java.util.function.Supplier multimapSupplier) { + return CollectCollectors.flatteningToMultimap( + keyFunction, valueFunction, multimapSupplier); + } + /** * Creates a new {@code Multimap} backed by {@code map}, whose internal value collections are - * generated by {@code factory}. + * generated by {@code factory}. Most users should prefer {@link MultimapBuilder}, though a small + * number of users will need this method to cover map or collection types that {@link + * MultimapBuilder} does not support. * *

    Warning: do not use this method when the collections returned by {@code factory} * implement either {@link List} or {@code Set}! Use the more specific method {@link @@ -104,12 +207,13 @@ private Multimaps() {} * key * @throws IllegalArgumentException if {@code map} is not empty */ - public static Multimap newMultimap( - Map> map, final Supplier> factory) { + public static Multimap newMultimap( + Map> map, Supplier> factory) { return new CustomMultimap<>(map, factory); } - private static class CustomMultimap extends AbstractMapBasedMultimap { + private static final class CustomMultimap + extends AbstractMapBasedMultimap { transient Supplier> factory; CustomMultimap(Map> map, Supplier> factory) { @@ -133,7 +237,8 @@ protected Collection createCollection() { } @Override - Collection unmodifiableCollectionSubclass(Collection collection) { + Collection unmodifiableCollectionSubclass( + Collection collection) { if (collection instanceof NavigableSet) { return Sets.unmodifiableNavigableSet((NavigableSet) collection); } else if (collection instanceof SortedSet) { @@ -148,7 +253,7 @@ Collection unmodifiableCollectionSubclass(Collection collection) { } @Override - Collection wrapCollection(K key, Collection collection) { + Collection wrapCollection(@ParametricNullness K key, Collection collection) { if (collection instanceof List) { return wrapList(key, (List) collection, null); } else if (collection instanceof NavigableSet) { @@ -165,30 +270,35 @@ Collection wrapCollection(K key, Collection collection) { // can't use Serialization writeMultimap and populateMultimap methods since // there's no way to generate the empty backing map. - /** @serialData the factory and the backing map */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + /** + * @serialData the factory and the backing map + */ + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } - @GwtIncompatible // java.io.ObjectInputStream - @SuppressWarnings("unchecked") // reading data stored by writeObject + @GwtIncompatible + @J2ktIncompatible + @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - factory = (Supplier>) stream.readObject(); - Map> map = (Map>) stream.readObject(); + factory = (Supplier>) requireNonNull(stream.readObject()); + Map> map = (Map>) requireNonNull(stream.readObject()); setMap(map); } - @GwtIncompatible // java serialization not supported - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Creates a new {@code ListMultimap} that uses the provided map and factory. It can generate a - * multimap based on arbitrary {@link Map} and {@link List} classes. + * multimap based on arbitrary {@link Map} and {@link List} classes. Most users should prefer + * {@link MultimapBuilder}, though a small number of users will need this method to cover map or + * collection types that {@link MultimapBuilder} does not support. * *

    The {@code factory}-generated and {@code map} classes determine the multimap iteration * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code @@ -216,12 +326,15 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo * @param factory supplier of new, empty lists that will each hold all values for a given key * @throws IllegalArgumentException if {@code map} is not empty */ - public static ListMultimap newListMultimap( - Map> map, final Supplier> factory) { + public static + ListMultimap newListMultimap( + Map> map, Supplier> factory) { return new CustomListMultimap<>(map, factory); } - private static class CustomListMultimap extends AbstractListMultimap { + private static final class CustomListMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends AbstractListMultimap { transient Supplier> factory; CustomListMultimap(Map> map, Supplier> factory) { @@ -244,30 +357,35 @@ protected List createCollection() { return factory.get(); } - /** @serialData the factory and the backing map */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + /** + * @serialData the factory and the backing map + */ + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } - @GwtIncompatible // java.io.ObjectInputStream - @SuppressWarnings("unchecked") // reading data stored by writeObject + @GwtIncompatible + @J2ktIncompatible + @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - factory = (Supplier>) stream.readObject(); - Map> map = (Map>) stream.readObject(); + factory = (Supplier>) requireNonNull(stream.readObject()); + Map> map = (Map>) requireNonNull(stream.readObject()); setMap(map); } - @GwtIncompatible // java serialization not supported - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** * Creates a new {@code SetMultimap} that uses the provided map and factory. It can generate a - * multimap based on arbitrary {@link Map} and {@link Set} classes. + * multimap based on arbitrary {@link Map} and {@link Set} classes. Most users should prefer + * {@link MultimapBuilder}, though a small number of users will need this method to cover map or + * collection types that {@link MultimapBuilder} does not support. * *

    The {@code factory}-generated and {@code map} classes determine the multimap iteration * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code @@ -294,12 +412,15 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo * @param factory supplier of new, empty sets that will each hold all values for a given key * @throws IllegalArgumentException if {@code map} is not empty */ - public static SetMultimap newSetMultimap( - Map> map, final Supplier> factory) { + public static + SetMultimap newSetMultimap( + Map> map, Supplier> factory) { return new CustomSetMultimap<>(map, factory); } - private static class CustomSetMultimap extends AbstractSetMultimap { + private static final class CustomSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends AbstractSetMultimap { transient Supplier> factory; CustomSetMultimap(Map> map, Supplier> factory) { @@ -323,7 +444,8 @@ protected Set createCollection() { } @Override - Collection unmodifiableCollectionSubclass(Collection collection) { + Collection unmodifiableCollectionSubclass( + Collection collection) { if (collection instanceof NavigableSet) { return Sets.unmodifiableNavigableSet((NavigableSet) collection); } else if (collection instanceof SortedSet) { @@ -334,7 +456,7 @@ Collection unmodifiableCollectionSubclass(Collection collection) { } @Override - Collection wrapCollection(K key, Collection collection) { + Collection wrapCollection(@ParametricNullness K key, Collection collection) { if (collection instanceof NavigableSet) { return new WrappedNavigableSet(key, (NavigableSet) collection, null); } else if (collection instanceof SortedSet) { @@ -344,25 +466,28 @@ Collection wrapCollection(K key, Collection collection) { } } - /** @serialData the factory and the backing map */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + /** + * @serialData the factory and the backing map + */ + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } - @GwtIncompatible // java.io.ObjectInputStream - @SuppressWarnings("unchecked") // reading data stored by writeObject + @GwtIncompatible + @J2ktIncompatible + @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - factory = (Supplier>) stream.readObject(); - Map> map = (Map>) stream.readObject(); + factory = (Supplier>) requireNonNull(stream.readObject()); + Map> map = (Map>) requireNonNull(stream.readObject()); setMap(map); } - @GwtIncompatible // not needed in emulated source - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -394,14 +519,17 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo * key * @throws IllegalArgumentException if {@code map} is not empty */ - public static SortedSetMultimap newSortedSetMultimap( - Map> map, final Supplier> factory) { + public static + SortedSetMultimap newSortedSetMultimap( + Map> map, Supplier> factory) { return new CustomSortedSetMultimap<>(map, factory); } - private static class CustomSortedSetMultimap extends AbstractSortedSetMultimap { + private static final class CustomSortedSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends AbstractSortedSetMultimap { transient Supplier> factory; - transient Comparator valueComparator; + transient @Nullable Comparator valueComparator; CustomSortedSetMultimap(Map> map, Supplier> factory) { super(map); @@ -425,30 +553,33 @@ protected SortedSet createCollection() { } @Override - public Comparator valueComparator() { + public @Nullable Comparator valueComparator() { return valueComparator; } - /** @serialData the factory and the backing map */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + /** + * @serialData the factory and the backing map + */ + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } - @GwtIncompatible // java.io.ObjectInputStream - @SuppressWarnings("unchecked") // reading data stored by writeObject + @GwtIncompatible + @J2ktIncompatible + @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - factory = (Supplier>) stream.readObject(); + factory = (Supplier>) requireNonNull(stream.readObject()); valueComparator = factory.get().comparator(); - Map> map = (Map>) stream.readObject(); + Map> map = (Map>) requireNonNull(stream.readObject()); setMap(map); } - @GwtIncompatible // not needed in emulated source - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -463,8 +594,8 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFo * @return {@code dest} */ @CanIgnoreReturnValue - public static > M invertFrom( - Multimap source, M dest) { + public static > + M invertFrom(Multimap source, M dest) { checkNotNull(dest); for (Map.Entry entry : source.entries()) { dest.put(entry.getValue(), entry.getKey()); @@ -480,9 +611,8 @@ public static > M invertFrom( *

    It is imperative that the user manually synchronize on the returned multimap when accessing * any of its collection views: * - *

    {@code
    -   * Multimap multimap = Multimaps.synchronizedMultimap(
    -   *     HashMultimap.create());
    +   * {@snippet :
    +   * Multimap multimap = Multimaps.synchronizedMultimap(HashMultimap.create());
        * ...
        * Collection values = multimap.get(key);  // Needn't be in synchronized block
        * ...
    @@ -492,7 +622,7 @@ public static > M invertFrom(
        *     foo(i.next());
        *   }
        * }
    -   * }
    + * } * *

    Failure to follow this advice may result in non-deterministic behavior. * @@ -504,7 +634,9 @@ public static > M invertFrom( * @param multimap the multimap to be wrapped in a synchronized view * @return a synchronized view of the specified multimap */ - public static Multimap synchronizedMultimap(Multimap multimap) { + @J2ktIncompatible // Synchronized + public static + Multimap synchronizedMultimap(Multimap multimap) { return Synchronized.multimap(multimap, null); } @@ -519,7 +651,8 @@ public static Multimap synchronizedMultimap(Multimap multimap * @param delegate the multimap for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified multimap */ - public static Multimap unmodifiableMultimap(Multimap delegate) { + public static + Multimap unmodifiableMultimap(Multimap delegate) { if (delegate instanceof UnmodifiableMultimap || delegate instanceof ImmutableMultimap) { return delegate; } @@ -532,21 +665,24 @@ public static Multimap unmodifiableMultimap(Multimap delegate * @deprecated no need to use this * @since 10.0 */ + @InlineMe( + replacement = "checkNotNull(delegate)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated public static Multimap unmodifiableMultimap(ImmutableMultimap delegate) { return checkNotNull(delegate); } - private static class UnmodifiableMultimap extends ForwardingMultimap - implements Serializable { + private static class UnmodifiableMultimap + extends ForwardingMultimap implements Serializable { final Multimap delegate; - @LazyInit @NullableDecl transient Collection> entries; - @LazyInit @NullableDecl transient Multiset keys; - @LazyInit @NullableDecl transient Set keySet; - @LazyInit @NullableDecl transient Collection values; - @LazyInit @NullableDecl transient Map> map; + @LazyInit transient @Nullable Collection> entries; + @LazyInit transient @Nullable Multiset keys; + @LazyInit transient @Nullable Set keySet; + @LazyInit transient @Nullable Collection values; + @LazyInit transient @Nullable Map> map; - UnmodifiableMultimap(final Multimap delegate) { + UnmodifiableMultimap(Multimap delegate) { this.delegate = checkNotNull(delegate); } @@ -567,14 +703,7 @@ public Map> asMap() { result = map = Collections.unmodifiableMap( - Maps.transformValues( - delegate.asMap(), - new Function, Collection>() { - @Override - public Collection apply(Collection collection) { - return unmodifiableValueCollection(collection); - } - })); + Maps.transformValues(delegate.asMap(), Multimaps::unmodifiableValueCollection)); } return result; } @@ -589,7 +718,7 @@ public Collection> entries() { } @Override - public Collection get(K key) { + public Collection get(@ParametricNullness K key) { return unmodifiableValueCollection(delegate.get(key)); } @@ -612,12 +741,12 @@ public Set keySet() { } @Override - public boolean put(K key, V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { throw new UnsupportedOperationException(); } @Override - public boolean putAll(K key, Iterable values) { + public boolean putAll(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @@ -627,17 +756,17 @@ public boolean putAll(Multimap multimap) { } @Override - public boolean remove(Object key, Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { throw new UnsupportedOperationException(); } @Override - public Collection removeAll(Object key) { + public Collection removeAll(@Nullable Object key) { throw new UnsupportedOperationException(); } @Override - public Collection replaceValues(K key, Iterable values) { + public Collection replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @@ -650,11 +779,12 @@ public Collection values() { return result; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static class UnmodifiableListMultimap extends UnmodifiableMultimap - implements ListMultimap { + private static final class UnmodifiableListMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends UnmodifiableMultimap implements ListMultimap { UnmodifiableListMultimap(ListMultimap delegate) { super(delegate); } @@ -665,25 +795,26 @@ public ListMultimap delegate() { } @Override - public List get(K key) { + public List get(@ParametricNullness K key) { return Collections.unmodifiableList(delegate().get(key)); } @Override - public List removeAll(Object key) { + public List removeAll(@Nullable Object key) { throw new UnsupportedOperationException(); } @Override - public List replaceValues(K key, Iterable values) { + public List replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static class UnmodifiableSetMultimap extends UnmodifiableMultimap - implements SetMultimap { + private static class UnmodifiableSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends UnmodifiableMultimap implements SetMultimap { UnmodifiableSetMultimap(SetMultimap delegate) { super(delegate); } @@ -694,7 +825,7 @@ public SetMultimap delegate() { } @Override - public Set get(K key) { + public Set get(@ParametricNullness K key) { /* * Note that this doesn't return a SortedSet when delegate is a * SortedSetMultiset, unlike (SortedSet) super.get(). @@ -708,20 +839,21 @@ public Set> entries() { } @Override - public Set removeAll(Object key) { + public Set removeAll(@Nullable Object key) { throw new UnsupportedOperationException(); } @Override - public Set replaceValues(K key, Iterable values) { + public Set replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static class UnmodifiableSortedSetMultimap extends UnmodifiableSetMultimap - implements SortedSetMultimap { + private static final class UnmodifiableSortedSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends UnmodifiableSetMultimap implements SortedSetMultimap { UnmodifiableSortedSetMultimap(SortedSetMultimap delegate) { super(delegate); } @@ -732,26 +864,26 @@ public SortedSetMultimap delegate() { } @Override - public SortedSet get(K key) { + public SortedSet get(@ParametricNullness K key) { return Collections.unmodifiableSortedSet(delegate().get(key)); } @Override - public SortedSet removeAll(Object key) { + public SortedSet removeAll(@Nullable Object key) { throw new UnsupportedOperationException(); } @Override - public SortedSet replaceValues(K key, Iterable values) { + public SortedSet replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override - public Comparator valueComparator() { + public @Nullable Comparator valueComparator() { return delegate().valueComparator(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -764,7 +896,9 @@ public Comparator valueComparator() { * @param multimap the multimap to be wrapped * @return a synchronized view of the specified multimap */ - public static SetMultimap synchronizedSetMultimap(SetMultimap multimap) { + @J2ktIncompatible // Synchronized + public static + SetMultimap synchronizedSetMultimap(SetMultimap multimap) { return Synchronized.setMultimap(multimap, null); } @@ -779,7 +913,8 @@ public static SetMultimap synchronizedSetMultimap(SetMultimap * @param delegate the multimap for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified multimap */ - public static SetMultimap unmodifiableSetMultimap(SetMultimap delegate) { + public static + SetMultimap unmodifiableSetMultimap(SetMultimap delegate) { if (delegate instanceof UnmodifiableSetMultimap || delegate instanceof ImmutableSetMultimap) { return delegate; } @@ -792,6 +927,9 @@ public static SetMultimap unmodifiableSetMultimap(SetMultimap * @deprecated no need to use this * @since 10.0 */ + @InlineMe( + replacement = "checkNotNull(delegate)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated public static SetMultimap unmodifiableSetMultimap( ImmutableSetMultimap delegate) { @@ -809,8 +947,9 @@ public static SetMultimap unmodifiableSetMultimap( * @param multimap the multimap to be wrapped * @return a synchronized view of the specified multimap */ - public static SortedSetMultimap synchronizedSortedSetMultimap( - SortedSetMultimap multimap) { + @J2ktIncompatible // Synchronized + public static + SortedSetMultimap synchronizedSortedSetMultimap(SortedSetMultimap multimap) { return Synchronized.sortedSetMultimap(multimap, null); } @@ -825,8 +964,8 @@ public static SortedSetMultimap synchronizedSortedSetMultimap( * @param delegate the multimap for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified multimap */ - public static SortedSetMultimap unmodifiableSortedSetMultimap( - SortedSetMultimap delegate) { + public static + SortedSetMultimap unmodifiableSortedSetMultimap(SortedSetMultimap delegate) { if (delegate instanceof UnmodifiableSortedSetMultimap) { return delegate; } @@ -841,7 +980,9 @@ public static SortedSetMultimap unmodifiableSortedSetMultimap( * @param multimap the multimap to be wrapped * @return a synchronized view of the specified multimap */ - public static ListMultimap synchronizedListMultimap(ListMultimap multimap) { + @J2ktIncompatible // Synchronized + public static + ListMultimap synchronizedListMultimap(ListMultimap multimap) { return Synchronized.listMultimap(multimap, null); } @@ -856,7 +997,8 @@ public static ListMultimap synchronizedListMultimap(ListMultimap ListMultimap unmodifiableListMultimap(ListMultimap delegate) { + public static + ListMultimap unmodifiableListMultimap(ListMultimap delegate) { if (delegate instanceof UnmodifiableListMultimap || delegate instanceof ImmutableListMultimap) { return delegate; } @@ -869,6 +1011,9 @@ public static ListMultimap unmodifiableListMultimap(ListMultimap ListMultimap unmodifiableListMultimap( ImmutableListMultimap delegate) { @@ -883,7 +1028,8 @@ public static ListMultimap unmodifiableListMultimap( * @param collection the collection for which to return an unmodifiable view * @return an unmodifiable view of the collection */ - private static Collection unmodifiableValueCollection(Collection collection) { + private static Collection unmodifiableValueCollection( + Collection collection) { if (collection instanceof SortedSet) { return Collections.unmodifiableSortedSet((SortedSet) collection); } else if (collection instanceof Set) { @@ -902,8 +1048,8 @@ private static Collection unmodifiableValueCollection(Collection colle * @param entries the entries for which to return an unmodifiable view * @return an unmodifiable view of the entries */ - private static Collection> unmodifiableEntries( - Collection> entries) { + private static + Collection> unmodifiableEntries(Collection> entries) { if (entries instanceof Set) { return Maps.unmodifiableEntrySet((Set>) entries); } @@ -916,10 +1062,10 @@ private static Collection> unmodifiableEntries( * * @since 15.0 */ - @Beta @SuppressWarnings("unchecked") // safe by specification of ListMultimap.asMap() - public static Map> asMap(ListMultimap multimap) { + public static Map> asMap( + ListMultimap multimap) { return (Map>) (Map) multimap.asMap(); } @@ -929,10 +1075,10 @@ public static Map> asMap(ListMultimap multimap) { * * @since 15.0 */ - @Beta @SuppressWarnings("unchecked") // safe by specification of SetMultimap.asMap() - public static Map> asMap(SetMultimap multimap) { + public static Map> asMap( + SetMultimap multimap) { return (Map>) (Map) multimap.asMap(); } @@ -942,10 +1088,10 @@ public static Map> asMap(SetMultimap multimap) { * * @since 15.0 */ - @Beta @SuppressWarnings("unchecked") // safe by specification of SortedSetMultimap.asMap() - public static Map> asMap(SortedSetMultimap multimap) { + public static Map> asMap( + SortedSetMultimap multimap) { return (Map>) (Map) multimap.asMap(); } @@ -955,8 +1101,8 @@ public static Map> asMap(SortedSetMultimap multimap * * @since 15.0 */ - @Beta - public static Map> asMap(Multimap multimap) { + public static + Map> asMap(Multimap multimap) { return multimap.asMap(); } @@ -975,13 +1121,16 @@ public static Map> asMap(Multimap multimap) { * * @param map the backing map for the returned multimap view */ - public static SetMultimap forMap(Map map) { + public static SetMultimap forMap( + Map map) { return new MapMultimap<>(map); } - /** @see Multimaps#forMap */ - private static class MapMultimap extends AbstractMultimap - implements SetMultimap, Serializable { + /** + * @see Multimaps#forMap + */ + private static final class MapMultimap + extends AbstractMultimap implements SetMultimap, Serializable { final Map map; MapMultimap(Map map) { @@ -994,22 +1143,22 @@ public int size() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return map.containsKey(key); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(@Nullable Object value) { return map.containsValue(value); } @Override - public boolean containsEntry(Object key, Object value) { + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { return map.entrySet().contains(Maps.immutableEntry(key, value)); } @Override - public Set get(final K key) { + public Set get(@ParametricNullness K key) { return new Sets.ImprovedAbstractSet() { @Override public Iterator iterator() { @@ -1022,12 +1171,17 @@ public boolean hasNext() { } @Override + @ParametricNullness public V next() { if (!hasNext()) { throw new NoSuchElementException(); } i++; - return map.get(key); + /* + * The cast is safe because of the containsKey check in hasNext(). (That means it's + * unsafe under concurrent modification, but all bets are off then, anyway.) + */ + return uncheckedCastNullableTToT(map.get(key)); } @Override @@ -1047,12 +1201,12 @@ public int size() { } @Override - public boolean put(K key, V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { throw new UnsupportedOperationException(); } @Override - public boolean putAll(K key, Iterable values) { + public boolean putAll(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @@ -1062,18 +1216,18 @@ public boolean putAll(Multimap multimap) { } @Override - public Set replaceValues(K key, Iterable values) { + public Set replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override - public boolean remove(Object key, Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { return map.entrySet().remove(Maps.immutableEntry(key, value)); } @Override - public Set removeAll(Object key) { - Set values = new HashSet(2); + public Set removeAll(@Nullable Object key) { + Set values = new HashSet<>(2); if (!map.containsKey(key)) { return values; } @@ -1126,14 +1280,14 @@ public int hashCode() { return map.hashCode(); } - private static final long serialVersionUID = 7845222491160860175L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 7845222491160860175L; } /** * Returns a view of a multimap where each value is transformed by a function. All other * properties of the multimap, such as iteration order, are left intact. For example, the code: * - *

    {@code
    +   * {@snippet :
        * Multimap multimap =
        *     ImmutableSetMultimap.of("a", 2, "b", -3, "b", -3, "a", 4, "c", 6);
        * Function square = new Function() {
    @@ -1144,7 +1298,7 @@ public int hashCode() {
        * Multimap transformed =
        *     Multimaps.transformValues(multimap, square);
        *   System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {a=[4, 16], b=[9, 9], c=[36]}}. * @@ -1170,10 +1324,12 @@ public int hashCode() { * * @since 7.0 */ - public static Multimap transformValues( - Multimap fromMultimap, final Function function) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + Multimap transformValues( + Multimap fromMultimap, Function function) { checkNotNull(function); - EntryTransformer transformer = Maps.asEntryTransformer(function); + EntryTransformer transformer = (key, value) -> function.apply(value); return transformEntries(fromMultimap, transformer); } @@ -1182,19 +1338,14 @@ public static Multimap transformValues( * other properties of the multimap, such as iteration order, are left intact. For example, the * code: * - *
    {@code
    -   * ListMultimap multimap
    -   *      = ImmutableListMultimap.of("a", 4, "a", 16, "b", 9);
    -   * Function sqrt =
    -   *     new Function() {
    -   *       public Double apply(Integer in) {
    -   *         return Math.sqrt((int) in);
    -   *       }
    -   *     };
    +   * {@snippet :
    +   * ListMultimap multimap =
    +   *      ImmutableListMultimap.of("a", 4, "a", 16, "b", 9);
    +   * Function sqrt = (Integer in) -> Math.sqrt((int) in);
        * ListMultimap transformed = Multimaps.transformValues(map,
        *     sqrt);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {a=[2.0, 4.0], b=[3.0]}}. * @@ -1217,10 +1368,12 @@ public static Multimap transformValues( * * @since 7.0 */ - public static ListMultimap transformValues( - ListMultimap fromMultimap, final Function function) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + ListMultimap transformValues( + ListMultimap fromMultimap, Function function) { checkNotNull(function); - EntryTransformer transformer = Maps.asEntryTransformer(function); + EntryTransformer transformer = (key, value) -> function.apply(value); return transformEntries(fromMultimap, transformer); } @@ -1232,7 +1385,7 @@ public static ListMultimap transformValues( *

    All other properties of the transformed multimap, such as iteration order, are left intact. * For example, the code: * - *

    {@code
    +   * {@snippet :
        * SetMultimap multimap =
        *     ImmutableSetMultimap.of("a", 1, "a", 4, "b", -6);
        * EntryTransformer transformer =
    @@ -1244,7 +1397,7 @@ public static  ListMultimap transformValues(
        * Multimap transformed =
        *     Multimaps.transformEntries(multimap, transformer);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {a=[a, a], b=[nob]}}. * @@ -1275,8 +1428,10 @@ public static ListMultimap transformValues( * * @since 7.0 */ - public static Multimap transformEntries( - Multimap fromMap, EntryTransformer transformer) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + Multimap transformEntries( + Multimap fromMap, EntryTransformer transformer) { return new TransformedEntriesMultimap<>(fromMap, transformer); } @@ -1288,7 +1443,7 @@ public static Multimap transformEntries( *

    All other properties of the transformed multimap, such as iteration order, are left intact. * For example, the code: * - *

    {@code
    +   * {@snippet :
        * Multimap multimap =
        *     ImmutableMultimap.of("a", 1, "a", 4, "b", 6);
        * EntryTransformer transformer =
    @@ -1300,7 +1455,7 @@ public static  Multimap transformEntries(
        * Multimap transformed =
        *     Multimaps.transformEntries(multimap, transformer);
        * System.out.println(transformed);
    -   * }
    + * } * * ... prints {@code {"a"=["a1", "a4"], "b"=["b6"]}}. * @@ -1328,24 +1483,27 @@ public static Multimap transformEntries( * * @since 7.0 */ - public static ListMultimap transformEntries( - ListMultimap fromMap, EntryTransformer transformer) { + public static < + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + ListMultimap transformEntries( + ListMultimap fromMap, EntryTransformer transformer) { return new TransformedEntriesListMultimap<>(fromMap, transformer); } - private static class TransformedEntriesMultimap extends AbstractMultimap { + private static class TransformedEntriesMultimap< + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> + extends AbstractMultimap { final Multimap fromMultimap; final EntryTransformer transformer; TransformedEntriesMultimap( - Multimap fromMultimap, - final EntryTransformer transformer) { + Multimap fromMultimap, EntryTransformer transformer) { this.fromMultimap = checkNotNull(fromMultimap); this.transformer = checkNotNull(transformer); } - Collection transform(K key, Collection values) { - Function function = Maps.asValueToValueFunction(transformer, key); + Collection transform(@ParametricNullness K key, Collection values) { + Function function = v1 -> transformer.transformEntry(key, v1); if (values instanceof List) { return Lists.transform((List) values, function); } else { @@ -1355,14 +1513,7 @@ Collection transform(K key, Collection values) { @Override Map> createAsMap() { - return Maps.transformEntries( - fromMultimap.asMap(), - new EntryTransformer, Collection>() { - @Override - public Collection transformEntry(K key, Collection value) { - return transform(key, value); - } - }); + return Maps.transformEntries(fromMultimap.asMap(), this::transform); } @Override @@ -1371,7 +1522,7 @@ public void clear() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return fromMultimap.containsKey(key); } @@ -1383,11 +1534,11 @@ Collection> createEntries() { @Override Iterator> entryIterator() { return Iterators.transform( - fromMultimap.entries().iterator(), Maps.asEntryToEntryFunction(transformer)); + fromMultimap.entries().iterator(), Maps.asEntryToEntryFunction(transformer)); } @Override - public Collection get(final K key) { + public Collection get(@ParametricNullness K key) { return transform(key, fromMultimap.get(key)); } @@ -1407,12 +1558,12 @@ Multiset createKeys() { } @Override - public boolean put(K key, V2 value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V2 value) { throw new UnsupportedOperationException(); } @Override - public boolean putAll(K key, Iterable values) { + public boolean putAll(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @@ -1423,18 +1574,18 @@ public boolean putAll(Multimap multimap) { @SuppressWarnings("unchecked") @Override - public boolean remove(Object key, Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { return get((K) key).remove(value); } @SuppressWarnings("unchecked") @Override - public Collection removeAll(Object key) { + public Collection removeAll(@Nullable Object key) { return transform((K) key, fromMultimap.removeAll(key)); } @Override - public Collection replaceValues(K key, Iterable values) { + public Collection replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @@ -1446,11 +1597,13 @@ public int size() { @Override Collection createValues() { return Collections2.transform( - fromMultimap.entries(), Maps.asEntryToValueFunction(transformer)); + fromMultimap.entries(), + entry -> transformer.transformEntry(entry.getKey(), entry.getValue())); } } - private static final class TransformedEntriesListMultimap + private static final class TransformedEntriesListMultimap< + K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> extends TransformedEntriesMultimap implements ListMultimap { TransformedEntriesListMultimap( @@ -1459,23 +1612,23 @@ private static final class TransformedEntriesListMultimap } @Override - List transform(K key, Collection values) { - return Lists.transform((List) values, Maps.asValueToValueFunction(transformer, key)); + List transform(@ParametricNullness K key, Collection values) { + return Lists.transform((List) values, v1 -> transformer.transformEntry(key, v1)); } @Override - public List get(K key) { + public List get(@ParametricNullness K key) { return transform(key, fromMultimap.get(key)); } @SuppressWarnings("unchecked") @Override - public List removeAll(Object key) { + public List removeAll(@Nullable Object key) { return transform((K) key, fromMultimap.removeAll(key)); } @Override - public List replaceValues(K key, Iterable values) { + public List replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } } @@ -1491,20 +1644,20 @@ public List replaceValues(K key, Iterable values) { * *

    For example, * - *

    {@code
    +   * {@snippet :
        * List badGuys =
        *     Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
        * Function stringLengthFunction = ...;
        * Multimap index =
        *     Multimaps.index(badGuys, stringLengthFunction);
        * System.out.println(index);
    -   * }
    + * } * *

    prints * - *

    {@code
    +   * {@snippet :
        * {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
    -   * }
    + * } * *

    The returned multimap is serializable if its keys and values are all serializable. * @@ -1531,20 +1684,20 @@ public static ImmutableListMultimap index( * *

    For example, * - *

    {@code
    +   * {@snippet :
        * List badGuys =
        *     Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
        * Function stringLengthFunction = ...;
        * Multimap index =
        *     Multimaps.index(badGuys.iterator(), stringLengthFunction);
        * System.out.println(index);
    -   * }
    + * } * *

    prints * - *

    {@code
    +   * {@snippet :
        * {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
    -   * }
    + * } * *

    The returned multimap is serializable if its keys and values are all serializable. * @@ -1568,7 +1721,8 @@ public static ImmutableListMultimap index( return builder.build(); } - static class Keys extends AbstractMultiset { + static class Keys + extends AbstractMultiset { @Weak final Multimap multimap; Keys(Multimap multimap) { @@ -1580,9 +1734,10 @@ Iterator> entryIterator() { return new TransformedIterator>, Multiset.Entry>( multimap.asMap().entrySet().iterator()) { @Override - Multiset.Entry transform(final Map.Entry> backingEntry) { + Multiset.Entry transform(Map.Entry> backingEntry) { return new Multisets.AbstractEntry() { @Override + @ParametricNullness public K getElement() { return backingEntry.getKey(); } @@ -1607,7 +1762,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object element) { + public boolean contains(@Nullable Object element) { return multimap.containsKey(element); } @@ -1617,13 +1772,13 @@ public Iterator iterator() { } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { Collection values = Maps.safeGet(multimap.asMap(), element); return (values == null) ? 0 : values.size(); } @Override - public int remove(@NullableDecl Object element, int occurrences) { + public int remove(@Nullable Object element, int occurrences) { checkNonnegative(occurrences, "occurrences"); if (occurrences == 0) { return count(element); @@ -1665,7 +1820,8 @@ Iterator elementIterator() { } /** A skeleton implementation of {@link Multimap#entries()}. */ - abstract static class Entries extends AbstractCollection> { + abstract static class Entries + extends AbstractCollection> { abstract Multimap multimap(); @Override @@ -1674,7 +1830,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Map.Entry) { Map.Entry entry = (Map.Entry) o; return multimap().containsEntry(entry.getKey(), entry.getValue()); @@ -1683,7 +1839,7 @@ public boolean contains(@NullableDecl Object o) { } @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { if (o instanceof Map.Entry) { Map.Entry entry = (Map.Entry) o; return multimap().remove(entry.getKey(), entry.getValue()); @@ -1698,7 +1854,8 @@ public void clear() { } /** A skeleton implementation of {@link Multimap#asMap()}. */ - static final class AsMap extends Maps.ViewCachingAbstractMap> { + static final class AsMap + extends Maps.ViewCachingAbstractMap> { @Weak private final Multimap multimap; AsMap(Multimap multimap) { @@ -1715,12 +1872,12 @@ protected Set>> createEntrySet() { return new EntrySet(); } - void removeValuesForKey(Object key) { + void removeValuesForKey(@Nullable Object key) { multimap.keySet().remove(key); } @WeakOuter - class EntrySet extends Maps.EntrySet> { + final class EntrySet extends Maps.EntrySet> { @Override Map> map() { return AsMap.this; @@ -1728,22 +1885,16 @@ Map> map() { @Override public Iterator>> iterator() { - return Maps.asMapEntryIterator( - multimap.keySet(), - new Function>() { - @Override - public Collection apply(K key) { - return multimap.get(key); - } - }); + return Maps.asMapEntryIterator(multimap.keySet(), multimap::get); } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { if (!contains(o)) { return false; } - Map.Entry entry = (Map.Entry) o; + // requireNonNull is safe because of the contains check. + Map.Entry entry = requireNonNull((Map.Entry) o); removeValuesForKey(entry.getKey()); return true; } @@ -1751,12 +1902,12 @@ public boolean remove(Object o) { @SuppressWarnings("unchecked") @Override - public Collection get(Object key) { + public @Nullable Collection get(@Nullable Object key) { return containsKey(key) ? multimap.get((K) key) : null; } @Override - public Collection remove(Object key) { + public @Nullable Collection remove(@Nullable Object key) { return containsKey(key) ? multimap.removeAll(key) : null; } @@ -1771,7 +1922,7 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return multimap.containsKey(key); } @@ -1808,8 +1959,8 @@ public void clear() { * * @since 11.0 */ - public static Multimap filterKeys( - Multimap unfiltered, final Predicate keyPredicate) { + public static Multimap filterKeys( + Multimap unfiltered, Predicate keyPredicate) { if (unfiltered instanceof SetMultimap) { return filterKeys((SetMultimap) unfiltered, keyPredicate); } else if (unfiltered instanceof ListMultimap) { @@ -1817,10 +1968,10 @@ public static Multimap filterKeys( } else if (unfiltered instanceof FilteredKeyMultimap) { FilteredKeyMultimap prev = (FilteredKeyMultimap) unfiltered; return new FilteredKeyMultimap<>( - prev.unfiltered, Predicates.and(prev.keyPredicate, keyPredicate)); + prev.unfiltered, Predicates.and(prev.keyPredicate, keyPredicate)); } else if (unfiltered instanceof FilteredMultimap) { FilteredMultimap prev = (FilteredMultimap) unfiltered; - return filterFiltered(prev, Maps.keyPredicateOnEntries(keyPredicate)); + return filterFiltered(prev, Maps.keyPredicateOnEntries(keyPredicate)); } else { return new FilteredKeyMultimap<>(unfiltered, keyPredicate); } @@ -1853,15 +2004,16 @@ public static Multimap filterKeys( * * @since 14.0 */ - public static SetMultimap filterKeys( - SetMultimap unfiltered, final Predicate keyPredicate) { + public static + SetMultimap filterKeys( + SetMultimap unfiltered, Predicate keyPredicate) { if (unfiltered instanceof FilteredKeySetMultimap) { FilteredKeySetMultimap prev = (FilteredKeySetMultimap) unfiltered; return new FilteredKeySetMultimap<>( - prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); + prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); } else if (unfiltered instanceof FilteredSetMultimap) { FilteredSetMultimap prev = (FilteredSetMultimap) unfiltered; - return filterFiltered(prev, Maps.keyPredicateOnEntries(keyPredicate)); + return filterFiltered(prev, Maps.keyPredicateOnEntries(keyPredicate)); } else { return new FilteredKeySetMultimap<>(unfiltered, keyPredicate); } @@ -1894,12 +2046,13 @@ public static SetMultimap filterKeys( * * @since 14.0 */ - public static ListMultimap filterKeys( - ListMultimap unfiltered, final Predicate keyPredicate) { + public static + ListMultimap filterKeys( + ListMultimap unfiltered, Predicate keyPredicate) { if (unfiltered instanceof FilteredKeyListMultimap) { FilteredKeyListMultimap prev = (FilteredKeyListMultimap) unfiltered; return new FilteredKeyListMultimap<>( - prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); + prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); } else { return new FilteredKeyListMultimap<>(unfiltered, keyPredicate); } @@ -1932,9 +2085,9 @@ public static ListMultimap filterKeys( * * @since 11.0 */ - public static Multimap filterValues( - Multimap unfiltered, final Predicate valuePredicate) { - return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + public static + Multimap filterValues(Multimap unfiltered, Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } /** @@ -1964,9 +2117,10 @@ public static Multimap filterValues( * * @since 14.0 */ - public static SetMultimap filterValues( - SetMultimap unfiltered, final Predicate valuePredicate) { - return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + public static + SetMultimap filterValues( + SetMultimap unfiltered, Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } /** @@ -1994,8 +2148,9 @@ public static SetMultimap filterValues( * * @since 11.0 */ - public static Multimap filterEntries( - Multimap unfiltered, Predicate> entryPredicate) { + public static + Multimap filterEntries( + Multimap unfiltered, Predicate> entryPredicate) { checkNotNull(entryPredicate); if (unfiltered instanceof SetMultimap) { return filterEntries((SetMultimap) unfiltered, entryPredicate); @@ -2030,8 +2185,9 @@ public static Multimap filterEntries( * * @since 14.0 */ - public static SetMultimap filterEntries( - SetMultimap unfiltered, Predicate> entryPredicate) { + public static + SetMultimap filterEntries( + SetMultimap unfiltered, Predicate> entryPredicate) { checkNotNull(entryPredicate); return (unfiltered instanceof FilteredSetMultimap) ? filterFiltered((FilteredSetMultimap) unfiltered, entryPredicate) @@ -2044,10 +2200,10 @@ public static SetMultimap filterEntries( * lead to a multimap whose removal operations would fail. This method combines the predicates to * avoid that problem. */ - private static Multimap filterFiltered( - FilteredMultimap multimap, Predicate> entryPredicate) { - Predicate> predicate = - Predicates.>and(multimap.entryPredicate(), entryPredicate); + private static + Multimap filterFiltered( + FilteredMultimap multimap, Predicate> entryPredicate) { + Predicate> predicate = Predicates.and(multimap.entryPredicate(), entryPredicate); return new FilteredEntryMultimap<>(multimap.unfiltered(), predicate); } @@ -2057,14 +2213,14 @@ private static Multimap filterFiltered( * lead to a multimap whose removal operations would fail. This method combines the predicates to * avoid that problem. */ - private static SetMultimap filterFiltered( - FilteredSetMultimap multimap, Predicate> entryPredicate) { - Predicate> predicate = - Predicates.>and(multimap.entryPredicate(), entryPredicate); + private static + SetMultimap filterFiltered( + FilteredSetMultimap multimap, Predicate> entryPredicate) { + Predicate> predicate = Predicates.and(multimap.entryPredicate(), entryPredicate); return new FilteredEntrySetMultimap<>(multimap.unfiltered(), predicate); } - static boolean equalsImpl(Multimap multimap, @NullableDecl Object object) { + static boolean equalsImpl(Multimap multimap, @Nullable Object object) { if (object == multimap) { return true; } diff --git a/android/guava/src/com/google/common/collect/Multiset.java b/android/guava/src/com/google/common/collect/Multiset.java index faedb56071e5..cf6cffb6c6ce 100644 --- a/android/guava/src/com/google/common/collect/Multiset.java +++ b/android/guava/src/com/google/common/collect/Multiset.java @@ -24,7 +24,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A collection that supports order-independent equality, like {@link Set}, but may have duplicate @@ -50,8 +50,7 @@ *

    In addition to these required methods, implementations of {@code Multiset} are expected to * provide two {@code static} creation methods: {@code create()}, returning an empty multiset, and * {@code create(Iterable)}, returning a multiset containing the given initial - * elements. This is simply a refinement of {@code Collection}'s constructor recommendations, - * reflecting the new developments of Java 5. + * elements. This is simply a refinement of {@code Collection}'s constructor recommendations. * *

    As with other collection types, the modification operations are optional, and should throw * {@link UnsupportedOperationException} when they are not implemented. Most implementations should @@ -61,22 +60,34 @@ *

    A multiset uses {@link Object#equals} to determine whether two instances should be considered * "the same," unless specified otherwise by the implementation. * - *

    Common implementations include {@link ImmutableMultiset}, {@link HashMultiset}, and {@link - * ConcurrentHashMultiset}. + *

    Warning: as with normal {@link Set}s, it is almost always a bad idea to modify an + * element (in a way that affects its {@link Object#equals} behavior) while it is contained in a + * multiset. Undefined behavior and bugs will result. + * + *

    Implementations

    + * + *
      + *
    • {@link ImmutableMultiset} + *
    • {@link ImmutableSortedMultiset} + *
    • {@link HashMultiset} + *
    • {@link LinkedHashMultiset} + *
    • {@link TreeMultiset} + *
    • {@link EnumMultiset} + *
    • {@link ConcurrentHashMultiset} + *
    * *

    If your values may be zero, negative, or outside the range of an int, you may wish to use * {@link com.google.common.util.concurrent.AtomicLongMap} instead. Note, however, that unlike * {@code Multiset}, {@code AtomicLongMap} does not automatically remove zeros. * *

    See the Guava User Guide article on {@code - * Multiset}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}. * * @author Kevin Bourrillion * @since 2.0 */ @GwtCompatible -public interface Multiset extends Collection { +public interface Multiset extends Collection { // Query Operations /** @@ -101,7 +112,7 @@ public interface Multiset extends Collection { * @return the number of occurrences of the element in this multiset; possibly zero but never * negative */ - int count(@NullableDecl @CompatibleWith("E") Object element); + int count(@CompatibleWith("E") @Nullable Object element); // Bulk Operations @@ -124,7 +135,7 @@ public interface Multiset extends Collection { * return normally. */ @CanIgnoreReturnValue - int add(@NullableDecl E element, int occurrences); + int add(@ParametricNullness E element, int occurrences); /** * Adds a single occurrence of the specified element to this multiset. @@ -147,7 +158,7 @@ public interface Multiset extends Collection { */ @CanIgnoreReturnValue @Override - boolean add(E element); + boolean add(@ParametricNullness E element); /** * Removes a number of occurrences of the specified element from this multiset. If the multiset @@ -162,7 +173,7 @@ public interface Multiset extends Collection { * @throws IllegalArgumentException if {@code occurrences} is negative */ @CanIgnoreReturnValue - int remove(@NullableDecl @CompatibleWith("E") Object element, int occurrences); + int remove(@CompatibleWith("E") @Nullable Object element, int occurrences); /** * Removes a single occurrence of the specified element from this multiset, if present. @@ -178,7 +189,7 @@ public interface Multiset extends Collection { */ @CanIgnoreReturnValue @Override - boolean remove(@NullableDecl Object element); + boolean remove(@Nullable Object element); /** * Adds or removes the necessary occurrences of an element such that the element attains the @@ -194,7 +205,7 @@ public interface Multiset extends Collection { * zero instead. */ @CanIgnoreReturnValue - int setCount(E element, int count); + int setCount(@ParametricNullness E element, int count); /** * Conditionally sets the count of an element to a new value, as described in {@link @@ -213,7 +224,7 @@ public interface Multiset extends Collection { * implementor may optionally return {@code true} instead. */ @CanIgnoreReturnValue - boolean setCount(E element, int oldCount, int newCount); + boolean setCount(@ParametricNullness E element, int oldCount, int newCount); // Views @@ -259,7 +270,7 @@ public interface Multiset extends Collection { * * @since 2.0 */ - interface Entry { + interface Entry { /** * Returns the multiset element corresponding to this entry. Multiple calls to this method @@ -267,6 +278,7 @@ interface Entry { * * @return the element corresponding to this entry */ + @ParametricNullness E getElement(); /** @@ -287,14 +299,14 @@ interface Entry { * represent the same element and count. That is, two entries {@code a} and {@code b} are equal * if: * - *

    {@code
    -     * Objects.equal(a.getElement(), b.getElement())
    +     * {@snippet :
    +     * Objects.equals(a.getElement(), b.getElement())
          *     && a.getCount() == b.getCount()
    -     * }
    + * } */ @Override // TODO(kevinb): check this wrt TreeMultiset? - boolean equals(Object o); + boolean equals(@Nullable Object o); /** * {@inheritDoc} @@ -302,9 +314,9 @@ interface Entry { *

    The hash code of a multiset entry for element {@code element} and count {@code count} is * defined as: * - *

    {@code
    +     * {@snippet :
          * ((element == null) ? 0 : element.hashCode()) ^ count
    -     * }
    + * } */ @Override int hashCode(); @@ -328,14 +340,14 @@ interface Entry { */ @Override // TODO(kevinb): caveats about equivalence-relation? - boolean equals(@NullableDecl Object object); + boolean equals(@Nullable Object object); /** * Returns the hash code for this multiset. This is defined as the sum of * - *
    {@code
    +   * {@snippet :
        * ((element == null) ? 0 : element.hashCode()) ^ count(element)
    -   * }
    + * } * *

    over all distinct elements in the multiset. It follows that a multiset and its entry set * always have the same hash code. @@ -374,7 +386,7 @@ interface Entry { * @return {@code true} if this multiset contains at least one occurrence of the element */ @Override - boolean contains(@NullableDecl Object element); + boolean contains(@Nullable Object element); /** * Returns {@code true} if this multiset contains at least one occurrence of each element in the diff --git a/android/guava/src/com/google/common/collect/Multisets.java b/android/guava/src/com/google/common/collect/Multisets.java index 0f31b7420e45..b3b33c5b10c1 100644 --- a/android/guava/src/com/google/common/collect/Multisets.java +++ b/android/guava/src/com/google/common/collect/Multisets.java @@ -20,16 +20,22 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; import static com.google.common.collect.CollectPreconditions.checkRemove; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.util.Arrays.asList; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Multiset.Entry; import com.google.common.math.IntMath; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; @@ -37,14 +43,19 @@ import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * Provides static utility methods for creating and working with {@link Multiset} instances. * *

    See the Guava User Guide article on {@code + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#multisets">{@code * Multisets}. * * @author Kevin Bourrillion @@ -56,6 +67,32 @@ public final class Multisets { private Multisets() {} + /** + * Returns a {@code Collector} that accumulates elements into a multiset created via the specified + * {@code Supplier}, whose elements are the result of applying {@code elementFunction} to the + * inputs, with counts equal to the result of applying {@code countFunction} to the inputs. + * Elements are added in encounter order. + * + *

    If the mapped elements contain duplicates (according to {@link Object#equals}), the element + * will be added more than once, with the count summed over all appearances of the element. + * + *

    Note that {@code stream.collect(toMultiset(function, e -> 1, supplier))} is equivalent to + * {@code stream.map(function).collect(Collectors.toCollection(supplier))}. + * + *

    To collect to an {@link ImmutableMultiset}, use {@link + * ImmutableMultiset#toImmutableMultiset}. + * + * @since 33.2.0 (available since 22.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static > + Collector toMultiset( + Function elementFunction, + ToIntFunction countFunction, + Supplier multisetSupplier) { + return CollectCollectors.toMultiset(elementFunction, countFunction, multisetSupplier); + } + /** * Returns an unmodifiable view of the specified multiset. Query operations on the returned * multiset "read through" to the specified multiset, and attempts to modify the returned multiset @@ -66,13 +103,14 @@ private Multisets() {} * @param multiset the multiset for which an unmodifiable view is to be generated * @return an unmodifiable view of the multiset */ - public static Multiset unmodifiableMultiset(Multiset multiset) { + public static Multiset unmodifiableMultiset( + Multiset multiset) { if (multiset instanceof UnmodifiableMultiset || multiset instanceof ImmutableMultiset) { @SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe Multiset result = (Multiset) multiset; return result; } - return new UnmodifiableMultiset(checkNotNull(multiset)); + return new UnmodifiableMultiset<>(checkNotNull(multiset)); } /** @@ -81,12 +119,16 @@ public static Multiset unmodifiableMultiset(Multiset multise * @deprecated no need to use this * @since 10.0 */ + @InlineMe( + replacement = "checkNotNull(multiset)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated public static Multiset unmodifiableMultiset(ImmutableMultiset multiset) { return checkNotNull(multiset); } - static class UnmodifiableMultiset extends ForwardingMultiset implements Serializable { + static class UnmodifiableMultiset extends ForwardingMultiset + implements Serializable { final Multiset delegate; UnmodifiableMultiset(Multiset delegate) { @@ -100,10 +142,10 @@ protected Multiset delegate() { return (Multiset) delegate; } - @NullableDecl transient Set elementSet; + @LazyInit transient @Nullable Set elementSet; Set createElementSet() { - return Collections.unmodifiableSet(delegate.elementSet()); + return Collections.unmodifiableSet(delegate.elementSet()); } @Override @@ -112,7 +154,7 @@ public Set elementSet() { return (es == null) ? elementSet = createElementSet() : es; } - @NullableDecl transient Set> entrySet; + @LazyInit transient @Nullable Set> entrySet; @SuppressWarnings("unchecked") @Override @@ -127,16 +169,16 @@ public Set> entrySet() { @Override public Iterator iterator() { - return Iterators.unmodifiableIterator(delegate.iterator()); + return Iterators.unmodifiableIterator(delegate.iterator()); } @Override - public boolean add(E element) { + public boolean add(@ParametricNullness E element) { throw new UnsupportedOperationException(); } @Override - public int add(E element, int occurences) { + public int add(@ParametricNullness E element, int occurrences) { throw new UnsupportedOperationException(); } @@ -146,12 +188,12 @@ public boolean addAll(Collection elementsToAdd) { } @Override - public boolean remove(Object element) { + public boolean remove(@Nullable Object element) { throw new UnsupportedOperationException(); } @Override - public int remove(Object element, int occurrences) { + public int remove(@Nullable Object element, int occurrences) { throw new UnsupportedOperationException(); } @@ -171,16 +213,16 @@ public void clear() { } @Override - public int setCount(E element, int count) { + public int setCount(@ParametricNullness E element, int count) { throw new UnsupportedOperationException(); } @Override - public boolean setCount(E element, int oldCount, int newCount) { + public boolean setCount(@ParametricNullness E element, int oldCount, int newCount) { throw new UnsupportedOperationException(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -194,10 +236,10 @@ public boolean setCount(E element, int oldCount, int newCount) { * @return an unmodifiable view of the multiset * @since 11.0 */ - @Beta - public static SortedMultiset unmodifiableSortedMultiset(SortedMultiset sortedMultiset) { + public static SortedMultiset unmodifiableSortedMultiset( + SortedMultiset sortedMultiset) { // it's in its own file so it can be emulated for GWT - return new UnmodifiableSortedMultiset(checkNotNull(sortedMultiset)); + return new UnmodifiableSortedMultiset<>(checkNotNull(sortedMultiset)); } /** @@ -208,22 +250,24 @@ public static SortedMultiset unmodifiableSortedMultiset(SortedMultiset * @param n the count to be associated with the returned entry * @throws IllegalArgumentException if {@code n} is negative */ - public static Multiset.Entry immutableEntry(@NullableDecl E e, int n) { - return new ImmutableEntry(e, n); + public static Multiset.Entry immutableEntry( + @ParametricNullness E e, int n) { + return new ImmutableEntry<>(e, n); } - static class ImmutableEntry extends AbstractEntry implements Serializable { - @NullableDecl private final E element; + static class ImmutableEntry extends AbstractEntry + implements Serializable { + @ParametricNullness private final E element; private final int count; - ImmutableEntry(@NullableDecl E element, int count) { + ImmutableEntry(@ParametricNullness E element, int count) { this.element = element; this.count = count; checkNonnegative(count, "count"); } @Override - @NullableDecl + @ParametricNullness public final E getElement() { return element; } @@ -233,11 +277,11 @@ public final int getCount() { return count; } - public ImmutableEntry nextInBucket() { + public @Nullable ImmutableEntry nextInBucket() { return null; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -265,19 +309,19 @@ public ImmutableEntry nextInBucket() { * * @since 14.0 */ - @Beta - public static Multiset filter(Multiset unfiltered, Predicate predicate) { + public static Multiset filter( + Multiset unfiltered, Predicate predicate) { if (unfiltered instanceof FilteredMultiset) { // Support clear(), removeAll(), and retainAll() when filtering a filtered // collection. FilteredMultiset filtered = (FilteredMultiset) unfiltered; - Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); - return new FilteredMultiset(filtered.unfiltered, combinedPredicate); + Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); + return new FilteredMultiset<>(filtered.unfiltered, combinedPredicate); } - return new FilteredMultiset(unfiltered, predicate); + return new FilteredMultiset<>(unfiltered, predicate); } - private static final class FilteredMultiset extends ViewMultiset { + private static final class FilteredMultiset extends ViewMultiset { final Multiset unfiltered; final Predicate predicate; @@ -303,14 +347,7 @@ Iterator elementIterator() { @Override Set> createEntrySet() { - return Sets.filter( - unfiltered.entrySet(), - new Predicate>() { - @Override - public boolean apply(Entry entry) { - return predicate.apply(entry.getElement()); - } - }); + return Sets.filter(unfiltered.entrySet(), entry -> predicate.apply(entry.getElement())); } @Override @@ -319,7 +356,7 @@ Iterator> entryIterator() { } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { int count = unfiltered.count(element); if (count > 0) { @SuppressWarnings("unchecked") // element is equal to an E @@ -330,14 +367,14 @@ public int count(@NullableDecl Object element) { } @Override - public int add(@NullableDecl E element, int occurrences) { + public int add(@ParametricNullness E element, int occurrences) { checkArgument( predicate.apply(element), "Element %s does not match predicate %s", element, predicate); return unfiltered.add(element, occurrences); } @Override - public int remove(@NullableDecl Object element, int occurrences) { + public int remove(@Nullable Object element, int occurrences) { checkNonnegative(occurrences, "occurrences"); if (occurrences == 0) { return count(element); @@ -371,15 +408,14 @@ static int inferDistinctElements(Iterable elements) { * * @since 14.0 */ - @Beta - public static Multiset union( - final Multiset multiset1, final Multiset multiset2) { + public static Multiset union( + Multiset multiset1, Multiset multiset2) { checkNotNull(multiset1); checkNotNull(multiset2); return new ViewMultiset() { @Override - public boolean contains(@NullableDecl Object element) { + public boolean contains(@Nullable Object element) { return multiset1.contains(element) || multiset2.contains(element); } @@ -389,8 +425,8 @@ public boolean isEmpty() { } @Override - public int count(Object element) { - return Math.max(multiset1.count(element), multiset2.count(element)); + public int count(@Nullable Object element) { + return max(multiset1.count(element), multiset2.count(element)); } @Override @@ -405,16 +441,16 @@ Iterator elementIterator() { @Override Iterator> entryIterator() { - final Iterator> iterator1 = multiset1.entrySet().iterator(); - final Iterator> iterator2 = multiset2.entrySet().iterator(); + Iterator> iterator1 = multiset1.entrySet().iterator(); + Iterator> iterator2 = multiset2.entrySet().iterator(); // TODO(lowasser): consider making the entries live views return new AbstractIterator>() { @Override - protected Entry computeNext() { + protected @Nullable Entry computeNext() { if (iterator1.hasNext()) { Entry entry1 = iterator1.next(); E element = entry1.getElement(); - int count = Math.max(entry1.getCount(), multiset2.count(element)); + int count = max(entry1.getCount(), multiset2.count(element)); return immutableEntry(element, count); } while (iterator2.hasNext()) { @@ -443,16 +479,16 @@ protected Entry computeNext() { * * @since 2.0 */ - public static Multiset intersection( - final Multiset multiset1, final Multiset multiset2) { + public static Multiset intersection( + Multiset multiset1, Multiset multiset2) { checkNotNull(multiset1); checkNotNull(multiset2); return new ViewMultiset() { @Override - public int count(Object element) { + public int count(@Nullable Object element) { int count1 = multiset1.count(element); - return (count1 == 0) ? 0 : Math.min(count1, multiset2.count(element)); + return (count1 == 0) ? 0 : min(count1, multiset2.count(element)); } @Override @@ -467,15 +503,15 @@ Iterator elementIterator() { @Override Iterator> entryIterator() { - final Iterator> iterator1 = multiset1.entrySet().iterator(); + Iterator> iterator1 = multiset1.entrySet().iterator(); // TODO(lowasser): consider making the entries live views return new AbstractIterator>() { @Override - protected Entry computeNext() { + protected @Nullable Entry computeNext() { while (iterator1.hasNext()) { Entry entry1 = iterator1.next(); E element = entry1.getElement(); - int count = Math.min(entry1.getCount(), multiset2.count(element)); + int count = min(entry1.getCount(), multiset2.count(element)); if (count > 0) { return immutableEntry(element, count); } @@ -499,16 +535,15 @@ protected Entry computeNext() { * * @since 14.0 */ - @Beta - public static Multiset sum( - final Multiset multiset1, final Multiset multiset2) { + public static Multiset sum( + Multiset multiset1, Multiset multiset2) { checkNotNull(multiset1); checkNotNull(multiset2); // TODO(lowasser): consider making the entries live views return new ViewMultiset() { @Override - public boolean contains(@NullableDecl Object element) { + public boolean contains(@Nullable Object element) { return multiset1.contains(element) || multiset2.contains(element); } @@ -523,7 +558,7 @@ public int size() { } @Override - public int count(Object element) { + public int count(@Nullable Object element) { return multiset1.count(element) + multiset2.count(element); } @@ -539,11 +574,11 @@ Iterator elementIterator() { @Override Iterator> entryIterator() { - final Iterator> iterator1 = multiset1.entrySet().iterator(); - final Iterator> iterator2 = multiset2.entrySet().iterator(); + Iterator> iterator1 = multiset1.entrySet().iterator(); + Iterator> iterator2 = multiset2.entrySet().iterator(); return new AbstractIterator>() { @Override - protected Entry computeNext() { + protected @Nullable Entry computeNext() { if (iterator1.hasNext()) { Entry entry1 = iterator1.next(); E element = entry1.getElement(); @@ -576,18 +611,17 @@ protected Entry computeNext() { * * @since 14.0 */ - @Beta - public static Multiset difference( - final Multiset multiset1, final Multiset multiset2) { + public static Multiset difference( + Multiset multiset1, Multiset multiset2) { checkNotNull(multiset1); checkNotNull(multiset2); // TODO(lowasser): consider making the entries live views return new ViewMultiset() { @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { int count1 = multiset1.count(element); - return (count1 == 0) ? 0 : Math.max(0, count1 - multiset2.count(element)); + return (count1 == 0) ? 0 : max(0, count1 - multiset2.count(element)); } @Override @@ -597,10 +631,10 @@ public void clear() { @Override Iterator elementIterator() { - final Iterator> iterator1 = multiset1.entrySet().iterator(); + Iterator> iterator1 = multiset1.entrySet().iterator(); return new AbstractIterator() { @Override - protected E computeNext() { + protected @Nullable E computeNext() { while (iterator1.hasNext()) { Entry entry1 = iterator1.next(); E element = entry1.getElement(); @@ -615,10 +649,10 @@ protected E computeNext() { @Override Iterator> entryIterator() { - final Iterator> iterator1 = multiset1.entrySet().iterator(); + Iterator> iterator1 = multiset1.entrySet().iterator(); return new AbstractIterator>() { @Override - protected Entry computeNext() { + protected @Nullable Entry computeNext() { while (iterator1.hasNext()) { Entry entry1 = iterator1.next(); E element = entry1.getElement(); @@ -681,7 +715,7 @@ public static boolean retainOccurrences( } /** Delegate implementation which cares about the element type. */ - private static boolean retainOccurrencesImpl( + private static boolean retainOccurrencesImpl( Multiset multisetToModify, Multiset occurrencesToRetain) { checkNotNull(multisetToModify); checkNotNull(occurrencesToRetain); @@ -715,11 +749,11 @@ private static boolean retainOccurrencesImpl( * in {@code occurrencesToRemove}. However, this operation is equivalent to, albeit * sometimes more efficient than, the following: * - *

    {@code
    +   * {@snippet :
        * for (E e : occurrencesToRemove) {
        *   multisetToModify.remove(e);
        * }
    -   * }
    + * } * * @return {@code true} if {@code multisetToModify} was changed as a result of this operation * @since 18.0 (present in 10.0 with a requirement that the second parameter be a {@code @@ -754,11 +788,11 @@ public static boolean removeOccurrences( * in {@code occurrencesToRemove}. However, this operation is equivalent to, albeit * sometimes more efficient than, the following: * - *
    {@code
    +   * {@snippet :
        * for (E e : occurrencesToRemove) {
        *   multisetToModify.remove(e);
        * }
    -   * }
    + * } * * @return {@code true} if {@code multisetToModify} was changed as a result of this operation * @since 10.0 (missing in 18.0 when only the overload taking an {@code Iterable} was present) @@ -789,17 +823,17 @@ public static boolean removeOccurrences( * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} methods of {@link * Multiset.Entry}. */ - abstract static class AbstractEntry implements Multiset.Entry { + abstract static class AbstractEntry implements Multiset.Entry { /** * Indicates whether an object equals this entry, following the behavior specified in {@link * Multiset.Entry#equals}. */ @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof Multiset.Entry) { Multiset.Entry that = (Multiset.Entry) object; return this.getCount() == that.getCount() - && Objects.equal(this.getElement(), that.getElement()); + && Objects.equals(this.getElement(), that.getElement()); } return false; } @@ -829,7 +863,7 @@ public String toString() { } /** An implementation of {@link Multiset#equals}. */ - static boolean equalsImpl(Multiset multiset, @NullableDecl Object object) { + static boolean equalsImpl(Multiset multiset, @Nullable Object object) { if (object == multiset) { return true; } @@ -855,11 +889,12 @@ static boolean equalsImpl(Multiset multiset, @NullableDecl Object object) { } /** An implementation of {@link Multiset#addAll}. */ - static boolean addAllImpl(Multiset self, Collection elements) { + static boolean addAllImpl( + Multiset self, Collection elements) { checkNotNull(self); checkNotNull(elements); if (elements instanceof Multiset) { - return addAllImpl(self, cast(elements)); + return addAllImpl(self, (Multiset) elements); } else if (elements.isEmpty()) { return false; } else { @@ -868,7 +903,8 @@ static boolean addAllImpl(Multiset self, Collection elements } /** A specialization of {@code addAllImpl} for when {@code elements} is itself a Multiset. */ - private static boolean addAllImpl(Multiset self, Multiset elements) { + private static boolean addAllImpl( + Multiset self, Multiset elements) { // It'd be nice if we could specialize for ImmutableMultiset here without also retaining // its code when it's not in scope... if (elements instanceof AbstractMapBasedMultiset) { @@ -887,7 +923,7 @@ private static boolean addAllImpl(Multiset self, Multiset el * A specialization of {@code addAllImpl} for when {@code elements} is an * AbstractMapBasedMultiset. */ - private static boolean addAllImpl( + private static boolean addAllImpl( Multiset self, AbstractMapBasedMultiset elements) { if (elements.isEmpty()) { return false; @@ -918,7 +954,8 @@ static boolean retainAllImpl(Multiset self, Collection elementsToRetain) { } /** An implementation of {@link Multiset#setCount(Object, int)}. */ - static int setCountImpl(Multiset self, E element, int count) { + static int setCountImpl( + Multiset self, @ParametricNullness E element, int count) { checkNonnegative(count, "count"); int oldCount = self.count(element); @@ -934,7 +971,8 @@ static int setCountImpl(Multiset self, E element, int count) { } /** An implementation of {@link Multiset#setCount(Object, int, int)}. */ - static boolean setCountImpl(Multiset self, E element, int oldCount, int newCount) { + static boolean setCountImpl( + Multiset self, @ParametricNullness E element, int oldCount, int newCount) { checkNonnegative(oldCount, "oldCount"); checkNonnegative(newCount, "newCount"); @@ -946,16 +984,18 @@ static boolean setCountImpl(Multiset self, E element, int oldCount, int n } } - static Iterator elementIterator(Iterator> entryIterator) { + static Iterator elementIterator( + Iterator> entryIterator) { return new TransformedIterator, E>(entryIterator) { @Override + @ParametricNullness E transform(Entry entry) { return entry.getElement(); } }; } - abstract static class ElementSet extends Sets.ImprovedAbstractSet { + abstract static class ElementSet extends Sets.ImprovedAbstractSet { abstract Multiset multiset(); @Override @@ -964,7 +1004,7 @@ public void clear() { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { return multiset().contains(o); } @@ -982,7 +1022,7 @@ public boolean isEmpty() { public abstract Iterator iterator(); @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { return multiset().remove(o, Integer.MAX_VALUE) > 0; } @@ -992,16 +1032,13 @@ public int size() { } } - abstract static class EntrySet extends Sets.ImprovedAbstractSet> { + abstract static class EntrySet + extends Sets.ImprovedAbstractSet> { abstract Multiset multiset(); @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Entry) { - /* - * The GWT compiler wrongly issues a warning here. - */ - @SuppressWarnings("cast") Entry entry = (Entry) o; if (entry.getCount() <= 0) { return false; @@ -1012,18 +1049,17 @@ public boolean contains(@NullableDecl Object o) { return false; } - // GWT compiler warning; see contains(). - @SuppressWarnings("cast") @Override - public boolean remove(Object object) { + public boolean remove(@Nullable Object object) { if (object instanceof Multiset.Entry) { Entry entry = (Entry) object; Object element = entry.getElement(); int entryCount = entry.getCount(); if (entryCount != 0) { // Safe as long as we never add a new entry, which we won't. - @SuppressWarnings("unchecked") - Multiset multiset = (Multiset) multiset(); + // (Presumably it can still throw CCE/NPE but only if the underlying Multiset does.) + @SuppressWarnings({"unchecked", "nullness"}) + Multiset<@Nullable Object> multiset = (Multiset<@Nullable Object>) multiset(); return multiset.setCount(element, entryCount, 0); } } @@ -1037,14 +1073,14 @@ public void clear() { } /** An implementation of {@link Multiset#iterator}. */ - static Iterator iteratorImpl(Multiset multiset) { - return new MultisetIteratorImpl(multiset, multiset.entrySet().iterator()); + static Iterator iteratorImpl(Multiset multiset) { + return new MultisetIteratorImpl<>(multiset, multiset.entrySet().iterator()); } - static final class MultisetIteratorImpl implements Iterator { + static final class MultisetIteratorImpl implements Iterator { private final Multiset multiset; private final Iterator> entryIterator; - @NullableDecl private Entry currentEntry; + private @Nullable Entry currentEntry; /** Count of subsequent elements equal to current element */ private int laterCount; @@ -1065,6 +1101,7 @@ public boolean hasNext() { } @Override + @ParametricNullness public E next() { if (!hasNext()) { throw new NoSuchElementException(); @@ -1075,7 +1112,11 @@ public E next() { } laterCount--; canRemove = true; - return currentEntry.getElement(); + /* + * requireNonNull is safe because laterCount starts at 0, forcing us to initialize + * currentEntry above. After that, we never clear it. + */ + return requireNonNull(currentEntry).getElement(); } @Override @@ -1084,7 +1125,11 @@ public void remove() { if (totalCount == 1) { entryIterator.remove(); } else { - multiset.remove(currentEntry.getElement()); + /* + * requireNonNull is safe because canRemove is set to true only after we initialize + * currentEntry (which we never subsequently clear). + */ + multiset.remove(requireNonNull(currentEntry).getElement()); } totalCount--; canRemove = false; @@ -1100,26 +1145,22 @@ static int linearTimeSizeImpl(Multiset multiset) { return Ints.saturatedCast(size); } - /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ - static Multiset cast(Iterable iterable) { - return (Multiset) iterable; - } - /** - * Returns a copy of {@code multiset} as an {@link ImmutableMultiset} whose iteration order is - * highest count first, with ties broken by the iteration order of the original multiset. + * Returns a copy of {@code multiset} as an {@link ImmutableMultiset} whose iteration order puts + * the highest count first, with ties broken by the iteration order of the original multiset. * * @since 11.0 */ - @Beta public static ImmutableMultiset copyHighestCountFirst(Multiset multiset) { - Entry[] entries = (Entry[]) multiset.entrySet().toArray(new Entry[0]); + @SuppressWarnings("unchecked") // generics+arrays + // TODO(cpovirk): Consider storing an Entry instead of Entry. + Entry[] entries = (Entry[]) multiset.entrySet().toArray((Entry[]) new Entry[0]); Arrays.sort(entries, DecreasingCount.INSTANCE); - return ImmutableMultiset.copyFromEntries(Arrays.asList(entries)); + return ImmutableMultiset.copyFromEntries(asList(entries)); } private static final class DecreasingCount implements Comparator> { - static final DecreasingCount INSTANCE = new DecreasingCount(); + static final Comparator> INSTANCE = new DecreasingCount(); @Override public int compare(Entry entry1, Entry entry2) { @@ -1131,7 +1172,8 @@ public int compare(Entry entry1, Entry entry2) { * An {@link AbstractMultiset} with additional default implementations, some of them linear-time * implementations in terms of {@code elementSet} and {@code entrySet}. */ - private abstract static class ViewMultiset extends AbstractMultiset { + private abstract static class ViewMultiset + extends AbstractMultiset { @Override public int size() { return linearTimeSizeImpl(this); diff --git a/android/guava/src/com/google/common/collect/MutableClassToInstanceMap.java b/android/guava/src/com/google/common/collect/MutableClassToInstanceMap.java index 1f228a8f61b1..e8269ed9023d 100644 --- a/android/guava/src/com/google/common/collect/MutableClassToInstanceMap.java +++ b/android/guava/src/com/google/common/collect/MutableClassToInstanceMap.java @@ -19,37 +19,44 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.primitives.Primitives; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A mutable class-to-instance map backed by an arbitrary user-provided map. See also {@link * ImmutableClassToInstanceMap}. * *

    See the Guava User Guide article on {@code + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap">{@code * ClassToInstanceMap}. * * @author Kevin Bourrillion * @since 2.0 */ +@J2ktIncompatible @GwtIncompatible @SuppressWarnings("serial") // using writeReplace instead of standard serialization -public final class MutableClassToInstanceMap extends ForwardingMap, B> +public final class MutableClassToInstanceMap + extends ForwardingMap, B> implements ClassToInstanceMap, Serializable { /** * Returns a new {@code MutableClassToInstanceMap} instance backed by a {@link HashMap} using the * default initial capacity and load factor. */ - public static MutableClassToInstanceMap create() { - return new MutableClassToInstanceMap(new HashMap, B>()); + public static MutableClassToInstanceMap create() { + return new MutableClassToInstanceMap<>(new HashMap, B>()); } /** @@ -57,50 +64,59 @@ public static MutableClassToInstanceMap create() { * backingMap}. The caller surrenders control of the backing map, and thus should not allow any * direct references to it to remain accessible. */ - public static MutableClassToInstanceMap create(Map, B> backingMap) { - return new MutableClassToInstanceMap(backingMap); + public static MutableClassToInstanceMap create( + Map, B> backingMap) { + return new MutableClassToInstanceMap<>(backingMap); } - private final Map, B> delegate; + private final Map, B> delegate; - private MutableClassToInstanceMap(Map, B> delegate) { + private MutableClassToInstanceMap(Map, B> delegate) { this.delegate = checkNotNull(delegate); } @Override - protected Map, B> delegate() { + protected Map, B> delegate() { return delegate; } - static Entry, B> checkedEntry(final Entry, B> entry) { - return new ForwardingMapEntry, B>() { + /** + * Wraps the {@code setValue} implementation of an {@code Entry} to enforce the class constraint. + */ + private static Entry, B> checkedEntry( + Entry, B> entry) { + return new ForwardingMapEntry, B>() { @Override - protected Entry, B> delegate() { + protected Entry, B> delegate() { return entry; } @Override - public B setValue(B value) { - return super.setValue(cast(getKey(), value)); + @ParametricNullness + public B setValue(@ParametricNullness B value) { + cast(getKey(), value); + return super.setValue(value); } }; } @Override - public Set, B>> entrySet() { - return new ForwardingSet, B>>() { + public Set, B>> entrySet() { + return new ForwardingSet, B>>() { @Override - protected Set, B>> delegate() { + protected Set, B>> delegate() { return MutableClassToInstanceMap.this.delegate().entrySet(); } @Override - public Iterator, B>> iterator() { - return new TransformedIterator, B>, Entry, B>>( + public Iterator, B>> iterator() { + return new TransformedIterator< + Entry, B>, Entry, B>>( delegate().iterator()) { @Override - Entry, B> transform(Entry, B> from) { + Entry, B> transform( + Entry, B> from) { return checkedEntry(from); } }; @@ -108,11 +124,20 @@ Entry, B> transform(Entry, B> from) { @Override public Object[] toArray() { - return standardToArray(); + /* + * standardToArray returns `@Nullable Object[]` rather than `Object[]` but only because it + * can be used with collections that may contain null. This collection is a collection of + * non-null Entry objects (Entry objects that might contain null values but are not + * themselves null), so we can treat it as a plain `Object[]`. + */ + @SuppressWarnings("nullness") + Object[] result = standardToArray(); + return result; } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return standardToArray(array); } }; @@ -120,14 +145,15 @@ public T[] toArray(T[] array) { @Override @CanIgnoreReturnValue - public B put(Class key, B value) { - return super.put(key, cast(key, value)); + public @Nullable B put(Class key, @ParametricNullness B value) { + cast(key, value); + return super.put(key, value); } @Override - public void putAll(Map, ? extends B> map) { - Map, B> copy = new LinkedHashMap<>(map); - for (Entry, B> entry : copy.entrySet()) { + public void putAll(Map, ? extends B> map) { + Map, B> copy = new LinkedHashMap<>(map); + for (Entry, B> entry : copy.entrySet()) { cast(entry.getKey(), entry.getValue()); } super.putAll(copy); @@ -135,29 +161,34 @@ public void putAll(Map, ? extends B> map) { @CanIgnoreReturnValue @Override - public T putInstance(Class type, T value) { + public @Nullable T putInstance( + Class<@NonNull T> type, @ParametricNullness T value) { return cast(type, put(type, value)); } @Override - public T getInstance(Class type) { + public @Nullable T getInstance(Class type) { return cast(type, get(type)); } @CanIgnoreReturnValue - private static T cast(Class type, B value) { + private static @Nullable T cast(Class type, @Nullable Object value) { return Primitives.wrap(type).cast(value); } - private Object writeReplace() { - return new SerializedForm(delegate()); + private Object writeReplace() { + return new SerializedForm<>(delegate()); + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); } /** Serialized form of the map, to avoid serializing the constraint. */ - private static final class SerializedForm implements Serializable { - private final Map, B> backingMap; + private static final class SerializedForm implements Serializable { + private final Map, B> backingMap; - SerializedForm(Map, B> backingMap) { + SerializedForm(Map, B> backingMap) { this.backingMap = backingMap; } diff --git a/android/guava/src/com/google/common/collect/NaturalOrdering.java b/android/guava/src/com/google/common/collect/NaturalOrdering.java index d9792766b67b..d1a874b93b68 100644 --- a/android/guava/src/com/google/common/collect/NaturalOrdering.java +++ b/android/guava/src/com/google/common/collect/NaturalOrdering.java @@ -19,45 +19,52 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** An ordering that uses the natural order of the values. */ -@GwtCompatible(serializable = true) -@SuppressWarnings({"unchecked", "rawtypes"}) // TODO(kevinb): the right way to explain this?? -final class NaturalOrdering extends Ordering implements Serializable { +@GwtCompatible +final class NaturalOrdering extends Ordering> implements Serializable { static final NaturalOrdering INSTANCE = new NaturalOrdering(); - @NullableDecl private transient Ordering nullsFirst; - @NullableDecl private transient Ordering nullsLast; + // TODO: b/287198172 - Consider eagerly initializing these (but think about serialization). + @LazyInit private transient @Nullable Ordering<@Nullable Comparable> nullsFirst; + @LazyInit private transient @Nullable Ordering<@Nullable Comparable> nullsLast; @Override - public int compare(Comparable left, Comparable right) { + @SuppressWarnings("unchecked") // TODO(kevinb): the right way to explain this?? + public int compare(Comparable left, Comparable right) { checkNotNull(left); // for GWT checkNotNull(right); - return left.compareTo(right); + return ((Comparable) left).compareTo(right); } @Override - public Ordering nullsFirst() { - Ordering result = nullsFirst; + @SuppressWarnings("unchecked") // TODO(kevinb): the right way to explain this?? + public > Ordering<@Nullable S> nullsFirst() { + Ordering<@Nullable Comparable> result = nullsFirst; if (result == null) { - result = nullsFirst = super.nullsFirst(); + result = nullsFirst = super.>nullsFirst(); } - return (Ordering) result; + return (Ordering<@Nullable S>) result; } @Override - public Ordering nullsLast() { - Ordering result = nullsLast; + @SuppressWarnings("unchecked") // TODO(kevinb): the right way to explain this?? + public > Ordering<@Nullable S> nullsLast() { + Ordering<@Nullable Comparable> result = nullsLast; if (result == null) { - result = nullsLast = super.nullsLast(); + result = nullsLast = super.>nullsLast(); } - return (Ordering) result; + return (Ordering<@Nullable S>) result; } @Override - public Ordering reverse() { + @SuppressWarnings("unchecked") // TODO(kevinb): the right way to explain this?? + public > Ordering reverse() { return (Ordering) ReverseNaturalOrdering.INSTANCE; } @@ -73,5 +80,5 @@ public String toString() { private NaturalOrdering() {} - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/NullnessCasts.java b/android/guava/src/com/google/common/collect/NullnessCasts.java new file mode 100644 index 000000000000..6863dbfbfd7a --- /dev/null +++ b/android/guava/src/com/google/common/collect/NullnessCasts.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import org.jspecify.annotations.Nullable; + +/** A utility method to perform unchecked casts to suppress errors produced by nullness analyses. */ +@GwtCompatible +final class NullnessCasts { + /** + * Accepts a {@code @Nullable T} and returns a plain {@code T}, without performing any check that + * that conversion is safe. + * + *

    This method is intended to help with usages of type parameters that have {@linkplain + * ParametricNullness parametric nullness}. If a type parameter instead ranges over only non-null + * types (or if the type is a non-variable type, like {@code String}), then code should almost + * never use this method, preferring instead to call {@code requireNonNull} so as to benefit from + * its runtime check. + * + *

    An example use case for this method is in implementing an {@code Iterator} whose {@code + * next} field is lazily initialized. The type of that field would be {@code @Nullable T}, and the + * code would be responsible for populating a "real" {@code T} (which might still be the value + * {@code null}!) before returning it to callers. Depending on how the code is structured, a + * nullness analysis might not understand that the field has been populated. To avoid that problem + * without having to add {@code @SuppressWarnings}, the code can call this method. + * + *

    Why not just add {@code SuppressWarnings}? The problem is that this method is + * typically useful for {@code return} statements. That leaves the code with two options: Either + * add the suppression to the whole method (which turns off checking for a large section of code), + * or extract a variable, and put the suppression on that. However, a local variable typically + * doesn't work: Because nullness analyses typically infer the nullness of local variables, + * there's no way to assign a {@code @Nullable T} to a field {@code T foo;} and instruct the + * analysis that that means "plain {@code T}" rather than the inferred type {@code @Nullable T}. + * (And even if annotations on local variables were permitted as an optional hint, no annotation + * would be the right tool for the job here: {@code @Nullable} is the annotation that we're trying + * to get rid of, and {@code @NonNull} would be wrong for our use case for the same reason as + * {@code requireNonNull}: Our use case is the one in which {@code T} has parametric nullness—and + * thus its value may be legitimately {@code null}.) + */ + @ParametricNullness + @SuppressWarnings("nullness") + static T uncheckedCastNullableTToT(@Nullable T t) { + return t; + } + + /** Returns {@code null} as any type, even one that does not include {@code null}. */ + @SuppressWarnings({"nullness", "TypeParameterUnusedInFormals", "ReturnMissingNullable"}) + // The warnings are legitimate. Each time we use this method, we document why. + @ParametricNullness + static T unsafeNull() { + return null; + } + + private NullnessCasts() {} +} diff --git a/android/guava/src/com/google/common/collect/NullsFirstOrdering.java b/android/guava/src/com/google/common/collect/NullsFirstOrdering.java index 21540360eb3b..151c1ed6a18c 100644 --- a/android/guava/src/com/google/common/collect/NullsFirstOrdering.java +++ b/android/guava/src/com/google/common/collect/NullsFirstOrdering.java @@ -17,12 +17,16 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** An ordering that treats {@code null} as less than all other values. */ -@GwtCompatible(serializable = true) -final class NullsFirstOrdering extends Ordering implements Serializable { +@GwtCompatible +final class NullsFirstOrdering extends Ordering<@Nullable T> + implements Serializable { final Ordering ordering; NullsFirstOrdering(Ordering ordering) { @@ -30,7 +34,7 @@ final class NullsFirstOrdering extends Ordering implements Serializable { } @Override - public int compare(@NullableDecl T left, @NullableDecl T right) { + public int compare(@Nullable T left, @Nullable T right) { if (left == right) { return 0; } @@ -44,24 +48,25 @@ public int compare(@NullableDecl T left, @NullableDecl T right) { } @Override - public Ordering reverse() { + @SuppressWarnings("nullness") // should be safe, but not sure if we can avoid the warning + public Ordering reverse() { // ordering.reverse() might be optimized, so let it do its thing - return ordering.reverse().nullsLast(); + return ordering.reverse().<@NonNull S>nullsLast(); } @SuppressWarnings("unchecked") // still need the right way to explain this @Override - public Ordering nullsFirst() { - return (Ordering) this; + public Ordering<@Nullable S> nullsFirst() { + return (Ordering<@Nullable S>) this; } @Override - public Ordering nullsLast() { - return ordering.nullsLast(); + public Ordering<@Nullable S> nullsLast() { + return ordering.<@NonNull S>nullsLast(); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -82,5 +87,5 @@ public String toString() { return ordering + ".nullsFirst()"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/NullsLastOrdering.java b/android/guava/src/com/google/common/collect/NullsLastOrdering.java index 5dd8950c9f8a..4df4303c6941 100644 --- a/android/guava/src/com/google/common/collect/NullsLastOrdering.java +++ b/android/guava/src/com/google/common/collect/NullsLastOrdering.java @@ -17,12 +17,16 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** An ordering that treats {@code null} as greater than all other values. */ -@GwtCompatible(serializable = true) -final class NullsLastOrdering extends Ordering implements Serializable { +@GwtCompatible +final class NullsLastOrdering extends Ordering<@Nullable T> + implements Serializable { final Ordering ordering; NullsLastOrdering(Ordering ordering) { @@ -30,7 +34,7 @@ final class NullsLastOrdering extends Ordering implements Serializable { } @Override - public int compare(@NullableDecl T left, @NullableDecl T right) { + public int compare(@Nullable T left, @Nullable T right) { if (left == right) { return 0; } @@ -44,24 +48,25 @@ public int compare(@NullableDecl T left, @NullableDecl T right) { } @Override - public Ordering reverse() { + @SuppressWarnings("nullness") // should be safe, but not sure if we can avoid the warning + public Ordering reverse() { // ordering.reverse() might be optimized, so let it do its thing - return ordering.reverse().nullsFirst(); + return ordering.reverse().<@NonNull S>nullsFirst(); } @Override - public Ordering nullsFirst() { - return ordering.nullsFirst(); + public Ordering<@Nullable S> nullsFirst() { + return ordering.<@NonNull S>nullsFirst(); } @SuppressWarnings("unchecked") // still need the right way to explain this @Override - public Ordering nullsLast() { - return (Ordering) this; + public Ordering<@Nullable S> nullsLast() { + return (Ordering<@Nullable S>) this; } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -82,5 +87,5 @@ public String toString() { return ordering + ".nullsLast()"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ObjectArrays.java b/android/guava/src/com/google/common/collect/ObjectArrays.java index e09fc69a3b85..df639d0c11b2 100644 --- a/android/guava/src/com/google/common/collect/ObjectArrays.java +++ b/android/guava/src/com/google/common/collect/ObjectArrays.java @@ -17,6 +17,7 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.System.arraycopy; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; @@ -24,7 +25,8 @@ import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to object arrays. @@ -32,7 +34,8 @@ * @author Kevin Bourrillion * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible +@SuppressWarnings("AvoidObjectArrays") public final class ObjectArrays { private ObjectArrays() {} @@ -45,7 +48,7 @@ private ObjectArrays() {} */ @GwtIncompatible // Array.newInstance(Class, int) @SuppressWarnings("unchecked") - public static T[] newArray(Class type, int length) { + public static T[] newArray(Class<@NonNull T> type, int length) { return (T[]) Array.newInstance(type, length); } @@ -55,7 +58,7 @@ public static T[] newArray(Class type, int length) { * @param reference any array of the desired type * @param length the length of the new array */ - public static T[] newArray(T[] reference, int length) { + public static T[] newArray(T[] reference, int length) { return Platform.newArray(reference, length); } @@ -67,10 +70,11 @@ public static T[] newArray(T[] reference, int length) { * @param type the component type of the returned array */ @GwtIncompatible // Array.newInstance(Class, int) - public static T[] concat(T[] first, T[] second, Class type) { + public static T[] concat( + T[] first, T[] second, Class<@NonNull T> type) { T[] result = newArray(type, first.length + second.length); - System.arraycopy(first, 0, result, 0, first.length); - System.arraycopy(second, 0, result, first.length, second.length); + arraycopy(first, 0, result, 0, first.length); + arraycopy(second, 0, result, first.length, second.length); return result; } @@ -82,10 +86,10 @@ public static T[] concat(T[] first, T[] second, Class type) { * @return an array whose size is one larger than {@code array}, with {@code element} occupying * the first position, and the elements of {@code array} occupying the remaining elements. */ - public static T[] concat(@NullableDecl T element, T[] array) { + public static T[] concat(@ParametricNullness T element, T[] array) { T[] result = newArray(array, array.length + 1); result[0] = element; - System.arraycopy(array, 0, result, 1, array.length); + arraycopy(array, 0, result, 1, array.length); return result; } @@ -97,7 +101,7 @@ public static T[] concat(@NullableDecl T element, T[] array) { * @return an array whose size is one larger than {@code array}, with the same contents as {@code * array}, plus {@code element} occupying the last position. */ - public static T[] concat(T[] array, @NullableDecl T element) { + public static T[] concat(T[] array, @ParametricNullness T element) { T[] result = Arrays.copyOf(array, array.length + 1); result[array.length] = element; return result; @@ -124,14 +128,15 @@ public static T[] concat(T[] array, @NullableDecl T element) { * @throws ArrayStoreException if the runtime type of the specified array is not a supertype of * the runtime type of every element in the specified collection */ - static T[] toArrayImpl(Collection c, T[] array) { + static T[] toArrayImpl(Collection c, T[] array) { int size = c.size(); if (array.length < size) { array = newArray(array, size); } fillArray(c, array); if (array.length > size) { - array[size] = null; + @Nullable Object[] unsoundlyCovariantArray = array; + unsoundlyCovariantArray[size] = null; } return array; } @@ -147,14 +152,16 @@ static T[] toArrayImpl(Collection c, T[] array) { * collection is set to {@code null}. This is useful in determining the length of the collection * only if the caller knows that the collection does not contain any null elements. */ - static T[] toArrayImpl(Object[] src, int offset, int len, T[] dst) { + static T[] toArrayImpl( + @Nullable Object[] src, int offset, int len, T[] dst) { checkPositionIndexes(offset, offset + len, src.length); if (dst.length < len) { dst = newArray(dst, len); } else if (dst.length > len) { - dst[len] = null; + @Nullable Object[] unsoundlyCovariantArray = dst; + unsoundlyCovariantArray[len] = null; } - System.arraycopy(src, offset, dst, 0, len); + arraycopy(src, offset, dst, 0, len); return dst; } @@ -170,7 +177,7 @@ static T[] toArrayImpl(Object[] src, int offset, int len, T[] dst) { * * @param c the collection for which to return an array of elements */ - static Object[] toArrayImpl(Collection c) { + static @Nullable Object[] toArrayImpl(Collection c) { return fillArray(c, new Object[c.size()]); } @@ -178,18 +185,18 @@ static Object[] toArrayImpl(Collection c) { * Returns a copy of the specified subrange of the specified array that is literally an Object[], * and not e.g. a {@code String[]}. */ - static Object[] copyAsObjectArray(Object[] elements, int offset, int length) { + static @Nullable Object[] copyAsObjectArray(@Nullable Object[] elements, int offset, int length) { checkPositionIndexes(offset, offset + length, elements.length); if (length == 0) { return new Object[0]; } - Object[] result = new Object[length]; - System.arraycopy(elements, offset, result, 0, length); + @Nullable Object[] result = new Object[length]; + arraycopy(elements, offset, result, 0, length); return result; } @CanIgnoreReturnValue - private static Object[] fillArray(Iterable elements, Object[] array) { + private static @Nullable Object[] fillArray(Iterable elements, @Nullable Object[] array) { int i = 0; for (Object element : elements) { array[i++] = element; @@ -206,11 +213,12 @@ static void swap(Object[] array, int i, int j) { @CanIgnoreReturnValue static Object[] checkElementsNotNull(Object... array) { - return checkElementsNotNull(array, array.length); + checkElementsNotNull(array, array.length); + return array; } @CanIgnoreReturnValue - static Object[] checkElementsNotNull(Object[] array, int length) { + static @Nullable Object[] checkElementsNotNull(@Nullable Object[] array, int length) { for (int i = 0; i < length; i++) { checkElementNotNull(array[i], i); } @@ -220,7 +228,7 @@ static Object[] checkElementsNotNull(Object[] array, int length) { // We do this instead of Preconditions.checkNotNull to save boxing and array // creation cost. @CanIgnoreReturnValue - static Object checkElementNotNull(Object element, int index) { + static Object checkElementNotNull(@Nullable Object element, int index) { if (element == null) { throw new NullPointerException("at index " + index); } diff --git a/android/guava/src/com/google/common/collect/ObjectCountHashMap.java b/android/guava/src/com/google/common/collect/ObjectCountHashMap.java index 8f6003f2ce1d..7a716afc06d5 100644 --- a/android/guava/src/com/google/common/collect/ObjectCountHashMap.java +++ b/android/guava/src/com/google/common/collect/ObjectCountHashMap.java @@ -22,27 +22,28 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.Multiset.Entry; import com.google.common.collect.Multisets.AbstractEntry; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Arrays; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** - * ObjectCountHashMap is an implementation of {@code AbstractObjectCountMap} that uses arrays to - * store key objects and count values. Comparing to using a traditional {@code HashMap} - * implementation which stores keys and count values as map entries, {@code ObjectCountHashMap} - * minimizes object allocation and reduces memory footprint. + * {@code ObjectCountHashMap} uses arrays to store key objects and count values. Comparing to using + * a traditional {@code HashMap} implementation which stores keys and count values as map entries, + * {@code ObjectCountHashMap} minimizes object allocation and reduces memory footprint. * *

    In the absence of element deletions, this will iterate over elements in insertion order. */ -@GwtCompatible(serializable = true, emulated = true) -class ObjectCountHashMap { +@GwtCompatible +@NullMarked +class ObjectCountHashMap { /** Creates an empty {@code ObjectCountHashMap} instance. */ - public static ObjectCountHashMap create() { + static ObjectCountHashMap create() { return new ObjectCountHashMap(); } @@ -55,7 +56,8 @@ public static ObjectCountHashMap create() { * expectedSize} elements without resizing * @throws IllegalArgumentException if {@code expectedSize} is negative */ - public static ObjectCountHashMap createWithExpectedSize(int expectedSize) { + static ObjectCountHashMap createWithExpectedSize( + int expectedSize) { return new ObjectCountHashMap(expectedSize); } @@ -74,8 +76,13 @@ public static ObjectCountHashMap createWithExpectedSize(int expectedSize) // used to indicate blank table entries static final int UNSET = -1; + /* + * The array fields below are not initialized directly in the constructor, but they're initialized + * by init(), which the constructor calls. + */ + /** The keys of the entries in the map. */ - transient Object[] keys; + transient @Nullable Object[] keys; /** The values of the entries in the map. */ transient int[] values; @@ -140,7 +147,7 @@ void init(int expectedSize, float loadFactor) { this.table = newTable(buckets); this.loadFactor = loadFactor; - this.keys = new Object[expectedSize]; + this.keys = new @Nullable Object[expectedSize]; this.values = new int[expectedSize]; this.entries = newEntries(expectedSize); @@ -180,6 +187,7 @@ int size() { } @SuppressWarnings("unchecked") + @ParametricNullness K getKey(int index) { checkElementIndex(index, size); return (K) keys[index]; @@ -200,8 +208,8 @@ Entry getEntry(int index) { return new MapEntry(index); } - class MapEntry extends AbstractEntry { - @NullableDecl final K key; + private final class MapEntry extends AbstractEntry { + @ParametricNullness final K key; int lastKnownIndex; @@ -212,6 +220,7 @@ class MapEntry extends AbstractEntry { } @Override + @ParametricNullness public K getElement() { return key; } @@ -219,31 +228,16 @@ public K getElement() { void updateLastKnownIndex() { if (lastKnownIndex == -1 || lastKnownIndex >= size() - || !Objects.equal(key, keys[lastKnownIndex])) { + || !Objects.equals(key, keys[lastKnownIndex])) { lastKnownIndex = indexOf(key); } } - @SuppressWarnings("unchecked") // values only contains Vs @Override public int getCount() { updateLastKnownIndex(); return (lastKnownIndex == -1) ? 0 : values[lastKnownIndex]; } - - @SuppressWarnings("unchecked") // values only contains Vs - @CanIgnoreReturnValue - public int setCount(int count) { - updateLastKnownIndex(); - if (lastKnownIndex == -1) { - put(key, count); - return 0; - } else { - int old = values[lastKnownIndex]; - values[lastKnownIndex] = count; - return old; - } - } } private static int getHash(long entry) { @@ -271,10 +265,10 @@ void ensureCapacity(int minCapacity) { } @CanIgnoreReturnValue - public int put(@NullableDecl K key, int value) { + public int put(@ParametricNullness K key, int value) { checkPositive(value, "count"); long[] entries = this.entries; - Object[] keys = this.keys; + @Nullable Object[] keys = this.keys; int[] values = this.values; int hash = smearedHash(key); @@ -289,7 +283,7 @@ public int put(@NullableDecl K key, int value) { do { last = next; entry = entries[next]; - if (getHash(entry) == hash && Objects.equal(key, keys[next])) { + if (getHash(entry) == hash && Objects.equals(key, keys[next])) { int oldValue = values[next]; values[next] = value; @@ -316,7 +310,7 @@ public int put(@NullableDecl K key, int value) { /** * Creates a fresh entry with the specified object at the specified position in the entry array. */ - void insertEntry(int entryIndex, @NullableDecl K key, int value, int hash) { + void insertEntry(int entryIndex, @ParametricNullness K key, int value, int hash) { this.entries[entryIndex] = ((long) hash << 32) | (NEXT_MASK & UNSET); this.keys[entryIndex] = key; this.values[entryIndex] = value; @@ -377,12 +371,12 @@ private void resizeTable(int newCapacity) { // newCapacity always a power of two this.table = newTable; } - int indexOf(@NullableDecl Object key) { + int indexOf(@Nullable Object key) { int hash = smearedHash(key); int next = table[hash & hashTableMask()]; while (next != UNSET) { long entry = entries[next]; - if (getHash(entry) == hash && Objects.equal(key, keys[next])) { + if (getHash(entry) == hash && Objects.equals(key, keys[next])) { return next; } next = getNext(entry); @@ -390,21 +384,21 @@ int indexOf(@NullableDecl Object key) { return -1; } - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return indexOf(key) != -1; } - public int get(@NullableDecl Object key) { + public int get(@Nullable Object key) { int index = indexOf(key); return (index == -1) ? 0 : values[index]; } @CanIgnoreReturnValue - public int remove(@NullableDecl Object key) { + public int remove(@Nullable Object key) { return remove(key, smearedHash(key)); } - private int remove(@NullableDecl Object key, int hash) { + private int remove(@Nullable Object key, int hash) { int tableIndex = hash & hashTableMask(); int next = table[tableIndex]; if (next == UNSET) { // empty bucket @@ -413,7 +407,7 @@ private int remove(@NullableDecl Object key, int hash) { int last = UNSET; do { if (getHash(entries[next]) == hash) { - if (Objects.equal(key, keys[next])) { + if (Objects.equals(key, keys[next])) { int oldValue = values[next]; if (last == UNSET) { diff --git a/android/guava/src/com/google/common/collect/ObjectCountLinkedHashMap.java b/android/guava/src/com/google/common/collect/ObjectCountLinkedHashMap.java index 5a5a4766a40d..4ac27e510e4c 100644 --- a/android/guava/src/com/google/common/collect/ObjectCountLinkedHashMap.java +++ b/android/guava/src/com/google/common/collect/ObjectCountLinkedHashMap.java @@ -18,18 +18,21 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.VisibleForTesting; import java.util.Arrays; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** - * ObjectCountLinkedHashMap is an implementation of {@code AbstractObjectCountMap} with insertion + * {@code ObjectCountLinkedHashMap} is a subclass of {@code ObjectCountHashMap} with insertion * iteration order, and uses arrays to store key objects and count values. Comparing to using a * traditional {@code LinkedHashMap} implementation which stores keys and count values as map * entries, {@code ObjectCountLinkedHashMap} minimizes object allocation and reduces memory * footprint. */ -@GwtCompatible(serializable = true, emulated = true) -class ObjectCountLinkedHashMap extends ObjectCountHashMap { +@GwtCompatible +@NullMarked +final class ObjectCountLinkedHashMap extends ObjectCountHashMap { /** Creates an empty {@code ObjectCountLinkedHashMap} instance. */ - public static ObjectCountLinkedHashMap create() { + static ObjectCountLinkedHashMap create() { return new ObjectCountLinkedHashMap(); } @@ -42,12 +45,18 @@ public static ObjectCountLinkedHashMap create() { * expectedSize} elements without resizing * @throws IllegalArgumentException if {@code expectedSize} is negative */ - public static ObjectCountLinkedHashMap createWithExpectedSize(int expectedSize) { + static ObjectCountLinkedHashMap createWithExpectedSize( + int expectedSize) { return new ObjectCountLinkedHashMap(expectedSize); } private static final int ENDPOINT = -2; + /* + * The links field is not initialized directly in the constructor, but it's initialized by init(), + * which the superconstructor calls. + */ + /** * Contains the link pointers corresponding with the entries, in the range of [0, size()). The * high 32 bits of each long is the "prev" pointer, whereas the low 32 bits is the "succ" pointer @@ -141,7 +150,7 @@ private void setSucceeds(int pred, int succ) { } @Override - void insertEntry(int entryIndex, K key, int value, int hash) { + void insertEntry(int entryIndex, @ParametricNullness K key, int value, int hash) { super.insertEntry(entryIndex, key, value, hash); setSucceeds(lastEntry, entryIndex); setSucceeds(entryIndex, ENDPOINT); diff --git a/android/guava/src/com/google/common/collect/Ordering.java b/android/guava/src/com/google/common/collect/Ordering.java index 187aacf8fd67..ec7eef09118a 100644 --- a/android/guava/src/com/google/common/collect/Ordering.java +++ b/android/guava/src/com/google/common/collect/Ordering.java @@ -18,11 +18,19 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static java.util.Arrays.asList; +import static java.util.Arrays.sort; +import static java.util.Collections.emptyList; +import static java.util.Collections.sort; +import static java.util.Collections.unmodifiableList; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; -import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.InlineMeValidationDisabled; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -38,7 +46,8 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A comparator, with additional methods to support common operations. This is an "enriched" version @@ -88,13 +97,13 @@ * *

    Complex chained orderings like the following example can be challenging to understand. * - *

    {@code
    + * {@snippet :
      * Ordering ordering =
      *     Ordering.natural()
      *         .nullsFirst()
      *         .onResultOf(getBarFunction)
      *         .nullsLast();
    - * }
    + * } * * Note that each chaining method returns a new ordering instance which is backed by the previous * instance, but has the chance to act on values before handing off to that backing instance. @@ -121,12 +130,12 @@ * {@code function} can themselves be serialized, then {@code ordering.onResultOf(function)} can as * well. * - *

    For Java 8 users

    + *

    Java 8+ users

    * - *

    If you are using Java 8, this class is now obsolete. Most of its functionality is now provided - * by {@link java.util.stream.Stream Stream} and by {@link Comparator} itself, and the rest can now - * be found as static methods in our new {@link Comparators} class. See each method below for - * further instructions. Whenever possible, you should change any references of type {@code + *

    If you are using Java 8+, this class is now obsolete. Most of its functionality is now + * provided by {@link java.util.stream.Stream Stream} and by {@link Comparator} itself, and the rest + * can now be found as static methods in our new {@link Comparators} class. See each method below + * for further instructions. Whenever possible, you should change any references of type {@code * Ordering} to be of type {@code Comparator} instead. However, at this time we have no plan to * deprecate this class. * @@ -144,7 +153,7 @@ * @since 2.0 */ @GwtCompatible -public abstract class Ordering implements Comparator { +public abstract class Ordering implements Comparator { // Natural order /** @@ -154,10 +163,11 @@ public abstract class Ordering implements Comparator { *

    The type specification is {@code }, instead of the technically correct * {@code >}, to support legacy types from before Java 5. * - *

    Java 8 users: use {@link Comparator#naturalOrder} instead. + *

    Java 8+ users: use {@link Comparator#naturalOrder} instead. */ - @GwtCompatible(serializable = true) - @SuppressWarnings("unchecked") // TODO(kevinb): right way to explain this?? + @SuppressWarnings({"unchecked", "rawtypes"}) + // TODO(kevinb): right way to explain this?? + // plus https://github.com/google/guava/issues/989 public static Ordering natural() { return (Ordering) NaturalOrdering.INSTANCE; } @@ -170,15 +180,16 @@ public static Ordering natural() { * to pass it in here. Instead, simply subclass {@code Ordering} and implement its {@code compare} * method directly. * - *

    Java 8 users: this class is now obsolete as explained in the class documentation, so + *

    The returned object is serializable if {@code comparator} is serializable. + * + *

    Java 8+ users: this class is now obsolete as explained in the class documentation, so * there is no need to use this method. * * @param comparator the comparator that defines the order * @return comparator itself if it is already an {@code Ordering}; otherwise an ordering that * wraps that comparator */ - @GwtCompatible(serializable = true) - public static Ordering from(Comparator comparator) { + public static Ordering from(Comparator comparator) { return (comparator instanceof Ordering) ? (Ordering) comparator : new ComparatorOrdering(comparator); @@ -189,9 +200,11 @@ public static Ordering from(Comparator comparator) { * * @deprecated no need to use this */ - @GwtCompatible(serializable = true) + @InlineMe( + replacement = "checkNotNull(ordering)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated - public static Ordering from(Ordering ordering) { + public static Ordering from(Ordering ordering) { return checkNotNull(ordering); } @@ -215,9 +228,8 @@ public static Ordering from(Ordering ordering) { * (according to {@link Object#equals}) */ // TODO(kevinb): provide replacement - @GwtCompatible(serializable = true) public static Ordering explicit(List valuesInOrder) { - return new ExplicitOrdering(valuesInOrder); + return new ExplicitOrdering<>(valuesInOrder); } /** @@ -241,7 +253,6 @@ public static Ordering explicit(List valuesInOrder) { * Object#equals(Object)}) are present among the method arguments */ // TODO(kevinb): provide replacement - @GwtCompatible(serializable = true) public static Ordering explicit(T leastValue, T... remainingValuesInOrder) { return explicit(Lists.asList(leastValue, remainingValuesInOrder)); } @@ -256,10 +267,10 @@ public static Ordering explicit(T leastValue, T... remainingValuesInOrder * *

    Example: * - *

    {@code
    +   * {@snippet :
        * Ordering.allEqual().nullsLast().sortedCopy(
        *     asList(t, null, e, s, null, t, null))
    -   * }
    + * } * *

    Assuming {@code t}, {@code e} and {@code s} are non-null, this returns {@code [t, e, s, t, * null, null, null]} regardless of the true comparison order of those three values (which might @@ -271,14 +282,12 @@ public static Ordering explicit(T leastValue, T... remainingValuesInOrder * *

    The returned comparator is serializable. * - *

    Java 8 users: Use the lambda expression {@code (a, b) -> 0} instead (in certain cases - * you may need to cast that to {@code Comparator}). + *

    Java 8+ users: Use the lambda expression {@code (a, b) -> 0} instead (in certain + * cases you may need to cast that to {@code Comparator}). * * @since 13.0 */ - @GwtCompatible(serializable = true) - @SuppressWarnings("unchecked") - public static Ordering allEqual() { + public static Ordering<@Nullable Object> allEqual() { return AllEqualOrdering.INSTANCE; } @@ -288,9 +297,8 @@ public static Ordering allEqual() { * *

    The comparator is serializable. * - *

    Java 8 users: Use {@code Comparator.comparing(Object::toString)} instead. + *

    Java 8+ users: Use {@code Comparator.comparing(Object::toString)} instead. */ - @GwtCompatible(serializable = true) public static Ordering usingToString() { return UsingToStringOrdering.INSTANCE; } @@ -311,16 +319,19 @@ public static Ordering usingToString() { * @since 2.0 */ // TODO(kevinb): copy to Comparators, etc. - public static Ordering arbitrary() { + @J2ktIncompatible // MapMaker + public static Ordering<@Nullable Object> arbitrary() { return ArbitraryOrderingHolder.ARBITRARY_ORDERING; } - private static class ArbitraryOrderingHolder { - static final Ordering ARBITRARY_ORDERING = new ArbitraryOrdering(); + @J2ktIncompatible // MapMaker + private static final class ArbitraryOrderingHolder { + static final Ordering<@Nullable Object> ARBITRARY_ORDERING = new ArbitraryOrdering(); } + @J2ktIncompatible // MapMaker @VisibleForTesting - static class ArbitraryOrdering extends Ordering { + static class ArbitraryOrdering extends Ordering<@Nullable Object> { private final AtomicInteger counter = new AtomicInteger(0); private final ConcurrentMap uids = @@ -342,7 +353,7 @@ private Integer getUid(Object obj) { } @Override - public int compare(Object left, Object right) { + public int compare(@Nullable Object left, @Nullable Object right) { if (left == right) { return 0; } else if (left == null) { @@ -396,25 +407,25 @@ protected Ordering() {} * Returns the reverse of this ordering; the {@code Ordering} equivalent to {@link * Collections#reverseOrder(Comparator)}. * - *

    Java 8 users: Use {@code thisComparator.reversed()} instead. + *

    Java 8+ users: Use {@code thisComparator.reversed()} instead. */ // type parameter lets us avoid the extra in statements like: // Ordering o = Ordering.natural().reverse(); - @GwtCompatible(serializable = true) public Ordering reverse() { - return new ReverseOrdering(this); + return new ReverseOrdering<>(this); } /** * Returns an ordering that treats {@code null} as less than all other values and uses {@code * this} to compare non-null values. * - *

    Java 8 users: Use {@code Comparator.nullsFirst(thisComparator)} instead. + *

    The returned object is serializable if this object is serializable. + * + *

    Java 8+ users: Use {@code Comparator.nullsFirst(thisComparator)} instead. */ // type parameter lets us avoid the extra in statements like: // Ordering o = Ordering.natural().nullsFirst(); - @GwtCompatible(serializable = true) - public Ordering nullsFirst() { + public Ordering<@Nullable S> nullsFirst() { return new NullsFirstOrdering(this); } @@ -422,12 +433,13 @@ public Ordering nullsFirst() { * Returns an ordering that treats {@code null} as greater than all other values and uses this * ordering to compare non-null values. * - *

    Java 8 users: Use {@code Comparator.nullsLast(thisComparator)} instead. + *

    The returned object is serializable if this object is serializable. + * + *

    Java 8+ users: Use {@code Comparator.nullsLast(thisComparator)} instead. */ // type parameter lets us avoid the extra in statements like: // Ordering o = Ordering.natural().nullsLast(); - @GwtCompatible(serializable = true) - public Ordering nullsLast() { + public Ordering<@Nullable S> nullsLast() { return new NullsLastOrdering(this); } @@ -436,21 +448,20 @@ public Ordering nullsLast() { * then comparing those results using {@code this}. For example, to compare objects by their * string forms, in a case-insensitive manner, use: * - *

    {@code
    +   * {@snippet :
        * Ordering.from(String.CASE_INSENSITIVE_ORDER)
        *     .onResultOf(Functions.toStringFunction())
    -   * }
    + * } * - *

    Java 8 users: Use {@code Comparator.comparing(function, thisComparator)} instead (you - * can omit the comparator if it is the natural order). + *

    Java 8+ users: Use {@code Comparator.comparing(function, thisComparator)} instead + * (you can omit the comparator if it is the natural order). */ - @GwtCompatible(serializable = true) - public Ordering onResultOf(Function function) { + public Ordering onResultOf(Function function) { return new ByFunctionOrdering<>(function, this); } Ordering> onKeys() { - return onResultOf(Maps.keyFunction()); + return onResultOf(Entry::getKey); } /** @@ -462,13 +473,15 @@ public Ordering onResultOf(Function function) { *

    An ordering produced by this method, or a chain of calls to this method, is equivalent to * one created using {@link Ordering#compound(Iterable)} on the same component comparators. * - *

    Java 8 users: Use {@code thisComparator.thenComparing(secondaryComparator)} instead. + *

    The returned object is serializable if this object and {@code secondaryComparator} are both + * serializable. + * + *

    Java 8+ users: Use {@code thisComparator.thenComparing(secondaryComparator)} instead. * Depending on what {@code secondaryComparator} is, one of the other overloads of {@code * thenComparing} may be even more useful. */ - @GwtCompatible(serializable = true) public Ordering compound(Comparator secondaryComparator) { - return new CompoundOrdering(this, checkNotNull(secondaryComparator)); + return new CompoundOrdering<>(this, checkNotNull(secondaryComparator)); } /** @@ -480,19 +493,21 @@ public Ordering compound(Comparator secondaryCompara *

    The returned ordering is equivalent to that produced using {@code * Ordering.from(comp1).compound(comp2).compound(comp3) . . .}. * + *

    The returned object is serializable if each of the {@code comparators} is serializable. + * *

    Warning: Supplying an argument with undefined iteration order, such as a {@link * HashSet}, will produce non-deterministic results. * - *

    Java 8 users: Use a chain of calls to {@link Comparator#thenComparing(Comparator)}, + *

    Java 8+ users: Use a chain of calls to {@link Comparator#thenComparing(Comparator)}, * or {@code comparatorCollection.stream().reduce(Comparator::thenComparing).get()} (if the * collection might be empty, also provide a default comparator as the {@code identity} parameter * to {@code reduce}). * * @param comparators the comparators to try in order */ - @GwtCompatible(serializable = true) - public static Ordering compound(Iterable> comparators) { - return new CompoundOrdering(comparators); + public static Ordering compound( + Iterable> comparators) { + return new CompoundOrdering<>(comparators); } /** @@ -506,11 +521,10 @@ public static Ordering compound(Iterable> * ordering.reverse().lexicographical()} (consider how each would order {@code [1]} and {@code [1, * 1]}). * - *

    Java 8 users: Use {@link Comparators#lexicographical(Comparator)} instead. + *

    Java 8+ users: Use {@link Comparators#lexicographical(Comparator)} instead. * * @since 2.0 */ - @GwtCompatible(serializable = true) // type parameter lets us avoid the extra in statements like: // Ordering> o = // Ordering.natural().lexicographical(); @@ -527,17 +541,15 @@ public Ordering> lexicographical() { // Regular instance methods - // Override to add @NullableDecl - @CanIgnoreReturnValue // TODO(kak): Consider removing this @Override - public abstract int compare(@NullableDecl T left, @NullableDecl T right); + public abstract int compare(@ParametricNullness T left, @ParametricNullness T right); /** * Returns the least of the specified values according to this ordering. If there are multiple * least values, the first of those is returned. The iterator will be left exhausted: its {@code * hasNext()} method will return {@code false}. * - *

    Java 8 users: Use {@code Streams.stream(iterator).min(thisComparator).get()} instead + *

    Java 8+ users: Use {@code Streams.stream(iterator).min(thisComparator).get()} instead * (but note that it does not guarantee which tied minimum element is returned). * * @param iterator the iterator whose minimum element is to be determined @@ -546,12 +558,13 @@ public Ordering> lexicographical() { * ordering. * @since 11.0 */ + @ParametricNullness public E min(Iterator iterator) { // let this throw NoSuchElementException as necessary E minSoFar = iterator.next(); while (iterator.hasNext()) { - minSoFar = min(minSoFar, iterator.next()); + minSoFar = this.min(minSoFar, iterator.next()); } return minSoFar; @@ -561,16 +574,17 @@ public E min(Iterator iterator) { * Returns the least of the specified values according to this ordering. If there are multiple * least values, the first of those is returned. * - *

    Java 8 users: If {@code iterable} is a {@link Collection}, use {@code + *

    Java 8+ users: If {@code iterable} is a {@link Collection}, use {@code * Collections.min(collection, thisComparator)} instead. Otherwise, use {@code * Streams.stream(iterable).min(thisComparator).get()} instead. Note that these alternatives do - * not guarantee which tied minimum element is returned) + * not guarantee which tied minimum element is returned. * * @param iterable the iterable whose minimum element is to be determined * @throws NoSuchElementException if {@code iterable} is empty * @throws ClassCastException if the parameters are not mutually comparable under this * ordering. */ + @ParametricNullness public E min(Iterable iterable) { return min(iterable.iterator()); } @@ -590,7 +604,8 @@ public E min(Iterable iterable) { * @throws ClassCastException if the parameters are not mutually comparable under this * ordering. */ - public E min(@NullableDecl E a, @NullableDecl E b) { + @ParametricNullness + public E min(@ParametricNullness E a, @ParametricNullness E b) { return (compare(a, b) <= 0) ? a : b; } @@ -598,7 +613,7 @@ public E min(@NullableDecl E a, @NullableDecl E b) { * Returns the least of the specified values according to this ordering. If there are multiple * least values, the first of those is returned. * - *

    Java 8 users: Use {@code Collections.min(Arrays.asList(a, b, c...), thisComparator)} + *

    Java 8+ users: Use {@code Collections.min(Arrays.asList(a, b, c...), thisComparator)} * instead (but note that it does not guarantee which tied minimum element is returned). * * @param a value to compare, returned if less than or equal to the rest. @@ -608,7 +623,9 @@ public E min(@NullableDecl E a, @NullableDecl E b) { * @throws ClassCastException if the parameters are not mutually comparable under this * ordering. */ - public E min(@NullableDecl E a, @NullableDecl E b, @NullableDecl E c, E... rest) { + @ParametricNullness + public E min( + @ParametricNullness E a, @ParametricNullness E b, @ParametricNullness E c, E... rest) { E minSoFar = min(min(a, b), c); for (E r : rest) { @@ -623,7 +640,7 @@ public E min(@NullableDecl E a, @NullableDecl E b, @NullableDecl E * greatest values, the first of those is returned. The iterator will be left exhausted: its * {@code hasNext()} method will return {@code false}. * - *

    Java 8 users: Use {@code Streams.stream(iterator).max(thisComparator).get()} instead + *

    Java 8+ users: Use {@code Streams.stream(iterator).max(thisComparator).get()} instead * (but note that it does not guarantee which tied maximum element is returned). * * @param iterator the iterator whose maximum element is to be determined @@ -632,12 +649,13 @@ public E min(@NullableDecl E a, @NullableDecl E b, @NullableDecl E * ordering. * @since 11.0 */ + @ParametricNullness public E max(Iterator iterator) { // let this throw NoSuchElementException as necessary E maxSoFar = iterator.next(); while (iterator.hasNext()) { - maxSoFar = max(maxSoFar, iterator.next()); + maxSoFar = this.max(maxSoFar, iterator.next()); } return maxSoFar; @@ -647,16 +665,17 @@ public E max(Iterator iterator) { * Returns the greatest of the specified values according to this ordering. If there are multiple * greatest values, the first of those is returned. * - *

    Java 8 users: If {@code iterable} is a {@link Collection}, use {@code + *

    Java 8+ users: If {@code iterable} is a {@link Collection}, use {@code * Collections.max(collection, thisComparator)} instead. Otherwise, use {@code * Streams.stream(iterable).max(thisComparator).get()} instead. Note that these alternatives do - * not guarantee which tied maximum element is returned) + * not guarantee which tied maximum element is returned. * * @param iterable the iterable whose maximum element is to be determined * @throws NoSuchElementException if {@code iterable} is empty * @throws ClassCastException if the parameters are not mutually comparable under this * ordering. */ + @ParametricNullness public E max(Iterable iterable) { return max(iterable.iterator()); } @@ -676,7 +695,8 @@ public E max(Iterable iterable) { * @throws ClassCastException if the parameters are not mutually comparable under this * ordering. */ - public E max(@NullableDecl E a, @NullableDecl E b) { + @ParametricNullness + public E max(@ParametricNullness E a, @ParametricNullness E b) { return (compare(a, b) >= 0) ? a : b; } @@ -684,7 +704,7 @@ public E max(@NullableDecl E a, @NullableDecl E b) { * Returns the greatest of the specified values according to this ordering. If there are multiple * greatest values, the first of those is returned. * - *

    Java 8 users: Use {@code Collections.max(Arrays.asList(a, b, c...), thisComparator)} + *

    Java 8+ users: Use {@code Collections.max(Arrays.asList(a, b, c...), thisComparator)} * instead (but note that it does not guarantee which tied maximum element is returned). * * @param a value to compare, returned if greater than or equal to the rest. @@ -694,7 +714,9 @@ public E max(@NullableDecl E a, @NullableDecl E b) { * @throws ClassCastException if the parameters are not mutually comparable under this * ordering. */ - public E max(@NullableDecl E a, @NullableDecl E b, @NullableDecl E c, E... rest) { + @ParametricNullness + public E max( + @ParametricNullness E a, @ParametricNullness E b, @ParametricNullness E c, E... rest) { E maxSoFar = max(max(a, b), c); for (E r : rest) { @@ -712,8 +734,8 @@ public E max(@NullableDecl E a, @NullableDecl E b, @NullableDecl E *

    The implementation does not necessarily use a stable sorting algorithm; when multiple * elements are equivalent, it is undefined which will come first. * - *

    Java 8 users: Continue to use this method for now. After the next release of Guava, - * use {@code Streams.stream(iterable).collect(Comparators.least(k, thisComparator))} instead. + *

    Java 8+ users: Use {@code Streams.stream(iterable).collect(Comparators.least(k, + * thisComparator))} instead. * * @return an immutable {@code RandomAccess} list of the {@code k} least elements in ascending * order @@ -730,11 +752,11 @@ public List leastOf(Iterable iterable, int k) { @SuppressWarnings("unchecked") // c only contains E's and doesn't escape E[] array = (E[]) collection.toArray(); - Arrays.sort(array, this); + sort(array, this); if (array.length > k) { array = Arrays.copyOf(array, k); } - return Collections.unmodifiableList(Arrays.asList(array)); + return unmodifiableList(asList(array)); } } return leastOf(iterable.iterator(), k); @@ -748,7 +770,7 @@ public List leastOf(Iterable iterable, int k) { *

    The implementation does not necessarily use a stable sorting algorithm; when multiple * elements are equivalent, it is undefined which will come first. * - *

    Java 8 users: Use {@code Streams.stream(iterator).collect(Comparators.least(k, + *

    Java 8+ users: Use {@code Streams.stream(iterator).collect(Comparators.least(k, * thisComparator))} instead. * * @return an immutable {@code RandomAccess} list of the {@code k} least elements in ascending @@ -756,21 +778,22 @@ public List leastOf(Iterable iterable, int k) { * @throws IllegalArgumentException if {@code k} is negative * @since 14.0 */ + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types public List leastOf(Iterator iterator, int k) { checkNotNull(iterator); checkNonnegative(k, "k"); if (k == 0 || !iterator.hasNext()) { - return Collections.emptyList(); + return emptyList(); } else if (k >= Integer.MAX_VALUE / 2) { // k is really large; just do a straightforward sorted-copy-and-sublist ArrayList list = Lists.newArrayList(iterator); - Collections.sort(list, this); + sort(list, this); if (list.size() > k) { list.subList(k, list.size()).clear(); } list.trimToSize(); - return Collections.unmodifiableList(list); + return unmodifiableList(list); } else { TopKSelector selector = TopKSelector.least(k, this); selector.offerAll(iterator); @@ -786,8 +809,8 @@ public List leastOf(Iterator iterator, int k) { *

    The implementation does not necessarily use a stable sorting algorithm; when multiple * elements are equivalent, it is undefined which will come first. * - *

    Java 8 users: Continue to use this method for now. After the next release of Guava, - * use {@code Streams.stream(iterable).collect(Comparators.greatest(k, thisComparator))} instead. + *

    Java 8+ users: Use {@code Streams.stream(iterable).collect(Comparators.greatest(k, + * thisComparator))} instead. * * @return an immutable {@code RandomAccess} list of the {@code k} greatest elements in * descending order @@ -797,7 +820,7 @@ public List leastOf(Iterator iterator, int k) { public List greatestOf(Iterable iterable, int k) { // TODO(kevinb): see if delegation is hurting performance noticeably // TODO(kevinb): if we change this implementation, add full unit tests. - return reverse().leastOf(iterable, k); + return this.reverse().leastOf(iterable, k); } /** @@ -808,7 +831,7 @@ public List greatestOf(Iterable iterable, int k) { *

    The implementation does not necessarily use a stable sorting algorithm; when multiple * elements are equivalent, it is undefined which will come first. * - *

    Java 8 users: Use {@code Streams.stream(iterator).collect(Comparators.greatest(k, + *

    Java 8+ users: Use {@code Streams.stream(iterator).collect(Comparators.greatest(k, * thisComparator))} instead. * * @return an immutable {@code RandomAccess} list of the {@code k} greatest elements in @@ -817,7 +840,7 @@ public List greatestOf(Iterable iterable, int k) { * @since 14.0 */ public List greatestOf(Iterator iterator, int k) { - return reverse().leastOf(iterator, k); + return this.reverse().leastOf(iterator, k); } /** @@ -839,8 +862,8 @@ public List greatestOf(Iterator iterator, int k) { public List sortedCopy(Iterable elements) { @SuppressWarnings("unchecked") // does not escape, and contains only E's E[] array = (E[]) Iterables.toArray(elements); - Arrays.sort(array, this); - return Lists.newArrayList(Arrays.asList(array)); + sort(array, this); + return new ArrayList<>(asList(array)); } /** @@ -859,7 +882,7 @@ public List sortedCopy(Iterable elements) { * @since 3.0 */ // TODO(kevinb): rerun benchmarks including new options - public ImmutableList immutableSortedCopy(Iterable elements) { + public ImmutableList immutableSortedCopy(Iterable elements) { return ImmutableList.sortedCopyOf(this, elements); } @@ -868,7 +891,7 @@ public ImmutableList immutableSortedCopy(Iterable elements) * equal to the element that preceded it, according to this ordering. Note that this is always * true when the iterable has fewer than two elements. * - *

    Java 8 users: Use the equivalent {@link Comparators#isInOrder(Iterable, Comparator)} + *

    Java 8+ users: Use the equivalent {@link Comparators#isInOrder(Iterable, Comparator)} * instead, since the rest of {@code Ordering} is mostly obsolete (as explained in the class * documentation). */ @@ -892,7 +915,7 @@ public boolean isOrdered(Iterable iterable) { * greater than the element that preceded it, according to this ordering. Note that this is always * true when the iterable has fewer than two elements. * - *

    Java 8 users: Use the equivalent {@link Comparators#isInStrictOrder(Iterable, + *

    Java 8+ users: Use the equivalent {@link Comparators#isInStrictOrder(Iterable, * Comparator)} instead, since the rest of {@code Ordering} is mostly obsolete (as explained in * the class documentation). */ @@ -919,8 +942,16 @@ public boolean isStrictlyOrdered(Iterable iterable) { * @param key the key to be searched for * @deprecated Use {@link Collections#binarySearch(List, Object, Comparator)} directly. */ + @InlineMe( + replacement = "Collections.binarySearch(sortedList, key, this)", + imports = "java.util.Collections") + // We can't compatibly make this `final` now. + @InlineMeValidationDisabled( + "While binarySearch() is not final, the inlining is still safe as long as any overrides" + + " follow the contract.") @Deprecated - public int binarySearch(List sortedList, @NullableDecl T key) { + public int binarySearch( + List sortedList, @ParametricNullness T key) { return Collections.binarySearch(sortedList, key, this); } @@ -929,8 +960,7 @@ public int binarySearch(List sortedList, @NullableDecl T key) { * Object[])} comparator when comparing a value outside the set of values it can compare. * Extending {@link ClassCastException} may seem odd, but it is required. */ - @VisibleForTesting - static class IncomparableValueException extends ClassCastException { + static final class IncomparableValueException extends ClassCastException { final Object value; IncomparableValueException(Object value) { @@ -938,7 +968,7 @@ static class IncomparableValueException extends ClassCastException { this.value = value; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } // Never make these public diff --git a/android/guava/src/com/google/common/collect/ParametricNullness.java b/android/guava/src/com/google/common/collect/ParametricNullness.java new file mode 100644 index 000000000000..d3d67ef6a186 --- /dev/null +++ b/android/guava/src/com/google/common/collect/ParametricNullness.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. + */ +@GwtCompatible +@Retention(CLASS) +@Target({FIELD, METHOD, PARAMETER}) +@interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/collect/PeekingIterator.java b/android/guava/src/com/google/common/collect/PeekingIterator.java index 5a6c60b2bec4..2c214c1ec809 100644 --- a/android/guava/src/com/google/common/collect/PeekingIterator.java +++ b/android/guava/src/com/google/common/collect/PeekingIterator.java @@ -21,12 +21,13 @@ import com.google.errorprone.annotations.DoNotMock; import java.util.Iterator; import java.util.NoSuchElementException; +import org.jspecify.annotations.Nullable; /** * An iterator that supports a one-element lookahead while iterating. * *

    See the Guava User Guide article on {@code + * "https://github.com/google/guava/wiki/CollectionHelpersExplained#peekingiterator">{@code * PeekingIterator}. * * @author Mick Killianey @@ -34,7 +35,7 @@ */ @DoNotMock("Use Iterators.peekingIterator") @GwtCompatible -public interface PeekingIterator extends Iterator { +public interface PeekingIterator extends Iterator { /** * Returns the next element in the iteration, without advancing the iteration. * @@ -44,6 +45,7 @@ public interface PeekingIterator extends Iterator { * @throws NoSuchElementException if the iteration has no more elements according to {@link * #hasNext()} */ + @ParametricNullness E peek(); /** @@ -54,6 +56,7 @@ public interface PeekingIterator extends Iterator { */ @CanIgnoreReturnValue @Override + @ParametricNullness E next(); /** diff --git a/android/guava/src/com/google/common/collect/Platform.java b/android/guava/src/com/google/common/collect/Platform.java index e4d1c4c5fd15..a087f484c97b 100644 --- a/android/guava/src/com/google/common/collect/Platform.java +++ b/android/guava/src/com/google/common/collect/Platform.java @@ -17,20 +17,22 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import java.lang.reflect.Array; +import com.google.common.annotations.J2ktIncompatible; import java.util.Arrays; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * Methods factored out so that they can be emulated differently in GWT. * * @author Hayward Chan */ -@GwtCompatible(emulated = true) +@GwtCompatible final class Platform { /** Returns the platform preferred implementation of a map based on a hash table. */ - static Map newHashMapWithExpectedSize(int expectedSize) { + static + Map newHashMapWithExpectedSize(int expectedSize) { return CompactHashMap.createWithExpectedSize(expectedSize); } @@ -38,12 +40,13 @@ static Map newHashMapWithExpectedSize(int expectedSize) { * Returns the platform preferred implementation of an insertion ordered map based on a hash * table. */ - static Map newLinkedHashMapWithExpectedSize(int expectedSize) { + static + Map newLinkedHashMapWithExpectedSize(int expectedSize) { return CompactLinkedHashMap.createWithExpectedSize(expectedSize); } /** Returns the platform preferred implementation of a set based on a hash table. */ - static Set newHashSetWithExpectedSize(int expectedSize) { + static Set newHashSetWithExpectedSize(int expectedSize) { return CompactHashSet.createWithExpectedSize(expectedSize); } @@ -51,7 +54,7 @@ static Set newHashSetWithExpectedSize(int expectedSize) { * Returns the platform preferred implementation of an insertion ordered set based on a hash * table. */ - static Set newLinkedHashSetWithExpectedSize(int expectedSize) { + static Set newLinkedHashSetWithExpectedSize(int expectedSize) { return CompactLinkedHashSet.createWithExpectedSize(expectedSize); } @@ -59,15 +62,25 @@ static Set newLinkedHashSetWithExpectedSize(int expectedSize) { * Returns the platform preferred map implementation that preserves insertion order when used only * for insertions. */ - static Map preservesInsertionOrderOnPutsMap() { + static + Map preservesInsertionOrderOnPutsMap() { return CompactHashMap.create(); } + /** + * Returns the platform preferred map implementation that preserves insertion order when used only + * for insertions, with a hint for how many entries to expect. + */ + static + Map preservesInsertionOrderOnPutsMapWithExpectedSize(int expectedSize) { + return Maps.newLinkedHashMapWithExpectedSize(expectedSize); + } + /** * Returns the platform preferred set implementation that preserves insertion order when used only * for insertions. */ - static Set preservesInsertionOrderOnAddsSet() { + static Set preservesInsertionOrderOnAddsSet() { return CompactHashSet.create(); } @@ -77,18 +90,32 @@ static Set preservesInsertionOrderOnAddsSet() { * @param reference any array of the desired type * @param length the length of the new array */ - static T[] newArray(T[] reference, int length) { - Class type = reference.getClass().getComponentType(); - - // the cast is safe because - // result.getClass() == reference.getClass().getComponentType() - @SuppressWarnings("unchecked") - T[] result = (T[]) Array.newInstance(type, length); - return result; + /* + * The new array contains nulls, even if the old array did not. If we wanted to be accurate, we + * would declare a return type of `@Nullable T[]`. However, we've decided not to think too hard + * about arrays for now, as they're a mess. (We previously discussed this in the review of + * ObjectArrays, which is the main caller of this method.) + */ + static T[] newArray(T[] reference, int length) { + T[] empty = reference.length == 0 ? reference : Arrays.copyOf(reference, 0); + return Arrays.copyOf(empty, length); } /** Equivalent to Arrays.copyOfRange(source, from, to, arrayOfType.getClass()). */ - static T[] copy(Object[] source, int from, int to, T[] arrayOfType) { + /* + * Arrays are a mess from a nullness perspective, and Class instances for object-array types are + * even worse. For now, we just suppress and move on with our lives. + * + * - https://github.com/jspecify/jspecify/issues/65 + * + * - https://github.com/jspecify/jdk/commit/71d826792b8c7ef95d492c50a274deab938f2552 + */ + /* + * TODO(cpovirk): Is the unchecked cast avoidable? Would System.arraycopy be similarly fast (if + * likewise not type-checked)? Could our single caller do something different? + */ + @SuppressWarnings({"nullness", "unchecked"}) + static T[] copy(Object[] source, int from, int to, T[] arrayOfType) { return Arrays.copyOfRange(source, from, to, (Class) arrayOfType.getClass()); } @@ -97,10 +124,15 @@ static T[] copy(Object[] source, int from, int to, T[] arrayOfType) { * GWT). This is sometimes acceptable, when only server-side code could generate enough volume * that reclamation becomes important. */ + @J2ktIncompatible static MapMaker tryWeakKeys(MapMaker mapMaker) { return mapMaker.weakKeys(); } + static > Class getDeclaringClassOrObjectForJ2cl(E e) { + return e.getDeclaringClass(); + } + static int reduceIterationsIfGwt(int iterations) { return iterations; } @@ -109,7 +141,5 @@ static int reduceExponentIfGwt(int exponent) { return exponent; } - static void checkGwtRpcEnabled() {} - private Platform() {} } diff --git a/android/guava/src/com/google/common/collect/Queues.java b/android/guava/src/com/google/common/collect/Queues.java index fdeb369097ca..dd57dc482ace 100644 --- a/android/guava/src/com/google/common/collect/Queues.java +++ b/android/guava/src/com/google/common/collect/Queues.java @@ -14,11 +14,15 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; +import static com.google.common.collect.Internal.toNanosSaturated; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.time.Duration; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; @@ -32,6 +36,7 @@ import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@link Queue} and {@link Deque} instances. Also see this @@ -40,7 +45,7 @@ * @author Kurt Alfred Kluever * @since 11.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Queues { private Queues() {} @@ -50,9 +55,10 @@ private Queues() {} * Creates an empty {@code ArrayBlockingQueue} with the given (fixed) capacity and nonfair access * policy. */ + @J2ktIncompatible @GwtIncompatible // ArrayBlockingQueue public static ArrayBlockingQueue newArrayBlockingQueue(int capacity) { - return new ArrayBlockingQueue(capacity); + return new ArrayBlockingQueue<>(capacity); } // ArrayDeque @@ -63,7 +69,7 @@ public static ArrayBlockingQueue newArrayBlockingQueue(int capacity) { * @since 12.0 */ public static ArrayDeque newArrayDeque() { - return new ArrayDeque(); + return new ArrayDeque<>(); } /** @@ -74,9 +80,9 @@ public static ArrayDeque newArrayDeque() { */ public static ArrayDeque newArrayDeque(Iterable elements) { if (elements instanceof Collection) { - return new ArrayDeque((Collection) elements); + return new ArrayDeque<>((Collection) elements); } - ArrayDeque deque = new ArrayDeque(); + ArrayDeque deque = new ArrayDeque<>(); Iterables.addAll(deque, elements); return deque; } @@ -84,22 +90,24 @@ public static ArrayDeque newArrayDeque(Iterable elements) { // ConcurrentLinkedQueue /** Creates an empty {@code ConcurrentLinkedQueue}. */ + @J2ktIncompatible @GwtIncompatible // ConcurrentLinkedQueue public static ConcurrentLinkedQueue newConcurrentLinkedQueue() { - return new ConcurrentLinkedQueue(); + return new ConcurrentLinkedQueue<>(); } /** * Creates a {@code ConcurrentLinkedQueue} containing the elements of the specified iterable, in * the order they are returned by the iterable's iterator. */ + @J2ktIncompatible @GwtIncompatible // ConcurrentLinkedQueue public static ConcurrentLinkedQueue newConcurrentLinkedQueue( Iterable elements) { if (elements instanceof Collection) { - return new ConcurrentLinkedQueue((Collection) elements); + return new ConcurrentLinkedQueue<>((Collection) elements); } - ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); + ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); Iterables.addAll(queue, elements); return queue; } @@ -111,9 +119,10 @@ public static ConcurrentLinkedQueue newConcurrentLinkedQueue( * * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // LinkedBlockingDeque public static LinkedBlockingDeque newLinkedBlockingDeque() { - return new LinkedBlockingDeque(); + return new LinkedBlockingDeque<>(); } /** @@ -122,9 +131,10 @@ public static LinkedBlockingDeque newLinkedBlockingDeque() { * @throws IllegalArgumentException if {@code capacity} is less than 1 * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // LinkedBlockingDeque public static LinkedBlockingDeque newLinkedBlockingDeque(int capacity) { - return new LinkedBlockingDeque(capacity); + return new LinkedBlockingDeque<>(capacity); } /** @@ -134,12 +144,13 @@ public static LinkedBlockingDeque newLinkedBlockingDeque(int capacity) { * * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // LinkedBlockingDeque public static LinkedBlockingDeque newLinkedBlockingDeque(Iterable elements) { if (elements instanceof Collection) { - return new LinkedBlockingDeque((Collection) elements); + return new LinkedBlockingDeque<>((Collection) elements); } - LinkedBlockingDeque deque = new LinkedBlockingDeque(); + LinkedBlockingDeque deque = new LinkedBlockingDeque<>(); Iterables.addAll(deque, elements); return deque; } @@ -147,9 +158,10 @@ public static LinkedBlockingDeque newLinkedBlockingDeque(Iterable LinkedBlockingQueue newLinkedBlockingQueue() { - return new LinkedBlockingQueue(); + return new LinkedBlockingQueue<>(); } /** @@ -157,9 +169,10 @@ public static LinkedBlockingQueue newLinkedBlockingQueue() { * * @throws IllegalArgumentException if {@code capacity} is less than 1 */ + @J2ktIncompatible @GwtIncompatible // LinkedBlockingQueue public static LinkedBlockingQueue newLinkedBlockingQueue(int capacity) { - return new LinkedBlockingQueue(capacity); + return new LinkedBlockingQueue<>(capacity); } /** @@ -170,12 +183,13 @@ public static LinkedBlockingQueue newLinkedBlockingQueue(int capacity) { * @param elements the elements that the queue should contain, in order * @return a new {@code LinkedBlockingQueue} containing those elements */ + @J2ktIncompatible @GwtIncompatible // LinkedBlockingQueue public static LinkedBlockingQueue newLinkedBlockingQueue(Iterable elements) { if (elements instanceof Collection) { - return new LinkedBlockingQueue((Collection) elements); + return new LinkedBlockingQueue<>((Collection) elements); } - LinkedBlockingQueue queue = new LinkedBlockingQueue(); + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); Iterables.addAll(queue, elements); return queue; } @@ -188,11 +202,14 @@ public static LinkedBlockingQueue newLinkedBlockingQueue(Iterable PriorityBlockingQueue newPriorityBlockingQueue() { - return new PriorityBlockingQueue(); + return new PriorityBlockingQueue<>(); } /** @@ -201,15 +218,18 @@ public static PriorityBlockingQueue newPriorityBlockin *

    Note: If the specified iterable is a {@code SortedSet} or a {@code PriorityQueue}, * this priority queue will be ordered according to the same ordering. * - * @since 11.0 (requires that {@code E} be {@code Comparable} since 15.0). + * @since 11.0 (but the bound of {@code E} was changed from {@code Object} to {@code Comparable} + * in 15.0) */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 + @J2ktIncompatible @GwtIncompatible // PriorityBlockingQueue public static PriorityBlockingQueue newPriorityBlockingQueue( Iterable elements) { if (elements instanceof Collection) { - return new PriorityBlockingQueue((Collection) elements); + return new PriorityBlockingQueue<>((Collection) elements); } - PriorityBlockingQueue queue = new PriorityBlockingQueue(); + PriorityBlockingQueue queue = new PriorityBlockingQueue<>(); Iterables.addAll(queue, elements); return queue; } @@ -220,10 +240,12 @@ public static PriorityBlockingQueue newPriorityBlockin * Creates an empty {@code PriorityQueue} with the ordering given by its elements' natural * ordering. * - * @since 11.0 (requires that {@code E} be {@code Comparable} since 15.0). + * @since 11.0 (but the bound of {@code E} was changed from {@code Object} to {@code Comparable} + * in 15.0) */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static PriorityQueue newPriorityQueue() { - return new PriorityQueue(); + return new PriorityQueue<>(); } /** @@ -232,14 +254,16 @@ public static PriorityQueue newPriorityQueue() { *

    Note: If the specified iterable is a {@code SortedSet} or a {@code PriorityQueue}, * this priority queue will be ordered according to the same ordering. * - * @since 11.0 (requires that {@code E} be {@code Comparable} since 15.0). + * @since 11.0 (but the bound of {@code E} was changed from {@code Object} to {@code Comparable} + * in 15.0) */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static PriorityQueue newPriorityQueue( Iterable elements) { if (elements instanceof Collection) { - return new PriorityQueue((Collection) elements); + return new PriorityQueue<>((Collection) elements); } - PriorityQueue queue = new PriorityQueue(); + PriorityQueue queue = new PriorityQueue<>(); Iterables.addAll(queue, elements); return queue; } @@ -247,9 +271,32 @@ public static PriorityQueue newPriorityQueue( // SynchronousQueue /** Creates an empty {@code SynchronousQueue} with nonfair access policy. */ + @J2ktIncompatible @GwtIncompatible // SynchronousQueue public static SynchronousQueue newSynchronousQueue() { - return new SynchronousQueue(); + return new SynchronousQueue<>(); + } + + /** + * Drains the queue as {@link BlockingQueue#drainTo(Collection, int)}, but if the requested {@code + * numElements} elements are not available, it will wait for them up to the specified timeout. + * + * @param q the blocking queue to be drained + * @param buffer where to add the transferred elements + * @param numElements the number of elements to be waited for + * @param timeout how long to wait before giving up + * @return the number of elements transferred + * @throws InterruptedException if interrupted while waiting + * @since 33.4.0 (but since 28.0 in the JRE flavor) + */ + @CanIgnoreReturnValue + @J2ktIncompatible + @GwtIncompatible // BlockingQueue + @IgnoreJRERequirement // Users will use this only if they're already using Duration + public static int drain( + BlockingQueue q, Collection buffer, int numElements, Duration timeout) + throws InterruptedException { + return drain(q, buffer, numElements, toNanosSaturated(timeout), NANOSECONDS); } /** @@ -264,8 +311,8 @@ public static SynchronousQueue newSynchronousQueue() { * @return the number of elements transferred * @throws InterruptedException if interrupted while waiting */ - @Beta @CanIgnoreReturnValue + @J2ktIncompatible @GwtIncompatible // BlockingQueue @SuppressWarnings("GoodTime") // should accept a java.time.Duration public static int drain( @@ -288,7 +335,7 @@ public static int drain( // elements already available (e.g. LinkedBlockingQueue#drainTo locks only once) added += q.drainTo(buffer, numElements - added); if (added < numElements) { // not enough elements immediately available; will have to poll - E e = q.poll(deadline - System.nanoTime(), TimeUnit.NANOSECONDS); + E e = q.poll(deadline - System.nanoTime(), NANOSECONDS); if (e == null) { break; // we already waited enough, and there are no more elements in sight } @@ -299,6 +346,28 @@ public static int drain( return added; } + /** + * Drains the queue as {@linkplain #drain(BlockingQueue, Collection, int, Duration)}, but with a + * different behavior in case it is interrupted while waiting. In that case, the operation will + * continue as usual, and in the end the thread's interruption status will be set (no {@code + * InterruptedException} is thrown). + * + * @param q the blocking queue to be drained + * @param buffer where to add the transferred elements + * @param numElements the number of elements to be waited for + * @param timeout how long to wait before giving up + * @return the number of elements transferred + * @since 33.4.0 (but since 28.0 in the JRE flavor) + */ + @CanIgnoreReturnValue + @J2ktIncompatible + @GwtIncompatible // BlockingQueue + @IgnoreJRERequirement // Users will use this only if they're already using Duration + public static int drainUninterruptibly( + BlockingQueue q, Collection buffer, int numElements, Duration timeout) { + return drainUninterruptibly(q, buffer, numElements, toNanosSaturated(timeout), NANOSECONDS); + } + /** * Drains the queue as {@linkplain #drain(BlockingQueue, Collection, int, long, TimeUnit)}, but * with a different behavior in case it is interrupted while waiting. In that case, the operation @@ -312,8 +381,8 @@ public static int drain( * @param unit a {@code TimeUnit} determining how to interpret the timeout parameter * @return the number of elements transferred */ - @Beta @CanIgnoreReturnValue + @J2ktIncompatible @GwtIncompatible // BlockingQueue @SuppressWarnings("GoodTime") // should accept a java.time.Duration public static int drainUninterruptibly( @@ -335,7 +404,7 @@ public static int drainUninterruptibly( E e; // written exactly once, by a successful (uninterrupted) invocation of #poll while (true) { try { - e = q.poll(deadline - System.nanoTime(), TimeUnit.NANOSECONDS); + e = q.poll(deadline - System.nanoTime(), NANOSECONDS); break; } catch (InterruptedException ex) { interrupted = true; // note interruption and retry @@ -364,7 +433,7 @@ public static int drainUninterruptibly( *

    It is imperative that the user manually synchronize on the returned queue when accessing the * queue's iterator: * - *

    {@code
    +   * {@snippet :
        * Queue queue = Queues.synchronizedQueue(MinMaxPriorityQueue.create());
        * ...
        * queue.add(element);  // Needn't be in synchronized block
    @@ -375,7 +444,7 @@ public static  int drainUninterruptibly(
        *     foo(i.next());
        *   }
        * }
    -   * }
    + * } * *

    Failure to follow this advice may result in non-deterministic behavior. * @@ -385,7 +454,8 @@ public static int drainUninterruptibly( * @return a synchronized view of the specified queue * @since 14.0 */ - public static Queue synchronizedQueue(Queue queue) { + @J2ktIncompatible // Synchronized + public static Queue synchronizedQueue(Queue queue) { return Synchronized.queue(queue, null); } @@ -397,8 +467,8 @@ public static Queue synchronizedQueue(Queue queue) { *

    It is imperative that the user manually synchronize on the returned deque when accessing any * of the deque's iterators: * - *

    {@code
    -   * Deque deque = Queues.synchronizedDeque(Queues.newArrayDeque());
    +   * {@snippet :
    +   * Deque deque = Queues.synchronizedDeque(Queues.newArrayDeque());
        * ...
        * deque.add(element);  // Needn't be in synchronized block
        * ...
    @@ -408,7 +478,7 @@ public static  Queue synchronizedQueue(Queue queue) {
        *     foo(i.next());
        *   }
        * }
    -   * }
    + * } * *

    Failure to follow this advice may result in non-deterministic behavior. * @@ -418,7 +488,8 @@ public static Queue synchronizedQueue(Queue queue) { * @return a synchronized view of the specified deque * @since 15.0 */ - public static Deque synchronizedDeque(Deque deque) { + @J2ktIncompatible // Synchronized + public static Deque synchronizedDeque(Deque deque) { return Synchronized.deque(deque, null); } } diff --git a/android/guava/src/com/google/common/collect/Range.java b/android/guava/src/com/google/common/collect/Range.java index 5300b772ce82..2cda2e6d81da 100644 --- a/android/guava/src/com/google/common/collect/Range.java +++ b/android/guava/src/com/google/common/collect/Range.java @@ -16,18 +16,22 @@ package com.google.common.collect; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Equivalence; -import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A range (or "interval") defines the boundaries around a contiguous span of values of some @@ -104,6 +108,7 @@ * P if, for all ranges {@code b} also having property P, {@code a.encloses(b)}. * Likewise, {@code a} is minimal when {@code b.encloses(a)} for all {@code b} having * property P. See, for example, the definition of {@link #intersection intersection}. + *

  • A {@code Range} is serializable if it has no bounds, or if each bound is serializable. * * *

    Further reading

    @@ -116,44 +121,16 @@ * @since 10.0 */ @GwtCompatible -@SuppressWarnings("rawtypes") -public final class Range extends RangeGwtSerializationDependencies - implements Predicate, Serializable { - - static class LowerBoundFn implements Function { - static final LowerBoundFn INSTANCE = new LowerBoundFn(); - - @Override - public Cut apply(Range range) { - return range.lowerBound; - } - } - - static class UpperBoundFn implements Function { - static final UpperBoundFn INSTANCE = new UpperBoundFn(); - - @Override - public Cut apply(Range range) { - return range.upperBound; - } - } - +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 +@Immutable(containerOf = "C") +public final class Range implements Predicate, Serializable { @SuppressWarnings("unchecked") - static > Function, Cut> lowerBoundFn() { - return (Function) LowerBoundFn.INSTANCE; - } - - @SuppressWarnings("unchecked") - static > Function, Cut> upperBoundFn() { - return (Function) UpperBoundFn.INSTANCE; - } - static > Ordering> rangeLexOrdering() { - return (Ordering>) (Ordering) RangeLexOrdering.INSTANCE; + return (Ordering>) RangeLexOrdering.INSTANCE; } static > Range create(Cut lowerBound, Cut upperBound) { - return new Range(lowerBound, upperBound); + return new Range<>(lowerBound, upperBound); } /** @@ -231,7 +208,7 @@ public static > Range range( * @since 14.0 */ public static > Range lessThan(C endpoint) { - return create(Cut.belowAll(), Cut.belowValue(endpoint)); + return create(Cut.belowAll(), Cut.belowValue(endpoint)); } /** @@ -240,7 +217,7 @@ public static > Range lessThan(C endpoint) { * @since 14.0 */ public static > Range atMost(C endpoint) { - return create(Cut.belowAll(), Cut.aboveValue(endpoint)); + return create(Cut.belowAll(), Cut.aboveValue(endpoint)); } /** @@ -255,9 +232,8 @@ public static > Range upTo(C endpoint, BoundType boun return lessThan(endpoint); case CLOSED: return atMost(endpoint); - default: - throw new AssertionError(); } + throw new AssertionError(); } /** @@ -266,7 +242,7 @@ public static > Range upTo(C endpoint, BoundType boun * @since 14.0 */ public static > Range greaterThan(C endpoint) { - return create(Cut.aboveValue(endpoint), Cut.aboveAll()); + return create(Cut.aboveValue(endpoint), Cut.aboveAll()); } /** @@ -275,7 +251,7 @@ public static > Range greaterThan(C endpoint) { * @since 14.0 */ public static > Range atLeast(C endpoint) { - return create(Cut.belowValue(endpoint), Cut.aboveAll()); + return create(Cut.belowValue(endpoint), Cut.aboveAll()); } /** @@ -290,9 +266,8 @@ public static > Range downTo(C endpoint, BoundType bo return greaterThan(endpoint); case CLOSED: return atLeast(endpoint); - default: - throw new AssertionError(); } + throw new AssertionError(); } private static final Range ALL = new Range<>(Cut.belowAll(), Cut.aboveAll()); @@ -329,9 +304,9 @@ public static > Range singleton(C value) { public static > Range encloseAll(Iterable values) { checkNotNull(values); if (values instanceof SortedSet) { - SortedSet set = cast(values); + SortedSet set = (SortedSet) values; Comparator comparator = set.comparator(); - if (Ordering.natural().equals(comparator) || comparator == null) { + if (Ordering.natural().equals(comparator) || comparator == null) { return closed(set.first(), set.last()); } } @@ -340,8 +315,8 @@ public static > Range encloseAll(Iterable values) C max = min; while (valueIterator.hasNext()) { C value = checkNotNull(valueIterator.next()); - min = Ordering.natural().min(min, value); - max = Ordering.natural().max(max, value); + min = Ordering.natural().min(min, value); + max = Ordering.natural().max(max, value); } return closed(min, max); } @@ -439,6 +414,7 @@ public boolean contains(C value) { * @deprecated Provided only to satisfy the {@link Predicate} interface; use {@link #contains} * instead. */ + @InlineMe(replacement = "this.contains(input)") @Deprecated @Override public boolean apply(C input) { @@ -456,7 +432,7 @@ public boolean containsAll(Iterable values) { // this optimizes testing equality of two range-backed sets if (values instanceof SortedSet) { - SortedSet set = cast(values); + SortedSet set = (SortedSet) values; Comparator comparator = set.comparator(); if (Ordering.natural().equals(comparator) || comparator == null) { return contains(set.first()) && contains(set.last()); @@ -555,6 +531,15 @@ public Range intersection(Range connectedRange) { } else { Cut newLower = (lowerCmp >= 0) ? lowerBound : connectedRange.lowerBound; Cut newUpper = (upperCmp <= 0) ? upperBound : connectedRange.upperBound; + + // create() would catch this, but give a confusing error message + checkArgument( + newLower.compareTo(newUpper) <= 0, + "intersection is undefined for disconnected ranges %s and %s", + this, + connectedRange); + + // TODO(kevinb): all the precondition checks in the constructor are redundant... return create(newLower, newUpper); } } @@ -663,7 +648,7 @@ public Range canonical(DiscreteDomain domain) { * {@code [3..3)}, {@code (3..3]}, {@code (4..4]} are all unequal. */ @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof Range) { Range other = (Range) object; return lowerBound.equals(other.lowerBound) && upperBound.equals(other.upperBound); @@ -694,9 +679,14 @@ private static String toString(Cut lowerBound, Cut upperBound) { return sb.toString(); } - /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ - private static SortedSet cast(Iterable iterable) { - return (SortedSet) iterable; + // We declare accessors so that we can use method references like `Range::lowerBound`. + + Cut lowerBound() { + return lowerBound; + } + + Cut upperBound() { + return upperBound; } Object readResolve() { @@ -713,8 +703,8 @@ static int compareOrThrow(Comparable left, Comparable right) { } /** Needed to serialize sorted collections of Ranges. */ - private static class RangeLexOrdering extends Ordering> implements Serializable { - static final Ordering> INSTANCE = new RangeLexOrdering(); + private static final class RangeLexOrdering extends Ordering> implements Serializable { + static final Ordering INSTANCE = new RangeLexOrdering(); @Override public int compare(Range left, Range right) { @@ -724,8 +714,8 @@ public int compare(Range left, Range right) { .result(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/RangeGwtSerializationDependencies.java b/android/guava/src/com/google/common/collect/RangeGwtSerializationDependencies.java deleted file mode 100644 index 222c1285fb4b..000000000000 --- a/android/guava/src/com/google/common/collect/RangeGwtSerializationDependencies.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2016 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.collect; - -import com.google.common.annotations.GwtCompatible; -import java.io.Serializable; - -/** - * A dummy superclass to support GWT serialization of the element type of a {@link Range}. The GWT - * supersource for this class contains a field of type {@code C}. - * - *

    For details about this hack, see {@code GwtSerializationDependencies}, which takes the same - * approach but with a subclass rather than a superclass. - * - *

    TODO(cpovirk): Consider applying this subclass approach to our other types. - */ -@GwtCompatible(emulated = true) -abstract class RangeGwtSerializationDependencies implements Serializable {} diff --git a/android/guava/src/com/google/common/collect/RangeMap.java b/android/guava/src/com/google/common/collect/RangeMap.java index e6c902e9c9b4..083a12cc5c87 100644 --- a/android/guava/src/com/google/common/collect/RangeMap.java +++ b/android/guava/src/com/google/common/collect/RangeMap.java @@ -16,14 +16,13 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import com.google.errorprone.annotations.DoNotMock; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A mapping from disjoint nonempty ranges to non-null values. Queries look up the value associated @@ -35,25 +34,28 @@ * @author Louis Wasserman * @since 14.0 */ -@Beta +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 @DoNotMock("Use ImmutableRangeMap or TreeRangeMap") @GwtIncompatible public interface RangeMap { + /* + * TODO(cpovirk): These docs sometimes say "map" and sometimes say "range map." Pick one, or at + * least decide on a policy for when to use which. + */ + /** * Returns the value associated with the specified key, or {@code null} if there is no such value. * *

    Specifically, if any range in this range map contains the specified key, the value * associated with that range is returned. */ - @NullableDecl - V get(K key); + @Nullable V get(K key); /** * Returns the range containing this key and its associated value, if such a range is present in * the range map, or {@code null} otherwise. */ - @NullableDecl - Entry, V> getEntry(K key); + @Nullable Entry, V> getEntry(K key); /** * Returns the minimal range {@linkplain Range#encloses(Range) enclosing} the ranges in this @@ -95,7 +97,7 @@ public interface RangeMap { void putCoalescing(Range range, V value); /** Puts all the associations from {@code rangeMap} into this range map (optional operation). */ - void putAll(RangeMap rangeMap); + void putAll(RangeMap rangeMap); /** Removes all associations from this range map (optional operation). */ void clear(); @@ -146,6 +148,7 @@ public interface RangeMap { *

    The returned range map will throw an {@link IllegalArgumentException} on an attempt to * insert a range not {@linkplain Range#encloses(Range) enclosed} by {@code range}. */ + // TODO(cpovirk): Consider documenting that IAE on the various methods that can throw it. RangeMap subRangeMap(Range range); /** @@ -153,7 +156,7 @@ public interface RangeMap { * #asMapOfRanges()}. */ @Override - boolean equals(@NullableDecl Object o); + boolean equals(@Nullable Object o); /** Returns {@code asMapOfRanges().hashCode()}. */ @Override diff --git a/android/guava/src/com/google/common/collect/RangeSet.java b/android/guava/src/com/google/common/collect/RangeSet.java index 06c149cc1b59..d1e918217eb8 100644 --- a/android/guava/src/com/google/common/collect/RangeSet.java +++ b/android/guava/src/com/google/common/collect/RangeSet.java @@ -14,12 +14,11 @@ package com.google.common.collect; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import com.google.errorprone.annotations.DoNotMock; import java.util.NoSuchElementException; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A set comprising zero or more {@linkplain Range#isEmpty nonempty}, {@linkplain @@ -28,14 +27,14 @@ *

    Implementations that choose to support the {@link #add(Range)} operation are required to * ignore empty ranges and coalesce connected ranges. For example: * - *

    {@code
    + * {@snippet :
      * RangeSet rangeSet = TreeRangeSet.create();
      * rangeSet.add(Range.closed(1, 10)); // {[1, 10]}
      * rangeSet.add(Range.closedOpen(11, 15)); // disconnected range; {[1, 10], [11, 15)}
      * rangeSet.add(Range.closedOpen(15, 20)); // connected range; {[1, 10], [11, 20)}
      * rangeSet.add(Range.openClosed(0, 0)); // empty range; {[1, 10], [11, 20)}
      * rangeSet.remove(Range.open(5, 10)); // splits [1, 10]; {[1, 5], [10, 10], [11, 20)}
    - * }
    + * } * *

    Note that the behavior of {@link Range#isEmpty()} and {@link Range#isConnected(Range)} may not * be as expected on discrete ranges. See the Javadoc of those methods for details. @@ -43,13 +42,13 @@ *

    For a {@link Set} whose contents are specified by a {@link Range}, see {@link ContiguousSet}. * *

    See the Guava User Guide article on RangeSets. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#rangeset">RangeSets. * * @author Kevin Bourrillion * @author Louis Wasserman * @since 14.0 */ -@Beta +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 @DoNotMock("Use ImmutableRangeSet or TreeRangeSet") @GwtIncompatible public interface RangeSet { @@ -63,7 +62,7 @@ public interface RangeSet { * Returns the unique range from this range set that {@linkplain Range#contains contains} {@code * value}, or {@code null} if this range set does not contain {@code value}. */ - Range rangeContaining(C value); + @Nullable Range rangeContaining(C value); /** * Returns {@code true} if there exists a non-empty range enclosed by both a member range in this @@ -246,7 +245,7 @@ public interface RangeSet { * according to {@link Range#equals(Object)}. */ @Override - boolean equals(@NullableDecl Object obj); + boolean equals(@Nullable Object obj); /** Returns {@code asRanges().hashCode()}. */ @Override diff --git a/android/guava/src/com/google/common/collect/RegularContiguousSet.java b/android/guava/src/com/google/common/collect/RegularContiguousSet.java index d9a63306a8e2..2d25773825e8 100644 --- a/android/guava/src/com/google/common/collect/RegularContiguousSet.java +++ b/android/guava/src/com/google/common/collect/RegularContiguousSet.java @@ -18,20 +18,24 @@ import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.BoundType.CLOSED; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Collection; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An implementation of {@link ContiguousSet} that contains one or more elements. * * @author Gregory Kick */ -@GwtCompatible(emulated = true) -@SuppressWarnings("unchecked") // allow ungenerified Comparable types +@GwtCompatible +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 final class RegularContiguousSet extends ContiguousSet { private final Range range; @@ -52,11 +56,12 @@ ContiguousSet headSetImpl(C toElement, boolean inclusive) { } @Override + @SuppressWarnings("unchecked") // TODO(cpovirk): Use a shared unsafeCompare method. ContiguousSet subSetImpl( C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) { if (fromElement.compareTo(toElement) == 0 && !fromInclusive && !toInclusive) { // Range would reject our attempt to create (x, x). - return new EmptyContiguousSet(domain); + return new EmptyContiguousSet<>(domain); } return intersectionInCurrentDomain( Range.range( @@ -71,8 +76,15 @@ ContiguousSet tailSetImpl(C fromElement, boolean inclusive) { @GwtIncompatible // not used by GWT emulation @Override - int indexOf(Object target) { - return contains(target) ? (int) domain.distance(first(), (C) target) : -1; + int indexOf(@Nullable Object target) { + if (!contains(target)) { + return -1; + } + // The cast is safe because of the contains check—at least for any reasonable Comparable class. + @SuppressWarnings("unchecked") + // requireNonNull is safe because of the contains check. + C c = (C) requireNonNull(target); + return (int) domain.distance(first(), c); } @Override @@ -81,7 +93,7 @@ public UnmodifiableIterator iterator() { final C last = last(); @Override - protected C computeNext(C previous) { + protected @Nullable C computeNext(C previous) { return equalsOrThrow(previous, last) ? null : domain.next(previous); } }; @@ -94,13 +106,13 @@ public UnmodifiableIterator descendingIterator() { final C first = first(); @Override - protected C computeNext(C previous) { + protected @Nullable C computeNext(C previous) { return equalsOrThrow(previous, first) ? null : domain.previous(previous); } }; } - private static boolean equalsOrThrow(Comparable left, @NullableDecl Comparable right) { + private static boolean equalsOrThrow(Comparable left, @Nullable Comparable right) { return right != null && Range.compareOrThrow(left, right) == 0; } @@ -111,12 +123,14 @@ boolean isPartialView() { @Override public C first() { - return range.lowerBound.leastValueAbove(domain); + // requireNonNull is safe because we checked the range is not empty in ContiguousSet.create. + return requireNonNull(range.lowerBound.leastValueAbove(domain)); } @Override public C last() { - return range.upperBound.greatestValueBelow(domain); + // requireNonNull is safe because we checked the range is not empty in ContiguousSet.create. + return requireNonNull(range.upperBound.greatestValueBelow(domain)); } @Override @@ -133,6 +147,15 @@ public C get(int i) { checkElementIndex(i, size()); return domain.offset(first(), i); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } }; } else { return super.createAsList(); @@ -146,12 +169,14 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { if (object == null) { return false; } try { - return range.contains((C) object); + @SuppressWarnings("unchecked") // The worst case is usually CCE, which we catch. + C c = (C) object; + return range.contains(c); } catch (ClassCastException e) { return false; } @@ -168,14 +193,15 @@ public boolean isEmpty() { } @Override + @SuppressWarnings("unchecked") // TODO(cpovirk): Use a shared unsafeCompare method. public ContiguousSet intersection(ContiguousSet other) { checkNotNull(other); checkArgument(this.domain.equals(other.domain)); if (other.isEmpty()) { return other; } else { - C lowerEndpoint = Ordering.natural().max(this.first(), other.first()); - C upperEndpoint = Ordering.natural().min(this.last(), other.last()); + C lowerEndpoint = Ordering.natural().max(this.first(), other.first()); + C upperEndpoint = Ordering.natural().min(this.last(), other.last()); return (lowerEndpoint.compareTo(upperEndpoint) <= 0) ? ContiguousSet.create(Range.closed(lowerEndpoint, upperEndpoint), domain) : new EmptyContiguousSet(domain); @@ -195,7 +221,7 @@ public Range range(BoundType lowerBoundType, BoundType upperBoundType) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } else if (object instanceof RegularContiguousSet) { @@ -213,7 +239,8 @@ public int hashCode() { return Sets.hashCodeImpl(this); } - @GwtIncompatible // serialization + @GwtIncompatible + @J2ktIncompatible private static final class SerializedForm implements Serializable { final Range range; final DiscreteDomain domain; @@ -224,15 +251,22 @@ private SerializedForm(Range range, DiscreteDomain domain) { } private Object readResolve() { - return new RegularContiguousSet(range, domain); + return new RegularContiguousSet<>(range, domain); } } - @GwtIncompatible // serialization - @Override + @GwtIncompatible + @J2ktIncompatible + @Override Object writeReplace() { - return new SerializedForm(range, domain); + return new SerializedForm<>(range, domain); + } + + @GwtIncompatible + @J2ktIncompatible + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableAsList.java b/android/guava/src/com/google/common/collect/RegularImmutableAsList.java index 01e5ddd3f00c..68806c6854b8 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableAsList.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableAsList.java @@ -18,6 +18,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * An {@link ImmutableAsList} implementation specialized for when the delegate collection is already @@ -25,7 +27,7 @@ * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible @SuppressWarnings("serial") // uses writeReplace, not default serialization class RegularImmutableAsList extends ImmutableAsList { private final ImmutableCollection delegate; @@ -37,11 +39,11 @@ class RegularImmutableAsList extends ImmutableAsList { } RegularImmutableAsList(ImmutableCollection delegate, Object[] array) { - this(delegate, ImmutableList.asImmutableList(array)); + this(delegate, asImmutableList(array)); } RegularImmutableAsList(ImmutableCollection delegate, Object[] array, int size) { - this(delegate, ImmutableList.asImmutableList(array, size)); + this(delegate, asImmutableList(array, size)); } @Override @@ -61,12 +63,12 @@ public UnmodifiableListIterator listIterator(int index) { @GwtIncompatible // not present in emulated superclass @Override - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { return delegateList.copyIntoArray(dst, offset); } @Override - Object[] internalArray() { + @Nullable Object @Nullable [] internalArray() { return delegateList.internalArray(); } @@ -84,4 +86,13 @@ int internalArrayEnd() { public E get(int index) { return delegateList.get(index); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableBiMap.java b/android/guava/src/com/google/common/collect/RegularImmutableBiMap.java index 2bdaf8b3abdd..15e1a60bdddd 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableBiMap.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableBiMap.java @@ -16,22 +16,26 @@ package com.google.common.collect; +import static com.google.common.collect.RegularImmutableMap.createHashTableOrThrow; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Bimap with zero or more mappings. * * @author Louis Wasserman */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // uses writeReplace(), not default serialization final class RegularImmutableBiMap extends ImmutableBiMap { static final RegularImmutableBiMap EMPTY = new RegularImmutableBiMap<>(); - private final transient Object keyHashTable; - @VisibleForTesting final transient Object[] alternatingKeysAndValues; + private final transient @Nullable Object keyHashTable; + @VisibleForTesting final transient @Nullable Object[] alternatingKeysAndValues; private final transient int keyOffset; // 0 for K-to-V, 1 for V-to-K private final transient int size; private final transient RegularImmutableBiMap inverse; @@ -47,23 +51,21 @@ private RegularImmutableBiMap() { } /** K-to-V constructor. */ - RegularImmutableBiMap(Object[] alternatingKeysAndValues, int size) { + RegularImmutableBiMap(@Nullable Object[] alternatingKeysAndValues, int size) { this.alternatingKeysAndValues = alternatingKeysAndValues; this.size = size; this.keyOffset = 0; int tableSize = (size >= 2) ? ImmutableSet.chooseTableSize(size) : 0; - this.keyHashTable = - RegularImmutableMap.createHashTable(alternatingKeysAndValues, size, tableSize, 0); - Object valueHashTable = - RegularImmutableMap.createHashTable(alternatingKeysAndValues, size, tableSize, 1); + this.keyHashTable = createHashTableOrThrow(alternatingKeysAndValues, size, tableSize, 0); + Object valueHashTable = createHashTableOrThrow(alternatingKeysAndValues, size, tableSize, 1); this.inverse = - new RegularImmutableBiMap(valueHashTable, alternatingKeysAndValues, size, this); + new RegularImmutableBiMap<>(valueHashTable, alternatingKeysAndValues, size, this); } /** V-to-K constructor. */ private RegularImmutableBiMap( - Object valueHashTable, - Object[] alternatingKeysAndValues, + @Nullable Object valueHashTable, + @Nullable Object[] alternatingKeysAndValues, int size, RegularImmutableBiMap inverse) { this.keyHashTable = valueHashTable; @@ -85,9 +87,18 @@ public ImmutableBiMap inverse() { @SuppressWarnings("unchecked") @Override - public V get(@NullableDecl Object key) { - return (V) + public @Nullable V get(@Nullable Object key) { + Object result = RegularImmutableMap.get(keyHashTable, alternatingKeysAndValues, size, keyOffset, key); + /* + * We can't simply cast the result of `RegularImmutableMap.get` to V because of a bug in our + * nullness checker (resulting from https://github.com/jspecify/checker-framework/issues/8). + */ + if (result == null) { + return null; + } else { + return (V) result; + } } @Override @@ -108,4 +119,13 @@ ImmutableSet createKeySet() { boolean isPartialView() { return false; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableList.java b/android/guava/src/com/google/common/collect/RegularImmutableList.java index 44cbab761950..35eba6b713dd 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableList.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableList.java @@ -17,24 +17,30 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkElementIndex; +import static java.lang.System.arraycopy; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link ImmutableList} backed by a simple array. * * @author Kevin Bourrillion */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // uses writeReplace(), not default serialization -class RegularImmutableList extends ImmutableList { +final class RegularImmutableList extends ImmutableList { static final ImmutableList EMPTY = new RegularImmutableList<>(new Object[0], 0); - @VisibleForTesting final transient Object[] array; + // The first `size` elements are non-null. + @VisibleForTesting final transient @Nullable Object[] array; private final transient int size; - RegularImmutableList(Object[] array, int size) { + RegularImmutableList(@Nullable Object[] array, int size) { this.array = array; this.size = size; } @@ -50,7 +56,7 @@ boolean isPartialView() { } @Override - Object[] internalArray() { + @Nullable Object[] internalArray() { return array; } @@ -65,8 +71,8 @@ int internalArrayEnd() { } @Override - int copyIntoArray(Object[] dst, int dstOff) { - System.arraycopy(array, 0, dst, dstOff, size); + int copyIntoArray(@Nullable Object[] dst, int dstOff) { + arraycopy(array, 0, dst, dstOff, size); return dstOff + size; } @@ -75,8 +81,18 @@ int copyIntoArray(Object[] dst, int dstOff) { @SuppressWarnings("unchecked") public E get(int index) { checkElementIndex(index, size); - return (E) array[index]; + // requireNonNull is safe because we guarantee that the first `size` elements are non-null. + return (E) requireNonNull(array[index]); } // TODO(lowasser): benchmark optimizations for equals() and see if they're worthwhile + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableMap.java b/android/guava/src/com/google/common/collect/RegularImmutableMap.java index 4e1681ef0249..3221cfa01c3e 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableMap.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableMap.java @@ -19,20 +19,23 @@ import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkPositionIndex; import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import java.util.AbstractMap; import java.util.Arrays; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A hash-based implementation of {@link ImmutableMap}. * * @author Louis Wasserman */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible final class RegularImmutableMap extends ImmutableMap { private static final byte ABSENT = -1; @@ -64,37 +67,98 @@ final class RegularImmutableMap extends ImmutableMap { * & (table.length - 1) instead of % table.length, though. */ - private final transient Object hashTable; - @VisibleForTesting final transient Object[] alternatingKeysAndValues; + private final transient @Nullable Object hashTable; + @VisibleForTesting final transient @Nullable Object[] alternatingKeysAndValues; private final transient int size; - @SuppressWarnings("unchecked") - static RegularImmutableMap create(int n, Object[] alternatingKeysAndValues) { + /* + * We have some considerable complexity in these create methods because of + * Builder.buildKeepingLast(). The same Builder might be called with buildKeepingLast() and then + * buildOrThrow(), or vice versa. So in particular, if we modify alternatingKeysAndValues to + * eliminate duplicate keys (for buildKeepingLast()) then we have to ensure that a later call to + * buildOrThrow() will still throw as if the duplicates had not been eliminated. And the exception + * message must mention two values that were associated with the duplicate key in two different + * calls to Builder.put (though we don't really care *which* two values if there were more than + * two). These considerations lead us to have a field of type DuplicateKey in the Builder, which + * will remember the first duplicate key we encountered. All later calls to buildOrThrow() can + * mention that key with its values. Further duplicates might be added in the meantime but since + * builders only ever accumulate entries it will always be valid to throw from buildOrThrow() with + * the first duplicate. + */ + + // This entry point is for callers other than ImmutableMap.Builder. + static RegularImmutableMap create( + int n, @Nullable Object[] alternatingKeysAndValues) { + return create(n, alternatingKeysAndValues, /* builder= */ null); + } + + // This entry point is used by the other create method but also directly by + // ImmutableMap.Builder, so that it can remember any DuplicateKey encountered and produce an + // exception for a later buildOrThrow(). If builder is null that means that a duplicate + // key will lead to an immediate exception. If it is not null then a duplicate key will instead be + // stored in the builder, which may use it to throw an exception later. + static RegularImmutableMap create( + int n, @Nullable Object[] alternatingKeysAndValues, @Nullable Builder builder) { if (n == 0) { - return (RegularImmutableMap) EMPTY; + @SuppressWarnings("unchecked") + RegularImmutableMap empty = (RegularImmutableMap) EMPTY; + return empty; } else if (n == 1) { - checkEntryNotNull(alternatingKeysAndValues[0], alternatingKeysAndValues[1]); + // requireNonNull is safe because the first `2*n` elements have been filled in. + checkEntryNotNull( + requireNonNull(alternatingKeysAndValues[0]), requireNonNull(alternatingKeysAndValues[1])); return new RegularImmutableMap(null, alternatingKeysAndValues, 1); } checkPositionIndex(n, alternatingKeysAndValues.length >> 1); int tableSize = ImmutableSet.chooseTableSize(n); - Object hashTable = createHashTable(alternatingKeysAndValues, n, tableSize, 0); + // If there are no duplicate keys, hashTablePlus is the final hashTable value. If there *are* + // duplicate keys, hashTablePlus consists of 3 elements: [0] the hashTable; [1] the number of + // entries in alternatingKeysAndValues that are still valid after rewriting to remove + // duplicates; [2] a Builder.DuplicateKey that records the first duplicate key we encountered + // for possible later use in exceptions, perhaps straight away. + Object hashTablePlus = createHashTable(alternatingKeysAndValues, n, tableSize, 0); + Object hashTable; + if (hashTablePlus instanceof Object[]) { + Object[] hashTableAndSizeAndDuplicate = (Object[]) hashTablePlus; + Builder.DuplicateKey duplicateKey = (Builder.DuplicateKey) hashTableAndSizeAndDuplicate[2]; + if (builder == null) { + throw duplicateKey.exception(); + } + builder.duplicateKey = duplicateKey; + hashTable = hashTableAndSizeAndDuplicate[0]; + n = (Integer) hashTableAndSizeAndDuplicate[1]; + alternatingKeysAndValues = Arrays.copyOf(alternatingKeysAndValues, n * 2); + } else { + hashTable = hashTablePlus; + } return new RegularImmutableMap(hashTable, alternatingKeysAndValues, n); } /** * Returns a hash table for the specified keys and values, and ensures that neither keys nor - * values are null. + * values are null. This method may update {@code alternatingKeysAndValues} if there are duplicate + * keys. If so, the return value will indicate how many entries are still valid, and will also + * include a {@link Builder.DuplicateKey} in case duplicate keys are not allowed now or will not + * be allowed on a later {@link Builder#buildOrThrow()} call. + * + * @param keyOffset 1 if this is the reverse direction of a BiMap, 0 otherwise. + * @return an {@code Object} that is a {@code byte[]}, {@code short[]}, or {@code int[]}, the + * smallest possible to fit {@code tableSize}; or an {@code Object[]} where [0] is one of + * these; [1] indicates how many element pairs in {@code alternatingKeysAndValues} are valid; + * and [2] is a {@link Builder.DuplicateKey} for the first duplicate key encountered. */ - static Object createHashTable( - Object[] alternatingKeysAndValues, int n, int tableSize, int keyOffset) { + private static @Nullable Object createHashTable( + @Nullable Object[] alternatingKeysAndValues, int n, int tableSize, int keyOffset) { if (n == 1) { // for n=1 we don't create a hash table, but we need to do the checkEntryNotNull check! + // requireNonNull is safe because the first `2*n` elements have been filled in. checkEntryNotNull( - alternatingKeysAndValues[keyOffset], alternatingKeysAndValues[keyOffset ^ 1]); + requireNonNull(alternatingKeysAndValues[keyOffset]), + requireNonNull(alternatingKeysAndValues[keyOffset ^ 1])); return null; } int mask = tableSize - 1; + Builder.DuplicateKey duplicateKey = null; if (tableSize <= BYTE_MAX_SIZE) { /* * Use 8 bits per entry. The value is unsigned to allow use up to a size of 2^8. @@ -105,23 +169,36 @@ static Object createHashTable( byte[] hashTable = new byte[tableSize]; Arrays.fill(hashTable, ABSENT); + int outI = 0; + entries: for (int i = 0; i < n; i++) { int keyIndex = 2 * i + keyOffset; - Object key = alternatingKeysAndValues[keyIndex]; - Object value = alternatingKeysAndValues[keyIndex ^ 1]; + int outKeyIndex = 2 * outI + keyOffset; + // requireNonNull is safe because the first `2*n` elements have been filled in. + Object key = requireNonNull(alternatingKeysAndValues[keyIndex]); + Object value = requireNonNull(alternatingKeysAndValues[keyIndex ^ 1]); checkEntryNotNull(key, value); for (int h = Hashing.smear(key.hashCode()); ; h++) { h &= mask; int previousKeyIndex = hashTable[h] & BYTE_MASK; // unsigned read if (previousKeyIndex == BYTE_MASK) { // -1 signed becomes 255 unsigned - hashTable[h] = (byte) keyIndex; + hashTable[h] = (byte) outKeyIndex; break; - } else if (alternatingKeysAndValues[previousKeyIndex].equals(key)) { - throw duplicateKeyException(key, value, alternatingKeysAndValues, previousKeyIndex); + } else if (key.equals(alternatingKeysAndValues[previousKeyIndex])) { + duplicateKey = + new Builder.DuplicateKey( + key, value, requireNonNull(alternatingKeysAndValues[previousKeyIndex ^ 1])); + alternatingKeysAndValues[previousKeyIndex ^ 1] = value; + continue entries; } } + if (outI < i) { // if outI == i don't bother writing the values back where they came from + alternatingKeysAndValues[outKeyIndex] = key; + alternatingKeysAndValues[outKeyIndex ^ 1] = value; + } + outI++; } - return hashTable; + return outI == n ? hashTable : new Object[] {hashTable, outI, duplicateKey}; } else if (tableSize <= SHORT_MAX_SIZE) { /* * Use 16 bits per entry. The value is unsigned to allow use up to a size of 2^16. @@ -132,23 +209,36 @@ static Object createHashTable( short[] hashTable = new short[tableSize]; Arrays.fill(hashTable, ABSENT); + int outI = 0; + entries: for (int i = 0; i < n; i++) { int keyIndex = 2 * i + keyOffset; - Object key = alternatingKeysAndValues[keyIndex]; - Object value = alternatingKeysAndValues[keyIndex ^ 1]; + int outKeyIndex = 2 * outI + keyOffset; + // requireNonNull is safe because the first `2*n` elements have been filled in. + Object key = requireNonNull(alternatingKeysAndValues[keyIndex]); + Object value = requireNonNull(alternatingKeysAndValues[keyIndex ^ 1]); checkEntryNotNull(key, value); for (int h = Hashing.smear(key.hashCode()); ; h++) { h &= mask; int previousKeyIndex = hashTable[h] & SHORT_MASK; // unsigned read if (previousKeyIndex == SHORT_MASK) { // -1 signed becomes 65_535 unsigned - hashTable[h] = (short) keyIndex; + hashTable[h] = (short) outKeyIndex; break; - } else if (alternatingKeysAndValues[previousKeyIndex].equals(key)) { - throw duplicateKeyException(key, value, alternatingKeysAndValues, previousKeyIndex); + } else if (key.equals(alternatingKeysAndValues[previousKeyIndex])) { + duplicateKey = + new Builder.DuplicateKey( + key, value, requireNonNull(alternatingKeysAndValues[previousKeyIndex ^ 1])); + alternatingKeysAndValues[previousKeyIndex ^ 1] = value; + continue entries; } } + if (outI < i) { // if outI == i don't bother writing the values back where they came from + alternatingKeysAndValues[outKeyIndex] = key; + alternatingKeysAndValues[outKeyIndex ^ 1] = value; + } + outI++; } - return hashTable; + return outI == n ? hashTable : new Object[] {hashTable, outI, duplicateKey}; } else { /* * Use 32 bits per entry. @@ -156,40 +246,52 @@ static Object createHashTable( int[] hashTable = new int[tableSize]; Arrays.fill(hashTable, ABSENT); + int outI = 0; + entries: for (int i = 0; i < n; i++) { int keyIndex = 2 * i + keyOffset; - Object key = alternatingKeysAndValues[keyIndex]; - Object value = alternatingKeysAndValues[keyIndex ^ 1]; + int outKeyIndex = 2 * outI + keyOffset; + // requireNonNull is safe because the first `2*n` elements have been filled in. + Object key = requireNonNull(alternatingKeysAndValues[keyIndex]); + Object value = requireNonNull(alternatingKeysAndValues[keyIndex ^ 1]); checkEntryNotNull(key, value); for (int h = Hashing.smear(key.hashCode()); ; h++) { h &= mask; int previousKeyIndex = hashTable[h]; if (previousKeyIndex == ABSENT) { - hashTable[h] = keyIndex; + hashTable[h] = outKeyIndex; break; - } else if (alternatingKeysAndValues[previousKeyIndex].equals(key)) { - throw duplicateKeyException(key, value, alternatingKeysAndValues, previousKeyIndex); + } else if (key.equals(alternatingKeysAndValues[previousKeyIndex])) { + duplicateKey = + new Builder.DuplicateKey( + key, value, requireNonNull(alternatingKeysAndValues[previousKeyIndex ^ 1])); + alternatingKeysAndValues[previousKeyIndex ^ 1] = value; + continue entries; } } + if (outI < i) { // if outI == i don't bother writing the values back where they came from + alternatingKeysAndValues[outKeyIndex] = key; + alternatingKeysAndValues[outKeyIndex ^ 1] = value; + } + outI++; } - return hashTable; + return outI == n ? hashTable : new Object[] {hashTable, outI, duplicateKey}; } } - private static IllegalArgumentException duplicateKeyException( - Object key, Object value, Object[] alternatingKeysAndValues, int previousKeyIndex) { - return new IllegalArgumentException( - "Multiple entries with same key: " - + key - + "=" - + value - + " and " - + alternatingKeysAndValues[previousKeyIndex] - + "=" - + alternatingKeysAndValues[previousKeyIndex ^ 1]); + static @Nullable Object createHashTableOrThrow( + @Nullable Object[] alternatingKeysAndValues, int n, int tableSize, int keyOffset) { + Object hashTablePlus = createHashTable(alternatingKeysAndValues, n, tableSize, keyOffset); + if (hashTablePlus instanceof Object[]) { + Object[] hashTableAndSizeAndDuplicate = (Object[]) hashTablePlus; + Builder.DuplicateKey duplicateKey = (Builder.DuplicateKey) hashTableAndSizeAndDuplicate[2]; + throw duplicateKey.exception(); + } + return hashTablePlus; } - private RegularImmutableMap(Object hashTable, Object[] alternatingKeysAndValues, int size) { + private RegularImmutableMap( + @Nullable Object hashTable, @Nullable Object[] alternatingKeysAndValues, int size) { this.hashTable = hashTable; this.alternatingKeysAndValues = alternatingKeysAndValues; this.size = size; @@ -202,22 +304,31 @@ public int size() { @SuppressWarnings("unchecked") @Override - @NullableDecl - public V get(@NullableDecl Object key) { - return (V) get(hashTable, alternatingKeysAndValues, size, 0, key); + public @Nullable V get(@Nullable Object key) { + Object result = get(hashTable, alternatingKeysAndValues, size, 0, key); + /* + * We can't simply cast the result of `RegularImmutableMap.get` to V because of a bug in our + * nullness checker (resulting from https://github.com/jspecify/checker-framework/issues/8). + */ + if (result == null) { + return null; + } else { + return (V) result; + } } - static Object get( - @NullableDecl Object hashTableObject, - @NullableDecl Object[] alternatingKeysAndValues, + static @Nullable Object get( + @Nullable Object hashTableObject, + @Nullable Object[] alternatingKeysAndValues, int size, int keyOffset, - @NullableDecl Object key) { + @Nullable Object key) { if (key == null) { return null; } else if (size == 1) { - return alternatingKeysAndValues[keyOffset].equals(key) - ? alternatingKeysAndValues[keyOffset ^ 1] + // requireNonNull is safe because the first 2 elements have been filled in. + return requireNonNull(alternatingKeysAndValues[keyOffset]).equals(key) + ? requireNonNull(alternatingKeysAndValues[keyOffset ^ 1]) : null; } else if (hashTableObject == null) { return null; @@ -230,7 +341,7 @@ static Object get( int keyIndex = hashTable[h] & BYTE_MASK; // unsigned read if (keyIndex == BYTE_MASK) { // -1 signed becomes 255 unsigned return null; - } else if (alternatingKeysAndValues[keyIndex].equals(key)) { + } else if (key.equals(alternatingKeysAndValues[keyIndex])) { return alternatingKeysAndValues[keyIndex ^ 1]; } } @@ -242,7 +353,7 @@ static Object get( int keyIndex = hashTable[h] & SHORT_MASK; // unsigned read if (keyIndex == SHORT_MASK) { // -1 signed becomes 65_535 unsigned return null; - } else if (alternatingKeysAndValues[keyIndex].equals(key)) { + } else if (key.equals(alternatingKeysAndValues[keyIndex])) { return alternatingKeysAndValues[keyIndex ^ 1]; } } @@ -254,7 +365,7 @@ static Object get( int keyIndex = hashTable[h]; if (keyIndex == ABSENT) { return null; - } else if (alternatingKeysAndValues[keyIndex].equals(key)) { + } else if (key.equals(alternatingKeysAndValues[keyIndex])) { return alternatingKeysAndValues[keyIndex ^ 1]; } } @@ -266,13 +377,17 @@ ImmutableSet> createEntrySet() { return new EntrySet<>(this, alternatingKeysAndValues, 0, size); } - static class EntrySet extends ImmutableSet> { + static final class EntrySet extends ImmutableSet> { private final transient ImmutableMap map; - private final transient Object[] alternatingKeysAndValues; + private final transient @Nullable Object[] alternatingKeysAndValues; private final transient int keyOffset; private final transient int size; - EntrySet(ImmutableMap map, Object[] alternatingKeysAndValues, int keyOffset, int size) { + EntrySet( + ImmutableMap map, + @Nullable Object[] alternatingKeysAndValues, + int keyOffset, + int size) { this.map = map; this.alternatingKeysAndValues = alternatingKeysAndValues; this.keyOffset = keyOffset; @@ -285,7 +400,7 @@ public UnmodifiableIterator> iterator() { } @Override - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { return asList().copyIntoArray(dst, offset); } @@ -295,10 +410,14 @@ ImmutableList> createAsList() { @Override public Entry get(int index) { checkElementIndex(index, size); + /* + * requireNonNull is safe because the first `2*(size+keyOffset)` elements have been filled + * in. + */ @SuppressWarnings("unchecked") - K key = (K) alternatingKeysAndValues[2 * index + keyOffset]; + K key = (K) requireNonNull(alternatingKeysAndValues[2 * index + keyOffset]); @SuppressWarnings("unchecked") - V value = (V) alternatingKeysAndValues[2 * index + (keyOffset ^ 1)]; + V value = (V) requireNonNull(alternatingKeysAndValues[2 * index + (keyOffset ^ 1)]); return new AbstractMap.SimpleImmutableEntry(key, value); } @@ -311,11 +430,19 @@ public int size() { public boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } }; } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { if (object instanceof Entry) { Entry entry = (Entry) object; Object k = entry.getKey(); @@ -334,6 +461,15 @@ boolean isPartialView() { public int size() { return size; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } @Override @@ -345,11 +481,11 @@ ImmutableSet createKeySet() { } static final class KeysOrValuesAsList extends ImmutableList { - private final transient Object[] alternatingKeysAndValues; + private final transient @Nullable Object[] alternatingKeysAndValues; private final transient int offset; private final transient int size; - KeysOrValuesAsList(Object[] alternatingKeysAndValues, int offset, int size) { + KeysOrValuesAsList(@Nullable Object[] alternatingKeysAndValues, int offset, int size) { this.alternatingKeysAndValues = alternatingKeysAndValues; this.offset = offset; this.size = size; @@ -358,7 +494,8 @@ static final class KeysOrValuesAsList extends ImmutableList { @Override public Object get(int index) { checkElementIndex(index, size); - return alternatingKeysAndValues[2 * index + offset]; + // requireNonNull is safe because the first `2*(size+offset)` elements have been filled in. + return requireNonNull(alternatingKeysAndValues[2 * index + offset]); } @Override @@ -370,6 +507,13 @@ boolean isPartialView() { public int size() { return size; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + Object writeReplace() { + return super.writeReplace(); + } } static final class KeySet extends ImmutableSet { @@ -387,7 +531,7 @@ public UnmodifiableIterator iterator() { } @Override - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { return asList().copyIntoArray(dst, offset); } @@ -397,7 +541,7 @@ public ImmutableList asList() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return map.get(object) != null; } @@ -410,6 +554,15 @@ boolean isPartialView() { public int size() { return map.size(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } @SuppressWarnings("unchecked") @@ -423,7 +576,16 @@ boolean isPartialView() { return false; } + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } + // This class is never actually serialized directly, but we have to make the // warning go away (and suppressing would suppress for all nested classes too) - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableMultiset.java b/android/guava/src/com/google/common/collect/RegularImmutableMultiset.java index 3beec6fcfc8a..d4827bb479bd 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableMultiset.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableMultiset.java @@ -16,12 +16,13 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Multiset.Entry; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.WeakOuter; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link ImmutableMultiset} with zero or more elements. @@ -29,16 +30,16 @@ * @author Jared Levy * @author Louis Wasserman */ -@GwtCompatible(emulated = true, serializable = true) +@GwtCompatible @SuppressWarnings("serial") // uses writeReplace(), not default serialization -class RegularImmutableMultiset extends ImmutableMultiset { +final class RegularImmutableMultiset extends ImmutableMultiset { static final RegularImmutableMultiset EMPTY = new RegularImmutableMultiset<>(ObjectCountHashMap.create()); final transient ObjectCountHashMap contents; private final transient int size; - @LazyInit private transient ImmutableSet elementSet; + @LazyInit private transient @Nullable ImmutableSet elementSet; RegularImmutableMultiset(ObjectCountHashMap contents) { this.contents = contents; @@ -55,7 +56,7 @@ boolean isPartialView() { } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { return contents.get(element); } @@ -79,7 +80,7 @@ E get(int index) { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { return RegularImmutableMultiset.this.contains(object); } @@ -92,6 +93,15 @@ boolean isPartialView() { public int size() { return contents.size(); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @GwtIncompatible + @J2ktIncompatible + Object writeReplace() { + return super.writeReplace(); + } } @Override @@ -104,12 +114,13 @@ private static class SerializedForm implements Serializable { final Object[] elements; final int[] counts; - SerializedForm(Multiset multiset) { + // "extends Object" works around https://github.com/typetools/checker-framework/issues/3013 + SerializedForm(Multiset multiset) { int distinct = multiset.entrySet().size(); elements = new Object[distinct]; counts = new int[distinct]; int i = 0; - for (Entry entry : multiset.entrySet()) { + for (Entry entry : multiset.entrySet()) { elements[i] = entry.getElement(); counts[i] = entry.getCount(); i++; @@ -125,12 +136,13 @@ Object readResolve() { return builder.build(); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - @GwtIncompatible @Override - Object writeReplace() { + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { return new SerializedForm(this); } } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableSet.java b/android/guava/src/com/google/common/collect/RegularImmutableSet.java index b7202f84d2fe..2e1eb9a267f2 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableSet.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableSet.java @@ -16,41 +16,48 @@ package com.google.common.collect; +import static java.lang.System.arraycopy; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link ImmutableSet} with two or more elements. * * @author Kevin Bourrillion */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // uses writeReplace(), not default serialization final class RegularImmutableSet extends ImmutableSet { + private static final Object[] EMPTY_ARRAY = new Object[0]; static final RegularImmutableSet EMPTY = - new RegularImmutableSet<>(new Object[0], 0, null, 0, 0); + new RegularImmutableSet<>(EMPTY_ARRAY, 0, EMPTY_ARRAY, 0, 0); - @VisibleForTesting final transient Object[] elements; - // the same elements in hashed positions (plus nulls) - @VisibleForTesting final transient Object[] table; + // The first `size` elements are non-null. + @VisibleForTesting final transient @Nullable Object[] elements; + private final transient int hashCode; + // the same values as `elements` in hashed positions (plus nulls) + @VisibleForTesting final transient @Nullable Object[] table; // 'and' with an int to get a valid table index. private final transient int mask; - private final transient int hashCode; private final transient int size; - RegularImmutableSet(Object[] elements, int hashCode, Object[] table, int mask, int size) { + RegularImmutableSet( + @Nullable Object[] elements, int hashCode, @Nullable Object[] table, int mask, int size) { this.elements = elements; + this.hashCode = hashCode; this.table = table; this.mask = mask; - this.hashCode = hashCode; this.size = size; } @Override - public boolean contains(@NullableDecl Object target) { - Object[] table = this.table; - if (target == null || table == null) { + public boolean contains(@Nullable Object target) { + @Nullable Object[] table = this.table; + if (target == null || table.length == 0) { return false; } for (int i = Hashing.smearedHash(target); ; i++) { @@ -69,13 +76,16 @@ public int size() { return size; } + // We're careful to put only E instances into the array in the mainline. + // (In the backport, we don't need this suppression, but we keep it to minimize diffs.) + @SuppressWarnings("unchecked") @Override public UnmodifiableIterator iterator() { return asList().iterator(); } @Override - Object[] internalArray() { + @Nullable Object[] internalArray() { return elements; } @@ -90,8 +100,8 @@ int internalArrayEnd() { } @Override - int copyIntoArray(Object[] dst, int offset) { - System.arraycopy(elements, 0, dst, offset, size); + int copyIntoArray(@Nullable Object[] dst, int offset) { + arraycopy(elements, 0, dst, offset, size); return offset + size; } @@ -114,4 +124,13 @@ public int hashCode() { boolean isHashCodeFast() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableSortedMultiset.java b/android/guava/src/com/google/common/collect/RegularImmutableSortedMultiset.java index c1d739fd9c2d..c5fa21534e88 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableSortedMultiset.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableSortedMultiset.java @@ -19,10 +19,11 @@ import static com.google.common.collect.BoundType.CLOSED; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Ints; import java.util.Comparator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An immutable sorted multiset with one or more distinct elements. @@ -32,9 +33,9 @@ @SuppressWarnings("serial") // uses writeReplace, not default serialization @GwtIncompatible final class RegularImmutableSortedMultiset extends ImmutableSortedMultiset { - private static final long[] ZERO_CUMULATIVE_COUNTS = {0}; + private static final long[] zeroCumulativeCounts = {0}; - static final ImmutableSortedMultiset NATURAL_EMPTY_MULTISET = + static final ImmutableSortedMultiset NATURAL_EMPTY_MULTISET = new RegularImmutableSortedMultiset<>(Ordering.natural()); @VisibleForTesting final transient RegularImmutableSortedSet elementSet; @@ -44,7 +45,7 @@ final class RegularImmutableSortedMultiset extends ImmutableSortedMultiset RegularImmutableSortedMultiset(Comparator comparator) { this.elementSet = ImmutableSortedSet.emptySet(comparator); - this.cumulativeCounts = ZERO_CUMULATIVE_COUNTS; + this.cumulativeCounts = zeroCumulativeCounts; this.offset = 0; this.length = 0; } @@ -67,17 +68,17 @@ Entry getEntry(int index) { } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return isEmpty() ? null : getEntry(0); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return isEmpty() ? null : getEntry(length - 1); } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { int index = elementSet.indexOf(element); return (index >= 0) ? getCount(index) : 0; } @@ -112,7 +113,7 @@ ImmutableSortedMultiset getSubMultiset(int from, int to) { return this; } else { RegularImmutableSortedSet subElementSet = elementSet.getSubSet(from, to); - return new RegularImmutableSortedMultiset( + return new RegularImmutableSortedMultiset<>( subElementSet, cumulativeCounts, offset + from, to - from); } } @@ -121,4 +122,12 @@ ImmutableSortedMultiset getSubMultiset(int from, int to) { boolean isPartialView() { return offset > 0 || length < cumulativeCounts.length - 1; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible // serialization + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableSortedSet.java b/android/guava/src/com/google/common/collect/RegularImmutableSortedSet.java index d70d8fb74c12..a1a6b5a9a551 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableSortedSet.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableSortedSet.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import java.util.Collection; import java.util.Collections; @@ -27,7 +28,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An immutable sorted set with one or more elements. TODO(jlevy): Consider separate class for a @@ -36,11 +37,11 @@ * @author Jared Levy * @author Louis Wasserman */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings({"serial", "rawtypes"}) final class RegularImmutableSortedSet extends ImmutableSortedSet { static final RegularImmutableSortedSet NATURAL_EMPTY_SET = - new RegularImmutableSortedSet<>(ImmutableList.of(), Ordering.natural()); + new RegularImmutableSortedSet<>(ImmutableList.of(), Ordering.natural()); @VisibleForTesting final transient ImmutableList elements; @@ -50,7 +51,7 @@ final class RegularImmutableSortedSet extends ImmutableSortedSet { } @Override - Object[] internalArray() { + @Nullable Object @Nullable [] internalArray() { return elements.internalArray(); } @@ -81,7 +82,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { try { return o != null && unsafeBinarySearch(o) >= 0; } catch (ClassCastException e) { @@ -151,12 +152,12 @@ boolean isPartialView() { } @Override - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { return elements.copyIntoArray(dst, offset); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -209,25 +210,25 @@ public E last() { } @Override - public E lower(E element) { + public @Nullable E lower(E element) { int index = headIndex(element, false) - 1; return (index == -1) ? null : elements.get(index); } @Override - public E floor(E element) { + public @Nullable E floor(E element) { int index = headIndex(element, true) - 1; return (index == -1) ? null : elements.get(index); } @Override - public E ceiling(E element) { + public @Nullable E ceiling(E element) { int index = tailIndex(element, true); return (index == size()) ? null : elements.get(index); } @Override - public E higher(E element) { + public @Nullable E higher(E element) { int index = tailIndex(element, false); return (index == size()) ? null : elements.get(index); } @@ -278,7 +279,7 @@ RegularImmutableSortedSet getSubSet(int newFromIndex, int newToIndex) { if (newFromIndex == 0 && newToIndex == size()) { return this; } else if (newFromIndex < newToIndex) { - return new RegularImmutableSortedSet( + return new RegularImmutableSortedSet<>( elements.subList(newFromIndex, newToIndex), comparator); } else { return emptySet(comparator); @@ -286,7 +287,7 @@ RegularImmutableSortedSet getSubSet(int newFromIndex, int newToIndex) { } @Override - int indexOf(@NullableDecl Object target) { + int indexOf(@Nullable Object target) { if (target == null) { return -1; } @@ -311,4 +312,13 @@ ImmutableSortedSet createDescendingSet() { ? emptySet(reversedOrder) : new RegularImmutableSortedSet(elements.reverse(), reversedOrder); } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } diff --git a/android/guava/src/com/google/common/collect/RegularImmutableTable.java b/android/guava/src/com/google/common/collect/RegularImmutableTable.java index 6c35ee47cba3..5d40b30269a2 100644 --- a/android/guava/src/com/google/common/collect/RegularImmutableTable.java +++ b/android/guava/src/com/google/common/collect/RegularImmutableTable.java @@ -16,15 +16,17 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Collections.sort; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.j2objc.annotations.WeakOuter; -import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An implementation of {@link ImmutableTable} holding an arbitrary number of cells. @@ -39,7 +41,7 @@ abstract class RegularImmutableTable extends ImmutableTable { @Override final ImmutableSet> createCellSet() { - return isEmpty() ? ImmutableSet.>of() : new CellSet(); + return isEmpty() ? ImmutableSet.of() : new CellSet(); } @WeakOuter @@ -55,7 +57,7 @@ Cell get(int index) { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { if (object instanceof Cell) { Cell cell = (Cell) object; Object value = RegularImmutableTable.this.get(cell.getRowKey(), cell.getColumnKey()); @@ -68,13 +70,22 @@ public boolean contains(@NullableDecl Object object) { boolean isPartialView() { return false; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } abstract V getValue(int iterationIndex); @Override final ImmutableCollection createValues() { - return isEmpty() ? ImmutableList.of() : new Values(); + return isEmpty() ? ImmutableList.of() : new Values(); } @WeakOuter @@ -93,12 +104,21 @@ public V get(int index) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } } static RegularImmutableTable forCells( List> cells, - @NullableDecl final Comparator rowComparator, - @NullableDecl final Comparator columnComparator) { + @Nullable Comparator rowComparator, + @Nullable Comparator columnComparator) { checkNotNull(cells); if (rowComparator != null || columnComparator != null) { /* @@ -109,22 +129,19 @@ static RegularImmutableTable forCells( * column, the rows in the second column, etc. */ Comparator> comparator = - new Comparator>() { - @Override - public int compare(Cell cell1, Cell cell2) { - int rowCompare = - (rowComparator == null) - ? 0 - : rowComparator.compare(cell1.getRowKey(), cell2.getRowKey()); - if (rowCompare != 0) { - return rowCompare; - } - return (columnComparator == null) - ? 0 - : columnComparator.compare(cell1.getColumnKey(), cell2.getColumnKey()); + (Cell cell1, Cell cell2) -> { + int rowCompare = + (rowComparator == null) + ? 0 + : rowComparator.compare(cell1.getRowKey(), cell2.getRowKey()); + if (rowCompare != 0) { + return rowCompare; } + return (columnComparator == null) + ? 0 + : columnComparator.compare(cell1.getColumnKey(), cell2.getColumnKey()); }; - Collections.sort(cells, comparator); + sort(cells, comparator); } return forCellsInternal(cells, rowComparator, columnComparator); } @@ -135,8 +152,8 @@ static RegularImmutableTable forCells(Iterable> private static RegularImmutableTable forCellsInternal( Iterable> cells, - @NullableDecl Comparator rowComparator, - @NullableDecl Comparator columnComparator) { + @Nullable Comparator rowComparator, + @Nullable Comparator columnComparator) { Set rowSpaceBuilder = new LinkedHashSet<>(); Set columnSpaceBuilder = new LinkedHashSet<>(); ImmutableList> cellList = ImmutableList.copyOf(cells); @@ -169,12 +186,14 @@ static RegularImmutableTable forOrderedComponents( : new SparseImmutableTable(cellList, rowSpace, columnSpace); } - /** @throws IllegalArgumentException if {@code existingValue} is not null. */ + /** + * @throws IllegalArgumentException if {@code existingValue} is not null. + */ /* * We could have declared this method 'static' but the additional compile-time checks achieved by * referencing the type variables seem worthwhile. */ - final void checkNoDuplicate(R rowKey, C columnKey, V existingValue, V newValue) { + final void checkNoDuplicate(R rowKey, C columnKey, @Nullable V existingValue, V newValue) { checkArgument( existingValue == null, "Duplicate key: (row=%s, column=%s), values: [%s, %s].", @@ -183,4 +202,10 @@ final void checkNoDuplicate(R rowKey, C columnKey, V existingValue, V newValue) newValue, existingValue); } + + // redeclare to satisfy our test for b/310253115 + @Override + @J2ktIncompatible + @GwtIncompatible + abstract Object writeReplace(); } diff --git a/android/guava/src/com/google/common/collect/ReverseNaturalOrdering.java b/android/guava/src/com/google/common/collect/ReverseNaturalOrdering.java index 612d846a7727..2dc902e22870 100644 --- a/android/guava/src/com/google/common/collect/ReverseNaturalOrdering.java +++ b/android/guava/src/com/google/common/collect/ReverseNaturalOrdering.java @@ -19,69 +19,71 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.Iterator; /** An ordering that uses the reverse of the natural order of the values. */ -@GwtCompatible(serializable = true) -@SuppressWarnings({"unchecked", "rawtypes"}) // TODO(kevinb): the right way to explain this?? -final class ReverseNaturalOrdering extends Ordering implements Serializable { +@GwtCompatible +final class ReverseNaturalOrdering extends Ordering> implements Serializable { static final ReverseNaturalOrdering INSTANCE = new ReverseNaturalOrdering(); @Override - public int compare(Comparable left, Comparable right) { + @SuppressWarnings("unchecked") // TODO(kevinb): the right way to explain this?? + public int compare(Comparable left, Comparable right) { checkNotNull(left); // right null is caught later if (left == right) { return 0; } - return right.compareTo(left); + return ((Comparable) right).compareTo(left); } @Override - public Ordering reverse() { + public > Ordering reverse() { return Ordering.natural(); } // Override the min/max methods to "hoist" delegation outside loops @Override - public E min(E a, E b) { + public > E min(E a, E b) { return NaturalOrdering.INSTANCE.max(a, b); } @Override - public E min(E a, E b, E c, E... rest) { + public > E min(E a, E b, E c, E... rest) { return NaturalOrdering.INSTANCE.max(a, b, c, rest); } @Override - public E min(Iterator iterator) { + public > E min(Iterator iterator) { return NaturalOrdering.INSTANCE.max(iterator); } @Override - public E min(Iterable iterable) { + public > E min(Iterable iterable) { return NaturalOrdering.INSTANCE.max(iterable); } @Override - public E max(E a, E b) { + public > E max(E a, E b) { return NaturalOrdering.INSTANCE.min(a, b); } @Override - public E max(E a, E b, E c, E... rest) { + public > E max(E a, E b, E c, E... rest) { return NaturalOrdering.INSTANCE.min(a, b, c, rest); } @Override - public E max(Iterator iterator) { + public > E max(Iterator iterator) { return NaturalOrdering.INSTANCE.min(iterator); } @Override - public E max(Iterable iterable) { + public > E max(Iterable iterable) { return NaturalOrdering.INSTANCE.min(iterable); } @@ -97,5 +99,5 @@ public String toString() { private ReverseNaturalOrdering() {} - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/ReverseOrdering.java b/android/guava/src/com/google/common/collect/ReverseOrdering.java index 9f65e5976034..8d8b4ad7b064 100644 --- a/android/guava/src/com/google/common/collect/ReverseOrdering.java +++ b/android/guava/src/com/google/common/collect/ReverseOrdering.java @@ -19,13 +19,16 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** An ordering that uses the reverse of a given order. */ -@GwtCompatible(serializable = true) -final class ReverseOrdering extends Ordering implements Serializable { +@GwtCompatible +final class ReverseOrdering extends Ordering + implements Serializable { final Ordering forwardOrder; ReverseOrdering(Ordering forwardOrder) { @@ -33,7 +36,7 @@ final class ReverseOrdering extends Ordering implements Serializable { } @Override - public int compare(T a, T b) { + public int compare(@ParametricNullness T a, @ParametricNullness T b) { return forwardOrder.compare(b, a); } @@ -46,12 +49,13 @@ public Ordering reverse() { // Override the min/max methods to "hoist" delegation outside loops @Override - public E min(E a, E b) { + public E min(@ParametricNullness E a, @ParametricNullness E b) { return forwardOrder.max(a, b); } @Override - public E min(E a, E b, E c, E... rest) { + public E min( + @ParametricNullness E a, @ParametricNullness E b, @ParametricNullness E c, E... rest) { return forwardOrder.max(a, b, c, rest); } @@ -66,12 +70,13 @@ public E min(Iterable iterable) { } @Override - public E max(E a, E b) { + public E max(@ParametricNullness E a, @ParametricNullness E b) { return forwardOrder.min(a, b); } @Override - public E max(E a, E b, E c, E... rest) { + public E max( + @ParametricNullness E a, @ParametricNullness E b, @ParametricNullness E c, E... rest) { return forwardOrder.min(a, b, c, rest); } @@ -91,7 +96,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -107,5 +112,5 @@ public String toString() { return forwardOrder + ".reverse()"; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/RowSortedTable.java b/android/guava/src/com/google/common/collect/RowSortedTable.java index 9cdae791946d..99e59b9edd41 100644 --- a/android/guava/src/com/google/common/collect/RowSortedTable.java +++ b/android/guava/src/com/google/common/collect/RowSortedTable.java @@ -21,6 +21,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; +import org.jspecify.annotations.Nullable; /** * Interface that extends {@code Table} and whose rows are sorted. @@ -33,7 +34,9 @@ * @since 8.0 */ @GwtCompatible -public interface RowSortedTable extends Table { +public interface RowSortedTable< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + extends Table { /** * {@inheritDoc} * diff --git a/android/guava/src/com/google/common/collect/Serialization.java b/android/guava/src/com/google/common/collect/Serialization.java index 929a48f01c15..81035be120a9 100644 --- a/android/guava/src/com/google/common/collect/Serialization.java +++ b/android/guava/src/com/google/common/collect/Serialization.java @@ -17,12 +17,14 @@ package com.google.common.collect; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Collection; import java.util.Map; +import org.jspecify.annotations.Nullable; /** * Provides static methods for serializing collection classes. @@ -33,6 +35,7 @@ * @author Jared Levy */ @GwtIncompatible +@J2ktIncompatible final class Serialization { private Serialization() {} @@ -54,7 +57,8 @@ static int readCount(ObjectInputStream stream) throws IOException { *

    The serialized output consists of the number of entries, first key, first value, second key, * second value, and so on. */ - static void writeMap(Map map, ObjectOutputStream stream) throws IOException { + static void writeMap( + Map map, ObjectOutputStream stream) throws IOException { stream.writeInt(map.size()); for (Map.Entry entry : map.entrySet()) { stream.writeObject(entry.getKey()); @@ -66,8 +70,8 @@ static void writeMap(Map map, ObjectOutputStream stream) throws IOE * Populates a map by reading an input stream, as part of deserialization. See {@link #writeMap} * for the data format. */ - static void populateMap(Map map, ObjectInputStream stream) - throws IOException, ClassNotFoundException { + static void populateMap( + Map map, ObjectInputStream stream) throws IOException, ClassNotFoundException { int size = stream.readInt(); populateMap(map, stream, size); } @@ -76,7 +80,8 @@ static void populateMap(Map map, ObjectInputStream stream) * Populates a map by reading an input stream, as part of deserialization. See {@link #writeMap} * for the data format. The size is determined by a prior call to {@link #readCount}. */ - static void populateMap(Map map, ObjectInputStream stream, int size) + static void populateMap( + Map map, ObjectInputStream stream, int size) throws IOException, ClassNotFoundException { for (int i = 0; i < size; i++) { @SuppressWarnings("unchecked") // reading data stored by writeMap @@ -94,8 +99,8 @@ static void populateMap(Map map, ObjectInputStream stream, int size *

    The serialized output consists of the number of distinct elements, the first element, its * count, the second element, its count, and so on. */ - static void writeMultiset(Multiset multiset, ObjectOutputStream stream) - throws IOException { + static void writeMultiset( + Multiset multiset, ObjectOutputStream stream) throws IOException { int entryCount = multiset.entrySet().size(); stream.writeInt(entryCount); for (Multiset.Entry entry : multiset.entrySet()) { @@ -108,8 +113,8 @@ static void writeMultiset(Multiset multiset, ObjectOutputStream stream) * Populates a multiset by reading an input stream, as part of deserialization. See {@link * #writeMultiset} for the data format. */ - static void populateMultiset(Multiset multiset, ObjectInputStream stream) - throws IOException, ClassNotFoundException { + static void populateMultiset( + Multiset multiset, ObjectInputStream stream) throws IOException, ClassNotFoundException { int distinctElements = stream.readInt(); populateMultiset(multiset, stream, distinctElements); } @@ -119,7 +124,7 @@ static void populateMultiset(Multiset multiset, ObjectInputStream stream) * #writeMultiset} for the data format. The number of distinct elements is determined by a prior * call to {@link #readCount}. */ - static void populateMultiset( + static void populateMultiset( Multiset multiset, ObjectInputStream stream, int distinctElements) throws IOException, ClassNotFoundException { for (int i = 0; i < distinctElements; i++) { @@ -138,8 +143,8 @@ static void populateMultiset( *

    The serialized output consists of the number of distinct keys, and then for each distinct * key: the key, the number of values for that key, and the key's values. */ - static void writeMultimap(Multimap multimap, ObjectOutputStream stream) - throws IOException { + static void writeMultimap( + Multimap multimap, ObjectOutputStream stream) throws IOException { stream.writeInt(multimap.asMap().size()); for (Map.Entry> entry : multimap.asMap().entrySet()) { stream.writeObject(entry.getKey()); @@ -154,7 +159,8 @@ static void writeMultimap(Multimap multimap, ObjectOutputStream str * Populates a multimap by reading an input stream, as part of deserialization. See {@link * #writeMultimap} for the data format. */ - static void populateMultimap(Multimap multimap, ObjectInputStream stream) + static void populateMultimap( + Multimap multimap, ObjectInputStream stream) throws IOException, ClassNotFoundException { int distinctKeys = stream.readInt(); populateMultimap(multimap, stream, distinctKeys); @@ -165,7 +171,7 @@ static void populateMultimap(Multimap multimap, ObjectInputStream s * #writeMultimap} for the data format. The number of distinct keys is determined by a prior call * to {@link #readCount}. */ - static void populateMultimap( + static void populateMultimap( Multimap multimap, ObjectInputStream stream, int distinctKeys) throws IOException, ClassNotFoundException { for (int i = 0; i < distinctKeys; i++) { @@ -182,10 +188,10 @@ static void populateMultimap( } // Secret sauce for setting final fields; don't make it public. - static FieldSetter getFieldSetter(final Class clazz, String fieldName) { + static FieldSetter getFieldSetter(Class clazz, String fieldName) { try { Field field = clazz.getDeclaredField(fieldName); - return new FieldSetter(field); + return new FieldSetter<>(field); } catch (NoSuchFieldException e) { throw new AssertionError(e); // programmer error } diff --git a/android/guava/src/com/google/common/collect/SetMultimap.java b/android/guava/src/com/google/common/collect/SetMultimap.java index f2c6b57a2ad2..4b21111f9959 100644 --- a/android/guava/src/com/google/common/collect/SetMultimap.java +++ b/android/guava/src/com/google/common/collect/SetMultimap.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@code Multimap} that cannot hold duplicate key-value pairs. Adding a key-value pair that's @@ -41,15 +41,18 @@ * {@code equals} comparisons. Use caution if mutable objects are used as keys or values in a {@code * SetMultimap}. * + *

    Warning: Do not modify either a key or a value of a {@code SetMultimap} in a way + * that affects its {@link Object#equals} behavior. Undefined behavior and bugs will result. + * *

    See the Guava User Guide article on {@code - * Multimap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}. * * @author Jared Levy * @since 2.0 */ @GwtCompatible -public interface SetMultimap extends Multimap { +public interface SetMultimap + extends Multimap { /** * {@inheritDoc} * @@ -58,7 +61,7 @@ public interface SetMultimap extends Multimap { * interface. */ @Override - Set get(@NullableDecl K key); + Set get(@ParametricNullness K key); /** * {@inheritDoc} @@ -69,7 +72,7 @@ public interface SetMultimap extends Multimap { */ @CanIgnoreReturnValue @Override - Set removeAll(@NullableDecl Object key); + Set removeAll(@Nullable Object key); /** * {@inheritDoc} @@ -82,7 +85,7 @@ public interface SetMultimap extends Multimap { */ @CanIgnoreReturnValue @Override - Set replaceValues(K key, Iterable values); + Set replaceValues(@ParametricNullness K key, Iterable values); /** * {@inheritDoc} @@ -114,5 +117,5 @@ public interface SetMultimap extends Multimap { * empty {@code ListMultimap}. */ @Override - boolean equals(@NullableDecl Object obj); + boolean equals(@Nullable Object obj); } diff --git a/android/guava/src/com/google/common/collect/Sets.java b/android/guava/src/com/google/common/collect/Sets.java index 5f67e0f68874..53d776055828 100644 --- a/android/guava/src/com/google/common/collect/Sets.java +++ b/android/guava/src/com/google/common/collect/Sets.java @@ -19,15 +19,22 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.math.IntMath.saturatedAdd; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.util.Arrays.asList; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Collections2.FilteredCollection; import com.google.common.math.IntMath; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.DoNotCall; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.io.Serializable; import java.util.AbstractSet; import java.util.Arrays; @@ -48,21 +55,23 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.stream.Collector; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@link Set} instances. Also see this class's counterparts * {@link Lists}, {@link Maps} and {@link Queues}. * *

    See the Guava User Guide article on {@code Sets}. + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#sets">{@code Sets}. * * @author Kevin Bourrillion * @author Jared Levy * @author Chris Povirk * @since 2.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Sets { private Sets() {} @@ -70,7 +79,7 @@ private Sets() {} * {@link AbstractSet} substitute without the potentially-quadratic {@code removeAll} * implementation. */ - abstract static class ImprovedAbstractSet extends AbstractSet { + abstract static class ImprovedAbstractSet extends AbstractSet { @Override public boolean removeAll(Collection c) { return removeAllImpl(this, c); @@ -93,8 +102,6 @@ public boolean retainAll(Collection c) { * @param otherElements the rest of the elements the set should contain * @return an immutable set containing those elements, minus duplicates */ - // http://code.google.com/p/google-web-toolkit/issues/detail?id=3028 - @GwtCompatible(serializable = true) public static > ImmutableSet immutableEnumSet( E anElement, E... otherElements) { return ImmutableEnumSet.asImmutable(EnumSet.of(anElement, otherElements)); @@ -110,8 +117,6 @@ public static > ImmutableSet immutableEnumSet( * @param elements the elements, all of the same {@code enum} type, that the set should contain * @return an immutable set containing those elements, minus duplicates */ - // http://code.google.com/p/google-web-toolkit/issues/detail?id=3028 - @GwtCompatible(serializable = true) public static > ImmutableSet immutableEnumSet(Iterable elements) { if (elements instanceof ImmutableEnumSet) { return (ImmutableEnumSet) elements; @@ -134,6 +139,18 @@ public static > ImmutableSet immutableEnumSet(Iterable e } } + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code ImmutableSet} + * with an implementation specialized for enums. Unlike {@link ImmutableSet#toImmutableSet}, the + * resulting set will iterate over elements in their enum definition order, not encounter order. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static > Collector> toImmutableEnumSet() { + return CollectCollectors.toImmutableEnumSet(); + } + /** * Returns a new, mutable {@code EnumSet} instance containing the given elements in their * natural order. This method behaves identically to {@link EnumSet#copyOf(Collection)}, but also @@ -156,12 +173,14 @@ public static > EnumSet newEnumSet( * using a {@code LinkedHashSet} instead, at the cost of increased memory footprint, to get * deterministic iteration behavior. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code HashSet} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code HashSet} constructor directly, taking advantage of "diamond" + * syntax. */ - public static HashSet newHashSet() { - return new HashSet(); + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static HashSet newHashSet() { + return new HashSet<>(); } /** @@ -177,7 +196,8 @@ public static HashSet newHashSet() { * asList}{@code (...))}, or for creating an empty set then calling {@link Collections#addAll}. * This method is not actually very useful and will likely be deprecated in the future. */ - public static HashSet newHashSet(E... elements) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static HashSet newHashSet(E... elements) { HashSet set = newHashSetWithExpectedSize(elements.length); Collections.addAll(set, elements); return set; @@ -195,13 +215,15 @@ public static HashSet newHashSet(E... elements) { *

    Note: if {@code E} is an {@link Enum} type, use {@link #newEnumSet(Iterable, Class)} * instead. * - *

    Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't - * need this method. Instead, use the {@code HashSet} constructor directly, taking advantage of - * the new "diamond" syntax. + *

    Note: if {@code elements} is a {@link Collection}, you don't need this method. + * Instead, use the {@code HashSet} constructor directly, taking advantage of "diamond" + * syntax. * *

    Overall, this method is not very useful and will likely be deprecated in the future. */ - public static HashSet newHashSet(Iterable elements) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static HashSet newHashSet(Iterable elements) { return (elements instanceof Collection) ? new HashSet((Collection) elements) : newHashSet(elements.iterator()); @@ -219,8 +241,9 @@ public static HashSet newHashSet(Iterable elements) { * *

    Overall, this method is not very useful and will likely be deprecated in the future. */ - public static HashSet newHashSet(Iterator elements) { - HashSet set = newHashSet(); + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static HashSet newHashSet(Iterator elements) { + HashSet set = new HashSet<>(); Iterators.addAll(set, elements); return set; } @@ -237,8 +260,10 @@ public static HashSet newHashSet(Iterator elements) { * without resizing * @throws IllegalArgumentException if {@code expectedSize} is negative */ - public static HashSet newHashSetWithExpectedSize(int expectedSize) { - return new HashSet(Maps.capacity(expectedSize)); + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static HashSet newHashSetWithExpectedSize( + int expectedSize) { + return new HashSet<>(Maps.capacity(expectedSize)); } /** @@ -281,14 +306,16 @@ public static Set newConcurrentHashSet(Iterable elements) { * *

    Note: if mutability is not required, use {@link ImmutableSet#of()} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code LinkedHashSet} constructor directly, taking advantage of - * the new "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code LinkedHashSet} constructor directly, taking advantage of "diamond" + * syntax. * * @return a new, empty {@code LinkedHashSet} */ - public static LinkedHashSet newLinkedHashSet() { - return new LinkedHashSet(); + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static LinkedHashSet newLinkedHashSet() { + return new LinkedHashSet<>(); } /** @@ -297,20 +324,23 @@ public static LinkedHashSet newLinkedHashSet() { *

    Note: if mutability is not required and the elements are non-null, use {@link * ImmutableSet#copyOf(Iterable)} instead. * - *

    Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't - * need this method. Instead, use the {@code LinkedHashSet} constructor directly, taking advantage - * of the new "diamond" syntax. + *

    Note: if {@code elements} is a {@link Collection}, you don't need this method. + * Instead, use the {@code LinkedHashSet} constructor directly, taking advantage of "diamond" + * syntax. * *

    Overall, this method is not very useful and will likely be deprecated in the future. * * @param elements the elements that the set should contain, in order * @return a new {@code LinkedHashSet} containing those elements (minus duplicates) */ - public static LinkedHashSet newLinkedHashSet(Iterable elements) { + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static LinkedHashSet newLinkedHashSet( + Iterable elements) { if (elements instanceof Collection) { - return new LinkedHashSet((Collection) elements); + return new LinkedHashSet<>((Collection) elements); } - LinkedHashSet set = newLinkedHashSet(); + LinkedHashSet set = new LinkedHashSet<>(); Iterables.addAll(set, elements); return set; } @@ -327,8 +357,10 @@ public static LinkedHashSet newLinkedHashSet(Iterable elemen * @throws IllegalArgumentException if {@code expectedSize} is negative * @since 11.0 */ - public static LinkedHashSet newLinkedHashSetWithExpectedSize(int expectedSize) { - return new LinkedHashSet(Maps.capacity(expectedSize)); + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static LinkedHashSet newLinkedHashSetWithExpectedSize( + int expectedSize) { + return new LinkedHashSet<>(Maps.capacity(expectedSize)); } // TreeSet @@ -339,14 +371,19 @@ public static LinkedHashSet newLinkedHashSetWithExpectedSize(int expected * *

    Note: if mutability is not required, use {@link ImmutableSortedSet#of()} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code TreeSet} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code TreeSet} constructor directly, taking advantage of "diamond" + * syntax. * * @return a new, empty {@code TreeSet} */ + @SuppressWarnings({ + "rawtypes", // https://github.com/google/guava/issues/989 + "NonApiType", // acts as a direct substitute for a constructor call + }) public static TreeSet newTreeSet() { - return new TreeSet(); + return new TreeSet<>(); } /** @@ -360,9 +397,10 @@ public static TreeSet newTreeSet() { * method has different behavior than {@link TreeSet#TreeSet(SortedSet)}, which returns a {@code * TreeSet} with that comparator. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code TreeSet} constructor directly, taking advantage of the new - * "diamond" syntax. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code TreeSet} constructor directly, taking advantage of "diamond" + * syntax. * *

    This method is just a small convenience for creating an empty set and then calling {@link * Iterables#addAll}. This method is not very useful and will likely be deprecated in the future. @@ -370,6 +408,10 @@ public static TreeSet newTreeSet() { * @param elements the elements that the set should contain * @return a new {@code TreeSet} containing those elements (minus duplicates) */ + @SuppressWarnings({ + "rawtypes", // https://github.com/google/guava/issues/989 + "NonApiType", // acts as a direct substitute for a constructor call + }) public static TreeSet newTreeSet(Iterable elements) { TreeSet set = newTreeSet(); Iterables.addAll(set, elements); @@ -382,18 +424,21 @@ public static TreeSet newTreeSet(Iterable *

    Note: if mutability is not required, use {@code * ImmutableSortedSet.orderedBy(comparator).build()} instead. * - *

    Note for Java 7 and later: this method is now unnecessary and should be treated as - * deprecated. Instead, use the {@code TreeSet} constructor directly, taking advantage of the new - * "diamond" syntax. One caveat to this is that the {@code - * TreeSet} constructor uses a null {@code Comparator} to mean "natural ordering," whereas this - * factory rejects null. Clean your code accordingly. + *

    Note: this method is now unnecessary and should be treated as deprecated. Instead, + * use the {@code TreeSet} constructor directly, taking advantage of "diamond" + * syntax. One caveat to this is that the {@code TreeSet} constructor uses a null {@code + * Comparator} to mean "natural ordering," whereas this factory rejects null. Clean your code + * accordingly. * * @param comparator the comparator to use to sort the set * @return a new, empty {@code TreeSet} * @throws NullPointerException if {@code comparator} is null */ - public static TreeSet newTreeSet(Comparator comparator) { - return new TreeSet(checkNotNull(comparator)); + @SuppressWarnings("NonApiType") // acts as a direct substitute for a constructor call + public static TreeSet newTreeSet( + Comparator comparator) { + return new TreeSet<>(checkNotNull(comparator)); } /** @@ -405,8 +450,8 @@ public static TreeSet newTreeSet(Comparator comparator) { * * @since 8.0 */ - public static Set newIdentityHashSet() { - return Collections.newSetFromMap(Maps.newIdentityHashMap()); + public static Set newIdentityHashSet() { + return Collections.newSetFromMap(Maps.newIdentityHashMap()); } /** @@ -418,9 +463,10 @@ public static Set newIdentityHashSet() { * @return a new, empty {@code CopyOnWriteArraySet} * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArraySet - public static CopyOnWriteArraySet newCopyOnWriteArraySet() { - return new CopyOnWriteArraySet(); + public static CopyOnWriteArraySet newCopyOnWriteArraySet() { + return new CopyOnWriteArraySet<>(); } /** @@ -430,15 +476,17 @@ public static CopyOnWriteArraySet newCopyOnWriteArraySet() { * @return a new {@code CopyOnWriteArraySet} containing those elements * @since 12.0 */ + @J2ktIncompatible @GwtIncompatible // CopyOnWriteArraySet - public static CopyOnWriteArraySet newCopyOnWriteArraySet(Iterable elements) { + public static CopyOnWriteArraySet newCopyOnWriteArraySet( + Iterable elements) { // We copy elements to an ArrayList first, rather than incurring the // quadratic cost of adding them to the COWAS directly. Collection elementsCollection = (elements instanceof Collection) ? (Collection) elements : Lists.newArrayList(elements); - return new CopyOnWriteArraySet(elementsCollection); + return new CopyOnWriteArraySet<>(elementsCollection); } /** @@ -454,6 +502,8 @@ public static CopyOnWriteArraySet newCopyOnWriteArraySet(Iterable> EnumSet complementOf(Collection collection) { if (collection instanceof EnumSet) { return EnumSet.complementOf((EnumSet) collection); @@ -474,6 +524,8 @@ public static > EnumSet complementOf(Collection collecti * @return a new, modifiable {@code EnumSet} initially containing all the values of the enum not * present in the given collection */ + @J2ktIncompatible + @GwtIncompatible // EnumSet.complementOf public static > EnumSet complementOf( Collection collection, Class type) { checkNotNull(collection); @@ -482,6 +534,8 @@ public static > EnumSet complementOf( : makeComplementByHand(collection, type); } + @J2ktIncompatible + @GwtIncompatible private static > EnumSet makeComplementByHand( Collection collection, Class type) { EnumSet result = EnumSet.allOf(type); @@ -506,10 +560,10 @@ private static > EnumSet makeComplementByHand( * empty, passed directly to this method, and no reference to the map is retained, as illustrated * in the following code fragment: * - *

    {@code
    +   * {@snippet :
        * Set identityHashSet = Sets.newSetFromMap(
        *     new IdentityHashMap());
    -   * }
    +   * }
        *
        * 

    The returned set is serializable if the backing map is. * @@ -518,8 +572,10 @@ private static > EnumSet makeComplementByHand( * @throws IllegalArgumentException if {@code map} is not empty * @deprecated Use {@link Collections#newSetFromMap} instead. */ + @InlineMe(replacement = "Collections.newSetFromMap(map)", imports = "java.util.Collections") @Deprecated - public static Set newSetFromMap(Map map) { + public static Set newSetFromMap( + Map map) { return Collections.newSetFromMap(map); } @@ -532,7 +588,7 @@ public static Set newSetFromMap(Map map) { * * @since 2.0 */ - public abstract static class SetView extends AbstractSet { + public abstract static class SetView extends AbstractSet { private SetView() {} // no subclasses but our own /** @@ -543,8 +599,17 @@ private SetView() {} // no subclasses but our own * nonstandard notion of equivalence, for example if it is a {@link TreeSet} using a comparator * that is inconsistent with {@link Object#equals(Object)}. */ - public ImmutableSet immutableCopy() { - return ImmutableSet.copyOf(this); + public ImmutableSet<@NonNull E> immutableCopy() { + // Not using ImmutableSet.copyOf() to avoid iterating thrice (isEmpty, size, iterator). + int maxSize = maxSize(); + if (maxSize == 0) { + return ImmutableSet.of(); + } + ImmutableSet.Builder<@NonNull E> builder = ImmutableSet.builderWithExpectedSize(maxSize); + for (E element : this) { + builder.add(checkNotNull(element)); + } + return builder.build(); } /** @@ -571,7 +636,8 @@ public > S copyInto(S set) { @CanIgnoreReturnValue @Deprecated @Override - public final boolean add(E e) { + @DoNotCall("Always throws UnsupportedOperationException") + public final boolean add(@ParametricNullness E e) { throw new UnsupportedOperationException(); } @@ -584,7 +650,8 @@ public final boolean add(E e) { @CanIgnoreReturnValue @Deprecated @Override - public final boolean remove(Object object) { + @DoNotCall("Always throws UnsupportedOperationException") + public final boolean remove(@Nullable Object object) { throw new UnsupportedOperationException(); } @@ -597,6 +664,7 @@ public final boolean remove(Object object) { @CanIgnoreReturnValue @Deprecated @Override + @DoNotCall("Always throws UnsupportedOperationException") public final boolean addAll(Collection newElements) { throw new UnsupportedOperationException(); } @@ -610,6 +678,7 @@ public final boolean addAll(Collection newElements) { @CanIgnoreReturnValue @Deprecated @Override + @DoNotCall("Always throws UnsupportedOperationException") public final boolean removeAll(Collection oldElements) { throw new UnsupportedOperationException(); } @@ -623,6 +692,7 @@ public final boolean removeAll(Collection oldElements) { @CanIgnoreReturnValue @Deprecated @Override + @DoNotCall("Always throws UnsupportedOperationException") public final boolean retainAll(Collection elementsToKeep) { throw new UnsupportedOperationException(); } @@ -635,6 +705,7 @@ public final boolean retainAll(Collection elementsToKeep) { */ @Deprecated @Override + @DoNotCall("Always throws UnsupportedOperationException") public final void clear() { throw new UnsupportedOperationException(); } @@ -646,6 +717,90 @@ public final void clear() { */ @Override public abstract UnmodifiableIterator iterator(); + + @Override + @SuppressWarnings("EqualsHashCode") // same semantics + public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Set)) { + return false; + } + Set that = (Set) object; + + int thatMaxSize = maxSize(that); + if (minSize() > thatMaxSize) { + return false; // this.size() > that.size() + } + int thatMinSize = minSize(that); + if (maxSize() < thatMinSize) { + return false; // this.size() < that.size() + } + + // the base implementation from AbstractSet uses size() and containsAll() + // both require iterating over the entire SetView + // we avoid iterating twice by doing the equivalent of both in one iteration + int thisSize = 0; + for (E e : this) { + try { + if (!that.contains(e)) { + return false; + } + } catch (NullPointerException | ClassCastException ignored) { + return false; + } + thisSize++; + } // that.containsAll(this) so that.size() >= this.size() + + if (thisSize == thatMaxSize) { + // this.size() == maxSize(that) >= that.size() >= this.size() + return true; // this.size() == that.size() + } else if (thisSize < thatMinSize) { + // this.size() < minSize(that) <= that.size() + return false; // this.size() < that.size() + } else { // that can only be a SetView at this point + int thatSize = 0; + for (Object unused : that) { + if (++thatSize > thisSize) { + return false; + } + } + return true; // that.size() == this.size() + } + } + + /** + * Returns a lower bound for {@link #size()} based on the sizes of the backing sets. + * + *

    This is more efficient than {@link #size()}, which iterates over the entire {@link + * SetView}. + */ + abstract int minSize(); + + /** + * Returns the {@link #minSize()} of {@code set} if it is a {@link SetView}, or the exact {@link + * #size()} of {@code set} otherwise. + */ + static int minSize(Set set) { + return set instanceof SetView ? ((SetView) set).minSize() : set.size(); + } + + /** + * Returns an upper bound for {@link #size()} based on the sizes of the backing sets. + * + *

    This is more efficient than {@link #size()}, which iterates over the entire {@link + * SetView}. + */ + abstract int maxSize(); + + /** + * Returns the {@link #maxSize()} of {@code set} if it is a {@link SetView}, or the exact {@link + * #size()} of {@code set} otherwise. + */ + static int maxSize(Set set) { + return set instanceof SetView ? ((SetView) set).maxSize() : set.size(); + } } /** @@ -658,7 +813,8 @@ public final void clear() { * equivalence relations, for example if {@code set1} is a {@link HashSet} and {@code set2} is a * {@link TreeSet} or the {@link Map#keySet} of an {@code IdentityHashMap}. */ - public static SetView union(final Set set1, final Set set2) { + public static SetView union( + Set set1, Set set2) { checkNotNull(set1, "set1"); checkNotNull(set2, "set2"); @@ -686,7 +842,7 @@ public UnmodifiableIterator iterator() { final Iterator itr2 = set2.iterator(); @Override - protected E computeNext() { + protected @Nullable E computeNext() { if (itr1.hasNext()) { return itr1.next(); } @@ -702,7 +858,7 @@ protected E computeNext() { } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { return set1.contains(object) || set2.contains(object); } @@ -714,8 +870,13 @@ public > S copyInto(S set) { } @Override - public ImmutableSet immutableCopy() { - return new ImmutableSet.Builder().addAll(set1).addAll(set2).build(); + int minSize() { + return max(minSize(set1), minSize(set2)); + } + + @Override + int maxSize() { + return saturatedAdd(maxSize(set1), maxSize(set2)); } }; } @@ -735,7 +896,7 @@ public ImmutableSet immutableCopy() { * set based on the type of the first set passed, this could in rare cases force you to make a * cast, for example: * - *

    {@code
    +   * {@snippet :
        * Set aFewBadObjects = ...
        * Set manyBadStrings = ...
        *
    @@ -743,11 +904,11 @@ public ImmutableSet immutableCopy() {
        * SuppressWarnings("unchecked")
        * Set badStrings = (Set) Sets.intersection(
        *     aFewBadObjects, manyBadStrings);
    -   * }
    +   * }
        *
        * 

    This is unfortunate, but should come up only very rarely. */ - public static SetView intersection(final Set set1, final Set set2) { + public static SetView intersection(Set set1, Set set2) { checkNotNull(set1, "set1"); checkNotNull(set2, "set2"); @@ -758,7 +919,7 @@ public UnmodifiableIterator iterator() { final Iterator itr = set1.iterator(); @Override - protected E computeNext() { + protected @Nullable E computeNext() { while (itr.hasNext()) { E e = itr.next(); if (set2.contains(e)) { @@ -787,7 +948,7 @@ public boolean isEmpty() { } @Override - public boolean contains(Object object) { + public boolean contains(@Nullable Object object) { return set1.contains(object) && set2.contains(object); } @@ -795,6 +956,16 @@ public boolean contains(Object object) { public boolean containsAll(Collection collection) { return set1.containsAll(collection) && set2.containsAll(collection); } + + @Override + int minSize() { + return 0; + } + + @Override + int maxSize() { + return min(maxSize(set1), maxSize(set2)); + } }; } @@ -808,7 +979,7 @@ public boolean containsAll(Collection collection) { * equivalence relations, for example if {@code set1} is a {@link HashSet} and {@code set2} is a * {@link TreeSet} or the {@link Map#keySet} of an {@code IdentityHashMap}. */ - public static SetView difference(final Set set1, final Set set2) { + public static SetView difference(Set set1, Set set2) { checkNotNull(set1, "set1"); checkNotNull(set2, "set2"); @@ -819,7 +990,7 @@ public UnmodifiableIterator iterator() { final Iterator itr = set1.iterator(); @Override - protected E computeNext() { + protected @Nullable E computeNext() { while (itr.hasNext()) { E e = itr.next(); if (!set2.contains(e)) { @@ -848,9 +1019,19 @@ public boolean isEmpty() { } @Override - public boolean contains(Object element) { + public boolean contains(@Nullable Object element) { return set1.contains(element) && !set2.contains(element); } + + @Override + int minSize() { + return max(minSize(set1) - maxSize(set2), 0); + } + + @Override + int maxSize() { + return maxSize(set1); + } }; } @@ -865,19 +1046,19 @@ public boolean contains(Object element) { * * @since 3.0 */ - public static SetView symmetricDifference( - final Set set1, final Set set2) { + public static SetView symmetricDifference( + Set set1, Set set2) { checkNotNull(set1, "set1"); checkNotNull(set2, "set2"); return new SetView() { @Override public UnmodifiableIterator iterator() { - final Iterator itr1 = set1.iterator(); - final Iterator itr2 = set2.iterator(); + Iterator itr1 = set1.iterator(); + Iterator itr2 = set2.iterator(); return new AbstractIterator() { @Override - public E computeNext() { + public @Nullable E computeNext() { while (itr1.hasNext()) { E elem1 = itr1.next(); if (!set2.contains(elem1)) { @@ -917,9 +1098,20 @@ public boolean isEmpty() { } @Override - public boolean contains(Object element) { + public boolean contains(@Nullable Object element) { return set1.contains(element) ^ set2.contains(element); } + + @Override + int minSize() { + int difference = minSize(set1) - maxSize(set2); + return difference >= 0 ? difference : max(minSize(set2) - maxSize(set1), 0); + } + + @Override + int maxSize() { + return saturatedAdd(maxSize(set1), maxSize(set2)); + } }; } @@ -945,12 +1137,13 @@ public boolean contains(Object element) { * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. (See {@link * Iterables#filter(Iterable, Class)} for related functionality.) * - *

    Java 8 users: many use cases for this method are better addressed by {@link + *

    Java 8+ users: many use cases for this method are better addressed by {@link * java.util.stream.Stream#filter}. This method is not being deprecated, but we gently encourage * you to migrate to streams. */ // TODO(kevinb): how to omit that last sentence when building GWT javadoc? - public static Set filter(Set unfiltered, Predicate predicate) { + public static Set filter( + Set unfiltered, Predicate predicate) { if (unfiltered instanceof SortedSet) { return filter((SortedSet) unfiltered, predicate); } @@ -958,11 +1151,11 @@ public static Set filter(Set unfiltered, Predicate predicat // Support clear(), removeAll(), and retainAll() when filtering a filtered // collection. FilteredSet filtered = (FilteredSet) unfiltered; - Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); - return new FilteredSet((Set) filtered.unfiltered, combinedPredicate); + Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); + return new FilteredSet<>((Set) filtered.unfiltered, combinedPredicate); } - return new FilteredSet(checkNotNull(unfiltered), checkNotNull(predicate)); + return new FilteredSet<>(checkNotNull(unfiltered), checkNotNull(predicate)); } /** @@ -989,16 +1182,17 @@ public static Set filter(Set unfiltered, Predicate predicat * * @since 11.0 */ - public static SortedSet filter(SortedSet unfiltered, Predicate predicate) { + public static SortedSet filter( + SortedSet unfiltered, Predicate predicate) { if (unfiltered instanceof FilteredSet) { // Support clear(), removeAll(), and retainAll() when filtering a filtered // collection. FilteredSet filtered = (FilteredSet) unfiltered; - Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); - return new FilteredSortedSet((SortedSet) filtered.unfiltered, combinedPredicate); + Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); + return new FilteredSortedSet<>((SortedSet) filtered.unfiltered, combinedPredicate); } - return new FilteredSortedSet(checkNotNull(unfiltered), checkNotNull(predicate)); + return new FilteredSortedSet<>(checkNotNull(unfiltered), checkNotNull(predicate)); } /** @@ -1026,27 +1220,27 @@ public static SortedSet filter(SortedSet unfiltered, Predicate NavigableSet filter( + public static NavigableSet filter( NavigableSet unfiltered, Predicate predicate) { if (unfiltered instanceof FilteredSet) { // Support clear(), removeAll(), and retainAll() when filtering a filtered // collection. FilteredSet filtered = (FilteredSet) unfiltered; - Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); - return new FilteredNavigableSet((NavigableSet) filtered.unfiltered, combinedPredicate); + Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); + return new FilteredNavigableSet<>((NavigableSet) filtered.unfiltered, combinedPredicate); } - return new FilteredNavigableSet(checkNotNull(unfiltered), checkNotNull(predicate)); + return new FilteredNavigableSet<>(checkNotNull(unfiltered), checkNotNull(predicate)); } - private static class FilteredSet extends FilteredCollection implements Set { + private static class FilteredSet extends FilteredCollection + implements Set { FilteredSet(Set unfiltered, Predicate predicate) { super(unfiltered, predicate); } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { return equalsImpl(this, object); } @@ -1056,39 +1250,42 @@ public int hashCode() { } } - private static class FilteredSortedSet extends FilteredSet implements SortedSet { + private static class FilteredSortedSet extends FilteredSet + implements SortedSet { FilteredSortedSet(SortedSet unfiltered, Predicate predicate) { super(unfiltered, predicate); } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return ((SortedSet) unfiltered).comparator(); } @Override - public SortedSet subSet(E fromElement, E toElement) { - return new FilteredSortedSet( + public SortedSet subSet(@ParametricNullness E fromElement, @ParametricNullness E toElement) { + return new FilteredSortedSet<>( ((SortedSet) unfiltered).subSet(fromElement, toElement), predicate); } @Override - public SortedSet headSet(E toElement) { - return new FilteredSortedSet(((SortedSet) unfiltered).headSet(toElement), predicate); + public SortedSet headSet(@ParametricNullness E toElement) { + return new FilteredSortedSet<>(((SortedSet) unfiltered).headSet(toElement), predicate); } @Override - public SortedSet tailSet(E fromElement) { - return new FilteredSortedSet(((SortedSet) unfiltered).tailSet(fromElement), predicate); + public SortedSet tailSet(@ParametricNullness E fromElement) { + return new FilteredSortedSet<>(((SortedSet) unfiltered).tailSet(fromElement), predicate); } @Override + @ParametricNullness public E first() { return Iterators.find(unfiltered.iterator(), predicate); } @Override + @ParametricNullness public E last() { SortedSet sortedUnfiltered = (SortedSet) unfiltered; while (true) { @@ -1102,8 +1299,8 @@ public E last() { } @GwtIncompatible // NavigableSet - private static class FilteredNavigableSet extends FilteredSortedSet - implements NavigableSet { + private static final class FilteredNavigableSet + extends FilteredSortedSet implements NavigableSet { FilteredNavigableSet(NavigableSet unfiltered, Predicate predicate) { super(unfiltered, predicate); } @@ -1113,34 +1310,32 @@ NavigableSet unfiltered() { } @Override - @NullableDecl - public E lower(E e) { + public @Nullable E lower(@ParametricNullness E e) { return Iterators.find(unfiltered().headSet(e, false).descendingIterator(), predicate, null); } @Override - @NullableDecl - public E floor(E e) { + public @Nullable E floor(@ParametricNullness E e) { return Iterators.find(unfiltered().headSet(e, true).descendingIterator(), predicate, null); } @Override - public E ceiling(E e) { + public @Nullable E ceiling(@ParametricNullness E e) { return Iterables.find(unfiltered().tailSet(e, true), predicate, null); } @Override - public E higher(E e) { + public @Nullable E higher(@ParametricNullness E e) { return Iterables.find(unfiltered().tailSet(e, false), predicate, null); } @Override - public E pollFirst() { + public @Nullable E pollFirst() { return Iterables.removeFirstMatching(unfiltered(), predicate); } @Override - public E pollLast() { + public @Nullable E pollLast() { return Iterables.removeFirstMatching(unfiltered().descendingSet(), predicate); } @@ -1155,24 +1350,28 @@ public Iterator descendingIterator() { } @Override + @ParametricNullness public E last() { return Iterators.find(unfiltered().descendingIterator(), predicate); } @Override public NavigableSet subSet( - E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + @ParametricNullness E fromElement, + boolean fromInclusive, + @ParametricNullness E toElement, + boolean toInclusive) { return filter( unfiltered().subSet(fromElement, fromInclusive, toElement, toInclusive), predicate); } @Override - public NavigableSet headSet(E toElement, boolean inclusive) { + public NavigableSet headSet(@ParametricNullness E toElement, boolean inclusive) { return filter(unfiltered().headSet(toElement, inclusive), predicate); } @Override - public NavigableSet tailSet(E fromElement, boolean inclusive) { + public NavigableSet tailSet(@ParametricNullness E fromElement, boolean inclusive) { return filter(unfiltered().tailSet(fromElement, inclusive), predicate); } } @@ -1182,11 +1381,11 @@ public NavigableSet tailSet(E fromElement, boolean inclusive) { * sets in order; the "n-ary Cartesian * product" of the sets. For example: * - *

    {@code
    +   * {@snippet :
        * Sets.cartesianProduct(ImmutableList.of(
        *     ImmutableSet.of(1, 2),
        *     ImmutableSet.of("A", "B", "C")))
    -   * }
    + * } * *

    returns a set containing six lists: * @@ -1202,7 +1401,7 @@ public NavigableSet tailSet(E fromElement, boolean inclusive) { *

    The result is guaranteed to be in the "traditional", lexicographical order for Cartesian * products that you would get from nesting for loops: * - *

    {@code
    +   * {@snippet :
        * for (B b0 : sets.get(0)) {
        *   for (B b1 : sets.get(1)) {
        *     ...
    @@ -1210,7 +1409,7 @@ public NavigableSet tailSet(E fromElement, boolean inclusive) {
        *     // operate on tuple
        *   }
        * }
    -   * }
    + * } * *

    Note that if any input set is empty, the Cartesian product will also be empty. If no sets at * all are provided (an empty list), the resulting Cartesian product has one element, an empty @@ -1239,11 +1438,11 @@ public static Set> cartesianProduct(List> * sets in order; the "n-ary Cartesian * product" of the sets. For example: * - *

    {@code
    +   * {@snippet :
        * Sets.cartesianProduct(
        *     ImmutableSet.of(1, 2),
        *     ImmutableSet.of("A", "B", "C"))
    -   * }
    + * } * *

    returns a set containing six lists: * @@ -1259,7 +1458,7 @@ public static Set> cartesianProduct(List> *

    The result is guaranteed to be in the "traditional", lexicographical order for Cartesian * products that you would get from nesting for loops: * - *

    {@code
    +   * {@snippet :
        * for (B b0 : sets.get(0)) {
        *   for (B b1 : sets.get(1)) {
        *     ...
    @@ -1267,7 +1466,7 @@ public static  Set> cartesianProduct(List>
        *     // operate on tuple
        *   }
        * }
    -   * }
    + * } * *

    Note that if any input set is empty, the Cartesian product will also be empty. If no sets at * all are provided (an empty list), the resulting Cartesian product has one element, an empty @@ -1289,7 +1488,7 @@ public static Set> cartesianProduct(List> */ @SafeVarargs public static Set> cartesianProduct(Set... sets) { - return cartesianProduct(Arrays.asList(sets)); + return cartesianProduct(asList(sets)); } private static final class CartesianSet extends ForwardingCollection> @@ -1306,7 +1505,7 @@ static Set> create(List> sets) { } axesBuilder.add(copy); } - final ImmutableList> axes = axesBuilder.build(); + ImmutableList> axes = axesBuilder.build(); ImmutableList> listAxes = new ImmutableList>() { @Override @@ -1323,6 +1522,15 @@ public List get(int index) { boolean isPartialView() { return true; } + + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") + @Override + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); + } }; return new CartesianSet(axes, new CartesianList(listAxes)); } @@ -1338,7 +1546,7 @@ protected Collection> delegate() { } @Override - public boolean contains(@NullableDecl Object object) { + public boolean contains(@Nullable Object object) { if (!(object instanceof List)) { return false; } @@ -1357,14 +1565,18 @@ public boolean contains(@NullableDecl Object object) { } @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { // Warning: this is broken if size() == 0, so it is critical that we // substitute an empty ImmutableSet to the user in place of this if (object instanceof CartesianSet) { CartesianSet that = (CartesianSet) object; return this.axes.equals(that.axes); } - return super.equals(object); + if (object instanceof Set) { + Set that = (Set) object; + return this.size() == that.size() && this.containsAll(that); + } + return false; } @Override @@ -1414,7 +1626,6 @@ public int hashCode() { * @see Power set article at Wikipedia * @since 4.0 */ - @GwtCompatible(serializable = false) public static Set> powerSet(Set set) { return new PowerSet(set); } @@ -1457,7 +1668,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { Integer index = inputSet.get(o); return index != null && (mask & (1 << index)) != 0; } @@ -1486,14 +1697,14 @@ public boolean isEmpty() { public Iterator> iterator() { return new AbstractIndexedListIterator>(size()) { @Override - protected Set get(final int setBits) { - return new SubSet(inputSet, setBits); + protected Set get(int setBits) { + return new SubSet<>(inputSet, setBits); } }; } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { if (obj instanceof Set) { Set set = (Set) obj; return inputSet.keySet().containsAll(set); @@ -1502,7 +1713,7 @@ public boolean contains(@NullableDecl Object obj) { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof PowerSet) { PowerSet that = (PowerSet) obj; return inputSet.keySet().equals(that.inputSet.keySet()); @@ -1550,19 +1761,18 @@ public String toString() { * @throws NullPointerException if {@code set} is or contains {@code null} * @since 23.0 */ - @Beta - public static Set> combinations(Set set, final int size) { - final ImmutableMap index = Maps.indexMap(set); + public static Set> combinations(Set set, int size) { + ImmutableMap index = Maps.indexMap(set); checkNonnegative(size, "size"); checkArgument(size <= index.size(), "size (%s) must be <= set.size() (%s)", size, index.size()); if (size == 0) { - return ImmutableSet.>of(ImmutableSet.of()); + return ImmutableSet.of(ImmutableSet.of()); } else if (size == index.size()) { - return ImmutableSet.>of(index.keySet()); + return ImmutableSet.of(index.keySet()); } return new AbstractSet>() { @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Set) { Set s = (Set) o; return s.size() == size && index.keySet().containsAll(s); @@ -1576,7 +1786,7 @@ public Iterator> iterator() { final BitSet bits = new BitSet(index.size()); @Override - protected Set computeNext() { + protected @Nullable Set computeNext() { if (bits.isEmpty()) { bits.set(0, size); } else { @@ -1586,6 +1796,7 @@ protected Set computeNext() { if (bitToFlip == index.size()) { return endOfData(); } + /* * The current set in sorted order looks like * {firstSetBit, firstSetBit + 1, ..., bitToFlip - 1, ...} @@ -1603,10 +1814,10 @@ protected Set computeNext() { bits.clear(bitToFlip - firstSetBit - 1, bitToFlip); bits.set(bitToFlip); } - final BitSet copy = (BitSet) bits.clone(); + BitSet copy = (BitSet) bits.clone(); return new AbstractSet() { @Override - public boolean contains(@NullableDecl Object o) { + public boolean contains(@Nullable Object o) { Integer i = index.get(o); return i != null && copy.get(i); } @@ -1617,7 +1828,7 @@ public Iterator iterator() { int i = -1; @Override - protected E computeNext() { + protected @Nullable E computeNext() { i = copy.nextSetBit(i + 1); if (i == -1) { return endOfData(); @@ -1661,7 +1872,7 @@ static int hashCodeImpl(Set s) { } /** An implementation for {@link Set#equals(Object)}. */ - static boolean equalsImpl(Set s, @NullableDecl Object object) { + static boolean equalsImpl(Set s, @Nullable Object object) { if (s == object) { return true; } @@ -1686,19 +1897,22 @@ static boolean equalsImpl(Set s, @NullableDecl Object object) { *

    The returned navigable set will be serializable if the specified navigable set is * serializable. * + *

    Java 8+ users and later: Prefer {@link Collections#unmodifiableNavigableSet}. + * * @param set the navigable set for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified navigable set * @since 12.0 */ - public static NavigableSet unmodifiableNavigableSet(NavigableSet set) { + public static NavigableSet unmodifiableNavigableSet( + NavigableSet set) { if (set instanceof ImmutableCollection || set instanceof UnmodifiableNavigableSet) { return set; } - return new UnmodifiableNavigableSet(set); + return new UnmodifiableNavigableSet<>(set); } - static final class UnmodifiableNavigableSet extends ForwardingSortedSet - implements NavigableSet, Serializable { + static final class UnmodifiableNavigableSet + extends ForwardingSortedSet implements NavigableSet, Serializable { private final NavigableSet delegate; private final SortedSet unmodifiableDelegate; @@ -1713,42 +1927,42 @@ protected SortedSet delegate() { } @Override - public E lower(E e) { + public @Nullable E lower(@ParametricNullness E e) { return delegate.lower(e); } @Override - public E floor(E e) { + public @Nullable E floor(@ParametricNullness E e) { return delegate.floor(e); } @Override - public E ceiling(E e) { + public @Nullable E ceiling(@ParametricNullness E e) { return delegate.ceiling(e); } @Override - public E higher(E e) { + public @Nullable E higher(@ParametricNullness E e) { return delegate.higher(e); } @Override - public E pollFirst() { + public @Nullable E pollFirst() { throw new UnsupportedOperationException(); } @Override - public E pollLast() { + public @Nullable E pollLast() { throw new UnsupportedOperationException(); } - @NullableDecl private transient UnmodifiableNavigableSet descendingSet; + @LazyInit private transient @Nullable UnmodifiableNavigableSet descendingSet; @Override public NavigableSet descendingSet() { UnmodifiableNavigableSet result = descendingSet; if (result == null) { - result = descendingSet = new UnmodifiableNavigableSet(delegate.descendingSet()); + result = descendingSet = new UnmodifiableNavigableSet<>(delegate.descendingSet()); result.descendingSet = this; } return result; @@ -1761,22 +1975,25 @@ public Iterator descendingIterator() { @Override public NavigableSet subSet( - E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + @ParametricNullness E fromElement, + boolean fromInclusive, + @ParametricNullness E toElement, + boolean toInclusive) { return unmodifiableNavigableSet( delegate.subSet(fromElement, fromInclusive, toElement, toInclusive)); } @Override - public NavigableSet headSet(E toElement, boolean inclusive) { + public NavigableSet headSet(@ParametricNullness E toElement, boolean inclusive) { return unmodifiableNavigableSet(delegate.headSet(toElement, inclusive)); } @Override - public NavigableSet tailSet(E fromElement, boolean inclusive) { + public NavigableSet tailSet(@ParametricNullness E fromElement, boolean inclusive) { return unmodifiableNavigableSet(delegate.tailSet(fromElement, inclusive)); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -1788,7 +2005,7 @@ public NavigableSet tailSet(E fromElement, boolean inclusive) { * iterating over it or any of its {@code descendingSet}, {@code subSet}, {@code headSet}, or * {@code tailSet} views. * - *

    {@code
    +   * {@snippet :
        * NavigableSet set = synchronizedNavigableSet(new TreeSet());
        *  ...
        * synchronized (set) {
    @@ -1798,34 +2015,38 @@ public NavigableSet tailSet(E fromElement, boolean inclusive) {
        *     foo(it.next());
        *   }
        * }
    -   * }
    + * } * *

    or: * - *

    {@code
    +   * {@snippet :
        * NavigableSet set = synchronizedNavigableSet(new TreeSet());
        * NavigableSet set2 = set.descendingSet().headSet(foo);
        *  ...
        * synchronized (set) { // Note: set, not set2!!!
        *   // Must be in the synchronized block
        *   Iterator it = set2.descendingIterator();
    -   *   while (it.hasNext())
    +   *   while (it.hasNext()) {
        *     foo(it.next());
        *   }
        * }
    -   * }
    + * } * *

    Failure to follow this advice may result in non-deterministic behavior. * *

    The returned navigable set will be serializable if the specified navigable set is * serializable. * + *

    Java 8+ users and later: Prefer {@link Collections#synchronizedNavigableSet}. + * * @param navigableSet the navigable set to be "wrapped" in a synchronized navigable set. * @return a synchronized view of the specified navigable set. * @since 13.0 */ @GwtIncompatible // NavigableSet - public static NavigableSet synchronizedNavigableSet(NavigableSet navigableSet) { + @J2ktIncompatible // Synchronized + public static NavigableSet synchronizedNavigableSet( + NavigableSet navigableSet) { return Synchronized.navigableSet(navigableSet); } @@ -1848,7 +2069,7 @@ static boolean removeAllImpl(Set set, Collection collection) { * is just more than the set's size. We augment the test by * assuming that sets have fast contains() performance, and other * collections don't. See - * http://code.google.com/p/guava-libraries/issues/detail?id=1013 + * https://github.com/google/guava/issues/1013 */ if (collection instanceof Set && collection.size() > set.size()) { return Iterators.removeAll(set.iterator(), collection); @@ -1858,7 +2079,7 @@ static boolean removeAllImpl(Set set, Collection collection) { } @GwtIncompatible // NavigableSet - static class DescendingSet extends ForwardingNavigableSet { + static class DescendingSet extends ForwardingNavigableSet { private final NavigableSet forward; DescendingSet(NavigableSet forward) { @@ -1871,32 +2092,32 @@ protected NavigableSet delegate() { } @Override - public E lower(E e) { + public @Nullable E lower(@ParametricNullness E e) { return forward.higher(e); } @Override - public E floor(E e) { + public @Nullable E floor(@ParametricNullness E e) { return forward.ceiling(e); } @Override - public E ceiling(E e) { + public @Nullable E ceiling(@ParametricNullness E e) { return forward.floor(e); } @Override - public E higher(E e) { + public @Nullable E higher(@ParametricNullness E e) { return forward.lower(e); } @Override - public E pollFirst() { + public @Nullable E pollFirst() { return forward.pollLast(); } @Override - public E pollLast() { + public @Nullable E pollLast() { return forward.pollFirst(); } @@ -1912,32 +2133,35 @@ public Iterator descendingIterator() { @Override public NavigableSet subSet( - E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + @ParametricNullness E fromElement, + boolean fromInclusive, + @ParametricNullness E toElement, + boolean toInclusive) { return forward.subSet(toElement, toInclusive, fromElement, fromInclusive).descendingSet(); } @Override - public SortedSet subSet(E fromElement, E toElement) { + public SortedSet subSet(@ParametricNullness E fromElement, @ParametricNullness E toElement) { return standardSubSet(fromElement, toElement); } @Override - public NavigableSet headSet(E toElement, boolean inclusive) { + public NavigableSet headSet(@ParametricNullness E toElement, boolean inclusive) { return forward.tailSet(toElement, inclusive).descendingSet(); } @Override - public SortedSet headSet(E toElement) { + public SortedSet headSet(@ParametricNullness E toElement) { return standardHeadSet(toElement); } @Override - public NavigableSet tailSet(E fromElement, boolean inclusive) { + public NavigableSet tailSet(@ParametricNullness E fromElement, boolean inclusive) { return forward.headSet(fromElement, inclusive).descendingSet(); } @Override - public SortedSet tailSet(E fromElement) { + public SortedSet tailSet(@ParametricNullness E fromElement) { return standardTailSet(fromElement); } @@ -1953,16 +2177,18 @@ public Comparator comparator() { } // If we inline this, we get a javac error. - private static Ordering reverse(Comparator forward) { + private static Ordering reverse(Comparator forward) { return Ordering.from(forward).reverse(); } @Override + @ParametricNullness public E first() { return forward.last(); } @Override + @ParametricNullness public E last() { return forward.first(); } @@ -1973,12 +2199,13 @@ public Iterator iterator() { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { return standardToArray(); } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return standardToArray(array); } @@ -2004,7 +2231,6 @@ public String toString() { * * @since 20.0 */ - @Beta @GwtIncompatible // NavigableSet public static > NavigableSet subSet( NavigableSet set, Range range) { diff --git a/android/guava/src/com/google/common/collect/SingletonImmutableSet.java b/android/guava/src/com/google/common/collect/SingletonImmutableSet.java index 0f882b3eaad9..6f85ee90c83a 100644 --- a/android/guava/src/com/google/common/collect/SingletonImmutableSet.java +++ b/android/guava/src/com/google/common/collect/SingletonImmutableSet.java @@ -16,9 +16,13 @@ package com.google.common.collect; +import static com.google.common.collect.Iterators.singletonIterator; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.concurrent.LazyInit; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link ImmutableSet} with exactly one element. @@ -26,47 +30,35 @@ * @author Kevin Bourrillion * @author Nick Kralevich */ -@GwtCompatible(serializable = true, emulated = true) +@GwtCompatible @SuppressWarnings("serial") // uses writeReplace(), not default serialization final class SingletonImmutableSet extends ImmutableSet { + // We deliberately avoid caching the asList and hashCode here, to ensure that with + // compressed oops, a SingletonImmutableSet packs all the way down to the optimal 16 bytes. final transient E element; - // This is transient because it will be recalculated on the first - // call to hashCode(). - // - // A race condition is avoided since threads will either see that the value - // is zero and recalculate it themselves, or two threads will see it at - // the same time, and both recalculate it. If the cachedHashCode is 0, - // it will always be recalculated, unfortunately. - @LazyInit private transient int cachedHashCode; SingletonImmutableSet(E element) { this.element = Preconditions.checkNotNull(element); } - SingletonImmutableSet(E element, int hashCode) { - // Guaranteed to be non-null by the presence of the pre-computed hash code. - this.element = element; - cachedHashCode = hashCode; - } - @Override public int size() { return 1; } @Override - public boolean contains(Object target) { + public boolean contains(@Nullable Object target) { return element.equals(target); } @Override public UnmodifiableIterator iterator() { - return Iterators.singletonIterator(element); + return singletonIterator(element); } @Override - ImmutableList createAsList() { + public ImmutableList asList() { return ImmutableList.of(element); } @@ -76,28 +68,27 @@ boolean isPartialView() { } @Override - int copyIntoArray(Object[] dst, int offset) { + int copyIntoArray(@Nullable Object[] dst, int offset) { dst[offset] = element; return offset + 1; } @Override public final int hashCode() { - // Racy single-check. - int code = cachedHashCode; - if (code == 0) { - cachedHashCode = code = element.hashCode(); - } - return code; + return element.hashCode(); } @Override - boolean isHashCodeFast() { - return cachedHashCode != 0; + public String toString() { + return '[' + element.toString() + ']'; } + // redeclare to help optimizers with b/310253115 + @SuppressWarnings("RedundantOverride") @Override - public String toString() { - return '[' + element.toString() + ']'; + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { + return super.writeReplace(); } } diff --git a/android/guava/src/com/google/common/collect/SingletonImmutableTable.java b/android/guava/src/com/google/common/collect/SingletonImmutableTable.java index 58a182cccd3e..bad801d38010 100644 --- a/android/guava/src/com/google/common/collect/SingletonImmutableTable.java +++ b/android/guava/src/com/google/common/collect/SingletonImmutableTable.java @@ -19,6 +19,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.Map; /** @@ -27,7 +29,7 @@ * @author Gregory Kick */ @GwtCompatible -class SingletonImmutableTable extends ImmutableTable { +final class SingletonImmutableTable extends ImmutableTable { final R singleRowKey; final C singleColumnKey; final V singleValue; @@ -47,7 +49,7 @@ public ImmutableMap column(C columnKey) { checkNotNull(columnKey); return containsColumn(columnKey) ? ImmutableMap.of(singleRowKey, singleValue) - : ImmutableMap.of(); + : ImmutableMap.of(); } @Override @@ -76,7 +78,9 @@ ImmutableCollection createValues() { } @Override - SerializedForm createSerializedForm() { + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { return SerializedForm.create(this, new int[] {0}, new int[] {0}); } } diff --git a/android/guava/src/com/google/common/collect/SneakyThrows.java b/android/guava/src/com/google/common/collect/SneakyThrows.java new file mode 100644 index 000000000000..911fbc725a50 --- /dev/null +++ b/android/guava/src/com/google/common/collect/SneakyThrows.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +/** Static utility method for unchecked throwing of any {@link Throwable}. */ +@GwtCompatible +final class SneakyThrows { + /** + * Throws {@code t} as if it were an unchecked {@link Throwable}. + * + *

    This method is useful primarily when we make a reflective call to a method with no {@code + * throws} clause: Java forces us to handle an arbitrary {@link Throwable} from that method, + * rather than just the {@link RuntimeException} or {@link Error} that should be possible. (And in + * fact the static type of {@link Throwable} is occasionally justified even for a method with no + * {@code throws} clause: Some such methods can in fact throw a checked exception (e.g., by + * calling code written in Kotlin).) Typically, we want to let a {@link Throwable} from such a + * method propagate untouched, just as we'd typically let it do for a non-reflective call. + * However, we can't usually write {@code throw t;} when {@code t} has a static type of {@link + * Throwable}. But we can write {@code sneakyThrow(t);}. + * + *

    We sometimes also use {@code sneakyThrow} for testing how our code responds to + * sneaky checked exception. + * + * @return never; this method declares a return type of {@link Error} only so that callers can + * write {@code throw sneakyThrow(t);} to convince the compiler that the statement will always + * throw. + */ + @CanIgnoreReturnValue + static Error sneakyThrow(Throwable t) { + throw new SneakyThrows().throwIt(t); + } + + @SuppressWarnings("unchecked") // not really safe, but that's the point + private Error throwIt(Throwable t) throws T { + throw (T) t; + } + + private SneakyThrows() {} +} diff --git a/android/guava/src/com/google/common/collect/SortedIterable.java b/android/guava/src/com/google/common/collect/SortedIterable.java index d46e8afcd9c5..0b17fe6fa8c8 100644 --- a/android/guava/src/com/google/common/collect/SortedIterable.java +++ b/android/guava/src/com/google/common/collect/SortedIterable.java @@ -17,6 +17,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Comparator; import java.util.Iterator; +import org.jspecify.annotations.Nullable; /** * An {@code Iterable} whose elements are sorted relative to a {@code Comparator}, typically @@ -25,7 +26,7 @@ * @author Louis Wasserman */ @GwtCompatible -interface SortedIterable extends Iterable { +interface SortedIterable extends Iterable { /** * Returns the {@code Comparator} by which the elements of this iterable are ordered, or {@code * Ordering.natural()} if the elements are ordered by their natural ordering. diff --git a/android/guava/src/com/google/common/collect/SortedIterables.java b/android/guava/src/com/google/common/collect/SortedIterables.java index 2c0aa7ccac89..a1acb8f2811f 100644 --- a/android/guava/src/com/google/common/collect/SortedIterables.java +++ b/android/guava/src/com/google/common/collect/SortedIterables.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Comparator; import java.util.SortedSet; +import org.jspecify.annotations.Nullable; /** * Utilities for dealing with sorted collections of all types. @@ -49,7 +50,8 @@ public static boolean hasSameComparator(Comparator comparator, Iterable el @SuppressWarnings("unchecked") // if sortedSet.comparator() is null, the set must be naturally ordered - public static Comparator comparator(SortedSet sortedSet) { + public static Comparator comparator( + SortedSet sortedSet) { Comparator result = sortedSet.comparator(); if (result == null) { result = (Comparator) Ordering.natural(); diff --git a/android/guava/src/com/google/common/collect/SortedLists.java b/android/guava/src/com/google/common/collect/SortedLists.java index 744a458409c2..5ea430648869 100644 --- a/android/guava/src/com/google/common/collect/SortedLists.java +++ b/android/guava/src/com/google/common/collect/SortedLists.java @@ -15,15 +15,16 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Lists.transform; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.RandomAccess; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static methods pertaining to sorted {@link List} instances. @@ -35,7 +36,6 @@ * @author Louis Wasserman */ @GwtCompatible -@Beta final class SortedLists { private SortedLists() {} @@ -50,16 +50,22 @@ enum KeyPresentBehavior { */ ANY_PRESENT { @Override - int resultIndex( - Comparator comparator, E key, List list, int foundIndex) { + int resultIndex( + Comparator comparator, + @ParametricNullness E key, + List list, + int foundIndex) { return foundIndex; } }, /** Return the index of the last list element that compares as equal to the key. */ LAST_PRESENT { @Override - int resultIndex( - Comparator comparator, E key, List list, int foundIndex) { + int resultIndex( + Comparator comparator, + @ParametricNullness E key, + List list, + int foundIndex) { // Of course, we have to use binary search to find the precise // breakpoint... int lower = foundIndex; @@ -80,8 +86,11 @@ int resultIndex( /** Return the index of the first list element that compares as equal to the key. */ FIRST_PRESENT { @Override - int resultIndex( - Comparator comparator, E key, List list, int foundIndex) { + int resultIndex( + Comparator comparator, + @ParametricNullness E key, + List list, + int foundIndex) { // Of course, we have to use binary search to find the precise // breakpoint... int lower = 0; @@ -106,8 +115,11 @@ int resultIndex( */ FIRST_AFTER { @Override - public int resultIndex( - Comparator comparator, E key, List list, int foundIndex) { + public int resultIndex( + Comparator comparator, + @ParametricNullness E key, + List list, + int foundIndex) { return LAST_PRESENT.resultIndex(comparator, key, list, foundIndex) + 1; } }, @@ -117,14 +129,20 @@ public int resultIndex( */ LAST_BEFORE { @Override - public int resultIndex( - Comparator comparator, E key, List list, int foundIndex) { + public int resultIndex( + Comparator comparator, + @ParametricNullness E key, + List list, + int foundIndex) { return FIRST_PRESENT.resultIndex(comparator, key, list, foundIndex) - 1; } }; - abstract int resultIndex( - Comparator comparator, E key, List list, int foundIndex); + abstract int resultIndex( + Comparator comparator, + @ParametricNullness E key, + List list, + int foundIndex); } /** @@ -181,6 +199,7 @@ public int resultIndex(int higherIndex) { *

    Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior, * KeyAbsentBehavior)} using {@link Ordering#natural}. */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static int binarySearch( List list, E e, @@ -196,12 +215,14 @@ public static int binarySearch( *

    Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior, * KeyAbsentBehavior)} using {@link Ordering#natural}. */ - public static int binarySearch( + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 + public static int binarySearch( List list, Function keyFunction, - @NullableDecl K key, + K key, KeyPresentBehavior presentBehavior, KeyAbsentBehavior absentBehavior) { + checkNotNull(key); return binarySearch( list, keyFunction, key, Ordering.natural(), presentBehavior, absentBehavior); } @@ -213,15 +234,15 @@ public static int binarySearch( * KeyAbsentBehavior)} using {@link Lists#transform(List, Function) Lists.transform(list, * keyFunction)}. */ - public static int binarySearch( + public static int binarySearch( List list, Function keyFunction, - @NullableDecl K key, + @ParametricNullness K key, Comparator keyComparator, KeyPresentBehavior presentBehavior, KeyAbsentBehavior absentBehavior) { return binarySearch( - Lists.transform(list, keyFunction), key, keyComparator, presentBehavior, absentBehavior); + transform(list, keyFunction), key, keyComparator, presentBehavior, absentBehavior); } /** @@ -247,9 +268,9 @@ public static int binarySearch( * @return the index determined by the {@code KeyPresentBehavior}, if the key is in the list; * otherwise the index determined by the {@code KeyAbsentBehavior}. */ - public static int binarySearch( + public static int binarySearch( List list, - @NullableDecl E key, + @ParametricNullness E key, Comparator comparator, KeyPresentBehavior presentBehavior, KeyAbsentBehavior absentBehavior) { @@ -258,7 +279,7 @@ public static int binarySearch( checkNotNull(presentBehavior); checkNotNull(absentBehavior); if (!(list instanceof RandomAccess)) { - list = Lists.newArrayList(list); + list = new ArrayList<>(list); } // TODO(lowasser): benchmark when it's best to do a linear search diff --git a/android/guava/src/com/google/common/collect/SortedMapDifference.java b/android/guava/src/com/google/common/collect/SortedMapDifference.java index 4715e93e5c9c..ba4fd80b779c 100644 --- a/android/guava/src/com/google/common/collect/SortedMapDifference.java +++ b/android/guava/src/com/google/common/collect/SortedMapDifference.java @@ -18,6 +18,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.SortedMap; +import org.jspecify.annotations.Nullable; /** * An object representing the differences between two sorted maps. @@ -26,7 +27,8 @@ * @since 8.0 */ @GwtCompatible -public interface SortedMapDifference extends MapDifference { +public interface SortedMapDifference + extends MapDifference { @Override SortedMap entriesOnlyOnLeft(); diff --git a/android/guava/src/com/google/common/collect/SortedMultiset.java b/android/guava/src/com/google/common/collect/SortedMultiset.java index 2635b37ede33..b49d21356921 100644 --- a/android/guava/src/com/google/common/collect/SortedMultiset.java +++ b/android/guava/src/com/google/common/collect/SortedMultiset.java @@ -22,6 +22,7 @@ import java.util.Iterator; import java.util.NavigableSet; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * A {@link Multiset} which maintains the ordering of its elements, according to either their @@ -35,14 +36,14 @@ * Collection} contract, which is specified in terms of {@link Object#equals}. * *

    See the Guava User Guide article on {@code - * Multiset}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}. * * @author Louis Wasserman * @since 11.0 */ -@GwtCompatible(emulated = true) -public interface SortedMultiset extends SortedMultisetBridge, SortedIterable { +@GwtCompatible +public interface SortedMultiset + extends SortedMultisetBridge, SortedIterable { /** * Returns the comparator that orders this multiset, or {@link Ordering#natural()} if the natural * ordering of the elements is used. @@ -54,25 +55,25 @@ public interface SortedMultiset extends SortedMultisetBridge, SortedIterab * Returns the entry of the first element in this multiset, or {@code null} if this multiset is * empty. */ - Entry firstEntry(); + @Nullable Entry firstEntry(); /** * Returns the entry of the last element in this multiset, or {@code null} if this multiset is * empty. */ - Entry lastEntry(); + @Nullable Entry lastEntry(); /** * Returns and removes the entry associated with the lowest element in this multiset, or returns * {@code null} if this multiset is empty. */ - Entry pollFirstEntry(); + @Nullable Entry pollFirstEntry(); /** * Returns and removes the entry associated with the greatest element in this multiset, or returns * {@code null} if this multiset is empty. */ - Entry pollLastEntry(); + @Nullable Entry pollLastEntry(); /** * Returns a {@link NavigableSet} view of the distinct elements in this multiset. @@ -85,8 +86,8 @@ public interface SortedMultiset extends SortedMultisetBridge, SortedIterab /** * {@inheritDoc} * - *

    The {@code entrySet}'s iterator returns entries in ascending element order according to the - * this multiset's comparator. + *

    The {@code entrySet}'s iterator returns entries in ascending element order according to this + * multiset's comparator. */ @Override Set> entrySet(); @@ -115,7 +116,7 @@ public interface SortedMultiset extends SortedMultisetBridge, SortedIterab *

    The returned multiset will throw an {@link IllegalArgumentException} on attempts to add * elements outside its range. */ - SortedMultiset headMultiset(E upperBound, BoundType boundType); + SortedMultiset headMultiset(@ParametricNullness E upperBound, BoundType boundType); /** * Returns a view of this multiset restricted to the range between {@code lowerBound} and {@code @@ -130,7 +131,10 @@ public interface SortedMultiset extends SortedMultisetBridge, SortedIterab * lowerBoundType).headMultiset(upperBound, upperBoundType)}. */ SortedMultiset subMultiset( - E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType); + @ParametricNullness E lowerBound, + BoundType lowerBoundType, + @ParametricNullness E upperBound, + BoundType upperBoundType); /** * Returns a view of this multiset restricted to the elements greater than {@code lowerBound}, @@ -141,5 +145,5 @@ SortedMultiset subMultiset( *

    The returned multiset will throw an {@link IllegalArgumentException} on attempts to add * elements outside its range. */ - SortedMultiset tailMultiset(E lowerBound, BoundType boundType); + SortedMultiset tailMultiset(@ParametricNullness E lowerBound, BoundType boundType); } diff --git a/android/guava/src/com/google/common/collect/SortedMultisetBridge.java b/android/guava/src/com/google/common/collect/SortedMultisetBridge.java index 064cb7588f5d..6c46c73fb195 100644 --- a/android/guava/src/com/google/common/collect/SortedMultisetBridge.java +++ b/android/guava/src/com/google/common/collect/SortedMultisetBridge.java @@ -18,6 +18,7 @@ import com.google.common.annotations.GwtIncompatible; import java.util.SortedSet; +import org.jspecify.annotations.Nullable; /** * Superinterface of {@link SortedMultiset} to introduce a bridge method for {@code elementSet()}, @@ -27,7 +28,7 @@ * @author Louis Wasserman */ @GwtIncompatible -interface SortedMultisetBridge extends Multiset { +interface SortedMultisetBridge extends Multiset { @Override SortedSet elementSet(); } diff --git a/android/guava/src/com/google/common/collect/SortedMultisets.java b/android/guava/src/com/google/common/collect/SortedMultisets.java index 3c45c9f02e0f..2b4aee3c39c1 100644 --- a/android/guava/src/com/google/common/collect/SortedMultisets.java +++ b/android/guava/src/com/google/common/collect/SortedMultisets.java @@ -28,19 +28,21 @@ import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Provides static utility methods for creating and working with {@link SortedMultiset} instances. * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) +@GwtCompatible final class SortedMultisets { private SortedMultisets() {} /** A skeleton implementation for {@link SortedMultiset#elementSet}. */ - static class ElementSet extends Multisets.ElementSet implements SortedSet { + @SuppressWarnings("JdkObsolete") // TODO(b/6160855): Switch GWT emulations to NavigableSet. + static class ElementSet extends Multisets.ElementSet + implements SortedSet { @Weak private final SortedMultiset multiset; ElementSet(SortedMultiset multiset) { @@ -63,26 +65,28 @@ public Comparator comparator() { } @Override - public SortedSet subSet(E fromElement, E toElement) { + public SortedSet subSet(@ParametricNullness E fromElement, @ParametricNullness E toElement) { return multiset().subMultiset(fromElement, CLOSED, toElement, OPEN).elementSet(); } @Override - public SortedSet headSet(E toElement) { + public SortedSet headSet(@ParametricNullness E toElement) { return multiset().headMultiset(toElement, OPEN).elementSet(); } @Override - public SortedSet tailSet(E fromElement) { + public SortedSet tailSet(@ParametricNullness E fromElement) { return multiset().tailMultiset(fromElement, CLOSED).elementSet(); } @Override + @ParametricNullness public E first() { return getElementOrThrow(multiset().firstEntry()); } @Override + @ParametricNullness public E last() { return getElementOrThrow(multiset().lastEntry()); } @@ -90,34 +94,35 @@ public E last() { /** A skeleton navigable implementation for {@link SortedMultiset#elementSet}. */ @GwtIncompatible // Navigable - static class NavigableElementSet extends ElementSet implements NavigableSet { + static class NavigableElementSet extends ElementSet + implements NavigableSet { NavigableElementSet(SortedMultiset multiset) { super(multiset); } @Override - public E lower(E e) { + public @Nullable E lower(@ParametricNullness E e) { return getElementOrNull(multiset().headMultiset(e, OPEN).lastEntry()); } @Override - public E floor(E e) { + public @Nullable E floor(@ParametricNullness E e) { return getElementOrNull(multiset().headMultiset(e, CLOSED).lastEntry()); } @Override - public E ceiling(E e) { + public @Nullable E ceiling(@ParametricNullness E e) { return getElementOrNull(multiset().tailMultiset(e, CLOSED).firstEntry()); } @Override - public E higher(E e) { + public @Nullable E higher(@ParametricNullness E e) { return getElementOrNull(multiset().tailMultiset(e, OPEN).firstEntry()); } @Override public NavigableSet descendingSet() { - return new NavigableElementSet(multiset().descendingMultiset()); + return new NavigableElementSet<>(multiset().descendingMultiset()); } @Override @@ -126,19 +131,22 @@ public Iterator descendingIterator() { } @Override - public E pollFirst() { + public @Nullable E pollFirst() { return getElementOrNull(multiset().pollFirstEntry()); } @Override - public E pollLast() { + public @Nullable E pollLast() { return getElementOrNull(multiset().pollLastEntry()); } @Override public NavigableSet subSet( - E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { - return new NavigableElementSet( + @ParametricNullness E fromElement, + boolean fromInclusive, + @ParametricNullness E toElement, + boolean toInclusive) { + return new NavigableElementSet<>( multiset() .subMultiset( fromElement, BoundType.forBoolean(fromInclusive), @@ -146,26 +154,27 @@ public NavigableSet subSet( } @Override - public NavigableSet headSet(E toElement, boolean inclusive) { - return new NavigableElementSet( + public NavigableSet headSet(@ParametricNullness E toElement, boolean inclusive) { + return new NavigableElementSet<>( multiset().headMultiset(toElement, BoundType.forBoolean(inclusive))); } @Override - public NavigableSet tailSet(E fromElement, boolean inclusive) { - return new NavigableElementSet( + public NavigableSet tailSet(@ParametricNullness E fromElement, boolean inclusive) { + return new NavigableElementSet<>( multiset().tailMultiset(fromElement, BoundType.forBoolean(inclusive))); } } - private static E getElementOrThrow(Entry entry) { + private static E getElementOrThrow(@Nullable Entry entry) { if (entry == null) { throw new NoSuchElementException(); } return entry.getElement(); } - private static E getElementOrNull(@NullableDecl Entry entry) { + private static @Nullable E getElementOrNull( + @Nullable Entry entry) { return (entry == null) ? null : entry.getElement(); } } diff --git a/android/guava/src/com/google/common/collect/SortedSetMultimap.java b/android/guava/src/com/google/common/collect/SortedSetMultimap.java index 33be3d0cb29c..ab3f499910fc 100644 --- a/android/guava/src/com/google/common/collect/SortedSetMultimap.java +++ b/android/guava/src/com/google/common/collect/SortedSetMultimap.java @@ -24,7 +24,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@code SetMultimap} whose set of values for a given key are kept sorted; that is, they comprise @@ -37,15 +37,19 @@ * Though the method signature doesn't say so explicitly, the map returned by {@link #asMap} has * {@code SortedSet} values. * + *

    Warning: As in all {@link SetMultimap}s, do not modify either a key or a value + * of a {@code SortedSetMultimap} in a way that affects its {@link Object#equals} behavior (or its + * position in the order of the values). Undefined behavior and bugs will result. + * *

    See the Guava User Guide article on {@code - * Multimap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}. * * @author Jared Levy * @since 2.0 */ @GwtCompatible -public interface SortedSetMultimap extends SetMultimap { +public interface SortedSetMultimap + extends SetMultimap { // Following Javadoc copied from Multimap. /** @@ -59,7 +63,7 @@ public interface SortedSetMultimap extends SetMultimap { * {@link Multimap} interface. */ @Override - SortedSet get(@NullableDecl K key); + SortedSet get(@ParametricNullness K key); /** * Removes all values associated with a given key. @@ -70,7 +74,7 @@ public interface SortedSetMultimap extends SetMultimap { */ @CanIgnoreReturnValue @Override - SortedSet removeAll(@NullableDecl Object key); + SortedSet removeAll(@Nullable Object key); /** * Stores a collection of values with the same key, replacing any existing values for that key. @@ -83,7 +87,7 @@ public interface SortedSetMultimap extends SetMultimap { */ @CanIgnoreReturnValue @Override - SortedSet replaceValues(K key, Iterable values); + SortedSet replaceValues(@ParametricNullness K key, Iterable values); /** * Returns a map view that associates each key with the corresponding values in the multimap. @@ -109,5 +113,5 @@ public interface SortedSetMultimap extends SetMultimap { * Returns the comparator that orders the multimap values, with {@code null} indicating that * natural ordering is used. */ - Comparator valueComparator(); + @Nullable Comparator valueComparator(); } diff --git a/android/guava/src/com/google/common/collect/SparseImmutableTable.java b/android/guava/src/com/google/common/collect/SparseImmutableTable.java index a7fe85debd1c..cb945e295075 100644 --- a/android/guava/src/com/google/common/collect/SparseImmutableTable.java +++ b/android/guava/src/com/google/common/collect/SparseImmutableTable.java @@ -14,7 +14,11 @@ package com.google.common.collect; +import static java.util.Objects.requireNonNull; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.Immutable; import java.util.LinkedHashMap; import java.util.Map; @@ -25,8 +29,7 @@ @Immutable(containerOf = {"R", "C", "V"}) final class SparseImmutableTable extends RegularImmutableTable { static final ImmutableTable EMPTY = - new SparseImmutableTable<>( - ImmutableList.>of(), ImmutableSet.of(), ImmutableSet.of()); + new SparseImmutableTable<>(ImmutableList.of(), ImmutableSet.of(), ImmutableSet.of()); private final ImmutableMap> rowMap; private final ImmutableMap> columnMap; @@ -45,11 +48,11 @@ final class SparseImmutableTable extends RegularImmutableTable ImmutableSet rowSpace, ImmutableSet columnSpace) { Map rowIndex = Maps.indexMap(rowSpace); - Map> rows = Maps.newLinkedHashMap(); + Map> rows = new LinkedHashMap<>(); for (R row : rowSpace) { rows.put(row, new LinkedHashMap()); } - Map> columns = Maps.newLinkedHashMap(); + Map> columns = new LinkedHashMap<>(); for (C col : columnSpace) { columns.put(col, new LinkedHashMap()); } @@ -61,12 +64,16 @@ final class SparseImmutableTable extends RegularImmutableTable C columnKey = cell.getColumnKey(); V value = cell.getValue(); - cellRowIndices[i] = rowIndex.get(rowKey); - Map thisRow = rows.get(rowKey); + /* + * These requireNonNull calls are safe because we construct the maps to hold all the provided + * cells. + */ + cellRowIndices[i] = requireNonNull(rowIndex.get(rowKey)); + Map thisRow = requireNonNull(rows.get(rowKey)); cellColumnInRowIndices[i] = thisRow.size(); V oldValue = thisRow.put(columnKey, value); checkNoDuplicate(rowKey, columnKey, oldValue, value); - columns.get(columnKey).put(rowKey, value); + requireNonNull(columns.get(columnKey)).put(rowKey, value); } this.cellRowIndices = cellRowIndices; this.cellColumnInRowIndices = cellColumnInRowIndices; @@ -75,28 +82,26 @@ final class SparseImmutableTable extends RegularImmutableTable for (Entry> row : rows.entrySet()) { rowBuilder.put(row.getKey(), ImmutableMap.copyOf(row.getValue())); } - this.rowMap = rowBuilder.build(); + this.rowMap = rowBuilder.buildOrThrow(); ImmutableMap.Builder> columnBuilder = new ImmutableMap.Builder<>(columns.size()); for (Entry> col : columns.entrySet()) { columnBuilder.put(col.getKey(), ImmutableMap.copyOf(col.getValue())); } - this.columnMap = columnBuilder.build(); + this.columnMap = columnBuilder.buildOrThrow(); } @Override public ImmutableMap> columnMap() { // Casts without copying. - ImmutableMap> columnMap = this.columnMap; - return ImmutableMap.>copyOf(columnMap); + return ImmutableMap.copyOf(columnMap); } @Override public ImmutableMap> rowMap() { // Casts without copying. - ImmutableMap> rowMap = this.rowMap; - return ImmutableMap.>copyOf(rowMap); + return ImmutableMap.copyOf(rowMap); } @Override @@ -123,12 +128,15 @@ V getValue(int index) { } @Override - SerializedForm createSerializedForm() { + @J2ktIncompatible + @GwtIncompatible + Object writeReplace() { Map columnKeyToIndex = Maps.indexMap(columnKeySet()); int[] cellColumnIndices = new int[cellSet().size()]; int i = 0; for (Cell cell : cellSet()) { - cellColumnIndices[i++] = columnKeyToIndex.get(cell.getColumnKey()); + // requireNonNull is safe because the cell exists in the table. + cellColumnIndices[i++] = requireNonNull(columnKeyToIndex.get(cell.getColumnKey())); } return SerializedForm.create(this, cellRowIndices, cellColumnIndices); } diff --git a/android/guava/src/com/google/common/collect/StandardRowSortedTable.java b/android/guava/src/com/google/common/collect/StandardRowSortedTable.java index 19a14c38533f..d2a8adfb174a 100644 --- a/android/guava/src/com/google/common/collect/StandardRowSortedTable.java +++ b/android/guava/src/com/google/common/collect/StandardRowSortedTable.java @@ -19,6 +19,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Supplier; import com.google.j2objc.annotations.WeakOuter; import java.util.Comparator; @@ -26,6 +28,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; +import org.jspecify.annotations.Nullable; /** * Implementation of {@code Table} whose iteration ordering across row keys is sorted by their @@ -90,7 +93,7 @@ SortedMap> createRowMap() { } @WeakOuter - private class RowSortedMap extends RowMap implements SortedMap> { + private final class RowSortedMap extends RowMap implements SortedMap> { @Override public SortedSet keySet() { return (SortedSet) super.keySet(); @@ -102,7 +105,7 @@ SortedSet createKeySet() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { return sortedBackingMap().comparator(); } @@ -139,5 +142,5 @@ public SortedMap> tailMap(R fromKey) { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/StandardTable.java b/android/guava/src/com/google/common/collect/StandardTable.java index efed27a3451b..562904ee76cd 100644 --- a/android/guava/src/com/google/common/collect/StandardTable.java +++ b/android/guava/src/com/google/common/collect/StandardTable.java @@ -21,17 +21,25 @@ import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Maps.asMapEntryIterator; +import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.collect.Maps.safeContainsKey; import static com.google.common.collect.Maps.safeGet; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static com.google.common.collect.Tables.immutableCell; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Function; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.Maps.IteratorBasedAbstractMap; import com.google.common.collect.Maps.ViewCachingAbstractMap; import com.google.common.collect.Sets.ImprovedAbstractSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.WeakOuter; import java.io.Serializable; import java.util.Collection; @@ -40,7 +48,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * {@link Table} implementation backed by a map that associates row keys with column key / value @@ -63,8 +71,8 @@ */ @GwtCompatible class StandardTable extends AbstractTable implements Serializable { - @GwtTransient final Map> backingMap; - @GwtTransient final Supplier> factory; + final Map> backingMap; + final Supplier> factory; StandardTable(Map> backingMap, Supplier> factory) { this.backingMap = backingMap; @@ -74,12 +82,12 @@ class StandardTable extends AbstractTable implements Serializa // Accessors @Override - public boolean contains(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { return rowKey != null && columnKey != null && super.contains(rowKey, columnKey); } @Override - public boolean containsColumn(@NullableDecl Object columnKey) { + public boolean containsColumn(@Nullable Object columnKey) { if (columnKey == null) { return false; } @@ -92,17 +100,17 @@ public boolean containsColumn(@NullableDecl Object columnKey) { } @Override - public boolean containsRow(@NullableDecl Object rowKey) { + public boolean containsRow(@Nullable Object rowKey) { return rowKey != null && safeContainsKey(backingMap, rowKey); } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return value != null && super.containsValue(value); } @Override - public V get(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V get(@Nullable Object rowKey, @Nullable Object columnKey) { return (rowKey == null || columnKey == null) ? null : super.get(rowKey, columnKey); } @@ -138,7 +146,7 @@ private Map getOrCreate(R rowKey) { @CanIgnoreReturnValue @Override - public V put(R rowKey, C columnKey, V value) { + public @Nullable V put(R rowKey, C columnKey, V value) { checkNotNull(rowKey); checkNotNull(columnKey); checkNotNull(value); @@ -147,7 +155,7 @@ public V put(R rowKey, C columnKey, V value) { @CanIgnoreReturnValue @Override - public V remove(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { if ((rowKey == null) || (columnKey == null)) { return null; } @@ -163,7 +171,7 @@ public V remove(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { } @CanIgnoreReturnValue - private Map removeColumn(Object column) { + private Map removeColumn(@Nullable Object column) { Map output = new LinkedHashMap<>(); Iterator>> iterator = backingMap.entrySet().iterator(); while (iterator.hasNext()) { @@ -179,12 +187,14 @@ private Map removeColumn(Object column) { return output; } - private boolean containsMapping(Object rowKey, Object columnKey, Object value) { + private boolean containsMapping( + @Nullable Object rowKey, @Nullable Object columnKey, @Nullable Object value) { return value != null && value.equals(get(rowKey, columnKey)); } /** Remove a row key / column key / value mapping, if present. */ - private boolean removeMapping(Object rowKey, Object columnKey, Object value) { + private boolean removeMapping( + @Nullable Object rowKey, @Nullable Object columnKey, @Nullable Object value) { if (containsMapping(rowKey, columnKey, value)) { remove(rowKey, columnKey); return true; @@ -230,9 +240,9 @@ Iterator> cellIterator() { return new CellIterator(); } - private class CellIterator implements Iterator> { + private final class CellIterator implements Iterator> { final Iterator>> rowIterator = backingMap.entrySet().iterator(); - @NullableDecl Entry> rowEntry; + @Nullable Entry> rowEntry; Iterator> columnIterator = Iterators.emptyModifiableIterator(); @Override @@ -246,14 +256,38 @@ public Cell next() { rowEntry = rowIterator.next(); columnIterator = rowEntry.getValue().entrySet().iterator(); } + /* + * requireNonNull is safe because: + * + * - columnIterator started off pointing to an empty iterator, so we must have entered the + * `if` body above at least once. Thus, if we got this far, that `if` body initialized + * rowEntry at least once. + * + * - The only case in which rowEntry is cleared (during remove() below) happens only if the + * caller removed every element from columnIterator. During that process, we would have had + * to iterate it to exhaustion. Then we can apply the logic above about an empty + * columnIterator. (This assumes no concurrent modification, but behavior under concurrent + * modification is undefined, anyway.) + */ + requireNonNull(rowEntry); Entry columnEntry = columnIterator.next(); - return Tables.immutableCell(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + return immutableCell(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); } @Override public void remove() { columnIterator.remove(); - if (rowEntry.getValue().isEmpty()) { + /* + * requireNonNull is safe because: + * + * - columnIterator.remove() succeeded, so it must have returned a value, so it must have been + * initialized by next() -- which initializes rowEntry, too. + * + * - rowEntry isn't cleared except below. If it was cleared below, then either + * columnIterator.remove() would have failed above (if the user hasn't called next() since + * then) or rowEntry would have been initialized by next() (as discussed above). + */ + if (requireNonNull(rowEntry).getValue().isEmpty()) { rowIterator.remove(); rowEntry = null; } @@ -272,40 +306,41 @@ class Row extends IteratorBasedAbstractMap { this.rowKey = checkNotNull(rowKey); } - @NullableDecl Map backingRowMap; + @Nullable Map backingRowMap; - Map backingRowMap() { - return (backingRowMap == null || (backingRowMap.isEmpty() && backingMap.containsKey(rowKey))) - ? backingRowMap = computeBackingRowMap() - : backingRowMap; + final void updateBackingRowMapField() { + if (backingRowMap == null || (backingRowMap.isEmpty() && backingMap.containsKey(rowKey))) { + backingRowMap = computeBackingRowMap(); + } } - Map computeBackingRowMap() { + @Nullable Map computeBackingRowMap() { return backingMap.get(rowKey); } // Call this every time we perform a removal. void maintainEmptyInvariant() { - if (backingRowMap() != null && backingRowMap.isEmpty()) { + updateBackingRowMapField(); + if (backingRowMap != null && backingRowMap.isEmpty()) { backingMap.remove(rowKey); backingRowMap = null; } } @Override - public boolean containsKey(Object key) { - Map backingRowMap = backingRowMap(); + public boolean containsKey(@Nullable Object key) { + updateBackingRowMapField(); return (key != null && backingRowMap != null) && Maps.safeContainsKey(backingRowMap, key); } @Override - public V get(Object key) { - Map backingRowMap = backingRowMap(); - return (key != null && backingRowMap != null) ? Maps.safeGet(backingRowMap, key) : null; + public @Nullable V get(@Nullable Object key) { + updateBackingRowMapField(); + return (key != null && backingRowMap != null) ? safeGet(backingRowMap, key) : null; } @Override - public V put(C key, V value) { + public @Nullable V put(C key, V value) { checkNotNull(key); checkNotNull(value); if (backingRowMap != null && !backingRowMap.isEmpty()) { @@ -315,8 +350,8 @@ public V put(C key, V value) { } @Override - public V remove(Object key) { - Map backingRowMap = backingRowMap(); + public @Nullable V remove(@Nullable Object key) { + updateBackingRowMapField(); if (backingRowMap == null) { return null; } @@ -327,7 +362,7 @@ public V remove(Object key) { @Override public void clear() { - Map backingRowMap = backingRowMap(); + updateBackingRowMapField(); if (backingRowMap != null) { backingRowMap.clear(); } @@ -336,17 +371,17 @@ public void clear() { @Override public int size() { - Map map = backingRowMap(); - return (map == null) ? 0 : map.size(); + updateBackingRowMapField(); + return (backingRowMap == null) ? 0 : backingRowMap.size(); } @Override Iterator> entryIterator() { - final Map map = backingRowMap(); - if (map == null) { + updateBackingRowMapField(); + if (backingRowMap == null) { return Iterators.emptyModifiableIterator(); } - final Iterator> iterator = map.entrySet().iterator(); + Iterator> iterator = backingRowMap.entrySet().iterator(); return new Iterator>() { @Override public boolean hasNext() { @@ -366,7 +401,7 @@ public void remove() { }; } - Entry wrapEntry(final Entry entry) { + Entry wrapEntry(Entry entry) { return new ForwardingMapEntry() { @Override protected Entry delegate() { @@ -379,7 +414,7 @@ public V setValue(V value) { } @Override - public boolean equals(Object object) { + public boolean equals(@Nullable Object object) { // TODO(lowasser): identify why this affects GWT tests return standardEquals(object); } @@ -397,7 +432,7 @@ public Map column(C columnKey) { return new Column(columnKey); } - private class Column extends ViewCachingAbstractMap { + private final class Column extends ViewCachingAbstractMap { final C columnKey; Column(C columnKey) { @@ -405,22 +440,22 @@ private class Column extends ViewCachingAbstractMap { } @Override - public V put(R key, V value) { + public @Nullable V put(R key, V value) { return StandardTable.this.put(key, columnKey, value); } @Override - public V get(Object key) { + public @Nullable V get(@Nullable Object key) { return StandardTable.this.get(key, columnKey); } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return StandardTable.this.contains(key, columnKey); } @Override - public V remove(Object key) { + public @Nullable V remove(@Nullable Object key) { return StandardTable.this.remove(key, columnKey); } @@ -433,7 +468,7 @@ boolean removeFromColumnIf(Predicate> predicate) { Entry> entry = iterator.next(); Map map = entry.getValue(); V value = map.get(columnKey); - if (value != null && predicate.apply(Maps.immutableEntry(entry.getKey(), value))) { + if (value != null && predicate.apply(immutableEntry(entry.getKey(), value))) { map.remove(columnKey); changed = true; if (map.isEmpty()) { @@ -450,7 +485,7 @@ Set> createEntrySet() { } @WeakOuter - private class EntrySet extends ImprovedAbstractSet> { + private final class EntrySet extends ImprovedAbstractSet> { @Override public Iterator> iterator() { return new EntrySetIterator(); @@ -478,7 +513,7 @@ public void clear() { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { if (o instanceof Entry) { Entry entry = (Entry) o; return containsMapping(entry.getKey(), columnKey, entry.getValue()); @@ -487,7 +522,7 @@ public boolean contains(Object o) { } @Override - public boolean remove(Object obj) { + public boolean remove(@Nullable Object obj) { if (obj instanceof Entry) { Entry entry = (Entry) obj; return removeMapping(entry.getKey(), columnKey, entry.getValue()); @@ -501,16 +536,16 @@ public boolean retainAll(Collection c) { } } - private class EntrySetIterator extends AbstractIterator> { + private final class EntrySetIterator extends AbstractIterator> { final Iterator>> iterator = backingMap.entrySet().iterator(); @Override - protected Entry computeNext() { + protected @Nullable Entry computeNext() { while (iterator.hasNext()) { - final Entry> entry = iterator.next(); + Entry> entry = iterator.next(); if (entry.getValue().containsKey(columnKey)) { @WeakOuter - class EntryImpl extends AbstractMapEntry { + final class EntryImpl extends AbstractMapEntry { @Override public R getKey() { return entry.getKey(); @@ -523,7 +558,22 @@ public V getValue() { @Override public V setValue(V value) { - return entry.getValue().put(columnKey, checkNotNull(value)); + /* + * The cast is safe because of the containsKey check above. (Well, it's possible for + * the map to change between that call and this one. But if that happens, the + * behavior is undefined because of the concurrent mutation.) + * + * (Our prototype checker happens to be "smart" enough to understand this for the + * *get* call in getValue but not for the *put* call here.) + * + * (Arguably we should use requireNonNull rather than uncheckedCastNullableTToT: We + * know that V is a non-null type because that's the only kind of value type that + * StandardTable supports. Thus, requireNonNull is safe as long as the cell is still + * present. (And if it's not present, behavior is undefined.) However, that's a + * behavior change relative to the old code, so it didn't seem worth risking.) + */ + return uncheckedCastNullableTToT( + entry.getValue().put(columnKey, checkNotNull(value))); } } return new EntryImpl(); @@ -539,24 +589,24 @@ Set createKeySet() { } @WeakOuter - private class KeySet extends Maps.KeySet { + private final class KeySet extends Maps.KeySet { KeySet() { super(Column.this); } @Override - public boolean contains(Object obj) { + public boolean contains(@Nullable Object obj) { return StandardTable.this.contains(obj, columnKey); } @Override - public boolean remove(Object obj) { + public boolean remove(@Nullable Object obj) { return StandardTable.this.remove(obj, columnKey) != null; } @Override - public boolean retainAll(final Collection c) { - return removeFromColumnIf(Maps.keyPredicateOnEntries(not(in(c)))); + public boolean retainAll(Collection c) { + return removeFromColumnIf(Maps.keyPredicateOnEntries(not(in(c)))); } } @@ -566,24 +616,24 @@ Collection createValues() { } @WeakOuter - private class Values extends Maps.Values { + private final class Values extends Maps.Values { Values() { super(Column.this); } @Override - public boolean remove(Object obj) { - return obj != null && removeFromColumnIf(Maps.valuePredicateOnEntries(equalTo(obj))); + public boolean remove(@Nullable Object obj) { + return obj != null && removeFromColumnIf(Maps.valuePredicateOnEntries(equalTo(obj))); } @Override - public boolean removeAll(final Collection c) { - return removeFromColumnIf(Maps.valuePredicateOnEntries(in(c))); + public boolean removeAll(Collection c) { + return removeFromColumnIf(Maps.valuePredicateOnEntries(in(c))); } @Override - public boolean retainAll(final Collection c) { - return removeFromColumnIf(Maps.valuePredicateOnEntries(not(in(c)))); + public boolean retainAll(Collection c) { + return removeFromColumnIf(Maps.valuePredicateOnEntries(not(in(c)))); } } } @@ -593,7 +643,7 @@ public Set rowKeySet() { return rowMap().keySet(); } - @NullableDecl private transient Set columnKeySet; + @LazyInit private transient @Nullable Set columnKeySet; /** * {@inheritDoc} @@ -610,7 +660,7 @@ public Set columnKeySet() { } @WeakOuter - private class ColumnKeySet extends TableSet { + private final class ColumnKeySet extends TableSet { @Override public Iterator iterator() { return createColumnKeyIterator(); @@ -622,7 +672,7 @@ public int size() { } @Override - public boolean remove(Object obj) { + public boolean remove(@Nullable Object obj) { if (obj == null) { return false; } @@ -677,7 +727,7 @@ public boolean retainAll(Collection c) { } @Override - public boolean contains(Object obj) { + public boolean contains(@Nullable Object obj) { return containsColumn(obj); } } @@ -687,15 +737,15 @@ Iterator createColumnKeyIterator() { return new ColumnKeyIterator(); } - private class ColumnKeyIterator extends AbstractIterator { + private final class ColumnKeyIterator extends AbstractIterator { // Use the same map type to support TreeMaps with comparators that aren't // consistent with equals(). final Map seen = factory.get(); final Iterator> mapIterator = backingMap.values().iterator(); - Iterator> entryIterator = Iterators.emptyIterator(); + Iterator> entryIterator = emptyIterator(); @Override - protected C computeNext() { + protected @Nullable C computeNext() { while (true) { if (entryIterator.hasNext()) { Entry entry = entryIterator.next(); @@ -723,7 +773,7 @@ public Collection values() { return super.values(); } - @NullableDecl private transient Map> rowMap; + @LazyInit private transient @Nullable Map> rowMap; @Override public Map> rowMap() { @@ -738,19 +788,20 @@ Map> createRowMap() { @WeakOuter class RowMap extends ViewCachingAbstractMap> { @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return containsRow(key); } // performing cast only when key is in backing map and has the correct type @SuppressWarnings("unchecked") @Override - public Map get(Object key) { - return containsRow(key) ? row((R) key) : null; + public @Nullable Map get(@Nullable Object key) { + // requireNonNull is safe because of the containsRow check. + return containsRow(key) ? row((R) requireNonNull(key)) : null; } @Override - public Map remove(Object key) { + public @Nullable Map remove(@Nullable Object key) { return (key == null) ? null : backingMap.remove(key); } @@ -760,17 +811,10 @@ protected Set>> createEntrySet() { } @WeakOuter - class EntrySet extends TableSet>> { + private final class EntrySet extends TableSet>> { @Override public Iterator>> iterator() { - return Maps.asMapEntryIterator( - backingMap.keySet(), - new Function>() { - @Override - public Map apply(R rowKey) { - return row(rowKey); - } - }); + return asMapEntryIterator(backingMap.keySet(), StandardTable.this::row); } @Override @@ -779,7 +823,7 @@ public int size() { } @Override - public boolean contains(Object obj) { + public boolean contains(@Nullable Object obj) { if (obj instanceof Entry) { Entry entry = (Entry) obj; return entry.getKey() != null @@ -790,7 +834,7 @@ public boolean contains(Object obj) { } @Override - public boolean remove(Object obj) { + public boolean remove(@Nullable Object obj) { if (obj instanceof Entry) { Entry entry = (Entry) obj; return entry.getKey() != null @@ -802,7 +846,7 @@ public boolean remove(Object obj) { } } - @NullableDecl private transient ColumnMap columnMap; + @LazyInit private transient @Nullable ColumnMap columnMap; @Override public Map> columnMap() { @@ -811,22 +855,23 @@ public Map> columnMap() { } @WeakOuter - private class ColumnMap extends ViewCachingAbstractMap> { + private final class ColumnMap extends ViewCachingAbstractMap> { // The cast to C occurs only when the key is in the map, implying that it // has the correct type. @SuppressWarnings("unchecked") @Override - public Map get(Object key) { - return containsColumn(key) ? column((C) key) : null; + public @Nullable Map get(@Nullable Object key) { + // requireNonNull is safe because of the containsColumn check. + return containsColumn(key) ? column((C) requireNonNull(key)) : null; } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return containsColumn(key); } @Override - public Map remove(Object key) { + public @Nullable Map remove(@Nullable Object key) { return containsColumn(key) ? removeColumn(key) : null; } @@ -846,17 +891,10 @@ Collection> createValues() { } @WeakOuter - class ColumnMapEntrySet extends TableSet>> { + private final class ColumnMapEntrySet extends TableSet>> { @Override public Iterator>> iterator() { - return Maps.asMapEntryIterator( - columnKeySet(), - new Function>() { - @Override - public Map apply(C columnKey) { - return column(columnKey); - } - }); + return asMapEntryIterator(columnKeySet(), StandardTable.this::column); } @Override @@ -865,23 +903,24 @@ public int size() { } @Override - public boolean contains(Object obj) { + public boolean contains(@Nullable Object obj) { if (obj instanceof Entry) { Entry entry = (Entry) obj; if (containsColumn(entry.getKey())) { - // The cast to C occurs only when the key is in the map, implying - // that it has the correct type. - @SuppressWarnings("unchecked") - C columnKey = (C) entry.getKey(); - return get(columnKey).equals(entry.getValue()); + // requireNonNull is safe because of the containsColumn check. + return requireNonNull(get(entry.getKey())).equals(entry.getValue()); } } return false; } @Override - public boolean remove(Object obj) { - if (contains(obj)) { + public boolean remove(@Nullable Object obj) { + /* + * `o instanceof Entry` is guaranteed by `contains`, but we check it here to satisfy our + * nullness checker. + */ + if (contains(obj) && obj instanceof Entry) { Entry entry = (Entry) obj; removeColumn(entry.getKey()); return true; @@ -893,7 +932,7 @@ public boolean remove(Object obj) { public boolean removeAll(Collection c) { /* * We can't inherit the normal implementation (which calls - * Sets.removeAllImpl(Set, *Collection*) because, under some + * Sets.removeAllImpl(Set, *Collection*)) because, under some * circumstances, it attempts to call columnKeySet().iterator().remove, * which is unsupported. */ @@ -906,7 +945,7 @@ public boolean retainAll(Collection c) { checkNotNull(c); boolean changed = false; for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { - if (!c.contains(Maps.immutableEntry(columnKey, column(columnKey)))) { + if (!c.contains(immutableEntry(columnKey, column(columnKey)))) { removeColumn(columnKey); changed = true; } @@ -916,13 +955,13 @@ public boolean retainAll(Collection c) { } @WeakOuter - private class ColumnMapValues extends Maps.Values> { + private final class ColumnMapValues extends Maps.Values> { ColumnMapValues() { super(ColumnMap.this); } @Override - public boolean remove(Object obj) { + public boolean remove(@Nullable Object obj) { for (Entry> entry : ColumnMap.this.entrySet()) { if (entry.getValue().equals(obj)) { removeColumn(entry.getKey()); @@ -960,5 +999,5 @@ public boolean retainAll(Collection c) { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/Streams.java b/android/guava/src/com/google/common/collect/Streams.java new file mode 100644 index 000000000000..c58895f5c4dd --- /dev/null +++ b/android/guava/src/com/google/common/collect/Streams.java @@ -0,0 +1,997 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static com.google.common.collect.SneakyThrows.sneakyThrow; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.math.LongMath; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.InlineMeValidationDisabled; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.Spliterators.AbstractSpliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.IntConsumer; +import java.util.function.LongConsumer; +import java.util.stream.BaseStream; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.jspecify.annotations.Nullable; + +/** + * Static utility methods related to {@code Stream} instances. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ +@GwtCompatible +/* + * Users will use most of these methods only if they're already using Stream. For a few other + * methods, like stream(Iterable), we have to rely on users not to call them without library + * desugaring. + */ +@IgnoreJRERequirement +public final class Streams { + /** + * Returns a sequential {@link Stream} of the contents of {@code iterable}, delegating to {@link + * Collection#stream} if possible. + */ + public static Stream stream(Iterable iterable) { + return (iterable instanceof Collection) + ? ((Collection) iterable).stream() + : StreamSupport.stream(iterable.spliterator(), false); + } + + /** + * Returns {@link Collection#stream}. + * + * @deprecated There is no reason to use this; just invoke {@code collection.stream()} directly. + */ + @Deprecated + @InlineMe(replacement = "collection.stream()") + public static Stream stream(Collection collection) { + return collection.stream(); + } + + /** + * Returns a sequential {@link Stream} of the remaining contents of {@code iterator}. Do not use + * {@code iterator} directly after passing it to this method. + */ + public static Stream stream(Iterator iterator) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + */ + public static Stream stream(com.google.common.base.Optional optional) { + return optional.isPresent() ? Stream.of(optional.get()) : Stream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

    Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + @InlineMe(replacement = "optional.stream()") + @InlineMeValidationDisabled("Java 9+ API only") + public static Stream stream(java.util.Optional optional) { + return optional.isPresent() ? Stream.of(optional.get()) : Stream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

    Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + @InlineMe(replacement = "optional.stream()") + @InlineMeValidationDisabled("Java 9+ API only") + public static IntStream stream(OptionalInt optional) { + return optional.isPresent() ? IntStream.of(optional.getAsInt()) : IntStream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

    Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + @InlineMe(replacement = "optional.stream()") + @InlineMeValidationDisabled("Java 9+ API only") + public static LongStream stream(OptionalLong optional) { + return optional.isPresent() ? LongStream.of(optional.getAsLong()) : LongStream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

    Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + @InlineMe(replacement = "optional.stream()") + @InlineMeValidationDisabled("Java 9+ API only") + public static DoubleStream stream(OptionalDouble optional) { + return optional.isPresent() ? DoubleStream.of(optional.getAsDouble()) : DoubleStream.empty(); + } + + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception + private static void closeAll(BaseStream[] toClose) { + // If one of the streams throws an exception, continue closing the others, then throw the + // exception later. If more than one stream throws an exception, the later ones are added to the + // first as suppressed exceptions. We don't catch Error on the grounds that it should be allowed + // to propagate immediately. + Exception exception = null; + for (BaseStream stream : toClose) { + try { + stream.close(); + } catch (Exception e) { // sneaky checked exception + if (exception == null) { + exception = e; + } else { + exception.addSuppressed(e); + } + } + } + if (exception != null) { + // Normally this is a RuntimeException that doesn't need sneakyThrow. + // But theoretically we could see sneaky checked exception + sneakyThrow(exception); + } + } + + /** + * Returns a {@link Stream} containing the elements of the first stream, followed by the elements + * of the second stream, and so on. + * + *

    This is equivalent to {@code Stream.of(streams).flatMap(stream -> stream)}, but the returned + * stream may perform better. + * + * @see Stream#concat(Stream, Stream) + */ + @SuppressWarnings("unchecked") // could probably be avoided with a forwarding Spliterator + @SafeVarargs + public static Stream concat(Stream... streams) { + // TODO(lowasser): consider an implementation that can support SUBSIZED + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder> splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (Stream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.stream( + CollectSpliterators.flatMap( + splitrsBuilder.build().spliterator(), + splitr -> (Spliterator) splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns an {@link IntStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

    This is equivalent to {@code Stream.of(streams).flatMapToInt(stream -> stream)}, but the + * returned stream may perform better. + * + * @see IntStream#concat(IntStream, IntStream) + */ + public static IntStream concat(IntStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (IntStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfInt splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.intStream( + CollectSpliterators.flatMapToInt( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a {@link LongStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

    This is equivalent to {@code Stream.of(streams).flatMapToLong(stream -> stream)}, but the + * returned stream may perform better. + * + * @see LongStream#concat(LongStream, LongStream) + */ + public static LongStream concat(LongStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (LongStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfLong splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.longStream( + CollectSpliterators.flatMapToLong( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a {@link DoubleStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

    This is equivalent to {@code Stream.of(streams).flatMapToDouble(stream -> stream)}, but the + * returned stream may perform better. + * + * @see DoubleStream#concat(DoubleStream, DoubleStream) + */ + public static DoubleStream concat(DoubleStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (DoubleStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfDouble splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.doubleStream( + CollectSpliterators.flatMapToDouble( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a stream in which each element is the result of passing the corresponding element of + * each of {@code streamA} and {@code streamB} to {@code function}. + * + *

    For example: + * + * {@snippet : + * Streams.zip( + * Stream.of("foo1", "foo2", "foo3"), + * Stream.of("bar1", "bar2"), + * (arg1, arg2) -> arg1 + ":" + arg2) + * } + * + *

    will return {@code Stream.of("foo1:bar1", "foo2:bar2")}. + * + *

    The resulting stream will only be as long as the shorter of the two input streams; if one + * stream is longer, its extra elements will be ignored. + * + *

    Note that if you are calling {@link Stream#forEach} on the resulting stream, you might want + * to consider using {@link #forEachPair} instead of this method. + * + *

    Performance note: The resulting stream is not efficiently splittable. + * This may harm parallel performance. + */ + @Beta + public static + Stream zip( + Stream streamA, Stream streamB, BiFunction function) { + checkNotNull(streamA); + checkNotNull(streamB); + checkNotNull(function); + boolean isParallel = streamA.isParallel() || streamB.isParallel(); // same as Stream.concat + Spliterator splitrA = streamA.spliterator(); + Spliterator splitrB = streamB.spliterator(); + int characteristics = + splitrA.characteristics() + & splitrB.characteristics() + & (Spliterator.SIZED | Spliterator.ORDERED); + Iterator itrA = Spliterators.iterator(splitrA); + Iterator itrB = Spliterators.iterator(splitrB); + return StreamSupport.stream( + new AbstractSpliterator( + min(splitrA.estimateSize(), splitrB.estimateSize()), characteristics) { + @Override + public boolean tryAdvance(Consumer action) { + if (itrA.hasNext() && itrB.hasNext()) { + action.accept(function.apply(itrA.next(), itrB.next())); + return true; + } + return false; + } + }, + isParallel) + .onClose(streamA::close) + .onClose(streamB::close); + } + + /** + * Invokes {@code consumer} once for each pair of corresponding elements in {@code streamA} + * and {@code streamB}. If one stream is longer than the other, the extra elements are silently + * ignored. Elements passed to the consumer are guaranteed to come from the same position in their + * respective source streams. For example: + * + * {@snippet : + * Streams.forEachPair( + * Stream.of("foo1", "foo2", "foo3"), + * Stream.of("bar1", "bar2"), + * (arg1, arg2) -> System.out.println(arg1 + ":" + arg2) + * } + * + *

    will print: + * + * {@snippet : + * foo1:bar1 + * foo2:bar2 + * } + * + *

    Warning: If either supplied stream is a parallel stream, the same correspondence + * between elements will be made, but the order in which those pairs of elements are passed to the + * consumer is not defined. + * + *

    Note that many usages of this method can be replaced with simpler calls to {@link #zip}. + * This method behaves equivalently to {@linkplain #zip zipping} the stream elements into + * temporary pair objects and then using {@link Stream#forEach} on that stream. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @Beta + public static void forEachPair( + Stream streamA, Stream streamB, BiConsumer consumer) { + checkNotNull(consumer); + + if (streamA.isParallel() || streamB.isParallel()) { + zip(streamA, streamB, TemporaryPair::new).forEach(pair -> consumer.accept(pair.a, pair.b)); + } else { + Iterator iterA = streamA.iterator(); + Iterator iterB = streamB.iterator(); + while (iterA.hasNext() && iterB.hasNext()) { + consumer.accept(iterA.next(), iterB.next()); + } + } + } + + // Use this carefully - it doesn't implement value semantics + private static final class TemporaryPair { + @ParametricNullness final A a; + @ParametricNullness final B b; + + TemporaryPair(@ParametricNullness A a, @ParametricNullness B b) { + this.a = a; + this.b = b; + } + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indices in the stream. For example, + * + * {@snippet : + * mapWithIndex( + * Stream.of("a", "b", "c"), + * (e, index) -> index + ":" + e) + * } + * + *

    would return {@code Stream.of("0:a", "1:b", "2:c")}. + * + *

    The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

    The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + public static Stream mapWithIndex( + Stream stream, FunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + Iterator fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.next(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + final class Splitr extends MapWithIndexSpliterator, R, Splitr> + implements Consumer { + @Nullable T holder; + + Splitr(Spliterator splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(@ParametricNullness T t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + try { + // The cast is safe because tryAdvance puts a T into `holder`. + action.accept(function.apply(uncheckedCastNullableTToT(holder), index++)); + return true; + } finally { + holder = null; + } + } + return false; + } + + @Override + Splitr createSplit(Spliterator from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + * {@snippet : + * mapWithIndex( + * IntStream.of(10, 11, 12), + * (e, index) -> index + ":" + e) + * } + * + *

    ...would return {@code Stream.of("0:10", "1:11", "2:12")}. + * + *

    The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

    The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + public static Stream mapWithIndex( + IntStream stream, IntFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfInt fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfInt fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextInt(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + final class Splitr extends MapWithIndexSpliterator + implements IntConsumer { + int holder; + + Splitr(Spliterator.OfInt splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(int t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfInt from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + * {@snippet : + * mapWithIndex( + * LongStream.of(10, 11, 12), + * (e, index) -> index + ":" + e) + * } + * + *

    ...would return {@code Stream.of("0:10", "1:11", "2:12")}. + * + *

    The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

    The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + public static Stream mapWithIndex( + LongStream stream, LongFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfLong fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfLong fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextLong(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + final class Splitr extends MapWithIndexSpliterator + implements LongConsumer { + long holder; + + Splitr(Spliterator.OfLong splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(long t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfLong from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + * {@snippet : + * mapWithIndex( + * DoubleStream.of(0.0, 1.0, 2.0) + * (e, index) -> index + ":" + e) + * } + * + *

    ...would return {@code Stream.of("0:0.0", "1:1.0", "2:2.0")}. + * + *

    The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

    The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + public static Stream mapWithIndex( + DoubleStream stream, DoubleFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfDouble fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfDouble fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextDouble(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + final class Splitr extends MapWithIndexSpliterator + implements DoubleConsumer { + double holder; + + Splitr(Spliterator.OfDouble splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(double t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfDouble from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * An analogue of {@link java.util.function.Function} also accepting an index. + * + *

    This interface is only intended for use by callers of {@link #mapWithIndex(Stream, + * FunctionWithIndex)}. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ + public interface FunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + @ParametricNullness + R apply(@ParametricNullness T from, long index); + } + + /* + * @IgnoreJRERequirement should be redundant with the one on Streams itself, but it's necessary as + * of Animal Sniffer 1.24. Maybe Animal Sniffer processes this nested class before it processes + * Streams and thus hasn't had a chance to see Streams's annotation? + */ + @IgnoreJRERequirement + private abstract static class MapWithIndexSpliterator< + F extends Spliterator, + R extends @Nullable Object, + S extends MapWithIndexSpliterator> + implements Spliterator { + final F fromSpliterator; + long index; + + MapWithIndexSpliterator(F fromSpliterator, long index) { + this.fromSpliterator = fromSpliterator; + this.index = index; + } + + abstract S createSplit(F from, long i); + + @Override + public @Nullable S trySplit() { + Spliterator splitOrNull = fromSpliterator.trySplit(); + if (splitOrNull == null) { + return null; + } + @SuppressWarnings("unchecked") + F split = (F) splitOrNull; + S result = createSplit(split, index); + this.index += split.getExactSizeIfKnown(); + return result; + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & (Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED); + } + } + + /** + * An analogue of {@link java.util.function.IntFunction} also accepting an index. + * + *

    This interface is only intended for use by callers of {@link #mapWithIndex(IntStream, + * IntFunctionWithIndex)}. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ + public interface IntFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + @ParametricNullness + R apply(int from, long index); + } + + /** + * An analogue of {@link java.util.function.LongFunction} also accepting an index. + * + *

    This interface is only intended for use by callers of {@link #mapWithIndex(LongStream, + * LongFunctionWithIndex)}. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ + public interface LongFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + @ParametricNullness + R apply(long from, long index); + } + + /** + * An analogue of {@link java.util.function.DoubleFunction} also accepting an index. + * + *

    This interface is only intended for use by callers of {@link #mapWithIndex(DoubleStream, + * DoubleFunctionWithIndex)}. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + */ + public interface DoubleFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + @ParametricNullness + R apply(double from, long index); + } + + /** + * Returns the last element of the specified stream, or {@link java.util.Optional#empty} if the + * stream is empty. + * + *

    Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + *

    If the stream has nondeterministic order, this has equivalent semantics to {@link + * Stream#findAny} (which you might as well use). + * + * @see Stream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + /* + * By declaring instead of , we declare this method as requiring a + * stream whose elements are non-null. However, the method goes out of its way to still handle + * nulls in the stream. This means that the method can safely be used with a stream that contains + * nulls as long as the *last* element is *not* null. + * + * (To "go out of its way," the method tracks a `set` bit so that it can distinguish "the final + * split has a last element of null, so throw NPE" from "the final split was empty, so look for an + * element in the prior one.") + */ + public static java.util.Optional findLast(Stream stream) { + final class OptionalState { + boolean set = false; + @Nullable T value = null; + + void set(T value) { + this.set = true; + this.value = value; + } + + T get() { + /* + * requireNonNull is safe because we call get() only if we've previously called set(). + * + * (For further discussion of nullness, see the comment above the method.) + */ + return requireNonNull(value); + } + } + OptionalState state = new OptionalState(); + + Deque> splits = new ArrayDeque<>(); + splits.addLast(stream.spliterator()); + + while (!splits.isEmpty()) { + Spliterator spliterator = splits.removeLast(); + + if (spliterator.getExactSizeIfKnown() == 0) { + continue; // drop this split + } + + // Many spliterators will have trySplits that are SUBSIZED even if they are not themselves + // SUBSIZED. + if (spliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + // we can drill down to exactly the smallest nonempty spliterator + while (true) { + Spliterator prefix = spliterator.trySplit(); + if (prefix == null || prefix.getExactSizeIfKnown() == 0) { + break; + } else if (spliterator.getExactSizeIfKnown() == 0) { + spliterator = prefix; + break; + } + } + + // spliterator is known to be nonempty now + spliterator.forEachRemaining(state::set); + return java.util.Optional.of(state.get()); + } + + Spliterator prefix = spliterator.trySplit(); + if (prefix == null || prefix.getExactSizeIfKnown() == 0) { + // we can't split this any further + spliterator.forEachRemaining(state::set); + if (state.set) { + return java.util.Optional.of(state.get()); + } + // fall back to the last split + continue; + } + splits.addLast(prefix); + splits.addLast(spliterator); + } + return java.util.Optional.empty(); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalInt#empty} if the stream is + * empty. + * + *

    Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see IntStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + public static OptionalInt findLast(IntStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.map(OptionalInt::of).orElse(OptionalInt.empty()); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalLong#empty} if the stream + * is empty. + * + *

    Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see LongStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + public static OptionalLong findLast(LongStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.map(OptionalLong::of).orElse(OptionalLong.empty()); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalDouble#empty} if the stream + * is empty. + * + *

    Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see DoubleStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + public static OptionalDouble findLast(DoubleStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.map(OptionalDouble::of).orElse(OptionalDouble.empty()); + } + + private Streams() {} +} diff --git a/android/guava/src/com/google/common/collect/Synchronized.java b/android/guava/src/com/google/common/collect/Synchronized.java index c4cdb1795f84..dad78c1ed16c 100644 --- a/android/guava/src/com/google/common/collect/Synchronized.java +++ b/android/guava/src/com/google/common/collect/Synchronized.java @@ -17,9 +17,11 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Maps.transformValues; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.j2objc.annotations.RetainedWith; import java.io.IOException; @@ -32,7 +34,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; -import java.util.Map.Entry; import java.util.NavigableMap; import java.util.NavigableSet; import java.util.Queue; @@ -40,7 +41,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Synchronized collection views. The returned synchronized collection views are serializable if the @@ -54,15 +55,25 @@ * @author Mike Bostock * @author Jared Levy */ -@GwtCompatible(emulated = true) +@J2ktIncompatible +@GwtCompatible +/* + * I have decided not to bother adding @ParametricNullness annotations in this class. Adding them is + * a lot of busy work, and the annotation matters only when the APIs to be annotated are visible to + * Kotlin code. In this class, nothing is publicly visible (nor exposed indirectly through a + * publicly visible subclass), and I doubt any of our current or future Kotlin extensions for the + * package will refer to the class. Plus, @ParametricNullness is only a temporary workaround, + * anyway, so we just need to get by without the annotations here until Kotlin better understands + * our other nullness annotations. + */ final class Synchronized { private Synchronized() {} - static class SynchronizedObject implements Serializable { + private static class SynchronizedObject implements Serializable { final Object delegate; final Object mutex; - SynchronizedObject(Object delegate, @NullableDecl Object mutex) { + SynchronizedObject(Object delegate, @Nullable Object mutex) { this.delegate = checkNotNull(delegate); this.mutex = (mutex == null) ? this : mutex; } @@ -85,25 +96,26 @@ public String toString() { // they don't contain any non-transient member variables, while the // following writeObject() handles the SynchronizedObject members. - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { synchronized (mutex) { stream.defaultWriteObject(); } } - @GwtIncompatible // not needed in emulated source - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static Collection collection( - Collection collection, @NullableDecl Object mutex) { - return new SynchronizedCollection(collection, mutex); + private static Collection collection( + Collection collection, @Nullable Object mutex) { + return new SynchronizedCollection<>(collection, mutex); } @VisibleForTesting - static class SynchronizedCollection extends SynchronizedObject implements Collection { - private SynchronizedCollection(Collection delegate, @NullableDecl Object mutex) { + static class SynchronizedCollection extends SynchronizedObject + implements Collection { + private SynchronizedCollection(Collection delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -135,7 +147,7 @@ public void clear() { } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { synchronized (mutex) { return delegate().contains(o); } @@ -161,7 +173,7 @@ public Iterator iterator() { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { synchronized (mutex) { return delegate().remove(o); } @@ -189,30 +201,32 @@ public int size() { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { synchronized (mutex) { return delegate().toArray(); } } @Override - public T[] toArray(T[] a) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] a) { synchronized (mutex) { return delegate().toArray(a); } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @VisibleForTesting - static Set set(Set set, @NullableDecl Object mutex) { - return new SynchronizedSet(set, mutex); + static Set set(Set set, @Nullable Object mutex) { + return new SynchronizedSet<>(set, mutex); } - static class SynchronizedSet extends SynchronizedCollection implements Set { + static class SynchronizedSet extends SynchronizedCollection + implements Set { - SynchronizedSet(Set delegate, @NullableDecl Object mutex) { + SynchronizedSet(Set delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -222,7 +236,7 @@ Set delegate() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -238,15 +252,17 @@ public int hashCode() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static SortedSet sortedSet(SortedSet set, @NullableDecl Object mutex) { - return new SynchronizedSortedSet(set, mutex); + private static SortedSet sortedSet( + SortedSet set, @Nullable Object mutex) { + return new SynchronizedSortedSet<>(set, mutex); } - static class SynchronizedSortedSet extends SynchronizedSet implements SortedSet { - SynchronizedSortedSet(SortedSet delegate, @NullableDecl Object mutex) { + static class SynchronizedSortedSet extends SynchronizedSet + implements SortedSet { + SynchronizedSortedSet(SortedSet delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -256,7 +272,7 @@ SortedSet delegate() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { synchronized (mutex) { return delegate().comparator(); } @@ -297,17 +313,18 @@ public E last() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static List list(List list, @NullableDecl Object mutex) { + private static List list(List list, @Nullable Object mutex) { return (list instanceof RandomAccess) ? new SynchronizedRandomAccessList(list, mutex) : new SynchronizedList(list, mutex); } - private static class SynchronizedList extends SynchronizedCollection implements List { - SynchronizedList(List delegate, @NullableDecl Object mutex) { + private static class SynchronizedList + extends SynchronizedCollection implements List { + SynchronizedList(List delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -338,14 +355,14 @@ public E get(int index) { } @Override - public int indexOf(Object o) { + public int indexOf(@Nullable Object o) { synchronized (mutex) { return delegate().indexOf(o); } } @Override - public int lastIndexOf(Object o) { + public int lastIndexOf(@Nullable Object o) { synchronized (mutex) { return delegate().lastIndexOf(o); } @@ -383,7 +400,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -399,31 +416,32 @@ public int hashCode() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static class SynchronizedRandomAccessList extends SynchronizedList - implements RandomAccess { - SynchronizedRandomAccessList(List list, @NullableDecl Object mutex) { + static final class SynchronizedRandomAccessList + extends SynchronizedList implements RandomAccess { + SynchronizedRandomAccessList(List list, @Nullable Object mutex) { super(list, mutex); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static Multiset multiset(Multiset multiset, @NullableDecl Object mutex) { + static Multiset multiset( + Multiset multiset, @Nullable Object mutex) { if (multiset instanceof SynchronizedMultiset || multiset instanceof ImmutableMultiset) { return multiset; } - return new SynchronizedMultiset(multiset, mutex); + return new SynchronizedMultiset<>(multiset, mutex); } - private static class SynchronizedMultiset extends SynchronizedCollection - implements Multiset { - @NullableDecl transient Set elementSet; - @NullableDecl transient Set> entrySet; + static final class SynchronizedMultiset + extends SynchronizedCollection implements Multiset { + transient @Nullable Set elementSet; + transient @Nullable Set> entrySet; - SynchronizedMultiset(Multiset delegate, @NullableDecl Object mutex) { + SynchronizedMultiset(Multiset delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -433,35 +451,35 @@ Multiset delegate() { } @Override - public int count(Object o) { + public int count(@Nullable Object o) { synchronized (mutex) { return delegate().count(o); } } @Override - public int add(E e, int n) { + public int add(@ParametricNullness E e, int n) { synchronized (mutex) { return delegate().add(e, n); } } @Override - public int remove(Object o, int n) { + public int remove(@Nullable Object o, int n) { synchronized (mutex) { return delegate().remove(o, n); } } @Override - public int setCount(E element, int count) { + public int setCount(@ParametricNullness E element, int count) { synchronized (mutex) { return delegate().setCount(element, count); } } @Override - public boolean setCount(E element, int oldCount, int newCount) { + public boolean setCount(@ParametricNullness E element, int oldCount, int newCount) { synchronized (mutex) { return delegate().setCount(element, oldCount, newCount); } @@ -478,7 +496,7 @@ public Set elementSet() { } @Override - public Set> entrySet() { + public Set> entrySet() { synchronized (mutex) { if (entrySet == null) { entrySet = typePreservingSet(delegate().entrySet(), mutex); @@ -488,7 +506,7 @@ public Set> entrySet() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -504,23 +522,24 @@ public int hashCode() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static Multimap multimap(Multimap multimap, @NullableDecl Object mutex) { + static Multimap multimap( + Multimap multimap, @Nullable Object mutex) { if (multimap instanceof SynchronizedMultimap || multimap instanceof BaseImmutableMultimap) { return multimap; } return new SynchronizedMultimap<>(multimap, mutex); } - private static class SynchronizedMultimap extends SynchronizedObject - implements Multimap { - @NullableDecl transient Set keySet; - @NullableDecl transient Collection valuesCollection; - @NullableDecl transient Collection> entries; - @NullableDecl transient Map> asMap; - @NullableDecl transient Multiset keys; + private static class SynchronizedMultimap + extends SynchronizedObject implements Multimap { + transient @Nullable Set keySet; + transient @Nullable Collection valuesCollection; + transient @Nullable Collection> entries; + transient @Nullable Map> asMap; + transient @Nullable Multiset keys; @SuppressWarnings("unchecked") @Override @@ -528,7 +547,7 @@ Multimap delegate() { return (Multimap) super.delegate(); } - SynchronizedMultimap(Multimap delegate, @NullableDecl Object mutex) { + SynchronizedMultimap(Multimap delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -547,42 +566,42 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { synchronized (mutex) { return delegate().containsKey(key); } } @Override - public boolean containsValue(Object value) { + public boolean containsValue(@Nullable Object value) { synchronized (mutex) { return delegate().containsValue(value); } } @Override - public boolean containsEntry(Object key, Object value) { + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { synchronized (mutex) { return delegate().containsEntry(key, value); } } @Override - public Collection get(K key) { + public Collection get(@ParametricNullness K key) { synchronized (mutex) { return typePreservingCollection(delegate().get(key), mutex); } } @Override - public boolean put(K key, V value) { + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { synchronized (mutex) { return delegate().put(key, value); } } @Override - public boolean putAll(K key, Iterable values) { + public boolean putAll(@ParametricNullness K key, Iterable values) { synchronized (mutex) { return delegate().putAll(key, values); } @@ -596,21 +615,21 @@ public boolean putAll(Multimap multimap) { } @Override - public Collection replaceValues(K key, Iterable values) { + public Collection replaceValues(@ParametricNullness K key, Iterable values) { synchronized (mutex) { return delegate().replaceValues(key, values); // copy not synchronized } } @Override - public boolean remove(Object key, Object value) { + public boolean remove(@Nullable Object key, @Nullable Object value) { synchronized (mutex) { return delegate().remove(key, value); } } @Override - public Collection removeAll(Object key) { + public Collection removeAll(@Nullable Object key) { synchronized (mutex) { return delegate().removeAll(key); // copy not synchronized } @@ -644,7 +663,7 @@ public Collection values() { } @Override - public Collection> entries() { + public Collection> entries() { synchronized (mutex) { if (entries == null) { entries = typePreservingCollection(delegate().entries(), mutex); @@ -674,7 +693,9 @@ public Multiset keys() { } @Override - public boolean equals(Object o) { + // A forwarding implementation can't do any better than the underlying object. + @SuppressWarnings("UndefinedEquals") + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -690,20 +711,21 @@ public int hashCode() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static ListMultimap listMultimap( - ListMultimap multimap, @NullableDecl Object mutex) { + static ListMultimap listMultimap( + ListMultimap multimap, @Nullable Object mutex) { if (multimap instanceof SynchronizedListMultimap || multimap instanceof BaseImmutableMultimap) { return multimap; } return new SynchronizedListMultimap<>(multimap, mutex); } - private static class SynchronizedListMultimap extends SynchronizedMultimap - implements ListMultimap { - SynchronizedListMultimap(ListMultimap delegate, @NullableDecl Object mutex) { + static final class SynchronizedListMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends SynchronizedMultimap implements ListMultimap { + SynchronizedListMultimap(ListMultimap delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -720,7 +742,7 @@ public List get(K key) { } @Override - public List removeAll(Object key) { + public List removeAll(@Nullable Object key) { synchronized (mutex) { return delegate().removeAll(key); // copy not synchronized } @@ -733,22 +755,23 @@ public List replaceValues(K key, Iterable values) { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static SetMultimap setMultimap( - SetMultimap multimap, @NullableDecl Object mutex) { + static SetMultimap setMultimap( + SetMultimap multimap, @Nullable Object mutex) { if (multimap instanceof SynchronizedSetMultimap || multimap instanceof BaseImmutableMultimap) { return multimap; } return new SynchronizedSetMultimap<>(multimap, mutex); } - private static class SynchronizedSetMultimap extends SynchronizedMultimap - implements SetMultimap { - @NullableDecl transient Set> entrySet; + private static class SynchronizedSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends SynchronizedMultimap implements SetMultimap { + transient @Nullable Set> entrySet; - SynchronizedSetMultimap(SetMultimap delegate, @NullableDecl Object mutex) { + SynchronizedSetMultimap(SetMultimap delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -765,7 +788,7 @@ public Set get(K key) { } @Override - public Set removeAll(Object key) { + public Set removeAll(@Nullable Object key) { synchronized (mutex) { return delegate().removeAll(key); // copy not synchronized } @@ -779,7 +802,7 @@ public Set replaceValues(K key, Iterable values) { } @Override - public Set> entries() { + public Set> entries() { synchronized (mutex) { if (entrySet == null) { entrySet = set(delegate().entries(), mutex); @@ -788,20 +811,22 @@ public Set> entries() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static SortedSetMultimap sortedSetMultimap( - SortedSetMultimap multimap, @NullableDecl Object mutex) { + static + SortedSetMultimap sortedSetMultimap( + SortedSetMultimap multimap, @Nullable Object mutex) { if (multimap instanceof SynchronizedSortedSetMultimap) { return multimap; } return new SynchronizedSortedSetMultimap<>(multimap, mutex); } - private static class SynchronizedSortedSetMultimap extends SynchronizedSetMultimap - implements SortedSetMultimap { - SynchronizedSortedSetMultimap(SortedSetMultimap delegate, @NullableDecl Object mutex) { + static final class SynchronizedSortedSetMultimap< + K extends @Nullable Object, V extends @Nullable Object> + extends SynchronizedSetMultimap implements SortedSetMultimap { + SynchronizedSortedSetMultimap(SortedSetMultimap delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -818,7 +843,7 @@ public SortedSet get(K key) { } @Override - public SortedSet removeAll(Object key) { + public SortedSet removeAll(@Nullable Object key) { synchronized (mutex) { return delegate().removeAll(key); // copy not synchronized } @@ -832,17 +857,17 @@ public SortedSet replaceValues(K key, Iterable values) { } @Override - public Comparator valueComparator() { + public @Nullable Comparator valueComparator() { synchronized (mutex) { return delegate().valueComparator(); } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static Collection typePreservingCollection( - Collection collection, @NullableDecl Object mutex) { + private static Collection typePreservingCollection( + Collection collection, @Nullable Object mutex) { if (collection instanceof SortedSet) { return sortedSet((SortedSet) collection, mutex); } @@ -855,7 +880,8 @@ private static Collection typePreservingCollection( return collection(collection, mutex); } - private static Set typePreservingSet(Set set, @NullableDecl Object mutex) { + private static Set typePreservingSet( + Set set, @Nullable Object mutex) { if (set instanceof SortedSet) { return sortedSet((SortedSet) set, mutex); } else { @@ -863,22 +889,23 @@ private static Set typePreservingSet(Set set, @NullableDecl Object mut } } - private static class SynchronizedAsMapEntries - extends SynchronizedSet>> { - SynchronizedAsMapEntries(Set>> delegate, @NullableDecl Object mutex) { + static final class SynchronizedAsMapEntries< + K extends @Nullable Object, V extends @Nullable Object> + extends SynchronizedSet>> { + SynchronizedAsMapEntries(Set>> delegate, @Nullable Object mutex) { super(delegate, mutex); } @Override - public Iterator>> iterator() { + public Iterator>> iterator() { // Must be manually synchronized. - return new TransformedIterator>, Entry>>( + return new TransformedIterator>, Map.Entry>>( super.iterator()) { @Override - Entry> transform(final Entry> entry) { + Map.Entry> transform(Map.Entry> entry) { return new ForwardingMapEntry>() { @Override - protected Entry> delegate() { + protected Map.Entry> delegate() { return entry; } @@ -894,21 +921,28 @@ public Collection getValue() { // See Collections.CheckedMap.CheckedEntrySet for details on attacks. @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { synchronized (mutex) { + /* + * toArrayImpl returns `@Nullable Object[]` rather than `Object[]` but only because it can + * be used with collections that may contain null. This collection never contains nulls, so + * we could return `Object[]`. But this class is private and J2KT cannot change return types + * in overrides, so we declare `@Nullable Object[]` as the return type. + */ return ObjectArrays.toArrayImpl(delegate()); } } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { synchronized (mutex) { return ObjectArrays.toArrayImpl(delegate(), array); } } @Override - public boolean contains(Object o) { + public boolean contains(@Nullable Object o) { synchronized (mutex) { return Maps.containsEntryImpl(delegate(), o); } @@ -922,7 +956,7 @@ public boolean containsAll(Collection c) { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -932,7 +966,7 @@ public boolean equals(Object o) { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { synchronized (mutex) { return Maps.removeEntryImpl(delegate(), o); } @@ -952,20 +986,22 @@ public boolean retainAll(Collection c) { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @VisibleForTesting - static Map map(Map map, @NullableDecl Object mutex) { + static Map map( + Map map, @Nullable Object mutex) { return new SynchronizedMap<>(map, mutex); } - private static class SynchronizedMap extends SynchronizedObject implements Map { - @NullableDecl transient Set keySet; - @NullableDecl transient Collection values; - @NullableDecl transient Set> entrySet; + private static class SynchronizedMap + extends SynchronizedObject implements Map { + transient @Nullable Set keySet; + transient @Nullable Collection values; + transient @Nullable Set> entrySet; - SynchronizedMap(Map delegate, @NullableDecl Object mutex) { + SynchronizedMap(Map delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -983,21 +1019,21 @@ public void clear() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { synchronized (mutex) { return delegate().containsKey(key); } } @Override - public boolean containsValue(Object value) { + public boolean containsValue(@Nullable Object value) { synchronized (mutex) { return delegate().containsValue(value); } } @Override - public Set> entrySet() { + public Set> entrySet() { synchronized (mutex) { if (entrySet == null) { entrySet = set(delegate().entrySet(), mutex); @@ -1007,7 +1043,7 @@ public Set> entrySet() { } @Override - public V get(Object key) { + public @Nullable V get(@Nullable Object key) { synchronized (mutex) { return delegate().get(key); } @@ -1031,7 +1067,7 @@ public Set keySet() { } @Override - public V put(K key, V value) { + public @Nullable V put(K key, V value) { synchronized (mutex) { return delegate().put(key, value); } @@ -1045,7 +1081,7 @@ public void putAll(Map map) { } @Override - public V remove(Object key) { + public @Nullable V remove(@Nullable Object key) { synchronized (mutex) { return delegate().remove(key); } @@ -1069,7 +1105,7 @@ public Collection values() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } @@ -1085,17 +1121,18 @@ public int hashCode() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static SortedMap sortedMap(SortedMap sortedMap, @NullableDecl Object mutex) { + static SortedMap sortedMap( + SortedMap sortedMap, @Nullable Object mutex) { return new SynchronizedSortedMap<>(sortedMap, mutex); } - static class SynchronizedSortedMap extends SynchronizedMap - implements SortedMap { + static class SynchronizedSortedMap + extends SynchronizedMap implements SortedMap { - SynchronizedSortedMap(SortedMap delegate, @NullableDecl Object mutex) { + SynchronizedSortedMap(SortedMap delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -1105,7 +1142,7 @@ SortedMap delegate() { } @Override - public Comparator comparator() { + public @Nullable Comparator comparator() { synchronized (mutex) { return delegate().comparator(); } @@ -1146,24 +1183,24 @@ public SortedMap tailMap(K fromKey) { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static BiMap biMap(BiMap bimap, @NullableDecl Object mutex) { + static BiMap biMap( + BiMap bimap, @Nullable Object mutex) { if (bimap instanceof SynchronizedBiMap || bimap instanceof ImmutableBiMap) { return bimap; } return new SynchronizedBiMap<>(bimap, mutex, null); } - @VisibleForTesting - static class SynchronizedBiMap extends SynchronizedMap - implements BiMap, Serializable { - @NullableDecl private transient Set valueSet; - @RetainedWith @NullableDecl private transient BiMap inverse; + static final class SynchronizedBiMap + extends SynchronizedMap implements BiMap { + private transient @Nullable Set valueSet; + @RetainedWith private transient @Nullable BiMap inverse; private SynchronizedBiMap( - BiMap delegate, @NullableDecl Object mutex, @NullableDecl BiMap inverse) { + BiMap delegate, @Nullable Object mutex, @Nullable BiMap inverse) { super(delegate, mutex); this.inverse = inverse; } @@ -1184,7 +1221,7 @@ public Set values() { } @Override - public V forcePut(K key, V value) { + public @Nullable V forcePut(@ParametricNullness K key, @ParametricNullness V value) { synchronized (mutex) { return delegate().forcePut(key, value); } @@ -1200,19 +1237,20 @@ public BiMap inverse() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static class SynchronizedAsMap extends SynchronizedMap> { - @NullableDecl transient Set>> asMapEntrySet; - @NullableDecl transient Collection> asMapValues; + static final class SynchronizedAsMap + extends SynchronizedMap> { + transient @Nullable Set>> asMapEntrySet; + transient @Nullable Collection> asMapValues; - SynchronizedAsMap(Map> delegate, @NullableDecl Object mutex) { + SynchronizedAsMap(Map> delegate, @Nullable Object mutex) { super(delegate, mutex); } @Override - public Collection get(Object key) { + public @Nullable Collection get(@Nullable Object key) { synchronized (mutex) { Collection collection = super.get(key); return (collection == null) ? null : typePreservingCollection(collection, mutex); @@ -1220,7 +1258,7 @@ public Collection get(Object key) { } @Override - public Set>> entrySet() { + public Set>> entrySet() { synchronized (mutex) { if (asMapEntrySet == null) { asMapEntrySet = new SynchronizedAsMapEntries<>(delegate().entrySet(), mutex); @@ -1240,16 +1278,19 @@ public Collection> values() { } @Override - public boolean containsValue(Object o) { + // A forwarding implementation can't do any better than the underlying object. + @SuppressWarnings("CollectionUndefinedEquality") + public boolean containsValue(@Nullable Object o) { // values() and its contains() method are both synchronized. return values().contains(o); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static class SynchronizedAsMapValues extends SynchronizedCollection> { - SynchronizedAsMapValues(Collection> delegate, @NullableDecl Object mutex) { + static final class SynchronizedAsMapValues + extends SynchronizedCollection> { + SynchronizedAsMapValues(Collection> delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -1264,14 +1305,14 @@ Collection transform(Collection from) { }; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @GwtIncompatible // NavigableSet @VisibleForTesting - static class SynchronizedNavigableSet extends SynchronizedSortedSet - implements NavigableSet { - SynchronizedNavigableSet(NavigableSet delegate, @NullableDecl Object mutex) { + static final class SynchronizedNavigableSet + extends SynchronizedSortedSet implements NavigableSet { + SynchronizedNavigableSet(NavigableSet delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -1281,7 +1322,7 @@ NavigableSet delegate() { } @Override - public E ceiling(E e) { + public @Nullable E ceiling(E e) { synchronized (mutex) { return delegate().ceiling(e); } @@ -1292,7 +1333,7 @@ public Iterator descendingIterator() { return delegate().descendingIterator(); // manually synchronized } - @NullableDecl transient NavigableSet descendingSet; + transient @Nullable NavigableSet descendingSet; @Override public NavigableSet descendingSet() { @@ -1307,7 +1348,7 @@ public NavigableSet descendingSet() { } @Override - public E floor(E e) { + public @Nullable E floor(E e) { synchronized (mutex) { return delegate().floor(e); } @@ -1326,28 +1367,28 @@ public SortedSet headSet(E toElement) { } @Override - public E higher(E e) { + public @Nullable E higher(E e) { synchronized (mutex) { return delegate().higher(e); } } @Override - public E lower(E e) { + public @Nullable E lower(E e) { synchronized (mutex) { return delegate().lower(e); } } @Override - public E pollFirst() { + public @Nullable E pollFirst() { synchronized (mutex) { return delegate().pollFirst(); } } @Override - public E pollLast() { + public @Nullable E pollLast() { synchronized (mutex) { return delegate().pollLast(); } @@ -1379,37 +1420,39 @@ public SortedSet tailSet(E fromElement) { return tailSet(fromElement, true); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @GwtIncompatible // NavigableSet - static NavigableSet navigableSet( - NavigableSet navigableSet, @NullableDecl Object mutex) { - return new SynchronizedNavigableSet(navigableSet, mutex); + static NavigableSet navigableSet( + NavigableSet navigableSet, @Nullable Object mutex) { + return new SynchronizedNavigableSet<>(navigableSet, mutex); } @GwtIncompatible // NavigableSet - static NavigableSet navigableSet(NavigableSet navigableSet) { + static NavigableSet navigableSet(NavigableSet navigableSet) { return navigableSet(navigableSet, null); } @GwtIncompatible // NavigableMap - static NavigableMap navigableMap(NavigableMap navigableMap) { + static NavigableMap navigableMap( + NavigableMap navigableMap) { return navigableMap(navigableMap, null); } @GwtIncompatible // NavigableMap - static NavigableMap navigableMap( - NavigableMap navigableMap, @NullableDecl Object mutex) { + static NavigableMap navigableMap( + NavigableMap navigableMap, @Nullable Object mutex) { return new SynchronizedNavigableMap<>(navigableMap, mutex); } @GwtIncompatible // NavigableMap @VisibleForTesting - static class SynchronizedNavigableMap extends SynchronizedSortedMap - implements NavigableMap { + static final class SynchronizedNavigableMap< + K extends @Nullable Object, V extends @Nullable Object> + extends SynchronizedSortedMap implements NavigableMap { - SynchronizedNavigableMap(NavigableMap delegate, @NullableDecl Object mutex) { + SynchronizedNavigableMap(NavigableMap delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -1419,20 +1462,20 @@ NavigableMap delegate() { } @Override - public Entry ceilingEntry(K key) { + public Map.@Nullable Entry ceilingEntry(K key) { synchronized (mutex) { return nullableSynchronizedEntry(delegate().ceilingEntry(key), mutex); } } @Override - public K ceilingKey(K key) { + public @Nullable K ceilingKey(K key) { synchronized (mutex) { return delegate().ceilingKey(key); } } - @NullableDecl transient NavigableSet descendingKeySet; + transient @Nullable NavigableSet descendingKeySet; @Override public NavigableSet descendingKeySet() { @@ -1444,7 +1487,7 @@ public NavigableSet descendingKeySet() { } } - @NullableDecl transient NavigableMap descendingMap; + transient @Nullable NavigableMap descendingMap; @Override public NavigableMap descendingMap() { @@ -1457,21 +1500,21 @@ public NavigableMap descendingMap() { } @Override - public Entry firstEntry() { + public Map.@Nullable Entry firstEntry() { synchronized (mutex) { return nullableSynchronizedEntry(delegate().firstEntry(), mutex); } } @Override - public Entry floorEntry(K key) { + public Map.@Nullable Entry floorEntry(K key) { synchronized (mutex) { return nullableSynchronizedEntry(delegate().floorEntry(key), mutex); } } @Override - public K floorKey(K key) { + public @Nullable K floorKey(K key) { synchronized (mutex) { return delegate().floorKey(key); } @@ -1490,35 +1533,35 @@ public SortedMap headMap(K toKey) { } @Override - public Entry higherEntry(K key) { + public Map.@Nullable Entry higherEntry(K key) { synchronized (mutex) { return nullableSynchronizedEntry(delegate().higherEntry(key), mutex); } } @Override - public K higherKey(K key) { + public @Nullable K higherKey(K key) { synchronized (mutex) { return delegate().higherKey(key); } } @Override - public Entry lastEntry() { + public Map.@Nullable Entry lastEntry() { synchronized (mutex) { return nullableSynchronizedEntry(delegate().lastEntry(), mutex); } } @Override - public Entry lowerEntry(K key) { + public Map.@Nullable Entry lowerEntry(K key) { synchronized (mutex) { return nullableSynchronizedEntry(delegate().lowerEntry(key), mutex); } } @Override - public K lowerKey(K key) { + public @Nullable K lowerKey(K key) { synchronized (mutex) { return delegate().lowerKey(key); } @@ -1529,7 +1572,7 @@ public Set keySet() { return navigableKeySet(); } - @NullableDecl transient NavigableSet navigableKeySet; + transient @Nullable NavigableSet navigableKeySet; @Override public NavigableSet navigableKeySet() { @@ -1542,14 +1585,14 @@ public NavigableSet navigableKeySet() { } @Override - public Entry pollFirstEntry() { + public Map.@Nullable Entry pollFirstEntry() { synchronized (mutex) { return nullableSynchronizedEntry(delegate().pollFirstEntry(), mutex); } } @Override - public Entry pollLastEntry() { + public Map.@Nullable Entry pollLastEntry() { synchronized (mutex) { return nullableSynchronizedEntry(delegate().pollLastEntry(), mutex); } @@ -1580,12 +1623,13 @@ public SortedMap tailMap(K fromKey) { return tailMap(fromKey, true); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @GwtIncompatible // works but is needed only for NavigableMap - private static Entry nullableSynchronizedEntry( - @NullableDecl Entry entry, @NullableDecl Object mutex) { + private static + Map.@Nullable Entry nullableSynchronizedEntry( + Map.@Nullable Entry entry, @Nullable Object mutex) { if (entry == null) { return null; } @@ -1593,20 +1637,21 @@ private static Entry nullableSynchronizedEntry( } @GwtIncompatible // works but is needed only for NavigableMap - private static class SynchronizedEntry extends SynchronizedObject implements Entry { + static final class SynchronizedEntry + extends SynchronizedObject implements Map.Entry { - SynchronizedEntry(Entry delegate, @NullableDecl Object mutex) { + SynchronizedEntry(Map.Entry delegate, @Nullable Object mutex) { super(delegate, mutex); } @SuppressWarnings("unchecked") // guaranteed by the constructor @Override - Entry delegate() { - return (Entry) super.delegate(); + Map.Entry delegate() { + return (Map.Entry) super.delegate(); } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { synchronized (mutex) { return delegate().equals(obj); } @@ -1640,16 +1685,17 @@ public V setValue(V value) { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static Queue queue(Queue queue, @NullableDecl Object mutex) { + static Queue queue(Queue queue, @Nullable Object mutex) { return (queue instanceof SynchronizedQueue) ? queue : new SynchronizedQueue(queue, mutex); } - private static class SynchronizedQueue extends SynchronizedCollection implements Queue { + private static class SynchronizedQueue + extends SynchronizedCollection implements Queue { - SynchronizedQueue(Queue delegate, @NullableDecl Object mutex) { + SynchronizedQueue(Queue delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -1673,14 +1719,14 @@ public boolean offer(E e) { } @Override - public E peek() { + public @Nullable E peek() { synchronized (mutex) { return delegate().peek(); } } @Override - public E poll() { + public @Nullable E poll() { synchronized (mutex) { return delegate().poll(); } @@ -1693,16 +1739,17 @@ public E remove() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static Deque deque(Deque deque, @NullableDecl Object mutex) { - return new SynchronizedDeque(deque, mutex); + static Deque deque(Deque deque, @Nullable Object mutex) { + return new SynchronizedDeque<>(deque, mutex); } - private static final class SynchronizedDeque extends SynchronizedQueue implements Deque { + static final class SynchronizedDeque extends SynchronizedQueue + implements Deque { - SynchronizedDeque(Deque delegate, @NullableDecl Object mutex) { + SynchronizedDeque(Deque delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -1754,14 +1801,14 @@ public E removeLast() { } @Override - public E pollFirst() { + public @Nullable E pollFirst() { synchronized (mutex) { return delegate().pollFirst(); } } @Override - public E pollLast() { + public @Nullable E pollLast() { synchronized (mutex) { return delegate().pollLast(); } @@ -1782,28 +1829,28 @@ public E getLast() { } @Override - public E peekFirst() { + public @Nullable E peekFirst() { synchronized (mutex) { return delegate().peekFirst(); } } @Override - public E peekLast() { + public @Nullable E peekLast() { synchronized (mutex) { return delegate().peekLast(); } } @Override - public boolean removeFirstOccurrence(Object o) { + public boolean removeFirstOccurrence(@Nullable Object o) { synchronized (mutex) { return delegate().removeFirstOccurrence(o); } } @Override - public boolean removeLastOccurrence(Object o) { + public boolean removeLastOccurrence(@Nullable Object o) { synchronized (mutex) { return delegate().removeLastOccurrence(o); } @@ -1830,17 +1877,19 @@ public Iterator descendingIterator() { } } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - static Table table(Table table, Object mutex) { + static + Table table(Table table, @Nullable Object mutex) { return new SynchronizedTable<>(table, mutex); } - private static final class SynchronizedTable extends SynchronizedObject - implements Table { + static final class SynchronizedTable< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + extends SynchronizedObject implements Table { - SynchronizedTable(Table delegate, Object mutex) { + SynchronizedTable(Table delegate, @Nullable Object mutex) { super(delegate, mutex); } @@ -1851,35 +1900,35 @@ Table delegate() { } @Override - public boolean contains(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { synchronized (mutex) { return delegate().contains(rowKey, columnKey); } } @Override - public boolean containsRow(@NullableDecl Object rowKey) { + public boolean containsRow(@Nullable Object rowKey) { synchronized (mutex) { return delegate().containsRow(rowKey); } } @Override - public boolean containsColumn(@NullableDecl Object columnKey) { + public boolean containsColumn(@Nullable Object columnKey) { synchronized (mutex) { return delegate().containsColumn(columnKey); } } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { synchronized (mutex) { return delegate().containsValue(value); } } @Override - public V get(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V get(@Nullable Object rowKey, @Nullable Object columnKey) { synchronized (mutex) { return delegate().get(rowKey, columnKey); } @@ -1907,7 +1956,10 @@ public void clear() { } @Override - public V put(@NullableDecl R rowKey, @NullableDecl C columnKey, @NullableDecl V value) { + public @Nullable V put( + @ParametricNullness R rowKey, + @ParametricNullness C columnKey, + @ParametricNullness V value) { synchronized (mutex) { return delegate().put(rowKey, columnKey, value); } @@ -1921,21 +1973,21 @@ public void putAll(Table table) { } @Override - public V remove(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { synchronized (mutex) { return delegate().remove(rowKey, columnKey); } } @Override - public Map row(@NullableDecl R rowKey) { + public Map row(@ParametricNullness R rowKey) { synchronized (mutex) { return map(delegate().row(rowKey), mutex); } } @Override - public Map column(@NullableDecl C columnKey) { + public Map column(@ParametricNullness C columnKey) { synchronized (mutex) { return map(delegate().column(columnKey), mutex); } @@ -1972,32 +2024,14 @@ public Collection values() { @Override public Map> rowMap() { synchronized (mutex) { - return map( - Maps.transformValues( - delegate().rowMap(), - new com.google.common.base.Function, Map>() { - @Override - public Map apply(Map t) { - return map(t, mutex); - } - }), - mutex); + return map(transformValues(delegate().rowMap(), m -> map(m, mutex)), mutex); } } @Override public Map> columnMap() { synchronized (mutex) { - return map( - Maps.transformValues( - delegate().columnMap(), - new com.google.common.base.Function, Map>() { - @Override - public Map apply(Map t) { - return map(t, mutex); - } - }), - mutex); + return map(transformValues(delegate().columnMap(), m -> map(m, mutex)), mutex); } } @@ -2009,7 +2043,7 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } diff --git a/android/guava/src/com/google/common/collect/Table.java b/android/guava/src/com/google/common/collect/Table.java index 13487941f47b..83047c192bd8 100644 --- a/android/guava/src/com/google/common/collect/Table.java +++ b/android/guava/src/com/google/common/collect/Table.java @@ -17,14 +17,14 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CompatibleWith; import com.google.errorprone.annotations.DoNotMock; import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A collection that associates an ordered pair of keys, called a row key and a column key, with a @@ -44,8 +44,18 @@ * not be modifiable. When modification isn't supported, those methods will throw an {@link * UnsupportedOperationException}. * + *

    Implementations

    + * + *
      + *
    • {@link ImmutableTable} + *
    • {@link HashBasedTable} + *
    • {@link TreeBasedTable} + *
    • {@link ArrayTable} + *
    • {@link Tables#newCustomTable Tables.newCustomTable} + *
    + * *

    See the Guava User Guide article on {@code Table}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#table">{@code Table}. * * @author Jared Levy * @param the type of the table row keys @@ -55,7 +65,8 @@ */ @DoNotMock("Use ImmutableTable, HashBasedTable, or another implementation") @GwtCompatible -public interface Table { +public interface Table< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> { // TODO(jlevy): Consider adding methods similar to ConcurrentMap methods. // Accessors @@ -67,29 +78,29 @@ public interface Table { * @param columnKey key of column to search for */ boolean contains( - @NullableDecl @CompatibleWith("R") Object rowKey, - @NullableDecl @CompatibleWith("C") Object columnKey); + @CompatibleWith("R") @Nullable Object rowKey, + @CompatibleWith("C") @Nullable Object columnKey); /** * Returns {@code true} if the table contains a mapping with the specified row key. * * @param rowKey key of row to search for */ - boolean containsRow(@NullableDecl @CompatibleWith("R") Object rowKey); + boolean containsRow(@CompatibleWith("R") @Nullable Object rowKey); /** * Returns {@code true} if the table contains a mapping with the specified column. * * @param columnKey key of column to search for */ - boolean containsColumn(@NullableDecl @CompatibleWith("C") Object columnKey); + boolean containsColumn(@CompatibleWith("C") @Nullable Object columnKey); /** * Returns {@code true} if the table contains a mapping with the specified value. * * @param value value to search for */ - boolean containsValue(@NullableDecl @CompatibleWith("V") Object value); + boolean containsValue(@CompatibleWith("V") @Nullable Object value); /** * Returns the value corresponding to the given row and column keys, or {@code null} if no such @@ -98,10 +109,9 @@ boolean contains( * @param rowKey key of row to search for * @param columnKey key of column to search for */ - @NullableDecl - V get( - @NullableDecl @CompatibleWith("R") Object rowKey, - @NullableDecl @CompatibleWith("C") Object columnKey); + @Nullable V get( + @CompatibleWith("R") @Nullable Object rowKey, + @CompatibleWith("C") @Nullable Object columnKey); /** Returns {@code true} if the table contains no mappings. */ boolean isEmpty(); @@ -114,7 +124,7 @@ V get( * cell views, as returned by {@link #cellSet}, are equal. */ @Override - boolean equals(@NullableDecl Object obj); + boolean equals(@Nullable Object obj); /** * Returns the hash code for this table. The hash code of a table is defined as the hash code of @@ -139,8 +149,8 @@ V get( * for the keys */ @CanIgnoreReturnValue - @NullableDecl - V put(R rowKey, C columnKey, V value); + @Nullable V put( + @ParametricNullness R rowKey, @ParametricNullness C columnKey, @ParametricNullness V value); /** * Copies all mappings from the specified table to this table. The effect is equivalent to calling @@ -158,10 +168,9 @@ V get( * @return the value previously associated with the keys, or {@code null} if no such value existed */ @CanIgnoreReturnValue - @NullableDecl - V remove( - @NullableDecl @CompatibleWith("R") Object rowKey, - @NullableDecl @CompatibleWith("C") Object columnKey); + @Nullable V remove( + @CompatibleWith("R") @Nullable Object rowKey, + @CompatibleWith("C") @Nullable Object columnKey); // Views @@ -175,7 +184,7 @@ V remove( * @param rowKey key of row to search for in the table * @return the corresponding map from column keys to values */ - Map row(R rowKey); + Map row(@ParametricNullness R rowKey); /** * Returns a view of all mappings that have the given column key. For each row key / column key / @@ -187,7 +196,7 @@ V remove( * @param columnKey key of column to search for in the table * @return the corresponding map from row keys to values */ - Map column(C columnKey); + Map column(@ParametricNullness C columnKey); /** * Returns a set of all row key / column key / value triplets. Changes to the returned set will @@ -253,17 +262,18 @@ V remove( * * @since 7.0 */ - interface Cell { + interface Cell< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> { /** Returns the row key of this cell. */ - @NullableDecl + @ParametricNullness R getRowKey(); /** Returns the column key of this cell. */ - @NullableDecl + @ParametricNullness C getColumnKey(); /** Returns the value of this cell. */ - @NullableDecl + @ParametricNullness V getValue(); /** @@ -271,7 +281,7 @@ interface Cell { * equal row keys, column keys, and values. */ @Override - boolean equals(@NullableDecl Object obj); + boolean equals(@Nullable Object obj); /** * Returns the hash code of this cell. diff --git a/android/guava/src/com/google/common/collect/TableCollectors.java b/android/guava/src/com/google/common/collect/TableCollectors.java new file mode 100644 index 000000000000..c028f582acf3 --- /dev/null +++ b/android/guava/src/com/google/common/collect/TableCollectors.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.Tables.AbstractCell; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; + +/** Collectors utilities for {@code common.collect.Table} internals. */ +@GwtCompatible +@IgnoreJRERequirement // used only from APIs with Java 8 types in them +final class TableCollectors { + + static + Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction) { + checkNotNull(rowFunction, "rowFunction"); + checkNotNull(columnFunction, "columnFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + (Supplier>) ImmutableTable.Builder::new, + (builder, t) -> + builder.put(rowFunction.apply(t), columnFunction.apply(t), valueFunction.apply(t)), + ImmutableTable.Builder::combine, + ImmutableTable.Builder::buildOrThrow); + } + + static + Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + + checkNotNull(rowFunction, "rowFunction"); + checkNotNull(columnFunction, "columnFunction"); + checkNotNull(valueFunction, "valueFunction"); + checkNotNull(mergeFunction, "mergeFunction"); + + /* + * No mutable Table exactly matches the insertion order behavior of ImmutableTable.Builder, but + * the Builder can't efficiently support merging of duplicate values. Getting around this + * requires some work. + */ + + return Collector.of( + ImmutableTableCollectorState::new, + (state, input) -> + state.put( + rowFunction.apply(input), + columnFunction.apply(input), + valueFunction.apply(input), + mergeFunction), + (s1, s2) -> s1.combine(s2, mergeFunction), + state -> state.toTable()); + } + + static < + T extends @Nullable Object, + R extends @Nullable Object, + C extends @Nullable Object, + V, + I extends Table> + Collector toTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + Supplier tableSupplier) { + return TableCollectors.toTable( + rowFunction, + columnFunction, + valueFunction, + (v1, v2) -> { + throw new IllegalStateException("Conflicting values " + v1 + " and " + v2); + }, + tableSupplier); + } + + static < + T extends @Nullable Object, + R extends @Nullable Object, + C extends @Nullable Object, + V, + I extends Table> + Collector toTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + BinaryOperator mergeFunction, + Supplier tableSupplier) { + checkNotNull(rowFunction); + checkNotNull(columnFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + checkNotNull(tableSupplier); + return Collector.of( + tableSupplier, + (table, input) -> + mergeTables( + table, + rowFunction.apply(input), + columnFunction.apply(input), + valueFunction.apply(input), + mergeFunction), + (table1, table2) -> { + for (Table.Cell cell2 : table2.cellSet()) { + mergeTables( + table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction); + } + return table1; + }); + } + + private static final class ImmutableTableCollectorState { + final List> insertionOrder = new ArrayList<>(); + final Table> table = HashBasedTable.create(); + + void put(R row, C column, V value, BinaryOperator merger) { + MutableCell oldCell = table.get(row, column); + if (oldCell == null) { + MutableCell cell = new MutableCell<>(row, column, value); + insertionOrder.add(cell); + table.put(row, column, cell); + } else { + oldCell.merge(value, merger); + } + } + + /* + * While the current implementation returns `this`, that's not something we mean to guarantee. + * Anyway, the purpose of this method is to implement a BinaryOperator combiner for a Collector, + * so its return value will get used naturally. + */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") + ImmutableTableCollectorState combine( + ImmutableTableCollectorState other, BinaryOperator merger) { + for (MutableCell cell : other.insertionOrder) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue(), merger); + } + return this; + } + + ImmutableTable toTable() { + return ImmutableTable.copyOf(insertionOrder); + } + } + + @IgnoreJRERequirement // see enclosing class (whose annotation Animal Sniffer ignores here...) + private static final class MutableCell extends AbstractCell { + private final R row; + private final C column; + private V value; + + MutableCell(R row, C column, V value) { + this.row = checkNotNull(row, "row"); + this.column = checkNotNull(column, "column"); + this.value = checkNotNull(value, "value"); + } + + @Override + public R getRowKey() { + return row; + } + + @Override + public C getColumnKey() { + return column; + } + + @Override + public V getValue() { + return value; + } + + void merge(V value, BinaryOperator mergeFunction) { + checkNotNull(value, "value"); + this.value = checkNotNull(mergeFunction.apply(this.value, value), "mergeFunction.apply"); + } + } + + private static void mergeTables( + Table table, + @ParametricNullness R row, + @ParametricNullness C column, + V value, + BinaryOperator mergeFunction) { + checkNotNull(value); + V oldValue = table.get(row, column); + if (oldValue == null) { + table.put(row, column, value); + } else { + V newValue = mergeFunction.apply(oldValue, value); + if (newValue == null) { + table.remove(row, column); + } else { + table.put(row, column, newValue); + } + } + } + + private TableCollectors() {} +} diff --git a/android/guava/src/com/google/common/collect/Tables.java b/android/guava/src/com/google/common/collect/Tables.java index 77c920cda220..89479d7fb903 100644 --- a/android/guava/src/com/google/common/collect/Tables.java +++ b/android/guava/src/com/google/common/collect/Tables.java @@ -18,11 +18,14 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.util.Collections.unmodifiableMap; +import static java.util.Collections.unmodifiableSortedMap; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; -import com.google.common.base.Objects; import com.google.common.base.Supplier; import com.google.common.collect.Table.Cell; import java.io.Serializable; @@ -30,16 +33,19 @@ import java.util.Collections; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.BinaryOperator; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * Provides static methods that involve a {@code Table}. * *

    See the Guava User Guide article on {@code Tables}. + * "https://github.com/google/guava/wiki/CollectionUtilitiesExplained#tables">{@code Tables}. * * @author Jared Levy * @author Louis Wasserman @@ -49,6 +55,65 @@ public final class Tables { private Tables() {} + /** + * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the + * specified supplier, whose cells are generated by applying the provided mapping functions to the + * input elements. Cells are inserted into the generated {@code Table} in encounter order. + * + *

    If multiple input elements map to the same row and column, an {@code IllegalStateException} + * is thrown when the collection operation is performed. + * + *

    To collect to an {@link ImmutableTable}, use {@link ImmutableTable#toImmutableTable}. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static < + T extends @Nullable Object, + R extends @Nullable Object, + C extends @Nullable Object, + V, + I extends Table> + Collector toTable( + java.util.function.Function rowFunction, + java.util.function.Function columnFunction, + java.util.function.Function valueFunction, + java.util.function.Supplier tableSupplier) { + return TableCollectors.toTable( + rowFunction, columnFunction, valueFunction, tableSupplier); + } + + /** + * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the + * specified supplier, whose cells are generated by applying the provided mapping functions to the + * input elements. Cells are inserted into the generated {@code Table} in encounter order. + * + *

    If multiple input elements map to the same row and column, the specified merging function is + * used to combine the values. Like {@link + * java.util.stream.Collectors#toMap(java.util.function.Function, java.util.function.Function, + * BinaryOperator, java.util.function.Supplier)}, this Collector throws a {@code + * NullPointerException} on null values returned from {@code valueFunction}, and treats nulls + * returned from {@code mergeFunction} as removals of that row/column pair. + * + * @since 33.2.0 (available since 21.0 in guava-jre) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static < + T extends @Nullable Object, + R extends @Nullable Object, + C extends @Nullable Object, + V, + I extends Table> + Collector toTable( + java.util.function.Function rowFunction, + java.util.function.Function columnFunction, + java.util.function.Function valueFunction, + BinaryOperator mergeFunction, + java.util.function.Supplier tableSupplier) { + return TableCollectors.toTable( + rowFunction, columnFunction, valueFunction, mergeFunction, tableSupplier); + } + /** * Returns an immutable cell with the specified row key, column key, and value. * @@ -58,61 +123,74 @@ private Tables() {} * @param columnKey the column key to be associated with the returned cell * @param value the value to be associated with the returned cell */ - public static Cell immutableCell( - @NullableDecl R rowKey, @NullableDecl C columnKey, @NullableDecl V value) { + public static + Cell immutableCell( + @ParametricNullness R rowKey, + @ParametricNullness C columnKey, + @ParametricNullness V value) { return new ImmutableCell<>(rowKey, columnKey, value); } - static final class ImmutableCell extends AbstractCell implements Serializable { - @NullableDecl private final R rowKey; - @NullableDecl private final C columnKey; - @NullableDecl private final V value; - - ImmutableCell(@NullableDecl R rowKey, @NullableDecl C columnKey, @NullableDecl V value) { + static final class ImmutableCell< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + extends AbstractCell implements Serializable { + @ParametricNullness private final R rowKey; + @ParametricNullness private final C columnKey; + @ParametricNullness private final V value; + + ImmutableCell( + @ParametricNullness R rowKey, + @ParametricNullness C columnKey, + @ParametricNullness V value) { this.rowKey = rowKey; this.columnKey = columnKey; this.value = value; } @Override + @ParametricNullness public R getRowKey() { return rowKey; } @Override + @ParametricNullness public C getColumnKey() { return columnKey; } @Override + @ParametricNullness public V getValue() { return value; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - abstract static class AbstractCell implements Cell { + abstract static class AbstractCell< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + implements Cell { // needed for serialization AbstractCell() {} @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } if (obj instanceof Cell) { Cell other = (Cell) obj; - return Objects.equal(getRowKey(), other.getRowKey()) - && Objects.equal(getColumnKey(), other.getColumnKey()) - && Objects.equal(getValue(), other.getValue()); + return Objects.equals(getRowKey(), other.getRowKey()) + && Objects.equals(getColumnKey(), other.getColumnKey()) + && Objects.equals(getValue(), other.getValue()); } return false; } @Override public int hashCode() { - return Objects.hashCode(getRowKey(), getColumnKey(), getValue()); + return Objects.hash(getRowKey(), getColumnKey(), getValue()); } @Override @@ -133,13 +211,16 @@ public String toString() { * columnKeySet().iterator()} doesn't. With a transposed {@link HashBasedTable}, it's the other * way around. */ - public static Table transpose(Table table) { + public static + Table transpose(Table table) { return (table instanceof TransposeTable) ? ((TransposeTable) table).original : new TransposeTable(table); } - private static class TransposeTable extends AbstractTable { + private static final class TransposeTable< + C extends @Nullable Object, R extends @Nullable Object, V extends @Nullable Object> + extends AbstractTable { final Table original; TransposeTable(Table original) { @@ -152,7 +233,7 @@ public void clear() { } @Override - public Map column(R columnKey) { + public Map column(@ParametricNullness R columnKey) { return original.row(columnKey); } @@ -167,32 +248,35 @@ public Map> columnMap() { } @Override - public boolean contains(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { return original.contains(columnKey, rowKey); } @Override - public boolean containsColumn(@NullableDecl Object columnKey) { + public boolean containsColumn(@Nullable Object columnKey) { return original.containsRow(columnKey); } @Override - public boolean containsRow(@NullableDecl Object rowKey) { + public boolean containsRow(@Nullable Object rowKey) { return original.containsColumn(rowKey); } @Override - public boolean containsValue(@NullableDecl Object value) { + public boolean containsValue(@Nullable Object value) { return original.containsValue(value); } @Override - public V get(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V get(@Nullable Object rowKey, @Nullable Object columnKey) { return original.get(columnKey, rowKey); } @Override - public V put(C rowKey, R columnKey, V value) { + public @Nullable V put( + @ParametricNullness C rowKey, + @ParametricNullness R columnKey, + @ParametricNullness V value) { return original.put(columnKey, rowKey, value); } @@ -202,12 +286,12 @@ public void putAll(Table table) { } @Override - public V remove(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { return original.remove(columnKey, rowKey); } @Override - public Map row(C rowKey) { + public Map row(@ParametricNullness C rowKey) { return original.column(rowKey); } @@ -231,22 +315,18 @@ public Collection values() { return original.values(); } - // Will cast TRANSPOSE_CELL to a type that always succeeds - private static final Function, Cell> TRANSPOSE_CELL = - new Function, Cell>() { - @Override - public Cell apply(Cell cell) { - return immutableCell(cell.getColumnKey(), cell.getRowKey(), cell.getValue()); - } - }; - - @SuppressWarnings("unchecked") @Override Iterator> cellIterator() { - return Iterators.transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); + return Iterators.transform(original.cellSet().iterator(), Tables::transposeCell); } } + private static < + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + Cell transposeCell(Cell cell) { + return immutableCell(cell.getColumnKey(), cell.getRowKey(), cell.getValue()); + } + /** * Creates a table that uses the specified backing map and factory. It can generate a table based * on arbitrary {@link Map} classes. @@ -285,7 +365,6 @@ Iterator> cellIterator() { * @throws IllegalArgumentException if {@code backingMap} is not empty * @since 10.0 */ - @Beta public static Table newCustomTable( Map> backingMap, Supplier> factory) { checkArgument(backingMap.isEmpty()); @@ -315,13 +394,22 @@ public static Table newCustomTable( * * @since 10.0 */ - @Beta - public static Table transformValues( - Table fromTable, Function function) { + public static < + R extends @Nullable Object, + C extends @Nullable Object, + V1 extends @Nullable Object, + V2 extends @Nullable Object> + Table transformValues( + Table fromTable, Function function) { return new TransformedTable<>(fromTable, function); } - private static class TransformedTable extends AbstractTable { + private static final class TransformedTable< + R extends @Nullable Object, + C extends @Nullable Object, + V1 extends @Nullable Object, + V2 extends @Nullable Object> + extends AbstractTable { final Table fromTable; final Function function; @@ -331,15 +419,18 @@ private static class TransformedTable extends AbstractTable table) { } @Override - public V2 remove(Object rowKey, Object columnKey) { + public @Nullable V2 remove(@Nullable Object rowKey, @Nullable Object columnKey) { return contains(rowKey, columnKey) - ? function.apply(fromTable.remove(rowKey, columnKey)) + // The cast is safe because of the contains() check. + ? function.apply(uncheckedCastNullableTToT(fromTable.remove(rowKey, columnKey))) : null; } @Override - public Map row(R rowKey) { + public Map row(@ParametricNullness R rowKey) { return Maps.transformValues(fromTable.row(rowKey), function); } @Override - public Map column(C columnKey) { + public Map column(@ParametricNullness C columnKey) { return Maps.transformValues(fromTable.column(columnKey), function); } - Function, Cell> cellFunction() { - return new Function, Cell>() { - @Override - public Cell apply(Cell cell) { - return immutableCell( - cell.getRowKey(), cell.getColumnKey(), function.apply(cell.getValue())); - } - }; + Cell applyToValue(Cell cell) { + return immutableCell(cell.getRowKey(), cell.getColumnKey(), function.apply(cell.getValue())); } @Override Iterator> cellIterator() { - return Iterators.transform(fromTable.cellSet().iterator(), cellFunction()); + return Iterators.transform(fromTable.cellSet().iterator(), this::applyToValue); } @Override @@ -411,26 +500,13 @@ Collection createValues() { @Override public Map> rowMap() { - Function, Map> rowFunction = - new Function, Map>() { - @Override - public Map apply(Map row) { - return Maps.transformValues(row, function); - } - }; - return Maps.transformValues(fromTable.rowMap(), rowFunction); + return Maps.transformValues(fromTable.rowMap(), row -> Maps.transformValues(row, function)); } @Override public Map> columnMap() { - Function, Map> columnFunction = - new Function, Map>() { - @Override - public Map apply(Map column) { - return Maps.transformValues(column, function); - } - }; - return Maps.transformValues(fromTable.columnMap(), columnFunction); + return Maps.transformValues( + fromTable.columnMap(), column -> Maps.transformValues(column, function)); } } @@ -446,13 +522,14 @@ public Map apply(Map column) { * * @since 11.0 */ - public static Table unmodifiableTable( - Table table) { + public static + Table unmodifiableTable(Table table) { return new UnmodifiableTable<>(table); } - private static class UnmodifiableTable extends ForwardingTable - implements Serializable { + private static class UnmodifiableTable< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + extends ForwardingTable implements Serializable { final Table delegate; UnmodifiableTable(Table delegate) { @@ -476,7 +553,7 @@ public void clear() { } @Override - public Map column(@NullableDecl C columnKey) { + public Map column(@ParametricNullness C columnKey) { return Collections.unmodifiableMap(super.column(columnKey)); } @@ -487,12 +564,14 @@ public Set columnKeySet() { @Override public Map> columnMap() { - Function, Map> wrapper = unmodifiableWrapper(); - return Collections.unmodifiableMap(Maps.transformValues(super.columnMap(), wrapper)); + return unmodifiableMap(Maps.transformValues(super.columnMap(), Collections::unmodifiableMap)); } @Override - public V put(@NullableDecl R rowKey, @NullableDecl C columnKey, @NullableDecl V value) { + public @Nullable V put( + @ParametricNullness R rowKey, + @ParametricNullness C columnKey, + @ParametricNullness V value) { throw new UnsupportedOperationException(); } @@ -502,12 +581,12 @@ public void putAll(Table table) { } @Override - public V remove(@NullableDecl Object rowKey, @NullableDecl Object columnKey) { + public @Nullable V remove(@Nullable Object rowKey, @Nullable Object columnKey) { throw new UnsupportedOperationException(); } @Override - public Map row(@NullableDecl R rowKey) { + public Map row(@ParametricNullness R rowKey) { return Collections.unmodifiableMap(super.row(rowKey)); } @@ -518,8 +597,7 @@ public Set rowKeySet() { @Override public Map> rowMap() { - Function, Map> wrapper = unmodifiableWrapper(); - return Collections.unmodifiableMap(Maps.transformValues(super.rowMap(), wrapper)); + return unmodifiableMap(Maps.transformValues(super.rowMap(), Collections::unmodifiableMap)); } @Override @@ -527,7 +605,7 @@ public Collection values() { return Collections.unmodifiableCollection(super.values()); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -542,9 +620,9 @@ public Collection values() { * @return an unmodifiable view of the specified table * @since 11.0 */ - @Beta - public static RowSortedTable unmodifiableRowSortedTable( - RowSortedTable table) { + public static + RowSortedTable unmodifiableRowSortedTable( + RowSortedTable table) { /* * It's not ? extends R, because it's technically not covariant in R. Specifically, * table.rowMap().comparator() could return a comparator that only works for the ? extends R. @@ -553,10 +631,11 @@ public static RowSortedTable unmodifiableRowSortedTable( return new UnmodifiableRowSortedMap<>(table); } - static final class UnmodifiableRowSortedMap extends UnmodifiableTable - implements RowSortedTable { + private static final class UnmodifiableRowSortedMap< + R extends @Nullable Object, C extends @Nullable Object, V extends @Nullable Object> + extends UnmodifiableTable implements RowSortedTable { - public UnmodifiableRowSortedMap(RowSortedTable delegate) { + UnmodifiableRowSortedMap(RowSortedTable delegate) { super(delegate); } @@ -567,8 +646,8 @@ protected RowSortedTable delegate() { @Override public SortedMap> rowMap() { - Function, Map> wrapper = unmodifiableWrapper(); - return Collections.unmodifiableSortedMap(Maps.transformValues(delegate().rowMap(), wrapper)); + return unmodifiableSortedMap( + Maps.transformValues(delegate().rowMap(), Collections::unmodifiableMap)); } @Override @@ -576,22 +655,9 @@ public SortedSet rowKeySet() { return Collections.unmodifiableSortedSet(delegate().rowKeySet()); } - private static final long serialVersionUID = 0; - } - - @SuppressWarnings("unchecked") - private static Function, Map> unmodifiableWrapper() { - return (Function) UNMODIFIABLE_WRAPPER; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } - private static final Function, ? extends Map> UNMODIFIABLE_WRAPPER = - new Function, Map>() { - @Override - public Map apply(Map input) { - return Collections.unmodifiableMap(input); - } - }; - /** * Returns a synchronized (thread-safe) table backed by the specified table. In order to guarantee * serial access, it is critical that all access to the backing table is accomplished @@ -600,8 +666,8 @@ public Map apply(Map input) { *

    It is imperative that the user manually synchronize on the returned table when accessing any * of its collection views: * - *

    {@code
    -   * Table table = Tables.synchronizedTable(HashBasedTable.create());
    +   * {@snippet :
    +   * Table table = Tables.synchronizedTable(HashBasedTable.create());
        * ...
        * Map row = table.row(rowKey);  // Needn't be in synchronized block
        * ...
    @@ -611,7 +677,7 @@ public Map apply(Map input) {
        *     foo(i.next());
        *   }
        * }
    -   * }
    + * } * *

    Failure to follow this advice may result in non-deterministic behavior. * @@ -621,11 +687,13 @@ public Map apply(Map input) { * @return a synchronized view of the specified table * @since 22.0 */ - public static Table synchronizedTable(Table table) { + @J2ktIncompatible // Synchronized + public static + Table synchronizedTable(Table table) { return Synchronized.table(table, null); } - static boolean equalsImpl(Table table, @NullableDecl Object obj) { + static boolean equalsImpl(Table table, @Nullable Object obj) { if (obj == table) { return true; } else if (obj instanceof Table) { diff --git a/android/guava/src/com/google/common/collect/TopKSelector.java b/android/guava/src/com/google/common/collect/TopKSelector.java index 10b31bdfbb4b..29fadc397d8b 100644 --- a/android/guava/src/com/google/common/collect/TopKSelector.java +++ b/android/guava/src/com/google/common/collect/TopKSelector.java @@ -18,16 +18,20 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.lang.Math.max; +import static java.util.Arrays.asList; +import static java.util.Arrays.sort; +import static java.util.Collections.unmodifiableList; import com.google.common.annotations.GwtCompatible; import com.google.common.math.IntMath; import java.math.RoundingMode; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An accumulator that selects the "top" {@code k} elements added to it, relative to a provided @@ -51,7 +55,8 @@ * @author Louis Wasserman */ @GwtCompatible -final class TopKSelector { +final class TopKSelector< + T extends @Nullable Object> { /** * Returns a {@code TopKSelector} that collects the lowest {@code k} elements added to it, @@ -70,8 +75,9 @@ public static > TopKSelector least(int k) { * * @throws IllegalArgumentException if {@code k < 0} or {@code k > Integer.MAX_VALUE / 2} */ - public static TopKSelector least(int k, Comparator comparator) { - return new TopKSelector(comparator, k); + public static TopKSelector least( + int k, Comparator comparator) { + return new TopKSelector<>(comparator, k); } /** @@ -91,8 +97,9 @@ public static > TopKSelector greatest(int k) * * @throws IllegalArgumentException if {@code k < 0} or {@code k > Integer.MAX_VALUE / 2} */ - public static TopKSelector greatest(int k, Comparator comparator) { - return new TopKSelector(Ordering.from(comparator).reverse(), k); + public static TopKSelector greatest( + int k, Comparator comparator) { + return new TopKSelector<>(Ordering.from(comparator).reverse(), k); } private final int k; @@ -103,21 +110,22 @@ public static TopKSelector greatest(int k, Comparator comparat * for the top k elements. Whenever the buffer is filled, we quickselect the top k elements to the * range [0, k) and ignore the remaining elements. */ - private final T[] buffer; + private final @Nullable T[] buffer; private int bufferSize; /** * The largest of the lowest k elements we've seen so far relative to this comparator. If * bufferSize ≥ k, then we can ignore any elements greater than this value. */ - @NullableDecl private T threshold; + private @Nullable T threshold; + @SuppressWarnings("unchecked") // TODO(cpovirk): Consider storing Object[] instead of T[]. private TopKSelector(Comparator comparator, int k) { this.comparator = checkNotNull(comparator, "comparator"); this.k = k; checkArgument(k >= 0, "k (%s) must be >= 0", k); checkArgument(k <= Integer.MAX_VALUE / 2, "k (%s) must be <= Integer.MAX_VALUE / 2", k); - this.buffer = (T[]) new Object[IntMath.checkedMultiply(k, 2)]; + this.buffer = (T[]) new Object[Math.multiplyExact(k, 2)]; this.bufferSize = 0; this.threshold = null; } @@ -126,7 +134,7 @@ private TopKSelector(Comparator comparator, int k) { * Adds {@code elem} as a candidate for the top {@code k} elements. This operation takes amortized * O(1) time. */ - public void offer(@NullableDecl T elem) { + public void offer(@ParametricNullness T elem) { if (k == 0) { return; } else if (bufferSize == 0) { @@ -135,10 +143,12 @@ public void offer(@NullableDecl T elem) { bufferSize = 1; } else if (bufferSize < k) { buffer[bufferSize++] = elem; - if (comparator.compare(elem, threshold) > 0) { + // uncheckedCastNullableTToT is safe because bufferSize > 0. + if (comparator.compare(elem, uncheckedCastNullableTToT(threshold)) > 0) { threshold = elem; } - } else if (comparator.compare(elem, threshold) < 0) { + // uncheckedCastNullableTToT is safe because bufferSize > 0. + } else if (comparator.compare(elem, uncheckedCastNullableTToT(threshold)) < 0) { // Otherwise, we can ignore elem; we've seen k better elements. buffer[bufferSize++] = elem; if (bufferSize == 2 * k) { @@ -169,23 +179,27 @@ private void trim() { if (pivotNewIndex > k) { right = pivotNewIndex - 1; } else if (pivotNewIndex < k) { - left = Math.max(pivotNewIndex, left + 1); + left = max(pivotNewIndex, left + 1); minThresholdPosition = pivotNewIndex; } else { break; } iterations++; if (iterations >= maxIterations) { + @SuppressWarnings("nullness") // safe because we pass sort() a range that contains real Ts + T[] castBuffer = (T[]) buffer; // We've already taken O(k log k), let's make sure we don't take longer than O(k log k). - Arrays.sort(buffer, left, right, comparator); + sort(castBuffer, left, right + 1, comparator); break; } } bufferSize = k; - threshold = buffer[minThresholdPosition]; + threshold = uncheckedCastNullableTToT(buffer[minThresholdPosition]); for (int i = minThresholdPosition + 1; i < k; i++) { - if (comparator.compare(buffer[i], threshold) > 0) { + if (comparator.compare( + uncheckedCastNullableTToT(buffer[i]), uncheckedCastNullableTToT(threshold)) + > 0) { threshold = buffer[i]; } } @@ -198,12 +212,12 @@ private void trim() { * (pivotNewIndex, right] is greater than pivotValue. */ private int partition(int left, int right, int pivotIndex) { - T pivotValue = buffer[pivotIndex]; + T pivotValue = uncheckedCastNullableTToT(buffer[pivotIndex]); buffer[pivotIndex] = buffer[right]; int pivotNewIndex = left; for (int i = left; i < right; i++) { - if (comparator.compare(buffer[i], pivotValue) < 0) { + if (comparator.compare(uncheckedCastNullableTToT(buffer[i]), pivotValue) < 0) { swap(pivotNewIndex, i); pivotNewIndex++; } @@ -219,6 +233,19 @@ private void swap(int i, int j) { buffer[j] = tmp; } + /* + * While the current implementation returns `this`, that's not something we mean to guarantee. + * Anyway, the purpose of this method is to implement a BinaryOperator combiner for a Collector, + * so its return value will get used naturally. + */ + @SuppressWarnings("CanIgnoreReturnValueSuggester") + TopKSelector combine(TopKSelector other) { + for (int i = 0; i < other.bufferSize; i++) { + this.offer(uncheckedCastNullableTToT(other.buffer[i])); + } + return this; + } + /** * Adds each member of {@code elements} as a candidate for the top {@code k} elements. This * operation takes amortized linear time in the length of {@code elements}. @@ -253,13 +280,17 @@ public void offerAll(Iterator elements) { * this {@code TopKSelector}. This method returns in O(k log k) time. */ public List topK() { - Arrays.sort(buffer, 0, bufferSize, comparator); + @SuppressWarnings("nullness") // safe because we pass sort() a range that contains real Ts + T[] castBuffer = (T[]) buffer; + sort(castBuffer, 0, bufferSize, comparator); if (bufferSize > k) { Arrays.fill(buffer, k, buffer.length, null); bufferSize = k; threshold = buffer[k - 1]; } + // Up to bufferSize, all elements of buffer are real Ts (not null unless T includes null) + T[] topK = Arrays.copyOf(castBuffer, bufferSize); // we have to support null elements, so no ImmutableList for us - return Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(buffer, bufferSize))); + return unmodifiableList(asList(topK)); } } diff --git a/android/guava/src/com/google/common/collect/TransformedIterator.java b/android/guava/src/com/google/common/collect/TransformedIterator.java index b7214b8abd75..6499e7d80b49 100644 --- a/android/guava/src/com/google/common/collect/TransformedIterator.java +++ b/android/guava/src/com/google/common/collect/TransformedIterator.java @@ -20,6 +20,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.Iterator; +import org.jspecify.annotations.Nullable; /** * An iterator that transforms a backing iterator; for internal use. This avoids the object overhead @@ -28,14 +29,16 @@ * @author Louis Wasserman */ @GwtCompatible -abstract class TransformedIterator implements Iterator { +abstract class TransformedIterator + implements Iterator { final Iterator backingIterator; TransformedIterator(Iterator backingIterator) { this.backingIterator = checkNotNull(backingIterator); } - abstract T transform(F from); + @ParametricNullness + abstract T transform(@ParametricNullness F from); @Override public final boolean hasNext() { @@ -43,6 +46,7 @@ public final boolean hasNext() { } @Override + @ParametricNullness public final T next() { return transform(backingIterator.next()); } diff --git a/android/guava/src/com/google/common/collect/TransformedListIterator.java b/android/guava/src/com/google/common/collect/TransformedListIterator.java index ac2eea1e1516..111588987ee5 100644 --- a/android/guava/src/com/google/common/collect/TransformedListIterator.java +++ b/android/guava/src/com/google/common/collect/TransformedListIterator.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import java.util.ListIterator; +import org.jspecify.annotations.Nullable; /** * An iterator that transforms a backing list iterator; for internal use. This avoids the object @@ -27,14 +28,14 @@ * @author Louis Wasserman */ @GwtCompatible -abstract class TransformedListIterator extends TransformedIterator - implements ListIterator { +abstract class TransformedListIterator + extends TransformedIterator implements ListIterator { TransformedListIterator(ListIterator backingIterator) { super(backingIterator); } private ListIterator backingIterator() { - return Iterators.cast(backingIterator); + return (ListIterator) backingIterator; } @Override @@ -43,6 +44,7 @@ public final boolean hasPrevious() { } @Override + @ParametricNullness public final T previous() { return transform(backingIterator().previous()); } @@ -58,12 +60,12 @@ public final int previousIndex() { } @Override - public void set(T element) { + public void set(@ParametricNullness T element) { throw new UnsupportedOperationException(); } @Override - public void add(T element) { + public void add(@ParametricNullness T element) { throw new UnsupportedOperationException(); } } diff --git a/android/guava/src/com/google/common/collect/TreeBasedTable.java b/android/guava/src/com/google/common/collect/TreeBasedTable.java index dc665eb24e9c..395faddfff9c 100644 --- a/android/guava/src/com/google/common/collect/TreeBasedTable.java +++ b/android/guava/src/com/google/common/collect/TreeBasedTable.java @@ -18,10 +18,15 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Iterators.mergeSorted; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Function; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Supplier; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.Comparator; import java.util.Iterator; @@ -31,7 +36,7 @@ import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@code Table} whose row keys and column keys are ordered by their natural @@ -59,17 +64,17 @@ * concurrently and one of the threads modifies the table, it must be synchronized externally. * *

    See the Guava User Guide article on {@code Table}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#table">{@code Table}. * * @author Jared Levy * @author Louis Wasserman * @since 7.0 */ -@GwtCompatible(serializable = true) +@GwtCompatible public class TreeBasedTable extends StandardRowSortedTable { private final Comparator columnComparator; - private static class Factory implements Supplier>, Serializable { + private static final class Factory implements Supplier>, Serializable { final Comparator comparator; Factory(Comparator comparator) { @@ -77,11 +82,11 @@ private static class Factory implements Supplier>, Serializa } @Override - public TreeMap get() { + public Map get() { return new TreeMap<>(comparator); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -92,6 +97,7 @@ public TreeMap get() { * instead of {@code R extends Comparable}, and the same for {@code C}. That's * necessary to support classes defined without generics. */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static TreeBasedTable create() { return new TreeBasedTable<>(Ordering.natural(), Ordering.natural()); } @@ -115,7 +121,9 @@ public static TreeBasedTable create( */ public static TreeBasedTable create(TreeBasedTable table) { TreeBasedTable result = - new TreeBasedTable<>(table.rowComparator(), table.columnComparator()); + // requireNonNull is safe, as discussed in rowComparator() below. + new TreeBasedTable<>( + requireNonNull(table.rowKeySet().comparator()), table.columnComparator()); result.putAll(table); return result; } @@ -133,9 +141,16 @@ public static TreeBasedTable create(TreeBasedTable rowComparator() { - return rowKeySet().comparator(); + public final Comparator rowComparator() { + /* + * requireNonNull is safe because the factories require non-null Comparators, which they pass on + * to the backing collections. + */ + return requireNonNull(rowKeySet().comparator()); } /** @@ -168,15 +183,15 @@ public SortedMap row(R rowKey) { return new TreeRow(rowKey); } - private class TreeRow extends Row implements SortedMap { - @NullableDecl final C lowerBound; - @NullableDecl final C upperBound; + private final class TreeRow extends Row implements SortedMap { + final @Nullable C lowerBound; + final @Nullable C upperBound; TreeRow(R rowKey) { this(rowKey, null, null); } - TreeRow(R rowKey, @NullableDecl C lowerBound, @NullableDecl C upperBound) { + TreeRow(R rowKey, @Nullable C lowerBound, @Nullable C upperBound) { super(rowKey); this.lowerBound = lowerBound; this.upperBound = upperBound; @@ -201,7 +216,7 @@ int compare(Object a, Object b) { return cmp.compare(a, b); } - boolean rangeContains(@NullableDecl Object o) { + boolean rangeContains(@Nullable Object o) { return o != null && (lowerBound == null || compare(lowerBound, o) <= 0) && (upperBound == null || compare(upperBound, o) > 0); @@ -227,43 +242,35 @@ public SortedMap tailMap(C fromKey) { @Override public C firstKey() { - SortedMap backing = backingRowMap(); - if (backing == null) { + updateBackingRowMapField(); + if (backingRowMap == null) { throw new NoSuchElementException(); } - return backingRowMap().firstKey(); + return ((SortedMap) backingRowMap).firstKey(); } @Override public C lastKey() { - SortedMap backing = backingRowMap(); - if (backing == null) { + updateBackingRowMapField(); + if (backingRowMap == null) { throw new NoSuchElementException(); } - return backingRowMap().lastKey(); + return ((SortedMap) backingRowMap).lastKey(); } - @NullableDecl transient SortedMap wholeRow; + transient @Nullable SortedMap wholeRow; - /* - * If the row was previously empty, we check if there's a new row here every - * time we're queried. - */ - SortedMap wholeRow() { + // If the row was previously empty, we check if there's a new row here every time we're queried. + void updateWholeRowField() { if (wholeRow == null || (wholeRow.isEmpty() && backingMap.containsKey(rowKey))) { wholeRow = (SortedMap) backingMap.get(rowKey); } - return wholeRow; } @Override - SortedMap backingRowMap() { - return (SortedMap) super.backingRowMap(); - } - - @Override - SortedMap computeBackingRowMap() { - SortedMap map = wholeRow(); + @Nullable SortedMap computeBackingRowMap() { + updateWholeRowField(); + SortedMap map = wholeRow; if (map != null) { if (lowerBound != null) { map = map.tailMap(lowerBound); @@ -278,7 +285,8 @@ SortedMap computeBackingRowMap() { @Override void maintainEmptyInvariant() { - if (wholeRow() != null && wholeRow.isEmpty()) { + updateWholeRowField(); + if (wholeRow != null && wholeRow.isEmpty()) { backingMap.remove(rowKey); wholeRow = null; backingRowMap = null; @@ -286,51 +294,32 @@ void maintainEmptyInvariant() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return rangeContains(key) && super.containsKey(key); } @Override - public V put(C key, V value) { + public @Nullable V put(C key, V value) { checkArgument(rangeContains(checkNotNull(key))); return super.put(key, value); } } - // rowKeySet() and rowMap() are defined here so they appear in the Javadoc. - - @Override - public SortedSet rowKeySet() { - return super.rowKeySet(); - } - - @Override - public SortedMap> rowMap() { - return super.rowMap(); - } - /** Overridden column iterator to return columns values in globally sorted order. */ @Override Iterator createColumnKeyIterator() { - final Comparator comparator = columnComparator(); - - final Iterator merged = - Iterators.mergeSorted( - Iterables.transform( - backingMap.values(), - new Function, Iterator>() { - @Override - public Iterator apply(Map input) { - return input.keySet().iterator(); - } - }), + Comparator comparator = columnComparator(); + + Iterator merged = + mergeSorted( + transform(backingMap.values(), (Map input) -> input.keySet().iterator()), comparator); return new AbstractIterator() { - @NullableDecl C lastValue; + @Nullable C lastValue; @Override - protected C computeNext() { + protected @Nullable C computeNext() { while (merged.hasNext()) { C next = merged.next(); boolean duplicate = lastValue != null && comparator.compare(next, lastValue) == 0; @@ -348,5 +337,5 @@ protected C computeNext() { }; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/TreeMultimap.java b/android/guava/src/com/google/common/collect/TreeMultimap.java index 85531e3e3b33..5be9aa7c2fb6 100644 --- a/android/guava/src/com/google/common/collect/TreeMultimap.java +++ b/android/guava/src/com/google/common/collect/TreeMultimap.java @@ -17,9 +17,11 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -31,7 +33,7 @@ import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@code Multimap} whose keys and values are ordered by their natural ordering or @@ -64,21 +66,22 @@ * with a call to {@link Multimaps#synchronizedSortedSetMultimap}. * *

    See the Guava User Guide article on {@code - * Multimap}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}. * * @author Jared Levy * @author Louis Wasserman * @since 2.0 */ -@GwtCompatible(serializable = true, emulated = true) -public class TreeMultimap extends AbstractSortedKeySortedSetMultimap { +@GwtCompatible +public class TreeMultimap + extends AbstractSortedKeySortedSetMultimap { private transient Comparator keyComparator; private transient Comparator valueComparator; /** * Creates an empty {@code TreeMultimap} ordered by the natural ordering of its keys and values. */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static TreeMultimap create() { return new TreeMultimap<>(Ordering.natural(), Ordering.natural()); } @@ -90,7 +93,7 @@ public static TreeMultimap cr * @param keyComparator the comparator that determines the key ordering * @param valueComparator the comparator that determines the value ordering */ - public static TreeMultimap create( + public static TreeMultimap create( Comparator keyComparator, Comparator valueComparator) { return new TreeMultimap<>(checkNotNull(keyComparator), checkNotNull(valueComparator)); } @@ -101,6 +104,7 @@ public static TreeMultimap create( * * @param multimap the multimap whose contents are copied to this multimap */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static TreeMultimap create( Multimap multimap) { return new TreeMultimap<>(Ordering.natural(), Ordering.natural(), multimap); @@ -134,13 +138,13 @@ Map> createAsMap() { */ @Override SortedSet createCollection() { - return new TreeSet(valueComparator); + return new TreeSet<>(valueComparator); } @Override - Collection createCollection(@NullableDecl K key) { + Collection createCollection(@ParametricNullness K key) { if (key == null) { - keyComparator().compare(key, key); + int unused = keyComparator().compare(key, key); } return super.createCollection(key); } @@ -160,10 +164,12 @@ public Comparator valueComparator() { return valueComparator; } - /** @since 14.0 (present with return type {@code SortedSet} since 2.0) */ + /** + * @since 14.0 (present with return type {@code SortedSet} since 2.0) + */ @Override @GwtIncompatible // NavigableSet - public NavigableSet get(@NullableDecl K key) { + public NavigableSet get(@ParametricNullness K key) { return (NavigableSet) super.get(key); } @@ -199,24 +205,25 @@ public NavigableMap> asMap() { * @serialData key comparator, value comparator, number of distinct keys, and then for each * distinct key: the key, number of values for that key, and key values */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(keyComparator()); stream.writeObject(valueComparator()); Serialization.writeMultimap(this, stream); } - @GwtIncompatible // java.io.ObjectInputStream - @SuppressWarnings("unchecked") // reading data stored by writeObject + @GwtIncompatible + @J2ktIncompatible + @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - keyComparator = checkNotNull((Comparator) stream.readObject()); - valueComparator = checkNotNull((Comparator) stream.readObject()); + keyComparator = requireNonNull((Comparator) stream.readObject()); + valueComparator = requireNonNull((Comparator) stream.readObject()); setMap(new TreeMap>(keyComparator)); Serialization.populateMultimap(this, stream); } - @GwtIncompatible // not needed in emulated source - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/TreeMultiset.java b/android/guava/src/com/google/common/collect/TreeMultiset.java index dac0eab8869a..b359159dbb39 100644 --- a/android/guava/src/com/google/common/collect/TreeMultiset.java +++ b/android/guava/src/com/google/common/collect/TreeMultiset.java @@ -19,10 +19,13 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.CollectPreconditions.checkNonnegative; -import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.lang.Math.max; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.MoreObjects; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -34,7 +37,7 @@ import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A multiset which maintains the ordering of its elements, according to either their natural order @@ -47,15 +50,15 @@ * java.util.Collection} contract, which is specified in terms of {@link Object#equals}. * *

    See the Guava User Guide article on {@code - * Multiset}. + * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}. * * @author Louis Wasserman * @author Jared Levy * @since 2.0 */ -@GwtCompatible(emulated = true) -public final class TreeMultiset extends AbstractSortedMultiset implements Serializable { +@GwtCompatible +public final class TreeMultiset extends AbstractSortedMultiset + implements Serializable { /** * Creates a new, empty multiset, sorted according to the elements' natural order. All elements @@ -69,8 +72,9 @@ public final class TreeMultiset extends AbstractSortedMultiset implements *

    The type specification is {@code }, instead of the more specific * {@code >}, to support classes defined without generics. */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static TreeMultiset create() { - return new TreeMultiset(Ordering.natural()); + return new TreeMultiset<>(Ordering.natural()); } /** @@ -85,7 +89,8 @@ public static TreeMultiset create() { * indicates that the elements' natural ordering should be used. */ @SuppressWarnings("unchecked") - public static TreeMultiset create(@NullableDecl Comparator comparator) { + public static TreeMultiset create( + @Nullable Comparator comparator) { return (comparator == null) ? new TreeMultiset((Comparator) Ordering.natural()) : new TreeMultiset(comparator); @@ -100,6 +105,7 @@ public static TreeMultiset create(@NullableDecl Comparator com *

    The type specification is {@code }, instead of the more specific * {@code >}, to support classes defined without generics. */ + @SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 public static TreeMultiset create(Iterable elements) { TreeMultiset multiset = create(); Iterables.addAll(multiset, elements); @@ -120,7 +126,7 @@ public static TreeMultiset create(Iterable comparator) { super(comparator); this.range = GeneralRange.all(comparator); - this.header = new AvlNode(null, 1); + this.header = new AvlNode<>(); successor(header, header); this.rootReference = new Reference<>(); } @@ -134,7 +140,7 @@ int nodeAggregate(AvlNode node) { } @Override - long treeAggregate(@NullableDecl AvlNode root) { + long treeAggregate(@Nullable AvlNode root) { return (root == null) ? 0 : root.totalCount; } }, @@ -145,14 +151,14 @@ int nodeAggregate(AvlNode node) { } @Override - long treeAggregate(@NullableDecl AvlNode root) { + long treeAggregate(@Nullable AvlNode root) { return (root == null) ? 0 : root.distinctElements; } }; abstract int nodeAggregate(AvlNode node); - abstract long treeAggregate(@NullableDecl AvlNode root); + abstract long treeAggregate(@Nullable AvlNode root); } private long aggregateForEntries(Aggregate aggr) { @@ -167,11 +173,14 @@ private long aggregateForEntries(Aggregate aggr) { return total; } - private long aggregateBelowRange(Aggregate aggr, @NullableDecl AvlNode node) { + private long aggregateBelowRange(Aggregate aggr, @Nullable AvlNode node) { if (node == null) { return 0; } - int cmp = comparator().compare(range.getLowerEndpoint(), node.elem); + // The cast is safe because we call this method only if hasLowerBound(). + int cmp = + comparator() + .compare(uncheckedCastNullableTToT(range.getLowerEndpoint()), node.getElement()); if (cmp < 0) { return aggregateBelowRange(aggr, node.left); } else if (cmp == 0) { @@ -180,9 +189,8 @@ private long aggregateBelowRange(Aggregate aggr, @NullableDecl AvlNode node) return aggr.nodeAggregate(node) + aggr.treeAggregate(node.left); case CLOSED: return aggr.treeAggregate(node.left); - default: - throw new AssertionError(); } + throw new AssertionError(); } else { return aggr.treeAggregate(node.left) + aggr.nodeAggregate(node) @@ -190,11 +198,14 @@ private long aggregateBelowRange(Aggregate aggr, @NullableDecl AvlNode node) } } - private long aggregateAboveRange(Aggregate aggr, @NullableDecl AvlNode node) { + private long aggregateAboveRange(Aggregate aggr, @Nullable AvlNode node) { if (node == null) { return 0; } - int cmp = comparator().compare(range.getUpperEndpoint(), node.elem); + // The cast is safe because we call this method only if hasUpperBound(). + int cmp = + comparator() + .compare(uncheckedCastNullableTToT(range.getUpperEndpoint()), node.getElement()); if (cmp > 0) { return aggregateAboveRange(aggr, node.right); } else if (cmp == 0) { @@ -203,9 +214,8 @@ private long aggregateAboveRange(Aggregate aggr, @NullableDecl AvlNode node) return aggr.nodeAggregate(node) + aggr.treeAggregate(node.right); case CLOSED: return aggr.treeAggregate(node.right); - default: - throw new AssertionError(); } + throw new AssertionError(); } else { return aggr.treeAggregate(node.right) + aggr.nodeAggregate(node) @@ -223,12 +233,12 @@ int distinctElements() { return Ints.saturatedCast(aggregateForEntries(Aggregate.DISTINCT)); } - static int distinctElements(@NullableDecl AvlNode node) { + static int distinctElements(@Nullable AvlNode node) { return (node == null) ? 0 : node.distinctElements; } @Override - public int count(@NullableDecl Object element) { + public int count(@Nullable Object element) { try { @SuppressWarnings("unchecked") E e = (E) element; @@ -244,7 +254,7 @@ public int count(@NullableDecl Object element) { @CanIgnoreReturnValue @Override - public int add(@NullableDecl E element, int occurrences) { + public int add(@ParametricNullness E element, int occurrences) { checkNonnegative(occurrences, "occurrences"); if (occurrences == 0) { return count(element); @@ -252,8 +262,8 @@ public int add(@NullableDecl E element, int occurrences) { checkArgument(range.contains(element)); AvlNode root = rootReference.get(); if (root == null) { - comparator().compare(element, element); - AvlNode newRoot = new AvlNode(element, occurrences); + int unused = comparator().compare(element, element); + AvlNode newRoot = new AvlNode<>(element, occurrences); successor(header, newRoot, header); rootReference.checkAndSet(root, newRoot); return 0; @@ -266,7 +276,7 @@ public int add(@NullableDecl E element, int occurrences) { @CanIgnoreReturnValue @Override - public int remove(@NullableDecl Object element, int occurrences) { + public int remove(@Nullable Object element, int occurrences) { checkNonnegative(occurrences, "occurrences"); if (occurrences == 0) { return count(element); @@ -290,7 +300,7 @@ public int remove(@NullableDecl Object element, int occurrences) { @CanIgnoreReturnValue @Override - public int setCount(@NullableDecl E element, int count) { + public int setCount(@ParametricNullness E element, int count) { checkNonnegative(count, "count"); if (!range.contains(element)) { checkArgument(count == 0); @@ -312,7 +322,7 @@ public int setCount(@NullableDecl E element, int count) { @CanIgnoreReturnValue @Override - public boolean setCount(@NullableDecl E element, int oldCount, int newCount) { + public boolean setCount(@ParametricNullness E element, int oldCount, int newCount) { checkNonnegative(newCount, "newCount"); checkNonnegative(oldCount, "oldCount"); checkArgument(range.contains(element)); @@ -338,8 +348,8 @@ public boolean setCount(@NullableDecl E element, int oldCount, int newCount) { public void clear() { if (!range.hasLowerBound() && !range.hasUpperBound()) { // We can do this in O(n) rather than removing one by one, which could force rebalancing. - for (AvlNode current = header.succ; current != header; ) { - AvlNode next = current.succ; + for (AvlNode current = header.succ(); current != header; ) { + AvlNode next = current.succ(); current.elemCount = 0; // Also clear these fields so that one deleted Entry doesn't retain all elements. @@ -358,9 +368,10 @@ public void clear() { } } - private Entry wrapEntry(final AvlNode baseEntry) { + private Entry wrapEntry(AvlNode baseEntry) { return new Multisets.AbstractEntry() { @Override + @ParametricNullness public E getElement() { return baseEntry.getElement(); } @@ -378,48 +389,48 @@ public int getCount() { } /** Returns the first node in the tree that is in range. */ - @NullableDecl - private AvlNode firstNode() { + private @Nullable AvlNode firstNode() { AvlNode root = rootReference.get(); if (root == null) { return null; } AvlNode node; if (range.hasLowerBound()) { - E endpoint = range.getLowerEndpoint(); - node = rootReference.get().ceiling(comparator(), endpoint); + // The cast is safe because of the hasLowerBound check. + E endpoint = uncheckedCastNullableTToT(range.getLowerEndpoint()); + node = root.ceiling(comparator(), endpoint); if (node == null) { return null; } if (range.getLowerBoundType() == BoundType.OPEN && comparator().compare(endpoint, node.getElement()) == 0) { - node = node.succ; + node = node.succ(); } } else { - node = header.succ; + node = header.succ(); } return (node == header || !range.contains(node.getElement())) ? null : node; } - @NullableDecl - private AvlNode lastNode() { + private @Nullable AvlNode lastNode() { AvlNode root = rootReference.get(); if (root == null) { return null; } AvlNode node; if (range.hasUpperBound()) { - E endpoint = range.getUpperEndpoint(); - node = rootReference.get().floor(comparator(), endpoint); + // The cast is safe because of the hasUpperBound check. + E endpoint = uncheckedCastNullableTToT(range.getUpperEndpoint()); + node = root.floor(comparator(), endpoint); if (node == null) { return null; } if (range.getUpperBoundType() == BoundType.OPEN && comparator().compare(endpoint, node.getElement()) == 0) { - node = node.pred; + node = node.pred(); } } else { - node = header.pred; + node = header.pred(); } return (node == header || !range.contains(node.getElement())) ? null : node; } @@ -432,8 +443,8 @@ Iterator elementIterator() { @Override Iterator> entryIterator() { return new Iterator>() { - AvlNode current = firstNode(); - @NullableDecl Entry prevEntry; + @Nullable AvlNode current = firstNode(); + @Nullable Entry prevEntry; @Override public boolean hasNext() { @@ -452,19 +463,20 @@ public Entry next() { if (!hasNext()) { throw new NoSuchElementException(); } - Entry result = wrapEntry(current); + // requireNonNull is safe because current is only nulled out after iteration is complete. + Entry result = wrapEntry(requireNonNull(current)); prevEntry = result; - if (current.succ == header) { + if (current.succ() == header) { current = null; } else { - current = current.succ; + current = current.succ(); } return result; } @Override public void remove() { - checkRemove(prevEntry != null); + checkState(prevEntry != null, "no calls to next() since the last call to remove()"); setCount(prevEntry.getElement(), 0); prevEntry = null; } @@ -474,8 +486,8 @@ public void remove() { @Override Iterator> descendingEntryIterator() { return new Iterator>() { - AvlNode current = lastNode(); - Entry prevEntry = null; + @Nullable AvlNode current = lastNode(); + @Nullable Entry prevEntry = null; @Override public boolean hasNext() { @@ -494,19 +506,21 @@ public Entry next() { if (!hasNext()) { throw new NoSuchElementException(); } + // requireNonNull is safe because current is only nulled out after iteration is complete. + requireNonNull(current); Entry result = wrapEntry(current); prevEntry = result; - if (current.pred == header) { + if (current.pred() == header) { current = null; } else { - current = current.pred; + current = current.pred(); } return result; } @Override public void remove() { - checkRemove(prevEntry != null); + checkState(prevEntry != null, "no calls to next() since the last call to remove()"); setCount(prevEntry.getElement(), 0); prevEntry = null; } @@ -519,30 +533,29 @@ public Iterator iterator() { } @Override - public SortedMultiset headMultiset(@NullableDecl E upperBound, BoundType boundType) { - return new TreeMultiset( + public SortedMultiset headMultiset(@ParametricNullness E upperBound, BoundType boundType) { + return new TreeMultiset<>( rootReference, range.intersect(GeneralRange.upTo(comparator(), upperBound, boundType)), header); } @Override - public SortedMultiset tailMultiset(@NullableDecl E lowerBound, BoundType boundType) { - return new TreeMultiset( + public SortedMultiset tailMultiset(@ParametricNullness E lowerBound, BoundType boundType) { + return new TreeMultiset<>( rootReference, range.intersect(GeneralRange.downTo(comparator(), lowerBound, boundType)), header); } private static final class Reference { - @NullableDecl private T value; + private @Nullable T value; - @NullableDecl - public T get() { + @Nullable T get() { return value; } - public void checkAndSet(@NullableDecl T expected, T newValue) { + void checkAndSet(@Nullable T expected, @Nullable T newValue) { if (value != expected) { throw new ConcurrentModificationException(); } @@ -554,8 +567,18 @@ void clear() { } } - private static final class AvlNode { - @NullableDecl private final E elem; + private static final class AvlNode { + /* + * For "normal" nodes, the type of this field is `E`, not `@Nullable E` (though note that E is a + * type that can include null, as in a TreeMultiset<@Nullable String>). + * + * For the header node, though, this field contains `null`, regardless of the type of the + * multiset. + * + * Most code that operates on an AvlNode never operates on the header node. Such code can access + * the elem field without a null check by calling getElement(). + */ + private final @Nullable E elem; // elemCount is 0 iff this node has been deleted. private int elemCount; @@ -563,12 +586,23 @@ private static final class AvlNode { private int distinctElements; private long totalCount; private int height; - @NullableDecl private AvlNode left; - @NullableDecl private AvlNode right; - @NullableDecl private AvlNode pred; - @NullableDecl private AvlNode succ; - - AvlNode(@NullableDecl E elem, int elemCount) { + private @Nullable AvlNode left; + private @Nullable AvlNode right; + /* + * pred and succ are nullable after construction, but we always call successor() to initialize + * them immediately thereafter. + * + * They may be subsequently nulled out by TreeMultiset.clear(). I think that the only place that + * we can reference a node whose fields have been cleared is inside the iterator (and presumably + * only under concurrent modification). + * + * To access these fields when you know that they are not null, call the pred() and succ() + * methods, which perform null checks before returning the fields. + */ + private @Nullable AvlNode pred; + private @Nullable AvlNode succ; + + AvlNode(@ParametricNullness E elem, int elemCount) { checkArgument(elemCount > 0); this.elem = elem; this.elemCount = elemCount; @@ -579,8 +613,24 @@ private static final class AvlNode { this.right = null; } - public int count(Comparator comparator, E e) { - int cmp = comparator.compare(e, elem); + /** Constructor for the header node. */ + AvlNode() { + this.elem = null; + this.elemCount = 1; + } + + // For discussion of pred() and succ(), see the comment on the pred and succ fields. + + private AvlNode pred() { + return requireNonNull(pred); + } + + private AvlNode succ() { + return requireNonNull(succ); + } + + int count(Comparator comparator, @ParametricNullness E e) { + int cmp = comparator.compare(e, getElement()); if (cmp < 0) { return (left == null) ? 0 : left.count(comparator, e); } else if (cmp > 0) { @@ -590,30 +640,33 @@ public int count(Comparator comparator, E e) { } } - private AvlNode addRightChild(E e, int count) { - right = new AvlNode(e, count); - successor(this, right, succ); - height = Math.max(2, height); + @CanIgnoreReturnValue + private AvlNode addRightChild(@ParametricNullness E e, int count) { + right = new AvlNode<>(e, count); + successor(this, right, succ()); + height = max(2, height); distinctElements++; totalCount += count; return this; } - private AvlNode addLeftChild(E e, int count) { - left = new AvlNode(e, count); - successor(pred, left, this); - height = Math.max(2, height); + @CanIgnoreReturnValue + private AvlNode addLeftChild(@ParametricNullness E e, int count) { + left = new AvlNode<>(e, count); + successor(pred(), left, this); + height = max(2, height); distinctElements++; totalCount += count; return this; } - AvlNode add(Comparator comparator, @NullableDecl E e, int count, int[] result) { + AvlNode add( + Comparator comparator, @ParametricNullness E e, int count, int[] result) { /* * It speeds things up considerably to unconditionally add count to totalCount here, * but that destroys failure atomicity in the case of count overflow. =( */ - int cmp = comparator.compare(e, elem); + int cmp = comparator.compare(e, getElement()); if (cmp < 0) { AvlNode initLeft = left; if (initLeft == null) { @@ -653,9 +706,9 @@ AvlNode add(Comparator comparator, @NullableDecl E e, int count, i return this; } - AvlNode remove( - Comparator comparator, @NullableDecl E e, int count, int[] result) { - int cmp = comparator.compare(e, elem); + @Nullable AvlNode remove( + Comparator comparator, @ParametricNullness E e, int count, int[] result) { + int cmp = comparator.compare(e, getElement()); if (cmp < 0) { AvlNode initLeft = left; if (initLeft == null) { @@ -705,9 +758,9 @@ AvlNode remove( } } - AvlNode setCount( - Comparator comparator, @NullableDecl E e, int count, int[] result) { - int cmp = comparator.compare(e, elem); + @Nullable AvlNode setCount( + Comparator comparator, @ParametricNullness E e, int count, int[] result) { + int cmp = comparator.compare(e, getElement()); if (cmp < 0) { AvlNode initLeft = left; if (initLeft == null) { @@ -754,13 +807,13 @@ AvlNode setCount( return this; } - AvlNode setCount( + @Nullable AvlNode setCount( Comparator comparator, - @NullableDecl E e, + @ParametricNullness E e, int expectedCount, int newCount, int[] result) { - int cmp = comparator.compare(e, elem); + int cmp = comparator.compare(e, getElement()); if (cmp < 0) { AvlNode initLeft = left; if (initLeft == null) { @@ -817,16 +870,16 @@ AvlNode setCount( return this; } - private AvlNode deleteMe() { + private @Nullable AvlNode deleteMe() { int oldElemCount = this.elemCount; this.elemCount = 0; - successor(pred, succ); + successor(pred(), succ()); if (left == null) { return right; } else if (right == null) { return left; } else if (left.height >= right.height) { - AvlNode newTop = pred; + AvlNode newTop = pred(); // newTop is the maximum node in my left subtree newTop.left = left.removeMax(newTop); newTop.right = right; @@ -834,7 +887,7 @@ private AvlNode deleteMe() { newTop.totalCount = totalCount - oldElemCount; return newTop.rebalance(); } else { - AvlNode newTop = succ; + AvlNode newTop = succ(); newTop.right = right.removeMin(newTop); newTop.left = left; newTop.distinctElements = distinctElements - 1; @@ -844,7 +897,7 @@ private AvlNode deleteMe() { } // Removes the minimum node from this subtree to be reused elsewhere - private AvlNode removeMin(AvlNode node) { + private @Nullable AvlNode removeMin(AvlNode node) { if (left == null) { return right; } else { @@ -856,7 +909,7 @@ private AvlNode removeMin(AvlNode node) { } // Removes the maximum node from this subtree to be reused elsewhere - private AvlNode removeMax(AvlNode node) { + private @Nullable AvlNode removeMax(AvlNode node) { if (right == null) { return left; } else { @@ -874,7 +927,7 @@ private void recomputeMultiset() { } private void recomputeHeight() { - this.height = 1 + Math.max(height(left), height(right)); + this.height = 1 + max(height(left), height(right)); } private void recompute() { @@ -885,11 +938,15 @@ private void recompute() { private AvlNode rebalance() { switch (balanceFactor()) { case -2: + // requireNonNull is safe because right must exist in order to get a negative factor. + requireNonNull(right); if (right.balanceFactor() > 0) { right = right.rotateRight(); } return rotateLeft(); case 2: + // requireNonNull is safe because left must exist in order to get a positive factor. + requireNonNull(left); if (left.balanceFactor() < 0) { left = left.rotateLeft(); } @@ -928,17 +985,17 @@ private AvlNode rotateRight() { return newTop; } - private static long totalCount(@NullableDecl AvlNode node) { + private static long totalCount(@Nullable AvlNode node) { return (node == null) ? 0 : node.totalCount; } - private static int height(@NullableDecl AvlNode node) { + private static int height(@Nullable AvlNode node) { return (node == null) ? 0 : node.height; } - @NullableDecl - private AvlNode ceiling(Comparator comparator, E e) { - int cmp = comparator.compare(e, elem); + private @Nullable AvlNode ceiling( + Comparator comparator, @ParametricNullness E e) { + int cmp = comparator.compare(e, getElement()); if (cmp < 0) { return (left == null) ? this : MoreObjects.firstNonNull(left.ceiling(comparator, e), this); } else if (cmp == 0) { @@ -948,9 +1005,8 @@ private AvlNode ceiling(Comparator comparator, E e) { } } - @NullableDecl - private AvlNode floor(Comparator comparator, E e) { - int cmp = comparator.compare(e, elem); + private @Nullable AvlNode floor(Comparator comparator, @ParametricNullness E e) { + int cmp = comparator.compare(e, getElement()); if (cmp > 0) { return (right == null) ? this : MoreObjects.firstNonNull(right.floor(comparator, e), this); } else if (cmp == 0) { @@ -960,8 +1016,10 @@ private AvlNode floor(Comparator comparator, E e) { } } + @ParametricNullness E getElement() { - return elem; + // For discussion of this cast, see the comment on the elem field. + return uncheckedCastNullableTToT(elem); } int getCount() { @@ -974,12 +1032,13 @@ public String toString() { } } - private static void successor(AvlNode a, AvlNode b) { + private static void successor(AvlNode a, AvlNode b) { a.succ = b; b.pred = a; } - private static void successor(AvlNode a, AvlNode b, AvlNode c) { + private static void successor( + AvlNode a, AvlNode b, AvlNode c) { successor(a, b); successor(b, c); } @@ -994,30 +1053,31 @@ private static void successor(AvlNode a, AvlNode b, AvlNode c) { * @serialData the comparator, the number of distinct elements, the first element, its count, the * second element, its count, and so on */ - @GwtIncompatible // java.io.ObjectOutputStream - private void writeObject(ObjectOutputStream stream) throws IOException { + @GwtIncompatible + @J2ktIncompatible + private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(elementSet().comparator()); Serialization.writeMultiset(this, stream); } - @GwtIncompatible // java.io.ObjectInputStream - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + @J2ktIncompatible + @GwtIncompatible + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); @SuppressWarnings("unchecked") // reading data stored by writeObject - Comparator comparator = (Comparator) stream.readObject(); + Comparator comparator = (Comparator) requireNonNull(stream.readObject()); Serialization.getFieldSetter(AbstractSortedMultiset.class, "comparator").set(this, comparator); Serialization.getFieldSetter(TreeMultiset.class, "range") .set(this, GeneralRange.all(comparator)); Serialization.getFieldSetter(TreeMultiset.class, "rootReference") .set(this, new Reference>()); - AvlNode header = new AvlNode(null, 1); + AvlNode header = new AvlNode<>(); Serialization.getFieldSetter(TreeMultiset.class, "header").set(this, header); successor(header, header); Serialization.populateMultiset(this, stream); } - @GwtIncompatible // not needed in emulated source - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } diff --git a/android/guava/src/com/google/common/collect/TreeRangeMap.java b/android/guava/src/com/google/common/collect/TreeRangeMap.java index 8cb31b715453..6d0b98089f4a 100644 --- a/android/guava/src/com/google/common/collect/TreeRangeMap.java +++ b/android/guava/src/com/google/common/collect/TreeRangeMap.java @@ -21,15 +21,19 @@ import static com.google.common.base.Predicates.compose; import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Maps.immutableEntry; +import static java.util.Collections.emptyMap; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.MoreObjects; import com.google.common.base.Predicate; import com.google.common.collect.Maps.IteratorBasedAbstractMap; import java.util.AbstractMap; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -37,7 +41,7 @@ import java.util.NavigableMap; import java.util.NoSuchElementException; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An implementation of {@code RangeMap} based on a {@code TreeMap}, supporting all optional @@ -48,67 +52,78 @@ * @author Louis Wasserman * @since 14.0 */ -@Beta +@SuppressWarnings("rawtypes") // https://github.com/google/guava/issues/989 @GwtIncompatible // NavigableMap public final class TreeRangeMap implements RangeMap { private final NavigableMap, RangeMapEntry> entriesByLowerBound; + /** Returns a new, empty {@link TreeRangeMap}. */ public static TreeRangeMap create() { return new TreeRangeMap<>(); } + /** + * Returns a new {@link TreeRangeMap} containing the same ranges as the given {@code RangeMap}. + * + * @since 33.4.0 + */ + @SuppressWarnings("unchecked") + public static , V> TreeRangeMap copyOf( + RangeMap rangeMap) { + if (rangeMap instanceof TreeRangeMap) { + NavigableMap, RangeMapEntry> entriesByLowerBound = Maps.newTreeMap(); + entriesByLowerBound.putAll(((TreeRangeMap) rangeMap).entriesByLowerBound); + return new TreeRangeMap<>(entriesByLowerBound); + } else { + NavigableMap, RangeMapEntry> entriesByLowerBound = Maps.newTreeMap(); + for (Entry, ? extends V> entry : rangeMap.asMapOfRanges().entrySet()) { + entriesByLowerBound.put( + entry.getKey().lowerBound(), new RangeMapEntry(entry.getKey(), entry.getValue())); + } + return new TreeRangeMap<>(entriesByLowerBound); + } + } + private TreeRangeMap() { this.entriesByLowerBound = Maps.newTreeMap(); } - private static final class RangeMapEntry - extends AbstractMapEntry, V> { - private final Range range; - private final V value; + private TreeRangeMap(NavigableMap, RangeMapEntry> entriesByLowerBound) { + this.entriesByLowerBound = entriesByLowerBound; + } + private static final class RangeMapEntry + extends SimpleImmutableEntry, V> { RangeMapEntry(Cut lowerBound, Cut upperBound, V value) { this(Range.create(lowerBound, upperBound), value); } RangeMapEntry(Range range, V value) { - this.range = range; - this.value = value; - } - - @Override - public Range getKey() { - return range; - } - - @Override - public V getValue() { - return value; + super(range, value); } - public boolean contains(K value) { - return range.contains(value); + boolean contains(K value) { + return getKey().contains(value); } Cut getLowerBound() { - return range.lowerBound; + return getKey().lowerBound; } Cut getUpperBound() { - return range.upperBound; + return getKey().upperBound; } } @Override - @NullableDecl - public V get(K key) { + public @Nullable V get(K key) { Entry, V> entry = getEntry(key); return (entry == null) ? null : entry.getValue(); } @Override - @NullableDecl - public Entry, V> getEntry(K key) { + public @Nullable Entry, V> getEntry(K key) { Entry, RangeMapEntry> mapEntry = entriesByLowerBound.floorEntry(Cut.belowValue(key)); if (mapEntry != null && mapEntry.getValue().contains(key)) { @@ -155,7 +170,7 @@ private Range coalescedRange(Range range, V value) { /** Returns the range that spans the given range and entry, if the entry can be coalesced. */ private static Range coalesce( - Range range, V value, @NullableDecl Entry, RangeMapEntry> entry) { + Range range, V value, @Nullable Entry, RangeMapEntry> entry) { if (entry != null && entry.getValue().getKey().isConnected(range) && entry.getValue().getValue().equals(value)) { @@ -165,8 +180,8 @@ private static Range coalesce( } @Override - public void putAll(RangeMap rangeMap) { - for (Entry, V> entry : rangeMap.asMapOfRanges().entrySet()) { + public void putAll(RangeMap rangeMap) { + for (Entry, ? extends V> entry : rangeMap.asMapOfRanges().entrySet()) { put(entry.getKey(), entry.getValue()); } } @@ -180,7 +195,8 @@ public void clear() { public Range span() { Entry, RangeMapEntry> firstEntry = entriesByLowerBound.firstEntry(); Entry, RangeMapEntry> lastEntry = entriesByLowerBound.lastEntry(); - if (firstEntry == null) { + // Either both are null or neither is, but we check both to satisfy the nullness checker. + if (firstEntry == null || lastEntry == null) { throw new NoSuchElementException(); } return Range.create( @@ -261,12 +277,12 @@ private final class AsMapOfRanges extends IteratorBasedAbstractMap, V> } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } @Override - public V get(@NullableDecl Object key) { + public @Nullable V get(@Nullable Object key) { if (key instanceof Range) { Range range = (Range) key; RangeMapEntry rangeMapEntry = entriesByLowerBound.get(range.lowerBound); @@ -299,44 +315,43 @@ public RangeMap subRangeMap(Range subRange) { @SuppressWarnings("unchecked") private RangeMap emptySubRangeMap() { - return EMPTY_SUB_RANGE_MAP; + return (RangeMap) (RangeMap) EMPTY_SUB_RANGE_MAP; } - private static final RangeMap EMPTY_SUB_RANGE_MAP = - new RangeMap() { + @SuppressWarnings("ConstantCaseForConstants") // This RangeMap is immutable. + private static final RangeMap, Object> EMPTY_SUB_RANGE_MAP = + new RangeMap, Object>() { @Override - @NullableDecl - public Object get(Comparable key) { + public @Nullable Object get(Comparable key) { return null; } @Override - @NullableDecl - public Entry getEntry(Comparable key) { + public @Nullable Entry>, Object> getEntry(Comparable key) { return null; } @Override - public Range span() { + public Range> span() { throw new NoSuchElementException(); } @Override - public void put(Range range, Object value) { + public void put(Range> range, Object value) { checkNotNull(range); throw new IllegalArgumentException( "Cannot insert range " + range + " into an empty subRangeMap"); } @Override - public void putCoalescing(Range range, Object value) { + public void putCoalescing(Range> range, Object value) { checkNotNull(range); throw new IllegalArgumentException( "Cannot insert range " + range + " into an empty subRangeMap"); } @Override - public void putAll(RangeMap rangeMap) { + public void putAll(RangeMap, ? extends Object> rangeMap) { if (!rangeMap.asMapOfRanges().isEmpty()) { throw new IllegalArgumentException( "Cannot putAll(nonEmptyRangeMap) into an empty subRangeMap"); @@ -347,28 +362,28 @@ public void putAll(RangeMap rangeMap) { public void clear() {} @Override - public void remove(Range range) { + public void remove(Range> range) { checkNotNull(range); } @Override - public Map asMapOfRanges() { - return Collections.emptyMap(); + public Map>, Object> asMapOfRanges() { + return emptyMap(); } @Override - public Map asDescendingMapOfRanges() { - return Collections.emptyMap(); + public Map>, Object> asDescendingMapOfRanges() { + return emptyMap(); } @Override - public RangeMap subRangeMap(Range range) { + public RangeMap, Object> subRangeMap(Range> range) { checkNotNull(range); return this; } }; - private class SubRangeMap implements RangeMap { + private final class SubRangeMap implements RangeMap { private final Range subRange; @@ -377,18 +392,16 @@ private class SubRangeMap implements RangeMap { } @Override - @NullableDecl - public V get(K key) { + public @Nullable V get(K key) { return subRange.contains(key) ? TreeRangeMap.this.get(key) : null; } @Override - @NullableDecl - public Entry, V> getEntry(K key) { + public @Nullable Entry, V> getEntry(K key) { if (subRange.contains(key)) { Entry, V> entry = TreeRangeMap.this.getEntry(key); if (entry != null) { - return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); + return immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); } } return null; @@ -442,7 +455,7 @@ public void putCoalescing(Range range, V value) { } @Override - public void putAll(RangeMap rangeMap) { + public void putAll(RangeMap rangeMap) { if (rangeMap.asMapOfRanges().isEmpty()) { return; } @@ -488,9 +501,9 @@ public Map, V> asDescendingMapOfRanges() { @Override Iterator, V>> entryIterator() { if (subRange.isEmpty()) { - return Iterators.emptyIterator(); + return emptyIterator(); } - final Iterator> backingItr = + Iterator> backingItr = entriesByLowerBound .headMap(subRange.upperBound, false) .descendingMap() @@ -499,13 +512,13 @@ Iterator, V>> entryIterator() { return new AbstractIterator, V>>() { @Override - protected Entry, V> computeNext() { + protected @Nullable Entry, V> computeNext() { if (backingItr.hasNext()) { RangeMapEntry entry = backingItr.next(); if (entry.getUpperBound().compareTo(subRange.lowerBound) <= 0) { return endOfData(); } - return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); + return immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); } return endOfData(); } @@ -515,7 +528,7 @@ protected Entry, V> computeNext() { } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof RangeMap) { RangeMap rangeMap = (RangeMap) o; return asMapOfRanges().equals(rangeMap.asMapOfRanges()); @@ -536,12 +549,12 @@ public String toString() { class SubRangeMapAsMap extends AbstractMap, V> { @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } @Override - public V get(Object key) { + public @Nullable V get(@Nullable Object key) { try { if (key instanceof Range) { @SuppressWarnings("unchecked") // we catch ClassCastExceptions @@ -574,11 +587,12 @@ public V get(Object key) { } @Override - public V remove(Object key) { + public @Nullable V remove(@Nullable Object key) { V value = get(key); if (value != null) { - @SuppressWarnings("unchecked") // it's definitely in the map, so safe - Range range = (Range) key; + // it's definitely in the map, so the cast and requireNonNull are safe + @SuppressWarnings("unchecked") + Range range = (Range) requireNonNull(key); TreeRangeMap.this.remove(range); return value; } @@ -591,7 +605,7 @@ public void clear() { } private boolean removeEntryIf(Predicate, V>> predicate) { - List> toRemove = Lists.newArrayList(); + List> toRemove = new ArrayList<>(); for (Entry, V> entry : entrySet()) { if (predicate.apply(entry)) { toRemove.add(entry.getKey()); @@ -607,13 +621,13 @@ private boolean removeEntryIf(Predicate, V>> predicate) { public Set> keySet() { return new Maps.KeySet, V>(SubRangeMapAsMap.this) { @Override - public boolean remove(@NullableDecl Object o) { + public boolean remove(@Nullable Object o) { return SubRangeMapAsMap.this.remove(o) != null; } @Override public boolean retainAll(Collection c) { - return removeEntryIf(compose(not(in(c)), Maps.>keyFunction())); + return removeEntryIf(compose(not(in(c)), Entry::getKey)); } }; } @@ -650,24 +664,24 @@ public boolean isEmpty() { Iterator, V>> entryIterator() { if (subRange.isEmpty()) { - return Iterators.emptyIterator(); + return emptyIterator(); } Cut cutToStart = MoreObjects.firstNonNull( entriesByLowerBound.floorKey(subRange.lowerBound), subRange.lowerBound); - final Iterator> backingItr = + Iterator> backingItr = entriesByLowerBound.tailMap(cutToStart, true).values().iterator(); return new AbstractIterator, V>>() { @Override - protected Entry, V> computeNext() { + protected @Nullable Entry, V> computeNext() { while (backingItr.hasNext()) { RangeMapEntry entry = backingItr.next(); if (entry.getLowerBound().compareTo(subRange.upperBound) >= 0) { return endOfData(); } else if (entry.getUpperBound().compareTo(subRange.lowerBound) > 0) { // this might not be true e.g. at the start of the iteration - return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); + return immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); } } return endOfData(); @@ -680,12 +694,12 @@ public Collection values() { return new Maps.Values, V>(this) { @Override public boolean removeAll(Collection c) { - return removeEntryIf(compose(in(c), Maps.valueFunction())); + return removeEntryIf(compose(in(c), Entry::getValue)); } @Override public boolean retainAll(Collection c) { - return removeEntryIf(compose(not(in(c)), Maps.valueFunction())); + return removeEntryIf(compose(not(in(c)), Entry::getValue)); } }; } @@ -693,7 +707,7 @@ public boolean retainAll(Collection c) { } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof RangeMap) { RangeMap rangeMap = (RangeMap) o; return asMapOfRanges().equals(rangeMap.asMapOfRanges()); diff --git a/android/guava/src/com/google/common/collect/TreeRangeSet.java b/android/guava/src/com/google/common/collect/TreeRangeSet.java index c5a438a998ff..3083fd7ad290 100644 --- a/android/guava/src/com/google/common/collect/TreeRangeSet.java +++ b/android/guava/src/com/google/common/collect/TreeRangeSet.java @@ -14,13 +14,16 @@ package com.google.common.collect; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterators.emptyIterator; +import static com.google.common.collect.Iterators.peekingIterator; +import static com.google.common.collect.Maps.immutableEntry; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.MoreObjects; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.io.Serializable; import java.util.Collection; import java.util.Comparator; @@ -30,7 +33,7 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An implementation of {@link RangeSet} backed by a {@link TreeMap}. @@ -38,7 +41,6 @@ * @author Louis Wasserman * @since 14.0 */ -@Beta @GwtIncompatible // uses NavigableMap public class TreeRangeSet> extends AbstractRangeSet implements Serializable { @@ -47,7 +49,7 @@ public class TreeRangeSet> extends AbstractRangeSet /** Creates an empty {@code TreeRangeSet} instance. */ public static > TreeRangeSet create() { - return new TreeRangeSet(new TreeMap, Range>()); + return new TreeRangeSet<>(new TreeMap, Range>()); } /** Returns a {@code TreeRangeSet} initialized with the ranges in the specified range set. */ @@ -76,8 +78,8 @@ private TreeRangeSet(NavigableMap, Range> rangesByLowerCut) { this.rangesByLowerBound = rangesByLowerCut; } - @NullableDecl private transient Set> asRanges; - @NullableDecl private transient Set> asDescendingSetOfRanges; + @LazyInit private transient @Nullable Set> asRanges; + @LazyInit private transient @Nullable Set> asDescendingSetOfRanges; @Override public Set> asRanges() { @@ -112,14 +114,13 @@ public int hashCode() { } @Override - public boolean equals(@NullableDecl Object o) { + public boolean equals(@Nullable Object o) { return Sets.equalsImpl(this, o); } } @Override - @NullableDecl - public Range rangeContaining(C value) { + public @Nullable Range rangeContaining(C value) { checkNotNull(value); Entry, Range> floorEntry = rangesByLowerBound.floorEntry(Cut.belowValue(value)); if (floorEntry != null && floorEntry.getValue().contains(value)) { @@ -152,8 +153,7 @@ public boolean encloses(Range range) { return floorEntry != null && floorEntry.getValue().encloses(range); } - @NullableDecl - private Range rangeEnclosing(Range range) { + private @Nullable Range rangeEnclosing(Range range) { checkNotNull(range); Entry, Range> floorEntry = rangesByLowerBound.floorEntry(range.lowerBound); return (floorEntry != null && floorEntry.getValue().encloses(range)) @@ -165,7 +165,11 @@ private Range rangeEnclosing(Range range) { public Range span() { Entry, Range> firstEntry = rangesByLowerBound.firstEntry(); Entry, Range> lastEntry = rangesByLowerBound.lastEntry(); - if (firstEntry == null) { + if (firstEntry == null || lastEntry == null) { + /* + * Either both are null or neither is: Either the set is empty, or it's not. But we check both + * to make the nullness checker happy. + */ throw new NoSuchElementException(); } return Range.create(firstEntry.getValue().lowerBound, lastEntry.getValue().upperBound); @@ -184,31 +188,31 @@ public void add(Range rangeToAdd) { Cut lbToAdd = rangeToAdd.lowerBound; Cut ubToAdd = rangeToAdd.upperBound; - Entry, Range> entryBelowLB = rangesByLowerBound.lowerEntry(lbToAdd); - if (entryBelowLB != null) { + Entry, Range> entryBelowLb = rangesByLowerBound.lowerEntry(lbToAdd); + if (entryBelowLb != null) { // { < - Range rangeBelowLB = entryBelowLB.getValue(); - if (rangeBelowLB.upperBound.compareTo(lbToAdd) >= 0) { + Range rangeBelowLb = entryBelowLb.getValue(); + if (rangeBelowLb.upperBound.compareTo(lbToAdd) >= 0) { // { < }, and we will need to coalesce - if (rangeBelowLB.upperBound.compareTo(ubToAdd) >= 0) { + if (rangeBelowLb.upperBound.compareTo(ubToAdd) >= 0) { // { < > } - ubToAdd = rangeBelowLB.upperBound; + ubToAdd = rangeBelowLb.upperBound; /* * TODO(cpovirk): can we just "return;" here? Or, can we remove this if() entirely? If * not, add tests to demonstrate the problem with each approach */ } - lbToAdd = rangeBelowLB.lowerBound; + lbToAdd = rangeBelowLb.lowerBound; } } - Entry, Range> entryBelowUB = rangesByLowerBound.floorEntry(ubToAdd); - if (entryBelowUB != null) { + Entry, Range> entryBelowUb = rangesByLowerBound.floorEntry(ubToAdd); + if (entryBelowUb != null) { // { > - Range rangeBelowUB = entryBelowUB.getValue(); - if (rangeBelowUB.upperBound.compareTo(ubToAdd) >= 0) { + Range rangeBelowUb = entryBelowUb.getValue(); + if (rangeBelowUb.upperBound.compareTo(ubToAdd) >= 0) { // { > }, and we need to coalesce - ubToAdd = rangeBelowUB.upperBound; + ubToAdd = rangeBelowUb.upperBound; } } @@ -229,32 +233,32 @@ public void remove(Range rangeToRemove) { // We will use { } to illustrate ranges currently in the range set, and < > // to illustrate rangeToRemove. - Entry, Range> entryBelowLB = rangesByLowerBound.lowerEntry(rangeToRemove.lowerBound); - if (entryBelowLB != null) { + Entry, Range> entryBelowLb = rangesByLowerBound.lowerEntry(rangeToRemove.lowerBound); + if (entryBelowLb != null) { // { < - Range rangeBelowLB = entryBelowLB.getValue(); - if (rangeBelowLB.upperBound.compareTo(rangeToRemove.lowerBound) >= 0) { + Range rangeBelowLb = entryBelowLb.getValue(); + if (rangeBelowLb.upperBound.compareTo(rangeToRemove.lowerBound) >= 0) { // { < }, and we will need to subdivide if (rangeToRemove.hasUpperBound() - && rangeBelowLB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { + && rangeBelowLb.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { // { < > } replaceRangeWithSameLowerBound( - Range.create(rangeToRemove.upperBound, rangeBelowLB.upperBound)); + Range.create(rangeToRemove.upperBound, rangeBelowLb.upperBound)); } replaceRangeWithSameLowerBound( - Range.create(rangeBelowLB.lowerBound, rangeToRemove.lowerBound)); + Range.create(rangeBelowLb.lowerBound, rangeToRemove.lowerBound)); } } - Entry, Range> entryBelowUB = rangesByLowerBound.floorEntry(rangeToRemove.upperBound); - if (entryBelowUB != null) { + Entry, Range> entryBelowUb = rangesByLowerBound.floorEntry(rangeToRemove.upperBound); + if (entryBelowUb != null) { // { > - Range rangeBelowUB = entryBelowUB.getValue(); + Range rangeBelowUb = entryBelowUb.getValue(); if (rangeToRemove.hasUpperBound() - && rangeBelowUB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { + && rangeBelowUb.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { // { > } replaceRangeWithSameLowerBound( - Range.create(rangeToRemove.upperBound, rangeBelowUB.upperBound)); + Range.create(rangeToRemove.upperBound, rangeBelowUb.upperBound)); } } @@ -269,7 +273,7 @@ private void replaceRangeWithSameLowerBound(Range range) { } } - @NullableDecl private transient RangeSet complement; + @LazyInit private transient @Nullable RangeSet complement; @Override public RangeSet complement() { @@ -301,7 +305,7 @@ private RangesByUpperBound( private NavigableMap, Range> subMap(Range> window) { if (window.isConnected(upperBoundWindow)) { - return new RangesByUpperBound(rangesByLowerBound, window.intersection(upperBoundWindow)); + return new RangesByUpperBound<>(rangesByLowerBound, window.intersection(upperBoundWindow)); } else { return ImmutableSortedMap.of(); } @@ -328,16 +332,16 @@ public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) @Override public Comparator> comparator() { - return Ordering.>natural(); + return Ordering.natural(); } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } @Override - public Range get(@NullableDecl Object key) { + public @Nullable Range get(@Nullable Object key) { if (key instanceof Cut) { try { @SuppressWarnings("unchecked") // we catch CCEs @@ -362,7 +366,7 @@ Iterator, Range>> entryIterator() { * We want to start the iteration at the first range where the upper bound is in * upperBoundWindow. */ - final Iterator> backingItr; + Iterator> backingItr; if (!upperBoundWindow.hasLowerBound()) { backingItr = rangesByLowerBound.values().iterator(); } else { @@ -382,7 +386,7 @@ Iterator, Range>> entryIterator() { } return new AbstractIterator, Range>>() { @Override - protected Entry, Range> computeNext() { + protected @Nullable Entry, Range> computeNext() { if (!backingItr.hasNext()) { return endOfData(); } @@ -390,7 +394,7 @@ protected Entry, Range> computeNext() { if (upperBoundWindow.upperBound.isLessThan(range.upperBound)) { return endOfData(); } else { - return Maps.immutableEntry(range.upperBound, range); + return immutableEntry(range.upperBound, range); } } }; @@ -408,20 +412,20 @@ Iterator, Range>> descendingEntryIterator() { } else { candidates = rangesByLowerBound.descendingMap().values(); } - final PeekingIterator> backingItr = Iterators.peekingIterator(candidates.iterator()); + PeekingIterator> backingItr = peekingIterator(candidates.iterator()); if (backingItr.hasNext() && upperBoundWindow.upperBound.isLessThan(backingItr.peek().upperBound)) { backingItr.next(); } return new AbstractIterator, Range>>() { @Override - protected Entry, Range> computeNext() { + protected @Nullable Entry, Range> computeNext() { if (!backingItr.hasNext()) { return endOfData(); } Range range = backingItr.next(); return upperBoundWindow.lowerBound.isLessThan(range.upperBound) - ? Maps.immutableEntry(range.upperBound, range) + ? immutableEntry(range.upperBound, range) : endOfData(); } }; @@ -456,13 +460,13 @@ private static final class ComplementRangesByLowerBound> private final Range> complementLowerBoundWindow; ComplementRangesByLowerBound(NavigableMap, Range> positiveRangesByLowerBound) { - this(positiveRangesByLowerBound, Range.>all()); + this(positiveRangesByLowerBound, Range.all()); } private ComplementRangesByLowerBound( NavigableMap, Range> positiveRangesByLowerBound, Range> window) { this.positiveRangesByLowerBound = positiveRangesByLowerBound; - this.positiveRangesByUpperBound = new RangesByUpperBound(positiveRangesByLowerBound); + this.positiveRangesByUpperBound = new RangesByUpperBound<>(positiveRangesByLowerBound); this.complementLowerBoundWindow = window; } @@ -471,7 +475,7 @@ private NavigableMap, Range> subMap(Range> subWindow) { return ImmutableSortedMap.of(); } else { subWindow = subWindow.intersection(complementLowerBoundWindow); - return new ComplementRangesByLowerBound(positiveRangesByLowerBound, subWindow); + return new ComplementRangesByLowerBound<>(positiveRangesByLowerBound, subWindow); } } @@ -496,7 +500,7 @@ public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) @Override public Comparator> comparator() { - return Ordering.>natural(); + return Ordering.natural(); } @Override @@ -521,22 +525,21 @@ Iterator, Range>> entryIterator() { } else { positiveRanges = positiveRangesByUpperBound.values(); } - final PeekingIterator> positiveItr = - Iterators.peekingIterator(positiveRanges.iterator()); - final Cut firstComplementRangeLowerBound; - if (complementLowerBoundWindow.contains(Cut.belowAll()) + PeekingIterator> positiveItr = peekingIterator(positiveRanges.iterator()); + Cut firstComplementRangeLowerBound; + if (complementLowerBoundWindow.contains(Cut.belowAll()) && (!positiveItr.hasNext() || positiveItr.peek().lowerBound != Cut.belowAll())) { firstComplementRangeLowerBound = Cut.belowAll(); } else if (positiveItr.hasNext()) { firstComplementRangeLowerBound = positiveItr.next().upperBound; } else { - return Iterators.emptyIterator(); + return emptyIterator(); } return new AbstractIterator, Range>>() { Cut nextComplementRangeLowerBound = firstComplementRangeLowerBound; @Override - protected Entry, Range> computeNext() { + protected @Nullable Entry, Range> computeNext() { if (complementLowerBoundWindow.upperBound.isLessThan(nextComplementRangeLowerBound) || nextComplementRangeLowerBound == Cut.aboveAll()) { return endOfData(); @@ -547,10 +550,10 @@ protected Entry, Range> computeNext() { negativeRange = Range.create(nextComplementRangeLowerBound, positiveRange.lowerBound); nextComplementRangeLowerBound = positiveRange.upperBound; } else { - negativeRange = Range.create(nextComplementRangeLowerBound, Cut.aboveAll()); + negativeRange = Range.create(nextComplementRangeLowerBound, Cut.aboveAll()); nextComplementRangeLowerBound = Cut.aboveAll(); } - return Maps.immutableEntry(negativeRange.lowerBound, negativeRange); + return immutableEntry(negativeRange.lowerBound, negativeRange); } }; } @@ -568,12 +571,12 @@ Iterator, Range>> descendingEntryIterator() { Cut startingPoint = complementLowerBoundWindow.hasUpperBound() ? complementLowerBoundWindow.upperEndpoint() - : Cut.aboveAll(); + : Cut.aboveAll(); boolean inclusive = complementLowerBoundWindow.hasUpperBound() && complementLowerBoundWindow.upperBoundType() == BoundType.CLOSED; - final PeekingIterator> positiveItr = - Iterators.peekingIterator( + PeekingIterator> positiveItr = + peekingIterator( positiveRangesByUpperBound .headMap(startingPoint, inclusive) .descendingMap() @@ -585,19 +588,18 @@ Iterator, Range>> descendingEntryIterator() { (positiveItr.peek().upperBound == Cut.aboveAll()) ? positiveItr.next().lowerBound : positiveRangesByLowerBound.higherKey(positiveItr.peek().upperBound); - } else if (!complementLowerBoundWindow.contains(Cut.belowAll()) + } else if (!complementLowerBoundWindow.contains(Cut.belowAll()) || positiveRangesByLowerBound.containsKey(Cut.belowAll())) { - return Iterators.emptyIterator(); + return emptyIterator(); } else { - cut = positiveRangesByLowerBound.higherKey(Cut.belowAll()); + cut = positiveRangesByLowerBound.higherKey(Cut.belowAll()); } - final Cut firstComplementRangeUpperBound = - MoreObjects.firstNonNull(cut, Cut.aboveAll()); + Cut firstComplementRangeUpperBound = firstNonNull(cut, Cut.aboveAll()); return new AbstractIterator, Range>>() { Cut nextComplementRangeUpperBound = firstComplementRangeUpperBound; @Override - protected Entry, Range> computeNext() { + protected @Nullable Entry, Range> computeNext() { if (nextComplementRangeUpperBound == Cut.belowAll()) { return endOfData(); } else if (positiveItr.hasNext()) { @@ -606,12 +608,12 @@ protected Entry, Range> computeNext() { Range.create(positiveRange.upperBound, nextComplementRangeUpperBound); nextComplementRangeUpperBound = positiveRange.lowerBound; if (complementLowerBoundWindow.lowerBound.isLessThan(negativeRange.lowerBound)) { - return Maps.immutableEntry(negativeRange.lowerBound, negativeRange); + return immutableEntry(negativeRange.lowerBound, negativeRange); } - } else if (complementLowerBoundWindow.lowerBound.isLessThan(Cut.belowAll())) { - Range negativeRange = Range.create(Cut.belowAll(), nextComplementRangeUpperBound); + } else if (complementLowerBoundWindow.lowerBound.isLessThan(Cut.belowAll())) { + Range negativeRange = Range.create(Cut.belowAll(), nextComplementRangeUpperBound); nextComplementRangeUpperBound = Cut.belowAll(); - return Maps.immutableEntry(Cut.belowAll(), negativeRange); + return immutableEntry(Cut.belowAll(), negativeRange); } return endOfData(); } @@ -624,8 +626,7 @@ public int size() { } @Override - @NullableDecl - public Range get(Object key) { + public @Nullable Range get(@Nullable Object key) { if (key instanceof Cut) { try { @SuppressWarnings("unchecked") @@ -643,7 +644,7 @@ public Range get(Object key) { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } } @@ -698,14 +699,14 @@ private SubRangeSetRangesByLowerBound( this.lowerBoundWindow = checkNotNull(lowerBoundWindow); this.restriction = checkNotNull(restriction); this.rangesByLowerBound = checkNotNull(rangesByLowerBound); - this.rangesByUpperBound = new RangesByUpperBound(rangesByLowerBound); + this.rangesByUpperBound = new RangesByUpperBound<>(rangesByLowerBound); } private NavigableMap, Range> subMap(Range> window) { if (!window.isConnected(lowerBoundWindow)) { return ImmutableSortedMap.of(); } else { - return new SubRangeSetRangesByLowerBound( + return new SubRangeSetRangesByLowerBound<>( lowerBoundWindow.intersection(window), restriction, rangesByLowerBound); } } @@ -733,17 +734,16 @@ public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) @Override public Comparator> comparator() { - return Ordering.>natural(); + return Ordering.natural(); } @Override - public boolean containsKey(@NullableDecl Object key) { + public boolean containsKey(@Nullable Object key) { return get(key) != null; } @Override - @NullableDecl - public Range get(@NullableDecl Object key) { + public @Nullable Range get(@Nullable Object key) { if (key instanceof Cut) { try { @SuppressWarnings("unchecked") // we catch CCE's @@ -774,11 +774,11 @@ public Range get(@NullableDecl Object key) { @Override Iterator, Range>> entryIterator() { if (restriction.isEmpty()) { - return Iterators.emptyIterator(); + return emptyIterator(); } - final Iterator> completeRangeItr; + Iterator> completeRangeItr; if (lowerBoundWindow.upperBound.isLessThan(restriction.lowerBound)) { - return Iterators.emptyIterator(); + return emptyIterator(); } else if (lowerBoundWindow.lowerBound.isLessThan(restriction.lowerBound)) { // starts at the first range with upper bound strictly greater than restriction.lowerBound completeRangeItr = @@ -793,12 +793,12 @@ Iterator, Range>> entryIterator() { .values() .iterator(); } - final Cut> upperBoundOnLowerBounds = - Ordering.natural() + Cut> upperBoundOnLowerBounds = + Ordering.>>natural() .min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); return new AbstractIterator, Range>>() { @Override - protected Entry, Range> computeNext() { + protected @Nullable Entry, Range> computeNext() { if (!completeRangeItr.hasNext()) { return endOfData(); } @@ -807,7 +807,7 @@ protected Entry, Range> computeNext() { return endOfData(); } else { nextRange = nextRange.intersection(restriction); - return Maps.immutableEntry(nextRange.lowerBound, nextRange); + return immutableEntry(nextRange.lowerBound, nextRange); } } }; @@ -816,12 +816,12 @@ protected Entry, Range> computeNext() { @Override Iterator, Range>> descendingEntryIterator() { if (restriction.isEmpty()) { - return Iterators.emptyIterator(); + return emptyIterator(); } Cut> upperBoundOnLowerBounds = - Ordering.natural() + Ordering.>>natural() .min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); - final Iterator> completeRangeItr = + Iterator> completeRangeItr = rangesByLowerBound .headMap( upperBoundOnLowerBounds.endpoint(), @@ -831,7 +831,7 @@ Iterator, Range>> descendingEntryIterator() { .iterator(); return new AbstractIterator, Range>>() { @Override - protected Entry, Range> computeNext() { + protected @Nullable Entry, Range> computeNext() { if (!completeRangeItr.hasNext()) { return endOfData(); } @@ -841,7 +841,7 @@ protected Entry, Range> computeNext() { } nextRange = nextRange.intersection(restriction); if (lowerBoundWindow.contains(nextRange.lowerBound)) { - return Maps.immutableEntry(nextRange.lowerBound, nextRange); + return immutableEntry(nextRange.lowerBound, nextRange); } else { return endOfData(); } @@ -857,7 +857,7 @@ public int size() { @Override public RangeSet subRangeSet(Range view) { - return view.equals(Range.all()) ? this : new SubRangeSet(view); + return view.equals(Range.all()) ? this : new SubRangeSet(view); } private final class SubRangeSet extends TreeRangeSet { @@ -866,7 +866,7 @@ private final class SubRangeSet extends TreeRangeSet { SubRangeSet(Range restriction) { super( new SubRangeSetRangesByLowerBound( - Range.>all(), restriction, TreeRangeSet.this.rangesByLowerBound)); + Range.all(), restriction, TreeRangeSet.this.rangesByLowerBound)); this.restriction = restriction; } @@ -880,8 +880,7 @@ public boolean encloses(Range range) { } @Override - @NullableDecl - public Range rangeContaining(C value) { + public @Nullable Range rangeContaining(C value) { if (!restriction.contains(value)) { return null; } diff --git a/android/guava/src/com/google/common/collect/TreeTraverser.java b/android/guava/src/com/google/common/collect/TreeTraverser.java index 550cdfc72efb..bcffbc16bab6 100644 --- a/android/guava/src/com/google/common/collect/TreeTraverser.java +++ b/android/guava/src/com/google/common/collect/TreeTraverser.java @@ -17,6 +17,7 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterators.singletonIterator; import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; @@ -25,6 +26,7 @@ import java.util.Deque; import java.util.Iterator; import java.util.Queue; +import org.jspecify.annotations.Nullable; /** * Views elements of a type {@code T} as nodes in a tree, and provides methods to traverse the trees @@ -32,7 +34,7 @@ * *

    For example, the tree * - *

    {@code
    + * {@snippet :
      *        h
      *      / | \
      *     /  e  \
    @@ -40,26 +42,26 @@
      *   /|\      |
      *  / | \     f
      * a  b  c
    - * }
    + * } * *

    can be iterated over in preorder (hdabcegf), postorder (abcdefgh), or breadth-first order * (hdegabcf). * *

    Null nodes are strictly forbidden. * - *

    For Java 8 users: Because this is an abstract class, not an interface, you can't use a - * lambda expression to extend it: + *

    Because this is an abstract class, not an interface, you can't use a lambda expression to + * implement it: * - *

    {@code
    + * {@snippet :
      * // won't work
      * TreeTraverser traverser = node -> node.getChildNodes();
    - * }
    + * } * * Instead, you can pass a lambda expression to the {@code using} factory method: * - *
    {@code
    + * {@snippet :
      * TreeTraverser traverser = TreeTraverser.using(node -> node.getChildNodes());
    - * }
    + * } * * @author Louis Wasserman * @since 15.0 @@ -67,13 +69,16 @@ * their equivalent on the result of {@code Traverser.forTree(tree)} where {@code tree} * implements {@code SuccessorsFunction}, which has a similar API as {@link #children} or can be * the same lambda function as passed into {@link #using(Function)}. - *

    This class is scheduled to be removed in October 2019. */ -// TODO(b/68134636): Remove by 2019-10 +// This class is now only available externally for backwards compatibility; it should not be used +// internally (hence the package-private visibility and @Deprecated annotation). @Deprecated @Beta @GwtCompatible -public abstract class TreeTraverser { +public +abstract class TreeTraverser { + /** Constructor for use by subclasses. */ + public TreeTraverser() {} /** * Returns a tree traverser that uses the given function to navigate from a node to its children. @@ -87,7 +92,7 @@ public abstract class TreeTraverser { */ @Deprecated public static TreeTraverser using( - final Function> nodeToChildrenFunction) { + Function> nodeToChildrenFunction) { checkNotNull(nodeToChildrenFunction); return new TreeTraverser() { @Override @@ -111,7 +116,7 @@ public Iterable children(T root) { * the same behavior. */ @Deprecated - public final FluentIterable preOrderTraversal(final T root) { + public final FluentIterable preOrderTraversal(T root) { checkNotNull(root); return new FluentIterable() { @Override @@ -130,7 +135,7 @@ private final class PreOrderIterator extends UnmodifiableIterator { PreOrderIterator(T root) { this.stack = new ArrayDeque<>(); - stack.addLast(Iterators.singletonIterator(checkNotNull(root))); + stack.addLast(singletonIterator(checkNotNull(root))); } @Override @@ -164,7 +169,7 @@ public T next() { * has the same behavior. */ @Deprecated - public final FluentIterable postOrderTraversal(final T root) { + public final FluentIterable postOrderTraversal(T root) { checkNotNull(root); return new FluentIterable() { @Override @@ -197,7 +202,7 @@ private final class PostOrderIterator extends AbstractIterator { } @Override - protected T computeNext() { + protected @Nullable T computeNext() { while (!stack.isEmpty()) { PostOrderNode top = stack.getLast(); if (top.childIterator.hasNext()) { @@ -212,7 +217,7 @@ protected T computeNext() { } private PostOrderNode expand(T t) { - return new PostOrderNode(t, children(t).iterator()); + return new PostOrderNode<>(t, children(t).iterator()); } } @@ -227,7 +232,7 @@ private PostOrderNode expand(T t) { * same behavior. */ @Deprecated - public final FluentIterable breadthFirstTraversal(final T root) { + public final FluentIterable breadthFirstTraversal(T root) { checkNotNull(root); return new FluentIterable() { @Override @@ -242,7 +247,7 @@ private final class BreadthFirstIterator extends UnmodifiableIterator private final Queue queue; BreadthFirstIterator(T root) { - this.queue = new ArrayDeque(); + this.queue = new ArrayDeque<>(); queue.add(root); } diff --git a/android/guava/src/com/google/common/collect/UnmodifiableIterator.java b/android/guava/src/com/google/common/collect/UnmodifiableIterator.java index 71196dcb3e7d..a159121a7b90 100644 --- a/android/guava/src/com/google/common/collect/UnmodifiableIterator.java +++ b/android/guava/src/com/google/common/collect/UnmodifiableIterator.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.DoNotCall; import java.util.Iterator; +import org.jspecify.annotations.Nullable; /** * An iterator that does not support {@link #remove}. @@ -31,7 +32,7 @@ * @since 2.0 */ @GwtCompatible -public abstract class UnmodifiableIterator implements Iterator { +public abstract class UnmodifiableIterator implements Iterator { /** Constructor for use by subclasses. */ protected UnmodifiableIterator() {} diff --git a/android/guava/src/com/google/common/collect/UnmodifiableListIterator.java b/android/guava/src/com/google/common/collect/UnmodifiableListIterator.java index 4369ea102a3b..2917d5914b60 100644 --- a/android/guava/src/com/google/common/collect/UnmodifiableListIterator.java +++ b/android/guava/src/com/google/common/collect/UnmodifiableListIterator.java @@ -19,6 +19,7 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.DoNotCall; import java.util.ListIterator; +import org.jspecify.annotations.Nullable; /** * A list iterator that does not support {@link #remove}, {@link #add}, or {@link #set}. @@ -27,8 +28,8 @@ * @author Louis Wasserman */ @GwtCompatible -public abstract class UnmodifiableListIterator extends UnmodifiableIterator - implements ListIterator { +public abstract class UnmodifiableListIterator + extends UnmodifiableIterator implements ListIterator { /** Constructor for use by subclasses. */ protected UnmodifiableListIterator() {} @@ -41,7 +42,7 @@ protected UnmodifiableListIterator() {} @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final void add(E e) { + public final void add(@ParametricNullness E e) { throw new UnsupportedOperationException(); } @@ -54,7 +55,7 @@ public final void add(E e) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public final void set(E e) { + public final void set(@ParametricNullness E e) { throw new UnsupportedOperationException(); } } diff --git a/android/guava/src/com/google/common/collect/UnmodifiableSortedMultiset.java b/android/guava/src/com/google/common/collect/UnmodifiableSortedMultiset.java index 20286afe1156..d1761a9ff020 100644 --- a/android/guava/src/com/google/common/collect/UnmodifiableSortedMultiset.java +++ b/android/guava/src/com/google/common/collect/UnmodifiableSortedMultiset.java @@ -16,11 +16,16 @@ package com.google.common.collect; +import static com.google.common.collect.Sets.unmodifiableNavigableSet; + import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.Multisets.UnmodifiableMultiset; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.util.Comparator; import java.util.NavigableSet; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Implementation of {@link Multisets#unmodifiableSortedMultiset(SortedMultiset)}, split out into @@ -29,8 +34,8 @@ * * @author Louis Wasserman */ -@GwtCompatible(emulated = true) -final class UnmodifiableSortedMultiset extends UnmodifiableMultiset +@GwtCompatible +final class UnmodifiableSortedMultiset extends UnmodifiableMultiset implements SortedMultiset { UnmodifiableSortedMultiset(SortedMultiset delegate) { super(delegate); @@ -48,7 +53,7 @@ public Comparator comparator() { @Override NavigableSet createElementSet() { - return Sets.unmodifiableNavigableSet(delegate().elementSet()); + return unmodifiableNavigableSet(delegate().elementSet()); } @Override @@ -56,13 +61,17 @@ public NavigableSet elementSet() { return (NavigableSet) super.elementSet(); } - @NullableDecl private transient UnmodifiableSortedMultiset descendingMultiset; + @LazyInit private transient @Nullable UnmodifiableSortedMultiset descendingMultiset; + // TODO(b/418181860): This method creates retain cycles in J2ObjC. In order to break the cycle, + // there needs to be separate classes for primary and descending multiset, where the primary one + // would hold {@code @LazyInit @RetainedWith @Nullable} reference to its descending multiset, and + // the other {@code final} reference. @Override public SortedMultiset descendingMultiset() { UnmodifiableSortedMultiset result = descendingMultiset; if (result == null) { - result = new UnmodifiableSortedMultiset(delegate().descendingMultiset()); + result = new UnmodifiableSortedMultiset<>(delegate().descendingMultiset()); result.descendingMultiset = this; return descendingMultiset = result; } @@ -70,41 +79,44 @@ public SortedMultiset descendingMultiset() { } @Override - public Entry firstEntry() { + public @Nullable Entry firstEntry() { return delegate().firstEntry(); } @Override - public Entry lastEntry() { + public @Nullable Entry lastEntry() { return delegate().lastEntry(); } @Override - public Entry pollFirstEntry() { + public @Nullable Entry pollFirstEntry() { throw new UnsupportedOperationException(); } @Override - public Entry pollLastEntry() { + public @Nullable Entry pollLastEntry() { throw new UnsupportedOperationException(); } @Override - public SortedMultiset headMultiset(E upperBound, BoundType boundType) { + public SortedMultiset headMultiset(@ParametricNullness E upperBound, BoundType boundType) { return Multisets.unmodifiableSortedMultiset(delegate().headMultiset(upperBound, boundType)); } @Override public SortedMultiset subMultiset( - E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType) { + @ParametricNullness E lowerBound, + BoundType lowerBoundType, + @ParametricNullness E upperBound, + BoundType upperBoundType) { return Multisets.unmodifiableSortedMultiset( delegate().subMultiset(lowerBound, lowerBoundType, upperBound, upperBoundType)); } @Override - public SortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + public SortedMultiset tailMultiset(@ParametricNullness E lowerBound, BoundType boundType) { return Multisets.unmodifiableSortedMultiset(delegate().tailMultiset(lowerBound, boundType)); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/UsingToStringOrdering.java b/android/guava/src/com/google/common/collect/UsingToStringOrdering.java index 3167946b1582..779b63e98ab3 100644 --- a/android/guava/src/com/google/common/collect/UsingToStringOrdering.java +++ b/android/guava/src/com/google/common/collect/UsingToStringOrdering.java @@ -17,10 +17,12 @@ package com.google.common.collect; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Serializable; /** An ordering that uses the natural order of the string representation of the values. */ -@GwtCompatible(serializable = true) +@GwtCompatible final class UsingToStringOrdering extends Ordering implements Serializable { static final UsingToStringOrdering INSTANCE = new UsingToStringOrdering(); @@ -41,5 +43,5 @@ public String toString() { private UsingToStringOrdering() {} - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/collect/package-info.java b/android/guava/src/com/google/common/collect/package-info.java index d46e65fd3a13..d9f2331e1a64 100644 --- a/android/guava/src/com/google/common/collect/package-info.java +++ b/android/guava/src/com/google/common/collect/package-info.java @@ -15,206 +15,113 @@ */ /** - * This package contains generic collection interfaces and implementations, and other utilities for - * working with collections. It is a part of the open-source Guava library. + * Collection interfaces and implementations, and other utilities for collections. This package is a + * part of the open-source Guava library. * - *

    Collection Types

    + *

    The classes in this package include: + * + *

    Immutable collections

    + * + * These are collections whose contents will never change. They also offer a few additional + * guarantees (see {@link ImmutableCollection} for details). Implementations are available for both + * the JDK collection types and the Guava collection types (listed below). + * + *

    Collection types

    * *
    - *
    {@link com.google.common.collect.BiMap} + *
    {@link Multimap} + *
    A new type, which is similar to {@link java.util.Map}, but may contain multiple entries + * with the same key. Some behaviors of {@link Multimap} are left unspecified and are provided + * only by the subtypes mentioned below. + *
    {@link ListMultimap} + *
    An extension of {@link Multimap} which permits duplicate entries, supports random access of + * values for a particular key, and has partially order-dependent equality as defined + * by {@link ListMultimap#equals(Object)}. {@code ListMultimap} takes its name from the fact + * that the {@linkplain ListMultimap#get collection of values} associated with a given key + * fulfills the {@link java.util.List} contract. + *
    {@link SetMultimap} + *
    An extension of {@link Multimap} which has order-independent equality and does not allow + * duplicate entries; that is, while a key may appear twice in a {@code SetMultimap}, each + * must map to a different value. {@code SetMultimap} takes its name from the fact that the + * {@linkplain SetMultimap#get collection of values} associated with a given key fulfills the + * {@link java.util.Set} contract. + *
    {@link SortedSetMultimap} + *
    An extension of {@link SetMultimap} for which the {@linkplain SortedSetMultimap#get + * collection values} associated with a given key is a {@link java.util.SortedSet}. + *
    {@link BiMap} *
    An extension of {@link java.util.Map} that guarantees the uniqueness of its values as well * as that of its keys. This is sometimes called an "invertible map," since the restriction on - * values enables it to support an {@linkplain com.google.common.collect.BiMap#inverse inverse - * view} -- which is another instance of {@code BiMap}. - *
    {@link com.google.common.collect.Multiset} + * values enables it to support an {@linkplain BiMap#inverse inverse view} -- which is another + * instance of {@code BiMap}. + *
    {@link Table} + *
    A new type, which is similar to {@link java.util.Map}, but which indexes its values by an + * ordered pair of keys, a row key and column key. + *
    {@link Multiset} *
    An extension of {@link java.util.Collection} that may contain duplicate values like a * {@link java.util.List}, yet has order-independent equality like a {@link java.util.Set}. * One typical use for a multiset is to represent a histogram. - *
    {@link com.google.common.collect.Multimap} - *
    A new type, which is similar to {@link java.util.Map}, but may contain multiple entries - * with the same key. Some behaviors of {@link com.google.common.collect.Multimap} are left - * unspecified and are provided only by the subtypes mentioned below. - *
    {@link com.google.common.collect.ListMultimap} - *
    An extension of {@link com.google.common.collect.Multimap} which permits duplicate entries, - * supports random access of values for a particular key, and has partially order-dependent - * equality as defined by {@link com.google.common.collect.ListMultimap#equals(Object)}. - * {@code ListMultimap} takes its name from the fact that the {@linkplain - * com.google.common.collect.ListMultimap#get collection of values} associated with a given - * key fulfills the {@link java.util.List} contract. - *
    {@link com.google.common.collect.SetMultimap} - *
    An extension of {@link com.google.common.collect.Multimap} which has order-independent - * equality and does not allow duplicate entries; that is, while a key may appear twice in a - * {@code SetMultimap}, each must map to a different value. {@code SetMultimap} takes its name - * from the fact that the {@linkplain com.google.common.collect.SetMultimap#get collection of - * values} associated with a given key fulfills the {@link java.util.Set} contract. - *
    {@link com.google.common.collect.SortedSetMultimap} - *
    An extension of {@link com.google.common.collect.SetMultimap} for which the {@linkplain - * com.google.common.collect.SortedSetMultimap#get collection values} associated with a given - * key is a {@link java.util.SortedSet}. - *
    {@link com.google.common.collect.Table} - *
    A new type, which is similar to {@link java.util.Map}, but which indexes its values by an - * ordered pair of keys, a row key and column key. - *
    {@link com.google.common.collect.ClassToInstanceMap} + *
    {@link ClassToInstanceMap} *
    An extension of {@link java.util.Map} that associates a raw type with an instance of that * type. *
    * - *

    Collection Implementations

    - * - *

    of {@link java.util.List}

    - * - *
      - *
    • {@link com.google.common.collect.ImmutableList} - *
    - * - *

    of {@link java.util.Set}

    - * - *
      - *
    • {@link com.google.common.collect.ImmutableSet} - *
    • {@link com.google.common.collect.ImmutableSortedSet} - *
    • {@link com.google.common.collect.ContiguousSet} (see {@code Range}) - *
    - * - *

    of {@link java.util.Map}

    - * - *
      - *
    • {@link com.google.common.collect.ImmutableMap} - *
    • {@link com.google.common.collect.ImmutableSortedMap} - *
    • {@link com.google.common.collect.MapMaker} - *
    - * - *

    of {@link com.google.common.collect.BiMap}

    - * - *
      - *
    • {@link com.google.common.collect.ImmutableBiMap} - *
    • {@link com.google.common.collect.HashBiMap} - *
    • {@link com.google.common.collect.EnumBiMap} - *
    • {@link com.google.common.collect.EnumHashBiMap} - *
    - * - *

    of {@link com.google.common.collect.Multiset}

    - * - *
      - *
    • {@link com.google.common.collect.ImmutableMultiset} - *
    • {@link com.google.common.collect.ImmutableSortedMultiset} - *
    • {@link com.google.common.collect.HashMultiset} - *
    • {@link com.google.common.collect.LinkedHashMultiset} - *
    • {@link com.google.common.collect.TreeMultiset} - *
    • {@link com.google.common.collect.EnumMultiset} - *
    • {@link com.google.common.collect.ConcurrentHashMultiset} - *
    - * - *

    of {@link com.google.common.collect.Multimap}

    - * - *
      - *
    • {@link com.google.common.collect.ImmutableMultimap} - *
    • {@link com.google.common.collect.ImmutableListMultimap} - *
    • {@link com.google.common.collect.ImmutableSetMultimap} - *
    • {@link com.google.common.collect.ArrayListMultimap} - *
    • {@link com.google.common.collect.HashMultimap} - *
    • {@link com.google.common.collect.TreeMultimap} - *
    • {@link com.google.common.collect.LinkedHashMultimap} - *
    • {@link com.google.common.collect.LinkedListMultimap} - *
    - * - *

    of {@link com.google.common.collect.Table}

    - * - *
      - *
    • {@link com.google.common.collect.ImmutableTable} - *
    • {@link com.google.common.collect.ArrayTable} - *
    • {@link com.google.common.collect.HashBasedTable} - *
    • {@link com.google.common.collect.TreeBasedTable} - *
    - * - *

    of {@link com.google.common.collect.ClassToInstanceMap}

    + *

    Ranges

    * *
      - *
    • {@link com.google.common.collect.ImmutableClassToInstanceMap} - *
    • {@link com.google.common.collect.MutableClassToInstanceMap} + *
    • {@link Range} + *
    • {@link RangeMap} + *
    • {@link RangeSet} + *
    • {@link DiscreteDomain} + *
    • {@link ContiguousSet} *
    * *

    Classes of static utility methods

    * *
      - *
    • {@link com.google.common.collect.Collections2} - *
    • {@link com.google.common.collect.Iterators} - *
    • {@link com.google.common.collect.Iterables} - *
    • {@link com.google.common.collect.Lists} - *
    • {@link com.google.common.collect.Maps} - *
    • {@link com.google.common.collect.Queues} - *
    • {@link com.google.common.collect.Sets} - *
    • {@link com.google.common.collect.Multisets} - *
    • {@link com.google.common.collect.Multimaps} - *
    • {@link com.google.common.collect.Tables} - *
    • {@link com.google.common.collect.ObjectArrays} - *
    - * - *

    Comparison

    - * - *
      - *
    • {@link com.google.common.collect.Ordering} - *
    • {@link com.google.common.collect.ComparisonChain} + *
    • {@link Collections2} + *
    • {@link Comparators} + *
    • {@link Iterables} + *
    • {@link Iterators} + *
    • {@link Lists} + *
    • {@link Maps} + *
    • {@link MoreCollectors} + *
    • {@link Multimaps} + *
    • {@link Multisets} + *
    • {@link ObjectArrays} + *
    • {@link Queues} + *
    • {@link Sets} + *
    • {@link Streams} + *
    • {@link Tables} *
    * *

    Abstract implementations

    * *
      - *
    • {@link com.google.common.collect.AbstractIterator} - *
    • {@link com.google.common.collect.AbstractSequentialIterator} - *
    • {@link com.google.common.collect.ImmutableCollection} - *
    • {@link com.google.common.collect.UnmodifiableIterator} - *
    • {@link com.google.common.collect.UnmodifiableListIterator} + *
    • {@link AbstractIterator} + *
    • {@link AbstractSequentialIterator} + *
    • {@link UnmodifiableIterator} + *
    • {@link UnmodifiableListIterator} *
    * - *

    Ranges

    + *

    Forwarding collections

    * - *
      - *
    • {@link com.google.common.collect.Range} - *
    • {@link com.google.common.collect.RangeMap} - *
    • {@link com.google.common.collect.DiscreteDomain} - *
    • {@link com.google.common.collect.ContiguousSet} - *
    + * We provide implementations of collections that forward all method calls to a delegate collection + * by default. Subclasses can override one or more methods to implement the decorator pattern. For + * an example, see {@link ForwardingCollection}. * *

    Other

    * *
      - *
    • {@link com.google.common.collect.Interner}, {@link com.google.common.collect.Interners} - *
    • {@link com.google.common.collect.MapDifference}, {@link - * com.google.common.collect.SortedMapDifference} - *
    • {@link com.google.common.collect.MinMaxPriorityQueue} - *
    • {@link com.google.common.collect.PeekingIterator} - *
    - * - *

    Forwarding collections

    - * - *
      - *
    • {@link com.google.common.collect.ForwardingCollection} - *
    • {@link com.google.common.collect.ForwardingConcurrentMap} - *
    • {@link com.google.common.collect.ForwardingIterator} - *
    • {@link com.google.common.collect.ForwardingList} - *
    • {@link com.google.common.collect.ForwardingListIterator} - *
    • {@link com.google.common.collect.ForwardingListMultimap} - *
    • {@link com.google.common.collect.ForwardingMap} - *
    • {@link com.google.common.collect.ForwardingMapEntry} - *
    • {@link com.google.common.collect.ForwardingMultimap} - *
    • {@link com.google.common.collect.ForwardingMultiset} - *
    • {@link com.google.common.collect.ForwardingNavigableMap} - *
    • {@link com.google.common.collect.ForwardingNavigableSet} - *
    • {@link com.google.common.collect.ForwardingObject} - *
    • {@link com.google.common.collect.ForwardingQueue} - *
    • {@link com.google.common.collect.ForwardingSet} - *
    • {@link com.google.common.collect.ForwardingSetMultimap} - *
    • {@link com.google.common.collect.ForwardingSortedMap} - *
    • {@link com.google.common.collect.ForwardingSortedMultiset} - *
    • {@link com.google.common.collect.ForwardingSortedSet} - *
    • {@link com.google.common.collect.ForwardingSortedSetMultimap} - *
    • {@link com.google.common.collect.ForwardingTable} + *
    • {@link EvictingQueue} + *
    • {@link Interner}, {@link Interners} + *
    • {@link MapMaker} + *
    • {@link MinMaxPriorityQueue} + *
    • {@link PeekingIterator} *
    */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.collect; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/escape/ArrayBasedCharEscaper.java b/android/guava/src/com/google/common/escape/ArrayBasedCharEscaper.java index 20e856348476..8f9dddabe0d3 100644 --- a/android/guava/src/com/google/common/escape/ArrayBasedCharEscaper.java +++ b/android/guava/src/com/google/common/escape/ArrayBasedCharEscaper.java @@ -16,10 +16,9 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import java.util.Map; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * A {@link CharEscaper} that uses an array to quickly look up replacement characters for a given @@ -41,9 +40,7 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault public abstract class ArrayBasedCharEscaper extends CharEscaper { // The replacement array (see ArrayBasedEscaperMap). private final char[][] replacements; @@ -123,8 +120,7 @@ public final String escape(String s) { * @return the replacement characters, or {@code null} if no escaping was required */ @Override - @CheckForNull - protected final char[] escape(char c) { + protected final char @Nullable [] escape(char c) { if (c < replacementsLength) { char[] chars = replacements[c]; if (chars != null) { @@ -150,6 +146,5 @@ protected final char[] escape(char c) { * @return the replacement characters, or {@code null} if no escaping was required */ // TODO(dbeaumont,cpovirk): Rename this something better once refactoring done - @CheckForNull - protected abstract char[] escapeUnsafe(char c); + protected abstract char @Nullable [] escapeUnsafe(char c); } diff --git a/android/guava/src/com/google/common/escape/ArrayBasedEscaperMap.java b/android/guava/src/com/google/common/escape/ArrayBasedEscaperMap.java index a0883fea2d0f..68515dfa3512 100644 --- a/android/guava/src/com/google/common/escape/ArrayBasedEscaperMap.java +++ b/android/guava/src/com/google/common/escape/ArrayBasedEscaperMap.java @@ -15,11 +15,10 @@ package com.google.common.escape; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Collections.max; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.VisibleForTesting; -import java.util.Collections; import java.util.Map; /** @@ -36,9 +35,7 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault public final class ArrayBasedEscaperMap { /** * Returns a new ArrayBasedEscaperMap for creating ArrayBasedCharEscaper or @@ -72,7 +69,7 @@ static char[][] createReplacementArray(Map map) { if (map.isEmpty()) { return EMPTY_REPLACEMENT_ARRAY; } - char max = Collections.max(map.keySet()); + char max = max(map.keySet()); char[][] replacements = new char[max + 1][]; for (Character c : map.keySet()) { replacements[c] = map.get(c).toCharArray(); @@ -81,5 +78,6 @@ static char[][] createReplacementArray(Map map) { } // Immutable empty array for when there are no replacements. + @SuppressWarnings("ConstantCaseForConstants") // An empty array is a constant. private static final char[][] EMPTY_REPLACEMENT_ARRAY = new char[0][0]; } diff --git a/android/guava/src/com/google/common/escape/ArrayBasedUnicodeEscaper.java b/android/guava/src/com/google/common/escape/ArrayBasedUnicodeEscaper.java index 5ea780712a06..020d952b0299 100644 --- a/android/guava/src/com/google/common/escape/ArrayBasedUnicodeEscaper.java +++ b/android/guava/src/com/google/common/escape/ArrayBasedUnicodeEscaper.java @@ -15,12 +15,11 @@ package com.google.common.escape; import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.Math.min; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import java.util.Map; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A {@link UnicodeEscaper} that uses an array to quickly look up replacement characters for a given @@ -41,9 +40,8 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault +@SuppressWarnings("EscapedEntity") // We do mean for the user to see "&" etc. public abstract class ArrayBasedUnicodeEscaper extends UnicodeEscaper { // The replacement array (see ArrayBasedEscaperMap). private final char[][] replacements; @@ -130,10 +128,10 @@ protected ArrayBasedUnicodeEscaper( this.safeMinChar = Character.MAX_VALUE; this.safeMaxChar = 0; } else { - // The safe range is non empty and contains values below the surrogate + // The safe range is non-empty and contains values below the surrogate // range but may extend above it. We may need to clip the maximum value. this.safeMinChar = (char) safeMin; - this.safeMaxChar = (char) Math.min(safeMax, Character.MIN_HIGH_SURROGATE - 1); + this.safeMaxChar = (char) min(safeMax, Character.MIN_HIGH_SURROGATE - 1); } } @@ -163,8 +161,7 @@ public final String escape(String s) { * @return the replacement characters, or {@code null} if no escaping was required */ @Override - @CheckForNull - protected final char[] escape(int cp) { + protected final char @Nullable [] escape(int cp) { if (cp < replacementsLength) { char[] chars = replacements[cp]; if (chars != null) { @@ -204,6 +201,5 @@ protected final int nextEscapeIndex(CharSequence csq, int index, int end) { * @param cp the Unicode code point to escape * @return the replacement characters, or {@code null} if no escaping was required */ - @CheckForNull - protected abstract char[] escapeUnsafe(int cp); + protected abstract char @Nullable [] escapeUnsafe(int cp); } diff --git a/android/guava/src/com/google/common/escape/CharEscaper.java b/android/guava/src/com/google/common/escape/CharEscaper.java index 55090f69804c..0d4d3a196ce5 100644 --- a/android/guava/src/com/google/common/escape/CharEscaper.java +++ b/android/guava/src/com/google/common/escape/CharEscaper.java @@ -16,9 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * An object that converts literal text into a format safe for inclusion in a particular context @@ -40,9 +39,8 @@ * @author Sven Mawson * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault +@SuppressWarnings("EscapedEntity") // We do mean for the user to see "<" etc. public abstract class CharEscaper extends Escaper { /** Constructor for use by subclasses. */ protected CharEscaper() {} @@ -82,8 +80,7 @@ public String escape(String string) { * @param c the character to escape if necessary * @return the replacement characters, or {@code null} if no escaping was needed */ - @CheckForNull - protected abstract char[] escape(char c); + protected abstract char @Nullable [] escape(char c); /** * Returns the escaped form of a given literal string, starting at the given index. This method is diff --git a/android/guava/src/com/google/common/escape/CharEscaperBuilder.java b/android/guava/src/com/google/common/escape/CharEscaperBuilder.java index cbe6958f3b72..c32d6e2c931b 100644 --- a/android/guava/src/com/google/common/escape/CharEscaperBuilder.java +++ b/android/guava/src/com/google/common/escape/CharEscaperBuilder.java @@ -16,14 +16,12 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Simple helper class to build a "sparse" array of objects based on the indexes that were added to @@ -34,15 +32,13 @@ * @author Sven Mawson * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault public final class CharEscaperBuilder { /** * Simple decorator that turns an array of replacement char[]s into a CharEscaper, this results in * a very fast escape method. */ - private static class CharArrayDecorator extends CharEscaper { + private static final class CharArrayDecorator extends CharEscaper { private final char[] @Nullable [] replacements; private final int replaceLength; @@ -68,8 +64,7 @@ public String escape(String s) { } @Override - @CheckForNull - protected char[] escape(char c) { + protected char @Nullable [] escape(char c) { return c < replaceLength ? replacements[c] : null; } } diff --git a/android/guava/src/com/google/common/escape/ElementTypesAreNonnullByDefault.java b/android/guava/src/com/google/common/escape/ElementTypesAreNonnullByDefault.java deleted file mode 100755 index 992c9a3e4d84..000000000000 --- a/android/guava/src/com/google/common/escape/ElementTypesAreNonnullByDefault.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.escape; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import com.google.common.annotations.GwtCompatible; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; - -/** - * Marks all "top-level" types as non-null in a way that is recognized by Kotlin. Note that this - * unfortunately includes type-variable usages, so we also provide {@link ParametricNullness} to - * "undo" it as best we can. - */ -@GwtCompatible -@Retention(RUNTIME) -@Target(TYPE) -@TypeQualifierDefault({FIELD, METHOD, PARAMETER}) -@Nonnull -@interface ElementTypesAreNonnullByDefault {} diff --git a/android/guava/src/com/google/common/escape/Escaper.java b/android/guava/src/com/google/common/escape/Escaper.java index cdfe4e92a654..924e3d34c8d0 100644 --- a/android/guava/src/com/google/common/escape/Escaper.java +++ b/android/guava/src/com/google/common/escape/Escaper.java @@ -56,7 +56,7 @@ */ @DoNotMock("Use Escapers.nullEscaper() or another methods from the *Escapers classes") @GwtCompatible -@ElementTypesAreNonnullByDefault +@SuppressWarnings("EscapedEntity") // We do mean for the user to see "<" etc. public abstract class Escaper { // TODO(dbeaumont): evaluate custom implementations, considering package private constructor. /** Constructor for use by subclasses. */ @@ -85,13 +85,7 @@ protected Escaper() {} */ public abstract String escape(String string); - private final Function asFunction = - new Function() { - @Override - public String apply(String from) { - return escape(from); - } - }; + private final Function asFunction = this::escape; /** Returns a {@link Function} that invokes {@link #escape(String)} on this escaper. */ public final Function asFunction() { diff --git a/android/guava/src/com/google/common/escape/Escapers.java b/android/guava/src/com/google/common/escape/Escapers.java index 41af6688e904..c5f2bd026fdb 100644 --- a/android/guava/src/com/google/common/escape/Escapers.java +++ b/android/guava/src/com/google/common/escape/Escapers.java @@ -16,13 +16,11 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.HashMap; import java.util.Map; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@link Escaper} instances. @@ -31,9 +29,7 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Escapers { private Escapers() {} @@ -54,8 +50,7 @@ public String escape(String string) { } @Override - @CheckForNull - protected char[] escape(char c) { + protected char @Nullable [] escape(char c) { // TODO: Fix tests not to call this directly and make it throw an error. return null; } @@ -93,12 +88,11 @@ public static Builder builder() { * @author David Beaumont * @since 15.0 */ - @Beta public static final class Builder { private final Map replacementMap = new HashMap<>(); private char safeMin = Character.MIN_VALUE; private char safeMax = Character.MAX_VALUE; - @CheckForNull private String unsafeReplacement = null; + private @Nullable String unsafeReplacement = null; // The constructor is exposed via the builder() method above. private Builder() {} @@ -154,46 +148,17 @@ public Builder addEscape(char c, String replacement) { /** Returns a new escaper based on the current state of the builder. */ public Escaper build() { return new ArrayBasedCharEscaper(replacementMap, safeMin, safeMax) { - @CheckForNull - private final char[] replacementChars = + private final char @Nullable [] replacementChars = unsafeReplacement != null ? unsafeReplacement.toCharArray() : null; @Override - @CheckForNull - protected char[] escapeUnsafe(char c) { + protected char @Nullable [] escapeUnsafe(char c) { return replacementChars; } }; } } - /** - * Returns a {@link UnicodeEscaper} equivalent to the given escaper instance. If the escaper is - * already a UnicodeEscaper then it is simply returned, otherwise it is wrapped in a - * UnicodeEscaper. - * - *

    When a {@link CharEscaper} escaper is wrapped by this method it acquires extra behavior with - * respect to the well-formedness of Unicode character sequences and will throw {@link - * IllegalArgumentException} when given bad input. - * - * @param escaper the instance to be wrapped - * @return a UnicodeEscaper with the same behavior as the given instance - * @throws NullPointerException if escaper is null - * @throws IllegalArgumentException if escaper is not a UnicodeEscaper or a CharEscaper - */ - static UnicodeEscaper asUnicodeEscaper(Escaper escaper) { - checkNotNull(escaper); - if (escaper instanceof UnicodeEscaper) { - return (UnicodeEscaper) escaper; - } else if (escaper instanceof CharEscaper) { - return wrap((CharEscaper) escaper); - } - // In practice this shouldn't happen because it would be very odd not to - // extend either CharEscaper or UnicodeEscaper for non trivial cases. - throw new IllegalArgumentException( - "Cannot create a UnicodeEscaper from: " + escaper.getClass().getName()); - } - /** * Returns a string that would replace the given character in the specified escaper, or {@code * null} if no replacement should be made. This method is intended for use in tests through the @@ -203,8 +168,7 @@ static UnicodeEscaper asUnicodeEscaper(Escaper escaper) { * @param c the character to escape if necessary * @return the replacement string, or {@code null} if no escaping was needed */ - @CheckForNull - public static String computeReplacement(CharEscaper escaper, char c) { + public static @Nullable String computeReplacement(CharEscaper escaper, char c) { return stringOrNull(escaper.escape(c)); } @@ -217,64 +181,11 @@ public static String computeReplacement(CharEscaper escaper, char c) { * @param cp the Unicode code point to escape if necessary * @return the replacement string, or {@code null} if no escaping was needed */ - @CheckForNull - public static String computeReplacement(UnicodeEscaper escaper, int cp) { + public static @Nullable String computeReplacement(UnicodeEscaper escaper, int cp) { return stringOrNull(escaper.escape(cp)); } - @CheckForNull - private static String stringOrNull(@CheckForNull char[] in) { + private static @Nullable String stringOrNull(char @Nullable [] in) { return (in == null) ? null : new String(in); } - - /** Private helper to wrap a CharEscaper as a UnicodeEscaper. */ - private static UnicodeEscaper wrap(final CharEscaper escaper) { - return new UnicodeEscaper() { - @Override - @CheckForNull - protected char[] escape(int cp) { - // If a code point maps to a single character, just escape that. - if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) { - return escaper.escape((char) cp); - } - // Convert the code point to a surrogate pair and escape them both. - // Note: This code path is horribly slow and typically allocates 4 new - // char[] each time it is invoked. However this avoids any - // synchronization issues and makes the escaper thread safe. - char[] surrogateChars = new char[2]; - Character.toChars(cp, surrogateChars, 0); - char[] hiChars = escaper.escape(surrogateChars[0]); - char[] loChars = escaper.escape(surrogateChars[1]); - - // If either hiChars or lowChars are non-null, the CharEscaper is trying - // to escape the characters of a surrogate pair separately. This is - // uncommon and applies only to escapers that assume UCS-2 rather than - // UTF-16. See: http://en.wikipedia.org/wiki/UTF-16/UCS-2 - if (hiChars == null && loChars == null) { - // We expect this to be the common code path for most escapers. - return null; - } - // Combine the characters and/or escaped sequences into a single array. - int hiCount = hiChars != null ? hiChars.length : 1; - int loCount = loChars != null ? loChars.length : 1; - char[] output = new char[hiCount + loCount]; - if (hiChars != null) { - // TODO: Is this faster than System.arraycopy() for small arrays? - for (int n = 0; n < hiChars.length; ++n) { - output[n] = hiChars[n]; - } - } else { - output[0] = surrogateChars[0]; - } - if (loChars != null) { - for (int n = 0; n < loChars.length; ++n) { - output[hiCount + n] = loChars[n]; - } - } else { - output[hiCount] = surrogateChars[1]; - } - return output; - } - }; - } } diff --git a/android/guava/src/com/google/common/escape/ParametricNullness.java b/android/guava/src/com/google/common/escape/ParametricNullness.java old mode 100755 new mode 100644 index 2f03d59f30de..3ddd153ba04f --- a/android/guava/src/com/google/common/escape/ParametricNullness.java +++ b/android/guava/src/com/google/common/escape/ParametricNullness.java @@ -19,25 +19,54 @@ import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static javax.annotation.meta.When.UNKNOWN; +import static java.lang.annotation.RetentionPolicy.CLASS; import com.google.common.annotations.GwtCompatible; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierNickname; /** - * Marks a "top-level" type-variable usage as (a) a Kotlin platform type when the type argument is - * non-nullable and (b) nullable when the type argument is nullable. This is the closest we can get - * to "non-nullable when non-nullable; nullable when nullable" (like the Android {@code - * NullFromTypeParam}). We use this to "undo" {@link ElementTypesAreNonnullByDefault}. + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. */ @GwtCompatible -@Retention(RUNTIME) +@Retention(CLASS) @Target({FIELD, METHOD, PARAMETER}) -@TypeQualifierNickname -@Nonnull(when = UNKNOWN) @interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/escape/Platform.java b/android/guava/src/com/google/common/escape/Platform.java index dc6610c041bb..4dc0849a868a 100644 --- a/android/guava/src/com/google/common/escape/Platform.java +++ b/android/guava/src/com/google/common/escape/Platform.java @@ -14,6 +14,8 @@ package com.google.common.escape; +import static java.util.Objects.requireNonNull; + import com.google.common.annotations.GwtCompatible; /** @@ -21,14 +23,14 @@ * * @author Jesse Wilson */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible final class Platform { private Platform() {} /** Returns a thread-local 1024-char array. */ static char[] charBufferFromThreadLocal() { - return DEST_TL.get(); + // requireNonNull accommodates Android's @RecentlyNullable annotation on ThreadLocal.get + return requireNonNull(DEST_TL.get()); } /** diff --git a/android/guava/src/com/google/common/escape/UnicodeEscaper.java b/android/guava/src/com/google/common/escape/UnicodeEscaper.java index c10ae34cd348..caad7da1fdc1 100644 --- a/android/guava/src/com/google/common/escape/UnicodeEscaper.java +++ b/android/guava/src/com/google/common/escape/UnicodeEscaper.java @@ -16,9 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * An {@link Escaper} that converts literal text into a format safe for inclusion in a particular @@ -50,9 +49,8 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault +@SuppressWarnings("EscapedEntity") // We do mean for the user to see "<" etc. public abstract class UnicodeEscaper extends Escaper { /** The amount of padding (chars) to use when growing the escape buffer. */ private static final int DEST_PAD = 32; @@ -79,8 +77,7 @@ protected UnicodeEscaper() {} * @param cp the Unicode code point to escape if necessary * @return the replacement characters, or {@code null} if no escaping was needed */ - @CheckForNull - protected abstract char[] escape(int cp); + protected abstract char @Nullable [] escape(int cp); /** * Returns the escaped form of a given literal string. diff --git a/android/guava/src/com/google/common/escape/package-info.java b/android/guava/src/com/google/common/escape/package-info.java index 8cd29e6f85fb..173f811a3aae 100644 --- a/android/guava/src/com/google/common/escape/package-info.java +++ b/android/guava/src/com/google/common/escape/package-info.java @@ -14,19 +14,19 @@ /** * Interfaces, utilities, and simple implementations of escapers and encoders. The primary type is - * {@link com.google.common.escape.Escaper}. + * {@link Escaper}. * *

    Additional escapers implementations are found in the applicable packages: {@link * com.google.common.html.HtmlEscapers} in {@code com.google.common.html}, {@link * com.google.common.xml.XmlEscapers} in {@code com.google.common.xml}, and {@link * com.google.common.net.UrlEscapers} in {@code com.google.common.net}. * - *

    This package is a part of the open-source Guava + *

    This package is a part of the open-source Guava * library. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.escape; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/eventbus/AllowConcurrentEvents.java b/android/guava/src/com/google/common/eventbus/AllowConcurrentEvents.java index 28bc4b23e0c3..652e5e50bc6a 100644 --- a/android/guava/src/com/google/common/eventbus/AllowConcurrentEvents.java +++ b/android/guava/src/com/google/common/eventbus/AllowConcurrentEvents.java @@ -30,5 +30,4 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -@ElementTypesAreNonnullByDefault public @interface AllowConcurrentEvents {} diff --git a/android/guava/src/com/google/common/eventbus/AsyncEventBus.java b/android/guava/src/com/google/common/eventbus/AsyncEventBus.java index 4f387a712841..a6dac17f2289 100644 --- a/android/guava/src/com/google/common/eventbus/AsyncEventBus.java +++ b/android/guava/src/com/google/common/eventbus/AsyncEventBus.java @@ -23,7 +23,6 @@ * @author Cliff Biffle * @since 10.0 */ -@ElementTypesAreNonnullByDefault public class AsyncEventBus extends EventBus { /** diff --git a/android/guava/src/com/google/common/eventbus/DeadEvent.java b/android/guava/src/com/google/common/eventbus/DeadEvent.java index 2cdb23f712d7..90910b9b0805 100644 --- a/android/guava/src/com/google/common/eventbus/DeadEvent.java +++ b/android/guava/src/com/google/common/eventbus/DeadEvent.java @@ -27,7 +27,6 @@ * @author Cliff Biffle * @since 10.0 */ -@ElementTypesAreNonnullByDefault public class DeadEvent { private final Object source; diff --git a/android/guava/src/com/google/common/eventbus/Dispatcher.java b/android/guava/src/com/google/common/eventbus/Dispatcher.java index ff1ae2a197a4..388738163b7f 100644 --- a/android/guava/src/com/google/common/eventbus/Dispatcher.java +++ b/android/guava/src/com/google/common/eventbus/Dispatcher.java @@ -15,8 +15,9 @@ package com.google.common.eventbus; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; -import com.google.common.collect.Queues; +import java.util.ArrayDeque; import java.util.Iterator; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -31,7 +32,6 @@ * * @author Colin Decker */ -@ElementTypesAreNonnullByDefault abstract class Dispatcher { /** @@ -76,15 +76,17 @@ private static final class PerThreadQueuedDispatcher extends Dispatcher { // This dispatcher matches the original dispatch behavior of EventBus. /** Per-thread queue of events to dispatch. */ + @SuppressWarnings("ThreadLocalUsage") // Each Dispatcher needs its own state. private final ThreadLocal> queue = new ThreadLocal>() { @Override protected Queue initialValue() { - return Queues.newArrayDeque(); + return new ArrayDeque<>(); } }; /** Per-thread dispatch state, used to avoid reentrant event dispatching. */ + @SuppressWarnings("ThreadLocalUsage") // Each Dispatcher needs its own state. private final ThreadLocal dispatching = new ThreadLocal() { @Override @@ -97,7 +99,8 @@ protected Boolean initialValue() { void dispatch(Object event, Iterator subscribers) { checkNotNull(event); checkNotNull(subscribers); - Queue queueForThread = queue.get(); + // requireNonNull accommodates Android's @RecentlyNullable annotation on ThreadLocal.get + Queue queueForThread = requireNonNull(queue.get()); queueForThread.offer(new Event(event, subscribers)); if (!dispatching.get()) { @@ -133,7 +136,7 @@ private static final class LegacyAsyncDispatcher extends Dispatcher { // This dispatcher matches the original dispatch behavior of AsyncEventBus. // // We can't really make any guarantees about the overall dispatch order for this dispatcher in - // a multithreaded environment for a couple reasons: + // a multithreaded environment for a couple of reasons: // // 1. Subscribers to events posted on different threads can be interleaved with each other // freely. (A event on one thread, B event on another could yield any of @@ -149,8 +152,7 @@ private static final class LegacyAsyncDispatcher extends Dispatcher { // in some cases. /** Global event queue. */ - private final ConcurrentLinkedQueue queue = - Queues.newConcurrentLinkedQueue(); + private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); @Override void dispatch(Object event, Iterator subscribers) { diff --git a/android/guava/src/com/google/common/eventbus/ElementTypesAreNonnullByDefault.java b/android/guava/src/com/google/common/eventbus/ElementTypesAreNonnullByDefault.java deleted file mode 100755 index e8542bba63ec..000000000000 --- a/android/guava/src/com/google/common/eventbus/ElementTypesAreNonnullByDefault.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.eventbus; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import com.google.common.annotations.GwtCompatible; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; - -/** - * Marks all "top-level" types as non-null in a way that is recognized by Kotlin. Note that this - * unfortunately includes type-variable usages, so we also provide {@link ParametricNullness} to - * "undo" it as best we can. - */ -@GwtCompatible -@Retention(RUNTIME) -@Target(TYPE) -@TypeQualifierDefault({FIELD, METHOD, PARAMETER}) -@Nonnull -@interface ElementTypesAreNonnullByDefault {} diff --git a/android/guava/src/com/google/common/eventbus/EventBus.java b/android/guava/src/com/google/common/eventbus/EventBus.java index 6d58daa08599..6adf3c70f833 100644 --- a/android/guava/src/com/google/common/eventbus/EventBus.java +++ b/android/guava/src/com/google/common/eventbus/EventBus.java @@ -15,9 +15,9 @@ package com.google.common.eventbus; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import com.google.common.base.MoreObjects; -import com.google.common.util.concurrent.MoreExecutors; import java.lang.reflect.Method; import java.util.Iterator; import java.util.Locale; @@ -61,6 +61,8 @@ *

  • It makes the cross-references between producer and subscriber harder to find. This can * complicate debugging, lead to unintentional reentrant calls, and force apps to eagerly * initialize all possible subscribers at startup time. + *
  • It uses reflection in ways that break when code is processed by optimizers/minimizers like + * R8 and Proguard. *
  • It doesn't offer a way to wait for multiple events before taking action. For example, it * doesn't offer a way to wait for multiple producers to all report that they're "ready," nor * does it offer a way to batch multiple events from a single producer together. @@ -145,7 +147,6 @@ * @author Cliff Biffle * @since 10.0 */ -@ElementTypesAreNonnullByDefault public class EventBus { private static final Logger logger = Logger.getLogger(EventBus.class.getName()); @@ -170,10 +171,7 @@ public EventBus() { */ public EventBus(String identifier) { this( - identifier, - MoreExecutors.directExecutor(), - Dispatcher.perThreadDispatchQueue(), - LoggingHandler.INSTANCE); + identifier, directExecutor(), Dispatcher.perThreadDispatchQueue(), LoggingHandler.INSTANCE); } /** @@ -183,11 +181,7 @@ public EventBus(String identifier) { * @since 16.0 */ public EventBus(SubscriberExceptionHandler exceptionHandler) { - this( - "default", - MoreExecutors.directExecutor(), - Dispatcher.perThreadDispatchQueue(), - exceptionHandler); + this("default", directExecutor(), Dispatcher.perThreadDispatchQueue(), exceptionHandler); } EventBus( diff --git a/android/guava/src/com/google/common/eventbus/ParametricNullness.java b/android/guava/src/com/google/common/eventbus/ParametricNullness.java old mode 100755 new mode 100644 index fc5bb175f660..06ab743cb700 --- a/android/guava/src/com/google/common/eventbus/ParametricNullness.java +++ b/android/guava/src/com/google/common/eventbus/ParametricNullness.java @@ -19,25 +19,54 @@ import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static javax.annotation.meta.When.UNKNOWN; +import static java.lang.annotation.RetentionPolicy.CLASS; import com.google.common.annotations.GwtCompatible; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierNickname; /** - * Marks a "top-level" type-variable usage as (a) a Kotlin platform type when the type argument is - * non-nullable and (b) nullable when the type argument is nullable. This is the closest we can get - * to "non-nullable when non-nullable; nullable when nullable" (like the Android {@code - * NullFromTypeParam}). We use this to "undo" {@link ElementTypesAreNonnullByDefault}. + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *
      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. */ @GwtCompatible -@Retention(RUNTIME) +@Retention(CLASS) @Target({FIELD, METHOD, PARAMETER}) -@TypeQualifierNickname -@Nonnull(when = UNKNOWN) @interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/eventbus/Subscribe.java b/android/guava/src/com/google/common/eventbus/Subscribe.java index 88477f1bae75..4be88b35d28f 100644 --- a/android/guava/src/com/google/common/eventbus/Subscribe.java +++ b/android/guava/src/com/google/common/eventbus/Subscribe.java @@ -14,6 +14,7 @@ package com.google.common.eventbus; +import com.google.errorprone.annotations.Keep; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,5 +36,5 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -@ElementTypesAreNonnullByDefault +@Keep public @interface Subscribe {} diff --git a/android/guava/src/com/google/common/eventbus/Subscriber.java b/android/guava/src/com/google/common/eventbus/Subscriber.java index 73e7f420ac01..d2a1eda930c2 100644 --- a/android/guava/src/com/google/common/eventbus/Subscriber.java +++ b/android/guava/src/com/google/common/eventbus/Subscriber.java @@ -21,7 +21,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.concurrent.Executor; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * A subscriber method on a specific object, plus the executor that should be used for dispatching @@ -32,7 +32,6 @@ * * @author Colin Decker */ -@ElementTypesAreNonnullByDefault class Subscriber { /** Creates a {@code Subscriber} for {@code method} on {@code listener}. */ @@ -43,7 +42,7 @@ static Subscriber create(EventBus bus, Object listener, Method method) { } /** The event bus this subscriber belongs to. */ - @Weak private EventBus bus; + @Weak private final EventBus bus; /** The object with the subscriber method. */ @VisibleForTesting final Object target; @@ -64,16 +63,13 @@ private Subscriber(EventBus bus, Object target, Method method) { } /** Dispatches {@code event} to this subscriber using the proper executor. */ - final void dispatchEvent(final Object event) { + final void dispatchEvent(Object event) { executor.execute( - new Runnable() { - @Override - public void run() { - try { - invokeSubscriberMethod(event); - } catch (InvocationTargetException e) { - bus.handleSubscriberException(e.getCause(), context(event)); - } + () -> { + try { + invokeSubscriberMethod(event); + } catch (InvocationTargetException e) { + bus.handleSubscriberException(e.getCause(), context(event)); } }); } @@ -109,7 +105,7 @@ public final int hashCode() { } @Override - public final boolean equals(@CheckForNull Object obj) { + public final boolean equals(@Nullable Object obj) { if (obj instanceof Subscriber) { Subscriber that = (Subscriber) obj; // Use == so that different equal instances will still receive events. diff --git a/android/guava/src/com/google/common/eventbus/SubscriberExceptionContext.java b/android/guava/src/com/google/common/eventbus/SubscriberExceptionContext.java index 63c7d557fec9..df7b66d6deed 100644 --- a/android/guava/src/com/google/common/eventbus/SubscriberExceptionContext.java +++ b/android/guava/src/com/google/common/eventbus/SubscriberExceptionContext.java @@ -23,7 +23,6 @@ * * @since 16.0 */ -@ElementTypesAreNonnullByDefault public class SubscriberExceptionContext { private final EventBus eventBus; private final Object event; @@ -46,24 +45,24 @@ public class SubscriberExceptionContext { } /** - * @return The {@link EventBus} that handled the event and the subscriber. Useful for broadcasting - * a new event based on the error. + * Returns the {@link EventBus} that handled the event and the subscriber. Useful for broadcasting + * a new event based on the error. */ public EventBus getEventBus() { return eventBus; } - /** @return The event object that caused the subscriber to throw. */ + /** Returns the event object that caused the subscriber to throw. */ public Object getEvent() { return event; } - /** @return The object context that the subscriber was called on. */ + /** Returns the object context that the subscriber was called on. */ public Object getSubscriber() { return subscriber; } - /** @return The subscribed method that threw the exception. */ + /** Returns the subscribed method that threw the exception. */ public Method getSubscriberMethod() { return subscriberMethod; } diff --git a/android/guava/src/com/google/common/eventbus/SubscriberExceptionHandler.java b/android/guava/src/com/google/common/eventbus/SubscriberExceptionHandler.java index 1c2fbb109adb..c239ad74fc87 100644 --- a/android/guava/src/com/google/common/eventbus/SubscriberExceptionHandler.java +++ b/android/guava/src/com/google/common/eventbus/SubscriberExceptionHandler.java @@ -14,13 +14,11 @@ package com.google.common.eventbus; - /** * Handler for exceptions thrown by event subscribers. * * @since 16.0 */ -@ElementTypesAreNonnullByDefault public interface SubscriberExceptionHandler { /** Handles exceptions thrown by subscribers. */ void handleException(Throwable exception, SubscriberExceptionContext context); diff --git a/android/guava/src/com/google/common/eventbus/SubscriberRegistry.java b/android/guava/src/com/google/common/eventbus/SubscriberRegistry.java index 46e982016c55..ad0a69af187f 100644 --- a/android/guava/src/com/google/common/eventbus/SubscriberRegistry.java +++ b/android/guava/src/com/google/common/eventbus/SubscriberRegistry.java @@ -16,12 +16,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Throwables.throwIfUnchecked; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -39,21 +36,22 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Registry of subscribers to a single event bus. * * @author Colin Decker */ -@ElementTypesAreNonnullByDefault final class SubscriberRegistry { /** @@ -150,13 +148,7 @@ Iterator getSubscribers(Object event) { private static final LoadingCache, ImmutableList> subscriberMethodsCache = CacheBuilder.newBuilder() .weakKeys() - .build( - new CacheLoader, ImmutableList>() { - @Override - public ImmutableList load(Class concreteClass) throws Exception { - return getAnnotatedMethodsNotCached(concreteClass); - } - }); + .build(CacheLoader.from(SubscriberRegistry::getAnnotatedMethodsNotCached)); /** * Returns all subscribers for the given listener grouped by the type of event they subscribe to. @@ -176,14 +168,31 @@ private static ImmutableList getAnnotatedMethods(Class clazz) { try { return subscriberMethodsCache.getUnchecked(clazz); } catch (UncheckedExecutionException e) { - throwIfUnchecked(e.getCause()); + if (e.getCause() instanceof IllegalArgumentException) { + /* + * IllegalArgumentException is the one unchecked exception that we know is likely to happen + * (thanks to the checkArgument calls in getAnnotatedMethodsNotCached). If it happens, we'd + * prefer to propagate an IllegalArgumentException to the caller. However, we don't want to + * simply rethrow an exception (e.getCause()) that may in rare cases have come from another + * thread. To accomplish both goals, we wrap that IllegalArgumentException in a new + * instance. + */ + throw new IllegalArgumentException(e.getCause().getMessage(), e.getCause()); + } + /* + * If some other exception happened, we just propagate the wrapper + * UncheckedExecutionException, which has the stack trace from this thread and which has its + * cause set to the underlying exception (which may be from another thread). If we someday + * learn that some other exception besides IllegalArgumentException is common, then we could + * add another special case to throw an instance of it, too. + */ throw e; } } private static ImmutableList getAnnotatedMethodsNotCached(Class clazz) { Set> supertypes = TypeToken.of(clazz).getTypes().rawTypes(); - Map identifiers = Maps.newHashMap(); + Map identifiers = new HashMap<>(); for (Class supertype : supertypes) { for (Method method : supertype.getDeclaredMethods()) { if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) { @@ -220,15 +229,9 @@ private static ImmutableList getAnnotatedMethodsNotCached(Class clazz CacheBuilder.newBuilder() .weakKeys() .build( - new CacheLoader, ImmutableSet>>() { - // > is actually needed to compile - @SuppressWarnings("RedundantTypeArguments") - @Override - public ImmutableSet> load(Class concreteClass) { - return ImmutableSet.>copyOf( - TypeToken.of(concreteClass).getTypes().rawTypes()); - } - }); + CacheLoader.from( + concreteClass -> + ImmutableSet.copyOf(TypeToken.of(concreteClass).getTypes().rawTypes()))); /** * Flattens a class's type hierarchy into a set of {@code Class} objects including all @@ -236,11 +239,7 @@ public ImmutableSet> load(Class concreteClass) { */ @VisibleForTesting static ImmutableSet> flattenHierarchy(Class concreteClass) { - try { - return flattenHierarchyCache.getUnchecked(concreteClass); - } catch (UncheckedExecutionException e) { - throw Throwables.propagate(e.getCause()); - } + return flattenHierarchyCache.getUnchecked(concreteClass); } private static final class MethodIdentifier { @@ -255,11 +254,11 @@ private static final class MethodIdentifier { @Override public int hashCode() { - return Objects.hashCode(name, parameterTypes); + return Objects.hash(name, parameterTypes); } @Override - public boolean equals(@CheckForNull Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof MethodIdentifier) { MethodIdentifier ident = (MethodIdentifier) o; return name.equals(ident.name) && parameterTypes.equals(ident.parameterTypes); diff --git a/android/guava/src/com/google/common/eventbus/package-info.java b/android/guava/src/com/google/common/eventbus/package-info.java index fa7faa4ab4b0..2b467c08e805 100644 --- a/android/guava/src/com/google/common/eventbus/package-info.java +++ b/android/guava/src/com/google/common/eventbus/package-info.java @@ -13,245 +13,15 @@ */ /** - * The EventBus allows publish-subscribe-style communication between components without requiring - * the components to explicitly register with one another (and thus be aware of each other). It is - * designed exclusively to replace traditional Java in-process event distribution using explicit - * registration. It is not a general-purpose publish-subscribe system, nor is it intended - * for interprocess communication. + * {@linkplain EventBus Discouraged} in favor of dependency injection and concurrency frameworks, + * EventBus allows publish-subscribe-style communication. * *

    See the Guava User Guide article on {@code EventBus}. - * - *

    One-Minute Guide

    - * - *

    Converting an existing EventListener-based system to use the EventBus is easy. - * - *

    For Listeners

    - * - *

    To listen for a specific flavor of event (say, a CustomerChangeEvent)... - * - *

      - *
    • ...in traditional Java events: implement an interface defined with the - * event — such as CustomerChangeEventListener. - *
    • ...with EventBus: create a method that accepts CustomerChangeEvent as its - * sole argument, and mark it with the {@link com.google.common.eventbus.Subscribe} - * annotation. - *
    - * - *

    To register your listener methods with the event producers... - * - *

      - *
    • ...in traditional Java events: pass your object to each producer's {@code - * registerCustomerChangeEventListener} method. These methods are rarely defined in common - * interfaces, so in addition to knowing every possible producer, you must also know its type. - *
    • ...with EventBus: pass your object to the {@link - * com.google.common.eventbus.EventBus#register(Object)} method on an EventBus. You'll need to - * make sure that your object shares an EventBus instance with the event producers. - *
    - * - *

    To listen for a common event supertype (such as EventObject or Object)... - * - *

      - *
    • ...in traditional Java events: not easy. - *
    • ...with EventBus: events are automatically dispatched to listeners of any - * supertype, allowing listeners for interface types or "wildcard listeners" for Object. - *
    - * - *

    To listen for and detect events that were dispatched without listeners... - * - *

      - *
    • ...in traditional Java events: add code to each event-dispatching method - * (perhaps using AOP). - *
    • ...with EventBus: subscribe to {@link - * com.google.common.eventbus.DeadEvent}. The EventBus will notify you of any events that were - * posted but not delivered. (Handy for debugging.) - *
    - * - *

    For Producers

    - * - *

    To keep track of listeners to your events... - * - *

      - *
    • ...in traditional Java events: write code to manage a list of listeners to - * your object, including synchronization, or use a utility class like EventListenerList. - *
    • ...with EventBus: EventBus does this for you. - *
    - * - *

    To dispatch an event to listeners... - * - *

      - *
    • ...in traditional Java events: write a method to dispatch events to each - * event listener, including error isolation and (if desired) asynchronicity. - *
    • ...with EventBus: pass the event object to an EventBus's {@link - * com.google.common.eventbus.EventBus#post(Object)} method. - *
    - * - *

    Glossary

    - * - *

    The EventBus system and code use the following terms to discuss event distribution: - * - *

    - *
    Event - *
    Any object that may be posted to a bus. - *
    Subscribing - *
    The act of registering a listener with an EventBus, so that its subscriber - * methods will receive events. - *
    Listener - *
    An object that wishes to receive events, by exposing subscriber methods. - *
    Subscriber method - *
    A public method that the EventBus should use to deliver posted events. Subscriber - * methods are marked by the {@link com.google.common.eventbus.Subscribe} annotation. - *
    Posting an event - *
    Making the event available to any listeners through the EventBus. - *
    - * - *

    FAQ

    - * - *

    Why must I create my own Event Bus, rather than using a singleton?

    - * - *

    The Event Bus doesn't specify how you use it; there's nothing stopping your application from - * having separate EventBus instances for each component, or using separate instances to separate - * events by context or topic. This also makes it trivial to set up and tear down EventBus objects - * in your tests. - * - *

    Of course, if you'd like to have a process-wide EventBus singleton, there's nothing stopping - * you from doing it that way. Simply have your container (such as Guice) create the EventBus as a - * singleton at global scope (or stash it in a static field, if you're into that sort of thing). - * - *

    In short, the EventBus is not a singleton because we'd rather not make that decision for you. - * Use it how you like. - * - *

    Why use an annotation to mark subscriber methods, rather than requiring the listener to - * implement an interface?

    - * - *

    We feel that the Event Bus's {@code @Subscribe} annotation conveys your intentions just as - * explicitly as implementing an interface (or perhaps more so), while leaving you free to place - * event subscriber methods wherever you wish and give them intention-revealing names. - * - *

    Traditional Java Events use a listener interface which typically sports only a handful of - * methods -- typically one. This has a number of disadvantages: - * - *

      - *
    • Any one class can only implement a single response to a given event. - *
    • Listener interface methods may conflict. - *
    • The method must be named after the event (e.g. {@code handleChangeEvent}), rather than its - * purpose (e.g. {@code recordChangeInJournal}). - *
    • Each event usually has its own interface, without a common parent interface for a family of - * events (e.g. all UI events). - *
    - * - *

    The difficulties in implementing this cleanly has given rise to a pattern, particularly common - * in Swing apps, of using tiny anonymous classes to implement event listener interfaces. - * - *

    Compare these two cases: - * - *

    {@code
    - * class ChangeRecorder {
    - *   void setCustomer(Customer cust) {
    - *     cust.addChangeListener(new ChangeListener() {
    - *       void customerChanged(ChangeEvent e) {
    - *         recordChange(e.getChange());
    - *       }
    - *     };
    - *   }
    - * }
    - *
    - * // Class is typically registered by the container.
    - * class EventBusChangeRecorder {
    - *  }{@code @Subscribe void recordCustomerChange(ChangeEvent e) {
    - *     recordChange(e.getChange());
    - *   }
    - * }
    - * }
    - * - *

    The intent is actually clearer in the second case: there's less noise code, and the event - * subscriber has a clear and meaningful name. - * - *

    What about a generic {@code Subscriber} interface?

    - * - *

    Some have proposed a generic {@code Subscriber} interface for EventBus listeners. This runs - * into issues with Java's use of type erasure, not to mention problems in usability. - * - *

    Let's say the interface looked something like the following: - * - *

    {@code
    - * interface Subscriber {
    - *   void handleEvent(T event);
    - * }
    - * }
    - * - *

    Due to erasure, no single class can implement a generic interface more than once with - * different type parameters. This is a giant step backwards from traditional Java Events, where - * even if {@code actionPerformed} and {@code keyPressed} aren't very meaningful names, at least you - * can implement both methods! - * - *

    Doesn't EventBus destroy static typing and eliminate automated refactoring support?

    - * - *

    Some have freaked out about EventBus's {@code register(Object)} and {@code post(Object)} - * methods' use of the {@code Object} type. - * - *

    {@code Object} is used here for a good reason: the Event Bus library places no restrictions on - * the types of either your event listeners (as in {@code register(Object)}) or the events - * themselves (in {@code post(Object)}). - * - *

    Event subscriber methods, on the other hand, must explicitly declare their argument type -- - * the type of event desired (or one of its supertypes). Thus, searching for references to an event - * class will instantly find all subscriber methods for that event, and renaming the type will - * affect all subscriber methods within view of your IDE (and any code that creates the event). - * - *

    It's true that you can rename your {@code @Subscribed} event subscriber methods at will; Event - * Bus will not stop this or do anything to propagate the rename because, to Event Bus, the names of - * your subscriber methods are irrelevant. Test code that calls the methods directly, of course, - * will be affected by your renaming -- but that's what your refactoring tools are for. - * - *

    What happens if I {@code register} a listener without any subscriber methods?

    - * - *

    Nothing at all. - * - *

    The Event Bus was designed to integrate with containers and module systems, with Guice as the - * prototypical example. In these cases, it's convenient to have the container/factory/environment - * pass every created object to an EventBus's {@code register(Object)} method. - * - *

    This way, any object created by the container/factory/environment can hook into the system's - * event model simply by exposing subscriber methods. - * - *

    What Event Bus problems can be detected at compile time?

    - * - *

    Any problem that can be unambiguously detected by Java's type system. For example, defining a - * subscriber method for a nonexistent event type. - * - *

    What Event Bus problems can be detected immediately at registration?

    - * - *

    Immediately upon invoking {@code register(Object)}, the listener being registered is checked - * for the well-formedness of its subscriber methods. Specifically, any methods marked with - * {@code @Subscribe} must take only a single argument. - * - *

    Any violations of this rule will cause an {@code IllegalArgumentException} to be thrown. - * - *

    (This check could be moved to compile-time using APT, a solution we're researching.) - * - *

    What Event Bus problems may only be detected later, at runtime?

    - * - *

    If a component posts events with no registered listeners, it may indicate an error - * (typically an indication that you missed a {@code @Subscribe} annotation, or that the listening - * component is not loaded). - * - *

    (Note that this is not necessarily indicative of a problem. There are many cases where - * an application will deliberately ignore a posted event, particularly if the event is coming from - * code you don't control.) - * - *

    To handle such events, register a subscriber method for the {@code DeadEvent} class. Whenever - * EventBus receives an event with no registered subscribers, it will turn it into a {@code - * DeadEvent} and pass it your way -- allowing you to log it or otherwise recover. - * - *

    How do I test event listeners and their subscriber methods?

    - * - *

    Because subscriber methods on your listener classes are normal methods, you can simply call - * them from your test code to simulate the EventBus. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.eventbus; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/graph/AbstractBaseGraph.java b/android/guava/src/com/google/common/graph/AbstractBaseGraph.java index b68656b3d6e9..c1ec51da5c5a 100644 --- a/android/guava/src/com/google/common/graph/AbstractBaseGraph.java +++ b/android/guava/src/com/google/common/graph/AbstractBaseGraph.java @@ -20,8 +20,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.graph.GraphConstants.ENDPOINTS_MISMATCH; +import static com.google.common.graph.GraphConstants.NODE_PAIR_REMOVED_FROM_GRAPH; +import static com.google.common.graph.GraphConstants.NODE_REMOVED_FROM_GRAPH; -import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; import com.google.common.collect.Sets; @@ -30,7 +31,7 @@ import com.google.common.primitives.Ints; import java.util.AbstractSet; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of {@link BaseGraph}. @@ -44,9 +45,9 @@ abstract class AbstractBaseGraph implements BaseGraph { /** - * Returns the number of edges in this graph; used to calculate the size of {@link #edges()}. This - * implementation requires O(|N|) time. Classes extending this one may manually keep track of the - * number of edges as the graph is updated, and override this method for better performance. + * Returns the number of edges in this graph; used to calculate the size of {@link Graph#edges()}. + * This implementation requires O(|N|) time. Classes extending this one may manually keep track of + * the number of edges as the graph is updated, and override this method for better performance. */ protected long edgeCount() { long degreeSum = 0L; @@ -59,8 +60,8 @@ protected long edgeCount() { } /** - * An implementation of {@link BaseGraph#edges()} defined in terms of {@link #nodes()} and {@link - * #successors(Object)}. + * An implementation of {@link BaseGraph#edges()} defined in terms of {@link Graph#nodes()} and + * {@link #successors(Object)}. */ @Override public Set> edges() { @@ -76,7 +77,7 @@ public int size() { } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { throw new UnsupportedOperationException(); } @@ -85,7 +86,7 @@ public boolean remove(Object o) { // Graph. @SuppressWarnings("unchecked") @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { if (!(obj instanceof EndpointPair)) { return false; } @@ -106,42 +107,30 @@ public ElementOrder incidentEdgeOrder() { public Set> incidentEdges(N node) { checkNotNull(node); checkArgument(nodes().contains(node), "Node %s is not an element of this graph.", node); - return new IncidentEdgeSet(this, node) { - @Override - public UnmodifiableIterator> iterator() { - if (graph.isDirected()) { - return Iterators.unmodifiableIterator( - Iterators.concat( - Iterators.transform( - graph.predecessors(node).iterator(), - new Function>() { - @Override - public EndpointPair apply(N predecessor) { - return EndpointPair.ordered(predecessor, node); - } - }), + IncidentEdgeSet incident = + new IncidentEdgeSet(this, node, IncidentEdgeSet.EdgeType.BOTH) { + @Override + public UnmodifiableIterator> iterator() { + if (graph.isDirected()) { + return Iterators.unmodifiableIterator( + Iterators.concat( + Iterators.transform( + graph.predecessors(node).iterator(), + (N predecessor) -> EndpointPair.ordered(predecessor, node)), + Iterators.transform( + // filter out 'node' from successors (already covered by predecessors, + // above) + Sets.difference(graph.successors(node), ImmutableSet.of(node)).iterator(), + (N successor) -> EndpointPair.ordered(node, successor)))); + } else { + return Iterators.unmodifiableIterator( Iterators.transform( - // filter out 'node' from successors (already covered by predecessors, above) - Sets.difference(graph.successors(node), ImmutableSet.of(node)).iterator(), - new Function>() { - @Override - public EndpointPair apply(N successor) { - return EndpointPair.ordered(node, successor); - } - }))); - } else { - return Iterators.unmodifiableIterator( - Iterators.transform( - graph.adjacentNodes(node).iterator(), - new Function>() { - @Override - public EndpointPair apply(N adjacentNode) { - return EndpointPair.unordered(node, adjacentNode); - } - })); - } - } - }; + graph.adjacentNodes(node).iterator(), + (N adjacentNode) -> EndpointPair.unordered(node, adjacentNode))); + } + } + }; + return nodeInvalidatableSet(incident, node); } @Override @@ -183,6 +172,137 @@ public boolean hasEdgeConnecting(EndpointPair endpoints) { return nodes().contains(nodeU) && successors(nodeU).contains(nodeV); } + @Override + public Network> asNetwork() { + return new AbstractNetwork>() { + @Override + public Set nodes() { + return AbstractBaseGraph.this.nodes(); + } + + @Override + public Set> edges() { + return AbstractBaseGraph.this.edges(); + } + + @Override + public Graph asGraph() { + if (AbstractBaseGraph.this instanceof Graph) { + return (Graph) AbstractBaseGraph.this; + } else if (AbstractBaseGraph.this instanceof ValueGraph) { + return ((ValueGraph) AbstractBaseGraph.this).asGraph(); + } + throw new UnsupportedOperationException( + "Unexpected graph type: " + AbstractBaseGraph.this.getClass()); + } + + @Override + public boolean isDirected() { + return AbstractBaseGraph.this.isDirected(); + } + + @Override + public boolean allowsParallelEdges() { + return false; // Graph doesn't allow parallel edges + } + + @Override + public boolean allowsSelfLoops() { + return AbstractBaseGraph.this.allowsSelfLoops(); + } + + @Override + public ElementOrder nodeOrder() { + return AbstractBaseGraph.this.nodeOrder(); + } + + @Override + public ElementOrder> edgeOrder() { + return ElementOrder.unordered(); // Graph doesn't define edge order + } + + @Override + public Set adjacentNodes(N node) { + return AbstractBaseGraph.this.adjacentNodes(node); + } + + @Override + public Set predecessors(N node) { + return AbstractBaseGraph.this.predecessors(node); + } + + @Override + public Set successors(N node) { + return AbstractBaseGraph.this.successors(node); + } + + @Override + public Set> incidentEdges(N node) { + return AbstractBaseGraph.this.incidentEdges(node); + } + + @Override + public Set> inEdges(N node) { + checkNotNull(node); + checkArgument(nodes().contains(node)); + IncidentEdgeSet incident = + new IncidentEdgeSet(this, node, IncidentEdgeSet.EdgeType.INCOMING) { + @Override + public UnmodifiableIterator> iterator() { + return Iterators.unmodifiableIterator( + Iterators.transform( + graph.predecessors(node).iterator(), + (N predecessor) -> + graph.isDirected() + ? EndpointPair.ordered(predecessor, node) + : EndpointPair.unordered(predecessor, node))); + } + }; + return nodeInvalidatableSet(incident, node); + } + + @Override + public Set> outEdges(N node) { + checkNotNull(node); + checkArgument(nodes().contains(node)); + IncidentEdgeSet incident = + new IncidentEdgeSet(this, node, IncidentEdgeSet.EdgeType.OUTGOING) { + @Override + public UnmodifiableIterator> iterator() { + return Iterators.unmodifiableIterator( + Iterators.transform( + graph.successors(node).iterator(), + (N successor) -> + graph.isDirected() + ? EndpointPair.ordered(node, successor) + : EndpointPair.unordered(node, successor))); + } + }; + return nodeInvalidatableSet(incident, node); + } + + @Override + public Set> adjacentEdges(EndpointPair edge) { + checkArgument(edges().contains(edge)); + N nodeU = edge.nodeU(); + N nodeV = edge.nodeV(); + Set> endpointPairIncidentEdges = + Sets.union(incidentEdges(nodeU), incidentEdges(nodeV)); + return nodePairInvalidatableSet( + Sets.difference(endpointPairIncidentEdges, ImmutableSet.of(edge)), nodeU, nodeV); + } + + @Override + public EndpointPair incidentNodes(EndpointPair edge) { + checkArgument(edges().contains(edge)); + return edge; + } + + // Don't override the existing edge[s]Connecting() or *degree() AbstractNetwork + // implementations; they call in/outEdges() and should be fine. + }; + } + /** * Throws {@code IllegalArgumentException} if the ordering of {@code endpoints} is not compatible * with the directionality of this graph. @@ -192,7 +312,23 @@ protected final void validateEndpoints(EndpointPair endpoints) { checkArgument(isOrderingCompatible(endpoints), ENDPOINTS_MISMATCH); } + /** + * Returns {@code true} iff {@code endpoints}' ordering is compatible with the directionality of + * this graph. + */ protected final boolean isOrderingCompatible(EndpointPair endpoints) { - return endpoints.isOrdered() || !this.isDirected(); + return endpoints.isOrdered() == this.isDirected(); + } + + protected final Set nodeInvalidatableSet(Set set, N node) { + return InvalidatableSet.of( + set, () -> nodes().contains(node), () -> String.format(NODE_REMOVED_FROM_GRAPH, node)); + } + + protected final Set nodePairInvalidatableSet(Set set, N nodeU, N nodeV) { + return InvalidatableSet.of( + set, + () -> nodes().contains(nodeU) && nodes().contains(nodeV), + () -> String.format(NODE_PAIR_REMOVED_FROM_GRAPH, nodeU, nodeV)); } } diff --git a/android/guava/src/com/google/common/graph/AbstractDirectedNetworkConnections.java b/android/guava/src/com/google/common/graph/AbstractDirectedNetworkConnections.java index 69c941f87703..92b203872e5f 100644 --- a/android/guava/src/com/google/common/graph/AbstractDirectedNetworkConnections.java +++ b/android/guava/src/com/google/common/graph/AbstractDirectedNetworkConnections.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.graph.Graphs.checkNonNegative; import static com.google.common.graph.Graphs.checkPositive; +import static java.util.Objects.requireNonNull; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; @@ -30,7 +31,7 @@ import java.util.Collections; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A base implementation of {@link NetworkConnections} for directed networks. @@ -78,7 +79,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { return inEdgeMap.containsKey(obj) || outEdgeMap.containsKey(obj); } }; @@ -98,7 +99,8 @@ public Set outEdges() { public N adjacentNode(E edge) { // Since the reference node is defined to be 'source' for directed graphs, // we can assume this edge lives in the set of outgoing edges. - return checkNotNull(outEdgeMap.get(edge)); + // (We're relying on callers to call this method only with an edge that's in the graph.) + return requireNonNull(outEdgeMap.get(edge)); } @Override @@ -107,13 +109,15 @@ public N removeInEdge(E edge, boolean isSelfLoop) { checkNonNegative(--selfLoopCount); } N previousNode = inEdgeMap.remove(edge); - return checkNotNull(previousNode); + // We're relying on callers to call this method only with an edge that's in the graph. + return requireNonNull(previousNode); } @Override public N removeOutEdge(E edge) { N previousNode = outEdgeMap.remove(edge); - return checkNotNull(previousNode); + // We're relying on callers to call this method only with an edge that's in the graph. + return requireNonNull(previousNode); } @Override diff --git a/android/guava/src/com/google/common/graph/AbstractGraph.java b/android/guava/src/com/google/common/graph/AbstractGraph.java index fc71345ff6a6..968c627bc5cc 100644 --- a/android/guava/src/com/google/common/graph/AbstractGraph.java +++ b/android/guava/src/com/google/common/graph/AbstractGraph.java @@ -17,7 +17,7 @@ package com.google.common.graph; import com.google.common.annotations.Beta; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of {@link Graph}. It is recommended to extend this @@ -29,9 +29,11 @@ */ @Beta public abstract class AbstractGraph extends AbstractBaseGraph implements Graph { + /** Constructor for use by subclasses. */ + public AbstractGraph() {} @Override - public final boolean equals(@NullableDecl Object obj) { + public final boolean equals(@Nullable Object obj) { if (obj == this) { return true; } diff --git a/android/guava/src/com/google/common/graph/AbstractNetwork.java b/android/guava/src/com/google/common/graph/AbstractNetwork.java index fad15145f3b5..65eb8aeed842 100644 --- a/android/guava/src/com/google/common/graph/AbstractNetwork.java +++ b/android/guava/src/com/google/common/graph/AbstractNetwork.java @@ -18,12 +18,14 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.GraphConstants.EDGE_REMOVED_FROM_GRAPH; import static com.google.common.graph.GraphConstants.ENDPOINTS_MISMATCH; import static com.google.common.graph.GraphConstants.MULTIPLE_EDGES_CONNECTING; +import static com.google.common.graph.GraphConstants.NODE_PAIR_REMOVED_FROM_GRAPH; +import static com.google.common.graph.GraphConstants.NODE_REMOVED_FROM_GRAPH; import static java.util.Collections.unmodifiableSet; import com.google.common.annotations.Beta; -import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators; @@ -34,7 +36,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of {@link Network}. It is recommended to extend @@ -50,6 +52,8 @@ */ @Beta public abstract class AbstractNetwork implements Network { + /** Constructor for use by subclasses. */ + public AbstractNetwork() {} @Override public Graph asGraph() { @@ -70,13 +74,7 @@ public Set> edges() { @Override public Iterator> iterator() { return Iterators.transform( - AbstractNetwork.this.edges().iterator(), - new Function>() { - @Override - public EndpointPair apply(E edge) { - return incidentNodes(edge); - } - }); + AbstractNetwork.this.edges().iterator(), edge -> incidentNodes(edge)); } @Override @@ -89,7 +87,7 @@ public int size() { // Network. @SuppressWarnings("unchecked") @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { if (!(obj instanceof EndpointPair)) { return false; } @@ -166,16 +164,20 @@ public Set adjacentEdges(E edge) { EndpointPair endpointPair = incidentNodes(edge); // Verifies that edge is in this network. Set endpointPairIncidentEdges = Sets.union(incidentEdges(endpointPair.nodeU()), incidentEdges(endpointPair.nodeV())); - return Sets.difference(endpointPairIncidentEdges, ImmutableSet.of(edge)); + return edgeInvalidatableSet( + Sets.difference(endpointPairIncidentEdges, ImmutableSet.of(edge)), edge); } @Override public Set edgesConnecting(N nodeU, N nodeV) { Set outEdgesU = outEdges(nodeU); Set inEdgesV = inEdges(nodeV); - return outEdgesU.size() <= inEdgesV.size() - ? unmodifiableSet(Sets.filter(outEdgesU, connectedPredicate(nodeU, nodeV))) - : unmodifiableSet(Sets.filter(inEdgesV, connectedPredicate(nodeV, nodeU))); + return nodePairInvalidatableSet( + outEdgesU.size() <= inEdgesV.size() + ? unmodifiableSet(Sets.filter(outEdgesU, connectedPredicate(nodeU, nodeV))) + : unmodifiableSet(Sets.filter(inEdgesV, connectedPredicate(nodeV, nodeU))), + nodeU, + nodeV); } @Override @@ -184,18 +186,12 @@ public Set edgesConnecting(EndpointPair endpoints) { return edgesConnecting(endpoints.nodeU(), endpoints.nodeV()); } - private Predicate connectedPredicate(final N nodePresent, final N nodeToCheck) { - return new Predicate() { - @Override - public boolean apply(E edge) { - return incidentNodes(edge).adjacentNode(nodePresent).equals(nodeToCheck); - } - }; + private Predicate connectedPredicate(N nodePresent, N nodeToCheck) { + return edge -> incidentNodes(edge).adjacentNode(nodePresent).equals(nodeToCheck); } @Override - @NullableDecl - public E edgeConnectingOrNull(N nodeU, N nodeV) { + public @Nullable E edgeConnectingOrNull(N nodeU, N nodeV) { Set edgesConnecting = edgesConnecting(nodeU, nodeV); switch (edgesConnecting.size()) { case 0: @@ -208,8 +204,7 @@ public E edgeConnectingOrNull(N nodeU, N nodeV) { } @Override - @NullableDecl - public E edgeConnectingOrNull(EndpointPair endpoints) { + public @Nullable E edgeConnectingOrNull(EndpointPair endpoints) { validateEndpoints(endpoints); return edgeConnectingOrNull(endpoints.nodeU(), endpoints.nodeV()); } @@ -240,11 +235,11 @@ protected final void validateEndpoints(EndpointPair endpoints) { } protected final boolean isOrderingCompatible(EndpointPair endpoints) { - return endpoints.isOrdered() || !this.isDirected(); + return endpoints.isOrdered() == this.isDirected(); } @Override - public final boolean equals(@NullableDecl Object obj) { + public final boolean equals(@Nullable Object obj) { if (obj == this) { return true; } @@ -278,14 +273,42 @@ public String toString() { + edgeIncidentNodesMap(this); } - private static Map> edgeIncidentNodesMap(final Network network) { - Function> edgeToIncidentNodesFn = - new Function>() { - @Override - public EndpointPair apply(E edge) { - return network.incidentNodes(edge); - } - }; - return Maps.asMap(network.edges(), edgeToIncidentNodesFn); + /** + * Returns a {@link Set} whose methods throw {@link IllegalStateException} when the given edge is + * not present in this network. + * + * @since 33.1.0 + */ + protected final Set edgeInvalidatableSet(Set set, E edge) { + return InvalidatableSet.of( + set, () -> edges().contains(edge), () -> String.format(EDGE_REMOVED_FROM_GRAPH, edge)); + } + + /** + * Returns a {@link Set} whose methods throw {@link IllegalStateException} when the given node is + * not present in this network. + * + * @since 33.1.0 + */ + protected final Set nodeInvalidatableSet(Set set, N node) { + return InvalidatableSet.of( + set, () -> nodes().contains(node), () -> String.format(NODE_REMOVED_FROM_GRAPH, node)); + } + + /** + * Returns a {@link Set} whose methods throw {@link IllegalStateException} when either of the + * given nodes is not present in this network. + * + * @since 33.1.0 + */ + protected final Set nodePairInvalidatableSet(Set set, N nodeU, N nodeV) { + return InvalidatableSet.of( + set, + () -> nodes().contains(nodeU) && nodes().contains(nodeV), + () -> String.format(NODE_PAIR_REMOVED_FROM_GRAPH, nodeU, nodeV)); + } + + private static Map> edgeIncidentNodesMap(Network network) { + return Maps.asMap(network.edges(), network::incidentNodes); } } diff --git a/android/guava/src/com/google/common/graph/AbstractUndirectedNetworkConnections.java b/android/guava/src/com/google/common/graph/AbstractUndirectedNetworkConnections.java index a17da705e77c..dc4ab842f5b2 100644 --- a/android/guava/src/com/google/common/graph/AbstractUndirectedNetworkConnections.java +++ b/android/guava/src/com/google/common/graph/AbstractUndirectedNetworkConnections.java @@ -18,10 +18,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; import java.util.Collections; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * A base implementation of {@link NetworkConnections} for undirected networks. @@ -65,11 +67,12 @@ public Set outEdges() { @Override public N adjacentNode(E edge) { - return checkNotNull(incidentEdgeMap.get(edge)); + // We're relying on callers to call this method only with an edge that's in the graph. + return requireNonNull(incidentEdgeMap.get(edge)); } @Override - public N removeInEdge(E edge, boolean isSelfLoop) { + public @Nullable N removeInEdge(E edge, boolean isSelfLoop) { if (!isSelfLoop) { return removeOutEdge(edge); } @@ -79,7 +82,8 @@ public N removeInEdge(E edge, boolean isSelfLoop) { @Override public N removeOutEdge(E edge) { N previousNode = incidentEdgeMap.remove(edge); - return checkNotNull(previousNode); + // We're relying on callers to call this method only with an edge that's in the graph. + return requireNonNull(previousNode); } @Override diff --git a/android/guava/src/com/google/common/graph/AbstractValueGraph.java b/android/guava/src/com/google/common/graph/AbstractValueGraph.java index be2b7cce4bc0..167ffdcc2f5e 100644 --- a/android/guava/src/com/google/common/graph/AbstractValueGraph.java +++ b/android/guava/src/com/google/common/graph/AbstractValueGraph.java @@ -16,12 +16,13 @@ package com.google.common.graph; +import static java.util.Objects.requireNonNull; + import com.google.common.annotations.Beta; -import com.google.common.base.Function; import com.google.common.collect.Maps; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * This class provides a skeletal implementation of {@link ValueGraph}. It is recommended to extend @@ -38,6 +39,8 @@ @Beta public abstract class AbstractValueGraph extends AbstractBaseGraph implements ValueGraph { + /** Constructor for use by subclasses. */ + public AbstractValueGraph() {} @Override public Graph asGraph() { @@ -105,7 +108,7 @@ public int outDegree(N node) { } @Override - public final boolean equals(@NullableDecl Object obj) { + public final boolean equals(@Nullable Object obj) { if (obj == this) { return true; } @@ -137,14 +140,11 @@ public String toString() { + edgeValueMap(this); } - private static Map, V> edgeValueMap(final ValueGraph graph) { - Function, V> edgeToValueFn = - new Function, V>() { - @Override - public V apply(EndpointPair edge) { - return graph.edgeValueOrDefault(edge.nodeU(), edge.nodeV(), null); - } - }; - return Maps.asMap(graph.edges(), edgeToValueFn); + private static Map, V> edgeValueMap(ValueGraph graph) { + return Maps.asMap( + graph.edges(), + edge -> + // requireNonNull is safe because the endpoint pair comes from the graph. + requireNonNull(graph.edgeValueOrDefault(edge.nodeU(), edge.nodeV(), null))); } } diff --git a/android/guava/src/com/google/common/graph/ArchetypeGraph.java b/android/guava/src/com/google/common/graph/ArchetypeGraph.java new file mode 100644 index 000000000000..3e3e68e88502 --- /dev/null +++ b/android/guava/src/com/google/common/graph/ArchetypeGraph.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import java.util.Set; + +/** + * A non-public interface for the methods shared between {@link Graph}, {@link ValueGraph}, and + * {@link Network}. + * + * @author Joshua O'Madadhain + * @param Node parameter type + */ +interface ArchetypeGraph extends SuccessorsFunction, PredecessorsFunction { + // + // Graph-level accessors + // + + /** Returns all nodes in this graph, in the order specified by {@link #nodeOrder()}. */ + Set nodes(); + + // + // Graph properties + // + + /** + * Returns true if the edges in this graph are directed. Directed edges connect a {@link + * EndpointPair#source() source node} to a {@link EndpointPair#target() target node}, while + * undirected edges connect a pair of nodes to each other. + */ + boolean isDirected(); + + /** + * Returns true if this graph allows self-loops (edges that connect a node to itself). Attempting + * to add a self-loop to a graph that does not allow them will throw an {@link + * IllegalArgumentException}. + */ + boolean allowsSelfLoops(); + + /** Returns the order of iteration for the elements of {@link #nodes()}. */ + ElementOrder nodeOrder(); + + // + // Element-level accessors + // + + /** + * Returns a live view of the nodes which have an incident edge in common with {@code node} in + * this graph. + * + *

    This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. + * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals(...)} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + Set adjacentNodes(N node); + + /** + * Returns a live view of all nodes in this graph adjacent to {@code node} which can be reached by + * traversing {@code node}'s incoming edges against the direction (if any) of the edge. + * + *

    In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals(...)} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set predecessors(N node); + + /** + * Returns a live view of all nodes in this graph adjacent to {@code node} which can be reached by + * traversing {@code node}'s outgoing edges in the direction (if any) of the edge. + * + *

    In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + * + *

    This is not the same as "all nodes reachable from {@code node} by following outgoing + * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. + * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals(...)} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set successors(N node); + + /** + * Returns the count of {@code node}'s incident edges, counting self-loops twice (equivalently, + * the number of times an edge touches {@code node}). + * + *

    For directed graphs, this is equal to {@code inDegree(node) + outDegree(node)}. + * + *

    For undirected graphs, this is equal to {@code incidentEdges(node).size()} + (number of + * self-loops incident to {@code node}). + * + *

    If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + int degree(N node); + + /** + * Returns the count of {@code node}'s incoming edges (equal to {@code predecessors(node).size()}) + * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. + * + *

    If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + int inDegree(N node); + + /** + * Returns the count of {@code node}'s outgoing edges (equal to {@code successors(node).size()}) + * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. + * + *

    If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + int outDegree(N node); + + /** + * Returns true if there is an edge that directly connects {@code nodeU} to {@code nodeV}. This is + * equivalent to {@code nodes().contains(nodeU) && successors(nodeU).contains(nodeV)}. + * + *

    In an undirected graph, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}. + * + * @since 23.0 + */ + boolean hasEdgeConnecting(N nodeU, N nodeV); + + /** + * Returns true if there is an edge that directly connects {@code endpoints} (in the order, if + * any, specified by {@code endpoints}). This is equivalent to {@code + * edges().contains(endpoints)}. + * + *

    Unlike the other {@code EndpointPair}-accepting methods, this method does not throw if the + * endpoints are unordered; it simply returns false. This is for consistency with the behavior of + * {@link Collection#contains(Object)} (which does not generally throw if the object cannot be + * present in the collection), and the desire to have this method's behavior be compatible with + * {@code edges().contains(endpoints)}. + * + * @since 27.1 + */ + boolean hasEdgeConnecting(EndpointPair endpoints); +} diff --git a/android/guava/src/com/google/common/graph/BaseGraph.java b/android/guava/src/com/google/common/graph/BaseGraph.java index 1df5de7f174e..736b91c7e31c 100644 --- a/android/guava/src/com/google/common/graph/BaseGraph.java +++ b/android/guava/src/com/google/common/graph/BaseGraph.java @@ -24,38 +24,14 @@ * @author James Sexton * @param Node parameter type */ -interface BaseGraph extends SuccessorsFunction, PredecessorsFunction { +interface BaseGraph extends ArchetypeGraph { // // Graph-level accessors // - /** Returns all nodes in this graph, in the order specified by {@link #nodeOrder()}. */ - Set nodes(); - /** Returns all edges in this graph. */ Set> edges(); - // - // Graph properties - // - - /** - * Returns true if the edges in this graph are directed. Directed edges connect a {@link - * EndpointPair#source() source node} to a {@link EndpointPair#target() target node}, while - * undirected edges connect a pair of nodes to each other. - */ - boolean isDirected(); - - /** - * Returns true if this graph allows self-loops (edges that connect a node to itself). Attempting - * to add a self-loop to a graph that does not allow them will throw an {@link - * IllegalArgumentException}. - */ - boolean allowsSelfLoops(); - - /** Returns the order of iteration for the elements of {@link #nodes()}. */ - ElementOrder nodeOrder(); - /** * Returns an {@link ElementOrder} that specifies the order of iteration for the elements of * {@link #edges()}, {@link #adjacentNodes(Object)}, {@link #predecessors(Object)}, {@link @@ -65,111 +41,43 @@ interface BaseGraph extends SuccessorsFunction, PredecessorsFunction { */ ElementOrder incidentEdgeOrder(); - // - // Element-level accessors - // - /** - * Returns the nodes which have an incident edge in common with {@code node} in this graph. + * Returns a live view of this graph as a {@link Network} whose edges {@code E} are {@code + * EndpointPair} objects (that is, a {@code Network>}). The resulting {@code + * Network}'s edge-oriented methods (such as {@code inEdges()}) will return views transformed from + * the corresponding node-oriented methods (such as {@code predecessors()}). * - *

    This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. + *

    This capability facilitates writing implementations of edge-oriented + * code. * - * @throws IllegalArgumentException if {@code node} is not an element of this graph + * @since NEXT */ - Set adjacentNodes(N node); + Network> asNetwork(); - /** - * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing - * {@code node}'s incoming edges against the direction (if any) of the edge. - * - *

    In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. - * - * @throws IllegalArgumentException if {@code node} is not an element of this graph - */ - @Override - Set predecessors(N node); + // + // Element-level accessors + // /** - * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing - * {@code node}'s outgoing edges in the direction (if any) of the edge. + * Returns a live view of the edges in this graph whose endpoints include {@code node}. * - *

    In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + *

    This is equal to the union of incoming and outgoing edges. * - *

    This is not the same as "all nodes reachable from {@code node} by following outgoing - * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: * - * @throws IllegalArgumentException if {@code node} is not an element of this graph - */ - @Override - Set successors(N node); - - /** - * Returns the edges in this graph whose endpoints include {@code node}. - * - *

    This is equal to the union of incoming and outgoing edges. + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals(...)} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    * * @throws IllegalArgumentException if {@code node} is not an element of this graph * @since 24.0 */ Set> incidentEdges(N node); - - /** - * Returns the count of {@code node}'s incident edges, counting self-loops twice (equivalently, - * the number of times an edge touches {@code node}). - * - *

    For directed graphs, this is equal to {@code inDegree(node) + outDegree(node)}. - * - *

    For undirected graphs, this is equal to {@code incidentEdges(node).size()} + (number of - * self-loops incident to {@code node}). - * - *

    If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. - * - * @throws IllegalArgumentException if {@code node} is not an element of this graph - */ - int degree(N node); - - /** - * Returns the count of {@code node}'s incoming edges (equal to {@code predecessors(node).size()}) - * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. - * - *

    If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. - * - * @throws IllegalArgumentException if {@code node} is not an element of this graph - */ - int inDegree(N node); - - /** - * Returns the count of {@code node}'s outgoing edges (equal to {@code successors(node).size()}) - * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. - * - *

    If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. - * - * @throws IllegalArgumentException if {@code node} is not an element of this graph - */ - int outDegree(N node); - - /** - * Returns true if there is an edge that directly connects {@code nodeU} to {@code nodeV}. This is - * equivalent to {@code nodes().contains(nodeU) && successors(nodeU).contains(nodeV)}. - * - *

    In an undirected graph, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}. - * - * @since 23.0 - */ - boolean hasEdgeConnecting(N nodeU, N nodeV); - - /** - * Returns true if there is an edge that directly connects {@code endpoints} (in the order, if - * any, specified by {@code endpoints}). This is equivalent to {@code - * edges().contains(endpoints)}. - * - *

    Unlike the other {@code EndpointPair}-accepting methods, this method does not throw if the - * endpoints are unordered; it simply returns false. This is for consistency with the behavior of - * {@link Collection#contains(Object)} (which does not generally throw if the object cannot be - * present in the collection), and the desire to have this method's behavior be compatible with - * {@code edges().contains(endpoints)}. - * - * @since 27.1 - */ - boolean hasEdgeConnecting(EndpointPair endpoints); } diff --git a/android/guava/src/com/google/common/graph/DirectedGraphConnections.java b/android/guava/src/com/google/common/graph/DirectedGraphConnections.java index 12887b739ecf..5864835cb476 100644 --- a/android/guava/src/com/google/common/graph/DirectedGraphConnections.java +++ b/android/guava/src/com/google/common/graph/DirectedGraphConnections.java @@ -40,7 +40,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An implementation of {@link GraphConnections} for directed graphs. @@ -82,7 +82,7 @@ static final class Pred extends NodeConnection { } @Override - public boolean equals(Object that) { + public boolean equals(@Nullable Object that) { if (that instanceof Pred) { return this.node.equals(((Pred) that).node); } else { @@ -103,7 +103,7 @@ static final class Succ extends NodeConnection { } @Override - public boolean equals(Object that) { + public boolean equals(@Nullable Object that) { if (that instanceof Succ) { return this.node.equals(((Succ) that).node); } else { @@ -133,14 +133,14 @@ public int hashCode() { * LinkedHashMap combines two such edges into a single node-value pair, even though the edges may * not have been inserted consecutively. */ - @NullableDecl private final List> orderedNodeConnections; + private final @Nullable List> orderedNodeConnections; private int predecessorCount; private int successorCount; private DirectedGraphConnections( Map adjacentNodeValues, - @NullableDecl List> orderedNodeConnections, + @Nullable List> orderedNodeConnections, int predecessorCount, int successorCount) { this.adjacentNodeValues = checkNotNull(adjacentNodeValues); @@ -162,17 +162,17 @@ static DirectedGraphConnections of(ElementOrder incidentEdgeOrde orderedNodeConnections = null; break; case STABLE: - orderedNodeConnections = new ArrayList>(); + orderedNodeConnections = new ArrayList<>(); break; default: throw new AssertionError(incidentEdgeOrder.type()); } - return new DirectedGraphConnections( - /* adjacentNodeValues = */ new HashMap(initialCapacity, INNER_LOAD_FACTOR), + return new DirectedGraphConnections<>( + /* adjacentNodeValues= */ new HashMap(initialCapacity, INNER_LOAD_FACTOR), orderedNodeConnections, - /* predecessorCount = */ 0, - /* successorCount = */ 0); + /* predecessorCount= */ 0, + /* successorCount= */ 0); } static DirectedGraphConnections ofImmutable( @@ -238,11 +238,11 @@ public Set adjacentNodes() { return new AbstractSet() { @Override public UnmodifiableIterator iterator() { - final Iterator> nodeConnections = orderedNodeConnections.iterator(); - final Set seenNodes = new HashSet<>(); + Iterator> nodeConnections = orderedNodeConnections.iterator(); + Set seenNodes = new HashSet<>(); return new AbstractIterator() { @Override - protected N computeNext() { + protected @Nullable N computeNext() { while (nodeConnections.hasNext()) { NodeConnection nodeConnection = nodeConnections.next(); boolean added = seenNodes.add(nodeConnection.node); @@ -261,7 +261,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { return adjacentNodeValues.containsKey(obj); } }; @@ -274,10 +274,10 @@ public Set predecessors() { @Override public UnmodifiableIterator iterator() { if (orderedNodeConnections == null) { - final Iterator> entries = adjacentNodeValues.entrySet().iterator(); + Iterator> entries = adjacentNodeValues.entrySet().iterator(); return new AbstractIterator() { @Override - protected N computeNext() { + protected @Nullable N computeNext() { while (entries.hasNext()) { Entry entry = entries.next(); if (isPredecessor(entry.getValue())) { @@ -288,10 +288,10 @@ protected N computeNext() { } }; } else { - final Iterator> nodeConnections = orderedNodeConnections.iterator(); + Iterator> nodeConnections = orderedNodeConnections.iterator(); return new AbstractIterator() { @Override - protected N computeNext() { + protected @Nullable N computeNext() { while (nodeConnections.hasNext()) { NodeConnection nodeConnection = nodeConnections.next(); if (nodeConnection instanceof NodeConnection.Pred) { @@ -310,7 +310,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { return isPredecessor(adjacentNodeValues.get(obj)); } }; @@ -322,10 +322,10 @@ public Set successors() { @Override public UnmodifiableIterator iterator() { if (orderedNodeConnections == null) { - final Iterator> entries = adjacentNodeValues.entrySet().iterator(); + Iterator> entries = adjacentNodeValues.entrySet().iterator(); return new AbstractIterator() { @Override - protected N computeNext() { + protected @Nullable N computeNext() { while (entries.hasNext()) { Entry entry = entries.next(); if (isSuccessor(entry.getValue())) { @@ -336,10 +336,10 @@ protected N computeNext() { } }; } else { - final Iterator> nodeConnections = orderedNodeConnections.iterator(); + Iterator> nodeConnections = orderedNodeConnections.iterator(); return new AbstractIterator() { @Override - protected N computeNext() { + protected @Nullable N computeNext() { while (nodeConnections.hasNext()) { NodeConnection nodeConnection = nodeConnections.next(); if (nodeConnection instanceof NodeConnection.Succ) { @@ -358,56 +358,43 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { return isSuccessor(adjacentNodeValues.get(obj)); } }; } @Override - public Iterator> incidentEdgeIterator(final N thisNode) { + public Iterator> incidentEdgeIterator(N thisNode) { checkNotNull(thisNode); - final Iterator> resultWithDoubleSelfLoop; + Iterator> resultWithDoubleSelfLoop; if (orderedNodeConnections == null) { resultWithDoubleSelfLoop = Iterators.concat( Iterators.transform( predecessors().iterator(), - new Function>() { - @Override - public EndpointPair apply(N predecessor) { - return EndpointPair.ordered(predecessor, thisNode); - } - }), + (N predecessor) -> EndpointPair.ordered(predecessor, thisNode)), Iterators.transform( successors().iterator(), - new Function>() { - @Override - public EndpointPair apply(N successor) { - return EndpointPair.ordered(thisNode, successor); - } - })); + (N successor) -> EndpointPair.ordered(thisNode, successor))); } else { resultWithDoubleSelfLoop = Iterators.transform( orderedNodeConnections.iterator(), - new Function, EndpointPair>() { - @Override - public EndpointPair apply(NodeConnection connection) { - if (connection instanceof NodeConnection.Succ) { - return EndpointPair.ordered(thisNode, connection.node); - } else { - return EndpointPair.ordered(connection.node, thisNode); - } + (NodeConnection connection) -> { + if (connection instanceof NodeConnection.Succ) { + return EndpointPair.ordered(thisNode, connection.node); + } else { + return EndpointPair.ordered(connection.node, thisNode); } }); } - final AtomicBoolean alreadySeenSelfLoop = new AtomicBoolean(false); + AtomicBoolean alreadySeenSelfLoop = new AtomicBoolean(false); return new AbstractIterator>() { @Override - protected EndpointPair computeNext() { + protected @Nullable EndpointPair computeNext() { while (resultWithDoubleSelfLoop.hasNext()) { EndpointPair edge = resultWithDoubleSelfLoop.next(); if (edge.nodeU().equals(edge.nodeV())) { @@ -425,7 +412,7 @@ protected EndpointPair computeNext() { @SuppressWarnings("unchecked") @Override - public V value(N node) { + public @Nullable V value(N node) { checkNotNull(node); Object value = adjacentNodeValues.get(node); if (value == PRED) { @@ -437,7 +424,6 @@ public V value(N node) { return (V) value; } - @SuppressWarnings("unchecked") @Override public void removePredecessor(N node) { checkNotNull(node); @@ -466,7 +452,7 @@ public void removePredecessor(N node) { @SuppressWarnings("unchecked") @Override - public V removeSuccessor(Object node) { + public @Nullable V removeSuccessor(Object node) { checkNotNull(node); Object previousValue = adjacentNodeValues.get(node); Object removedValue; @@ -489,7 +475,14 @@ public V removeSuccessor(Object node) { } } - return (V) removedValue; + /* + * TODO(cpovirk): `return (V) removedValue` once our checker permits that. + * + * (We promoted a class of warnings into errors because sometimes they indicate real problems. + * But now we need to "undo" some instance of spurious errors, as discussed in + * https://github.com/jspecify/checker-framework/issues/8.) + */ + return removedValue == null ? null : (V) removedValue; } @Override @@ -522,7 +515,7 @@ public void addPredecessor(N node, V unused) { @SuppressWarnings("unchecked") @Override - public V addSuccessor(N node, V value) { + public @Nullable V addSuccessor(N node, V value) { Object previousValue = adjacentNodeValues.put(node, value); Object previousSuccessor; @@ -546,14 +539,15 @@ public V addSuccessor(N node, V value) { } } - return (V) previousSuccessor; + // See the comment on the similar cast in removeSuccessor. + return previousSuccessor == null ? null : (V) previousSuccessor; } - private static boolean isPredecessor(@NullableDecl Object value) { + private static boolean isPredecessor(@Nullable Object value) { return (value == PRED) || (value instanceof PredAndSucc); } - private static boolean isSuccessor(@NullableDecl Object value) { + private static boolean isSuccessor(@Nullable Object value) { return (value != PRED) && (value != null); } } diff --git a/android/guava/src/com/google/common/graph/DirectedMultiNetworkConnections.java b/android/guava/src/com/google/common/graph/DirectedMultiNetworkConnections.java index e1ed3cc4d923..b21a63a48d99 100644 --- a/android/guava/src/com/google/common/graph/DirectedMultiNetworkConnections.java +++ b/android/guava/src/com/google/common/graph/DirectedMultiNetworkConnections.java @@ -30,7 +30,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An implementation of {@link NetworkConnections} for directed networks with parallel edges. @@ -59,7 +59,7 @@ static DirectedMultiNetworkConnections ofImmutable( ImmutableMap.copyOf(inEdges), ImmutableMap.copyOf(outEdges), selfLoopCount); } - @LazyInit private transient Reference> predecessorsReference; + @LazyInit private transient @Nullable Reference> predecessorsReference; @Override public Set predecessors() { @@ -75,7 +75,7 @@ private Multiset predecessorsMultiset() { return predecessors; } - @LazyInit private transient Reference> successorsReference; + @LazyInit private transient @Nullable Reference> successorsReference; @Override public Set successors() { @@ -92,7 +92,7 @@ private Multiset successorsMultiset() { } @Override - public Set edgesConnecting(final N node) { + public Set edgesConnecting(N node) { return new MultiEdgesConnecting(outEdgeMap, node) { @Override public int size() { @@ -139,8 +139,7 @@ public void addOutEdge(E edge, N node) { } } - @NullableDecl - private static T getReference(@NullableDecl Reference reference) { + private static @Nullable T getReference(@Nullable Reference reference) { return (reference == null) ? null : reference.get(); } } diff --git a/android/guava/src/com/google/common/graph/DirectedNetworkConnections.java b/android/guava/src/com/google/common/graph/DirectedNetworkConnections.java index 8e79bf4cdc25..c866a252c254 100644 --- a/android/guava/src/com/google/common/graph/DirectedNetworkConnections.java +++ b/android/guava/src/com/google/common/graph/DirectedNetworkConnections.java @@ -61,6 +61,6 @@ public Set successors() { @Override public Set edgesConnecting(N node) { - return new EdgesConnecting(((BiMap) outEdgeMap).inverse(), node); + return new EdgesConnecting<>(((BiMap) outEdgeMap).inverse(), node); } } diff --git a/android/guava/src/com/google/common/graph/EdgesConnecting.java b/android/guava/src/com/google/common/graph/EdgesConnecting.java index d62eefaee59e..1accefcca232 100644 --- a/android/guava/src/com/google/common/graph/EdgesConnecting.java +++ b/android/guava/src/com/google/common/graph/EdgesConnecting.java @@ -23,7 +23,7 @@ import com.google.common.collect.UnmodifiableIterator; import java.util.AbstractSet; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A class to represent the set of edges connecting an (implicit) origin node to a target node. @@ -58,13 +58,12 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object edge) { + public boolean contains(@Nullable Object edge) { E connectingEdge = getConnectingEdge(); - return (connectingEdge != null && connectingEdge.equals(edge)); + return connectingEdge != null && connectingEdge.equals(edge); } - @NullableDecl - private E getConnectingEdge() { + private @Nullable E getConnectingEdge() { return nodeToOutEdge.get(targetNode); } } diff --git a/android/guava/src/com/google/common/graph/ElementOrder.java b/android/guava/src/com/google/common/graph/ElementOrder.java index 568fb42b49e2..01db424c9ed4 100644 --- a/android/guava/src/com/google/common/graph/ElementOrder.java +++ b/android/guava/src/com/google/common/graph/ElementOrder.java @@ -22,13 +22,13 @@ import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; -import com.google.common.base.Objects; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.errorprone.annotations.Immutable; import java.util.Comparator; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * Used to represent the order of elements in a data structure that supports different options for @@ -36,10 +36,10 @@ * *

    Example usage: * - *

    {@code
    + * {@snippet :
      * MutableGraph graph =
      *     GraphBuilder.directed().nodeOrder(ElementOrder.natural()).build();
    - * }
    + * } * * @author Joshua O'Madadhain * @since 20.0 @@ -50,8 +50,7 @@ public final class ElementOrder { private final Type type; @SuppressWarnings("Immutable") // Hopefully the comparator provided is immutable! - @NullableDecl - private final Comparator comparator; + private final @Nullable Comparator comparator; /** * The type of ordering that this object specifies. @@ -71,7 +70,7 @@ public enum Type { SORTED } - private ElementOrder(Type type, @NullableDecl Comparator comparator) { + private ElementOrder(Type type, @Nullable Comparator comparator) { this.type = checkNotNull(type); this.comparator = comparator; checkState((type == Type.SORTED) == (comparator != null)); @@ -79,7 +78,7 @@ private ElementOrder(Type type, @NullableDecl Comparator comparator) { /** Returns an instance which specifies that no ordering is guaranteed. */ public static ElementOrder unordered() { - return new ElementOrder(Type.UNORDERED, null); + return new ElementOrder<>(Type.UNORDERED, null); } /** @@ -119,19 +118,19 @@ public static ElementOrder unordered() { * @since 29.0 */ public static ElementOrder stable() { - return new ElementOrder(Type.STABLE, null); + return new ElementOrder<>(Type.STABLE, null); } /** Returns an instance which specifies that insertion ordering is guaranteed. */ public static ElementOrder insertion() { - return new ElementOrder(Type.INSERTION, null); + return new ElementOrder<>(Type.INSERTION, null); } /** * Returns an instance which specifies that the natural ordering of the elements is guaranteed. */ public static > ElementOrder natural() { - return new ElementOrder(Type.SORTED, Ordering.natural()); + return new ElementOrder<>(Type.SORTED, Ordering.natural()); } /** @@ -139,7 +138,7 @@ public static > ElementOrder natural() { * determined by {@code comparator}. */ public static ElementOrder sorted(Comparator comparator) { - return new ElementOrder(Type.SORTED, checkNotNull(comparator)); + return new ElementOrder<>(Type.SORTED, checkNotNull(comparator)); } /** Returns the type of ordering used. */ @@ -160,7 +159,7 @@ public Comparator comparator() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } @@ -169,12 +168,12 @@ public boolean equals(@NullableDecl Object obj) { } ElementOrder other = (ElementOrder) obj; - return (type == other.type) && Objects.equal(comparator, other.comparator); + return (type == other.type) && Objects.equals(comparator, other.comparator); } @Override public int hashCode() { - return Objects.hashCode(type, comparator); + return Objects.hash(type, comparator); } @Override @@ -196,9 +195,8 @@ Map createMap(int expectedSize) { return Maps.newLinkedHashMapWithExpectedSize(expectedSize); case SORTED: return Maps.newTreeMap(comparator()); - default: - throw new AssertionError(); } + throw new AssertionError(); } @SuppressWarnings("unchecked") diff --git a/android/guava/src/com/google/common/graph/EndpointPair.java b/android/guava/src/com/google/common/graph/EndpointPair.java index 7caa43bb4888..34ec7117e86b 100644 --- a/android/guava/src/com/google/common/graph/EndpointPair.java +++ b/android/guava/src/com/google/common/graph/EndpointPair.java @@ -20,11 +20,11 @@ import static com.google.common.graph.GraphConstants.NOT_AVAILABLE_ON_UNDIRECTED; import com.google.common.annotations.Beta; -import com.google.common.base.Objects; import com.google.common.collect.Iterators; import com.google.common.collect.UnmodifiableIterator; import com.google.errorprone.annotations.Immutable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * An immutable pair representing the two endpoints of an edge in a graph. The {@link EndpointPair} @@ -50,13 +50,13 @@ private EndpointPair(N nodeU, N nodeV) { /** Returns an {@link EndpointPair} representing the endpoints of a directed edge. */ public static EndpointPair ordered(N source, N target) { - return new Ordered(source, target); + return new Ordered<>(source, target); } /** Returns an {@link EndpointPair} representing the endpoints of an undirected edge. */ public static EndpointPair unordered(N nodeU, N nodeV) { // Swap nodes on purpose to prevent callers from relying on the "ordering" of an unordered pair. - return new Unordered(nodeV, nodeU); + return new Unordered<>(nodeV, nodeU); } /** Returns an {@link EndpointPair} representing the endpoints of an edge in {@code graph}. */ @@ -103,8 +103,9 @@ public final N nodeV() { * Returns the node that is adjacent to {@code node} along the origin edge. * * @throws IllegalArgumentException if this {@link EndpointPair} does not contain {@code node} + * @since 20.0 (but the argument type was changed from {@code Object} to {@code N} in 31.0) */ - public final N adjacentNode(Object node) { + public final N adjacentNode(N node) { if (node.equals(nodeU)) { return nodeV; } else if (node.equals(nodeV)) { @@ -132,10 +133,10 @@ public final UnmodifiableIterator iterator() { * ordered {@link EndpointPair} is never equal to an unordered {@link EndpointPair}. */ @Override - public abstract boolean equals(@NullableDecl Object obj); + public abstract boolean equals(@Nullable Object obj); /** - * The hashcode of an ordered {@link EndpointPair} is equal to {@code Objects.hashCode(source(), + * The hashcode of an ordered {@link EndpointPair} is equal to {@code Objects.hash(source(), * target())}. The hashcode of an unordered {@link EndpointPair} is equal to {@code * nodeU().hashCode() + nodeV().hashCode()}. */ @@ -163,7 +164,7 @@ public boolean isOrdered() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } @@ -181,7 +182,7 @@ public boolean equals(@NullableDecl Object obj) { @Override public int hashCode() { - return Objects.hashCode(source(), target()); + return Objects.hash(source(), target()); } @Override @@ -211,7 +212,7 @@ public boolean isOrdered() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } diff --git a/android/guava/src/com/google/common/graph/EndpointPairIterator.java b/android/guava/src/com/google/common/graph/EndpointPairIterator.java index 4b8501616968..f9b563989bb9 100644 --- a/android/guava/src/com/google/common/graph/EndpointPairIterator.java +++ b/android/guava/src/com/google/common/graph/EndpointPairIterator.java @@ -17,12 +17,14 @@ package com.google.common.graph; import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Iterator; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * A class to facilitate the set returned by {@link Graph#edges()}. @@ -33,7 +35,8 @@ abstract class EndpointPairIterator extends AbstractIterator> private final BaseGraph graph; private final Iterator nodeIterator; - N node = null; // null is safe as an initial value because graphs don't allow null nodes + @Nullable N node = null; // null is safe as an initial value because graphs don't allow null nodes + Iterator successorIterator = ImmutableSet.of().iterator(); static EndpointPairIterator of(BaseGraph graph) { @@ -69,10 +72,11 @@ private Directed(BaseGraph graph) { } @Override - protected EndpointPair computeNext() { + protected @Nullable EndpointPair computeNext() { while (true) { if (successorIterator.hasNext()) { - return EndpointPair.ordered(node, successorIterator.next()); + // requireNonNull is safe because successorIterator is empty until we set this.node. + return EndpointPair.ordered(requireNonNull(node), successorIterator.next()); } if (!advance()) { return endOfData(); @@ -108,20 +112,27 @@ protected EndpointPair computeNext() { * */ private static final class Undirected extends EndpointPairIterator { - private Set visitedNodes; + // It's a little weird that we add `null` to this set, but it makes for slightly simpler code. + private @Nullable Set<@Nullable N> visitedNodes; private Undirected(BaseGraph graph) { super(graph); - this.visitedNodes = Sets.newHashSetWithExpectedSize(graph.nodes().size()); + this.visitedNodes = Sets.newHashSetWithExpectedSize(graph.nodes().size() + 1); } @Override - protected EndpointPair computeNext() { + protected @Nullable EndpointPair computeNext() { while (true) { + /* + * requireNonNull is safe because visitedNodes isn't cleared until this method calls + * endOfData() (after which this method is never called again). + */ + requireNonNull(visitedNodes); while (successorIterator.hasNext()) { N otherNode = successorIterator.next(); if (!visitedNodes.contains(otherNode)) { - return EndpointPair.unordered(node, otherNode); + // requireNonNull is safe because successorIterator is empty until we set node. + return EndpointPair.unordered(requireNonNull(node), otherNode); } } // Add to visited set *after* processing neighbors so we still include self-loops. diff --git a/android/guava/src/com/google/common/graph/ForwardingNetwork.java b/android/guava/src/com/google/common/graph/ForwardingNetwork.java index f6ccfe2450ff..4a2f62c17579 100644 --- a/android/guava/src/com/google/common/graph/ForwardingNetwork.java +++ b/android/guava/src/com/google/common/graph/ForwardingNetwork.java @@ -17,6 +17,7 @@ package com.google.common.graph; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * A class to allow {@link Network} implementations to be backed by a provided delegate. This is not @@ -130,12 +131,12 @@ public Set edgesConnecting(EndpointPair endpoints) { } @Override - public E edgeConnectingOrNull(N nodeU, N nodeV) { + public @Nullable E edgeConnectingOrNull(N nodeU, N nodeV) { return delegate().edgeConnectingOrNull(nodeU, nodeV); } @Override - public E edgeConnectingOrNull(EndpointPair endpoints) { + public @Nullable E edgeConnectingOrNull(EndpointPair endpoints) { return delegate().edgeConnectingOrNull(endpoints); } diff --git a/android/guava/src/com/google/common/graph/ForwardingValueGraph.java b/android/guava/src/com/google/common/graph/ForwardingValueGraph.java index 3731bfb3d380..04a4a39e25a8 100644 --- a/android/guava/src/com/google/common/graph/ForwardingValueGraph.java +++ b/android/guava/src/com/google/common/graph/ForwardingValueGraph.java @@ -17,7 +17,7 @@ package com.google.common.graph; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A class to allow {@link ValueGraph} implementations to be backed by a provided delegate. This is @@ -105,14 +105,12 @@ public boolean hasEdgeConnecting(EndpointPair endpoints) { } @Override - @NullableDecl - public V edgeValueOrDefault(N nodeU, N nodeV, @NullableDecl V defaultValue) { + public @Nullable V edgeValueOrDefault(N nodeU, N nodeV, @Nullable V defaultValue) { return delegate().edgeValueOrDefault(nodeU, nodeV, defaultValue); } @Override - @NullableDecl - public V edgeValueOrDefault(EndpointPair endpoints, @NullableDecl V defaultValue) { + public @Nullable V edgeValueOrDefault(EndpointPair endpoints, @Nullable V defaultValue) { return delegate().edgeValueOrDefault(endpoints, defaultValue); } } diff --git a/android/guava/src/com/google/common/graph/Graph.java b/android/guava/src/com/google/common/graph/Graph.java index 8ed35fb9afa0..52d18a5fa95d 100644 --- a/android/guava/src/com/google/common/graph/Graph.java +++ b/android/guava/src/com/google/common/graph/Graph.java @@ -20,7 +20,7 @@ import com.google.errorprone.annotations.DoNotMock; import java.util.Collection; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An interface for {@code + * {@snippet : * MutableGraph graph = GraphBuilder.undirected().build(); - * } + * } * *

    {@link GraphBuilder#build()} returns an instance of {@link MutableGraph}, which is a subtype * of {@code Graph} that provides methods for adding and removing nodes and edges. If you do not @@ -69,9 +69,9 @@ *

    You can create an immutable copy of an existing {@code Graph} using {@link * ImmutableGraph#copyOf(Graph)}: * - *

    {@code
    + * {@snippet :
      * ImmutableGraph immutableGraph = ImmutableGraph.copyOf(graph);
    - * }
    + * } * *

    Instances of {@link ImmutableGraph} do not implement {@link MutableGraph} (obviously!) and are * contractually guaranteed to be unmodifiable and thread-safe. @@ -155,45 +155,94 @@ public interface Graph extends BaseGraph { // /** - * Returns the nodes which have an incident edge in common with {@code node} in this graph. + * Returns a live view of the nodes which have an incident edge in common with {@code node} in + * this graph. * *

    This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this graph */ @Override Set adjacentNodes(N node); /** - * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing - * {@code node}'s incoming edges against the direction (if any) of the edge. + * Returns a live view of all nodes in this graph adjacent to {@code node} which can be reached by + * traversing {@code node}'s incoming edges against the direction (if any) of the edge. * *

    In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this graph */ @Override Set predecessors(N node); /** - * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing - * {@code node}'s outgoing edges in the direction (if any) of the edge. + * Returns a live view of all nodes in this graph adjacent to {@code node} which can be reached by + * traversing {@code node}'s outgoing edges in the direction (if any) of the edge. * *

    In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. * *

    This is not the same as "all nodes reachable from {@code node} by following outgoing * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this graph */ @Override Set successors(N node); /** - * Returns the edges in this graph whose endpoints include {@code node}. + * Returns a live view of the edges in this graph whose endpoints include {@code node}. * *

    This is equal to the union of incoming and outgoing edges. * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this graph * @since 24.0 */ @@ -289,7 +338,7 @@ public interface Graph extends BaseGraph { *

    A reference implementation of this is provided by {@link AbstractGraph#equals(Object)}. */ @Override - boolean equals(@NullableDecl Object object); + boolean equals(@Nullable Object object); /** * Returns the hash code for this graph. The hash code of a graph is defined as the hash code of diff --git a/android/guava/src/com/google/common/graph/GraphBuilder.java b/android/guava/src/com/google/common/graph/GraphBuilder.java index f00d7b1f93be..970c3ad888ba 100644 --- a/android/guava/src/com/google/common/graph/GraphBuilder.java +++ b/android/guava/src/com/google/common/graph/GraphBuilder.java @@ -22,22 +22,29 @@ import com.google.common.annotations.Beta; import com.google.common.base.Optional; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotMock; /** * A builder for constructing instances of {@link MutableGraph} or {@link ImmutableGraph} with * user-defined properties. * - *

    A graph built by this class will have the following properties by default: + *

    A {@code Graph} built by this class has the following default properties: * *

      *
    • does not allow self-loops - *
    • orders {@link Graph#nodes()} in the order in which the elements were added + *
    • orders {@link Graph#nodes()} in the order in which the elements were added (insertion + * order) *
    * + *

    {@code Graph}s built by this class also guarantee that each collection-returning accessor + * returns a (live) unmodifiable view; see the external + * documentation for details. + * *

    Examples of use: * - *

    {@code
    + * {@snippet :
      * // Building a mutable graph
      * MutableGraph graph = GraphBuilder.undirected().allowsSelfLoops(true).build();
      * graph.putEdge("bread", "bread");
    @@ -53,7 +60,7 @@
      *         .putEdge("chocolate", "peanut butter")
      *         .putEdge("peanut butter", "jelly")
      *         .build();
    - * }
    + * } * * @author James Sexton * @author Joshua O'Madadhain @@ -117,6 +124,7 @@ public ImmutableGraph.Builder immutable() { * *

    The default value is {@code false}. */ + @CanIgnoreReturnValue public GraphBuilder allowsSelfLoops(boolean allowsSelfLoops) { this.allowsSelfLoops = allowsSelfLoops; return this; @@ -127,6 +135,7 @@ public GraphBuilder allowsSelfLoops(boolean allowsSelfLoops) { * * @throws IllegalArgumentException if {@code expectedNodeCount} is negative */ + @CanIgnoreReturnValue public GraphBuilder expectedNodeCount(int expectedNodeCount) { this.expectedNodeCount = Optional.of(checkNonNegative(expectedNodeCount)); return this; @@ -170,7 +179,7 @@ public GraphBuilder incidentEdgeOrder(ElementOrder incide /** Returns an empty {@link MutableGraph} with the properties of this {@link GraphBuilder}. */ public MutableGraph build() { - return new StandardMutableGraph(this); + return new StandardMutableGraph<>(this); } GraphBuilder copy() { diff --git a/android/guava/src/com/google/common/graph/GraphConnections.java b/android/guava/src/com/google/common/graph/GraphConnections.java index d783c782616c..61e36e9b1e43 100644 --- a/android/guava/src/com/google/common/graph/GraphConnections.java +++ b/android/guava/src/com/google/common/graph/GraphConnections.java @@ -19,7 +19,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Iterator; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An interface for representing and manipulating an origin node's adjacent nodes and edge values in @@ -48,8 +48,7 @@ interface GraphConnections { * Returns the value associated with the edge connecting the origin node to {@code node}, or null * if there is no such edge. */ - @NullableDecl - V value(N node); + @Nullable V value(N node); /** Remove {@code node} from the set of predecessors. */ void removePredecessor(N node); @@ -59,7 +58,7 @@ interface GraphConnections { * the edge connecting the two nodes. */ @CanIgnoreReturnValue - V removeSuccessor(N node); + @Nullable V removeSuccessor(N node); /** * Add {@code node} as a predecessor to the origin node. In the case of an undirected graph, it @@ -73,5 +72,5 @@ interface GraphConnections { * the value previously associated with the edge connecting the two nodes. */ @CanIgnoreReturnValue - V addSuccessor(N node, V value); + @Nullable V addSuccessor(N node, V value); } diff --git a/android/guava/src/com/google/common/graph/GraphConstants.java b/android/guava/src/com/google/common/graph/GraphConstants.java index 224c6d2486f7..8ede199e5a29 100644 --- a/android/guava/src/com/google/common/graph/GraphConstants.java +++ b/android/guava/src/com/google/common/graph/GraphConstants.java @@ -33,6 +33,12 @@ private GraphConstants() {} // Error messages static final String NODE_NOT_IN_GRAPH = "Node %s is not an element of this graph."; static final String EDGE_NOT_IN_GRAPH = "Edge %s is not an element of this graph."; + static final String NODE_REMOVED_FROM_GRAPH = + "Node %s that was used to generate this set is no longer in the graph."; + static final String NODE_PAIR_REMOVED_FROM_GRAPH = + "Node %s or node %s that were used to generate this set are no longer in the graph."; + static final String EDGE_REMOVED_FROM_GRAPH = + "Edge %s that was used to generate this set is no longer in the graph."; static final String REUSING_EDGE = "Edge %s already exists between the following nodes: %s, " + "so it cannot be reused to connect the following nodes: %s."; @@ -50,7 +56,7 @@ private GraphConstants() {} + "adjacentNode(node) if you already have a node, or nodeU()/nodeV() if you don't."; static final String EDGE_ALREADY_EXISTS = "Edge %s already exists in the graph."; static final String ENDPOINTS_MISMATCH = - "Mismatch: unordered endpoints cannot be used with directed graphs"; + "Mismatch: endpoints' ordering is not compatible with directionality of the graph"; /** Singleton edge value for {@link Graph} implementations backed by {@link ValueGraph}s. */ enum Presence { diff --git a/android/guava/src/com/google/common/graph/Graphs.java b/android/guava/src/com/google/common/graph/Graphs.java index 626ce7ad0228..fd24df13fe85 100644 --- a/android/guava/src/com/google/common/graph/Graphs.java +++ b/android/guava/src/com/google/common/graph/Graphs.java @@ -18,21 +18,23 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.graph.GraphConstants.NODE_NOT_IN_GRAPH; +import static com.google.common.graph.Graphs.TransitiveClosureSelfLoopStrategy.ADD_SELF_LOOPS_ALWAYS; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; -import com.google.common.base.Function; -import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayDeque; import java.util.Collection; -import java.util.HashSet; +import java.util.Deque; import java.util.Iterator; import java.util.Map; +import java.util.Objects; +import java.util.Queue; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods for {@link Graph}, {@link ValueGraph}, and {@link Network} instances. @@ -42,7 +44,7 @@ * @since 20.0 */ @Beta -public final class Graphs { +public final class Graphs extends GraphsBridgeMethods { private Graphs() {} @@ -67,7 +69,7 @@ public static boolean hasCycle(Graph graph) { Map visitedNodes = Maps.newHashMapWithExpectedSize(graph.nodes().size()); for (N node : graph.nodes()) { - if (subgraphHasCycle(graph, visitedNodes, node, null)) { + if (subgraphHasCycle(graph, visitedNodes, node)) { return true; } } @@ -93,34 +95,67 @@ public static boolean hasCycle(Network network) { } /** - * Performs a traversal of the nodes reachable from {@code node}. If we ever reach a node we've - * already visited (following only outgoing edges and without reusing edges), we know there's a - * cycle in the graph. + * Performs a traversal of the nodes reachable from {@code startNode}. If we ever reach a node + * we've already visited (following only outgoing edges and without reusing edges), we know + * there's a cycle in the graph. */ private static boolean subgraphHasCycle( - Graph graph, - Map visitedNodes, - N node, - @NullableDecl N previousNode) { - NodeVisitState state = visitedNodes.get(node); - if (state == NodeVisitState.COMPLETE) { - return false; - } - if (state == NodeVisitState.PENDING) { - return true; - } + Graph graph, Map visitedNodes, N startNode) { + Deque> stack = new ArrayDeque<>(); + stack.addLast(new NodeAndRemainingSuccessors<>(startNode)); + + while (!stack.isEmpty()) { + // To peek at the top two items, we need to temporarily remove one. + NodeAndRemainingSuccessors top = stack.removeLast(); + NodeAndRemainingSuccessors prev = stack.peekLast(); + stack.addLast(top); + + N node = top.node; + N previousNode = prev == null ? null : prev.node; + if (top.remainingSuccessors == null) { + NodeVisitState state = visitedNodes.get(node); + if (state == NodeVisitState.COMPLETE) { + stack.removeLast(); + continue; + } + if (state == NodeVisitState.PENDING) { + return true; + } - visitedNodes.put(node, NodeVisitState.PENDING); - for (N nextNode : graph.successors(node)) { - if (canTraverseWithoutReusingEdge(graph, nextNode, previousNode) - && subgraphHasCycle(graph, visitedNodes, nextNode, node)) { - return true; + visitedNodes.put(node, NodeVisitState.PENDING); + top.remainingSuccessors = new ArrayDeque<>(graph.successors(node)); } + + if (!top.remainingSuccessors.isEmpty()) { + N nextNode = top.remainingSuccessors.remove(); + if (canTraverseWithoutReusingEdge(graph, nextNode, previousNode)) { + stack.addLast(new NodeAndRemainingSuccessors<>(nextNode)); + continue; + } + } + + stack.removeLast(); + visitedNodes.put(node, NodeVisitState.COMPLETE); } - visitedNodes.put(node, NodeVisitState.COMPLETE); return false; } + private static final class NodeAndRemainingSuccessors { + final N node; + + /** + * The successors left to be visited, or {@code null} if we just added this {@code + * NodeAndRemainingSuccessors} instance to the stack. In the latter case, we'll compute the + * successors if we determine that we need them after we've performed the initial processing of + * the node. + */ + @Nullable Queue remainingSuccessors; + + NodeAndRemainingSuccessors(N node) { + this.node = node; + } + } + /** * Determines whether an edge has already been used during traversal. In the directed case a cycle * is always detected before reusing an edge, so no special logic is required. In the undirected @@ -128,8 +163,8 @@ && subgraphHasCycle(graph, visitedNodes, nextNode, node)) { * from B to A). */ private static boolean canTraverseWithoutReusingEdge( - Graph graph, Object nextNode, @NullableDecl Object previousNode) { - if (graph.isDirected() || !Objects.equal(previousNode, nextNode)) { + Graph graph, Object nextNode, @Nullable Object previousNode) { + if (graph.isDirected() || !Objects.equals(previousNode, nextNode)) { return true; } // This falls into the undirected A->B->A case. The Graph interface does not support parallel @@ -138,60 +173,110 @@ private static boolean canTraverseWithoutReusingEdge( } /** - * Returns the transitive closure of {@code graph}. The transitive closure of a graph is another - * graph with an edge connecting node A to node B if node B is {@link #reachableNodes(Graph, - * Object) reachable} from node A. + * Returns the transitive closure of {@code graph}. The transitive closure of a graph {@code G} is + * a graph {@code T} that is a supergraph of {@code G}, augmented by, for each pair of nodes A and + * B, an edge connecting node A to node B if there is a sequence of edges in {@code G} starting at + * A and ending at B. + * + *

    {@code strategy} defines the circumstances under which self-loops will be added to the + * transitive closure graph. * *

    This is a "snapshot" based on the current topology of {@code graph}, rather than a live view * of the transitive closure of {@code graph}. In other words, the returned {@link Graph} will not * be updated after modifications to {@code graph}. + * + * @since NEXT */ - // TODO(b/31438252): Consider potential optimizations for this algorithm. - public static Graph transitiveClosure(Graph graph) { - MutableGraph transitiveClosure = GraphBuilder.from(graph).allowsSelfLoops(true).build(); - // Every node is, at a minimum, reachable from itself. Since the resulting transitive closure - // will have no isolated nodes, we can skip adding nodes explicitly and let putEdge() do it. - - if (graph.isDirected()) { - // Note: works for both directed and undirected graphs, but we only use in the directed case. - for (N node : graph.nodes()) { - for (N reachableNode : reachableNodes(graph, node)) { - transitiveClosure.putEdge(node, reachableNode); - } - } - } else { - // An optimization for the undirected case: for every node B reachable from node A, - // node A and node B have the same reachability set. - Set visitedNodes = new HashSet(); - for (N node : graph.nodes()) { - if (!visitedNodes.contains(node)) { - Set reachableNodes = reachableNodes(graph, node); - visitedNodes.addAll(reachableNodes); - int pairwiseMatch = 1; // start at 1 to include self-loops - for (N nodeU : reachableNodes) { - for (N nodeV : Iterables.limit(reachableNodes, pairwiseMatch++)) { - transitiveClosure.putEdge(nodeU, nodeV); - } - } - } + // TODO(b/31438252): Consider optimizing for undirected graphs. + public static ImmutableGraph transitiveClosure( + Graph graph, TransitiveClosureSelfLoopStrategy strategy) { + ImmutableGraph.Builder transitiveClosure = + GraphBuilder.from(graph).allowsSelfLoops(true).immutable(); + + for (N node : graph.nodes()) { + // add each node explicitly to include isolated nodes + transitiveClosure.addNode(node); + for (N reachableNode : getReachableNodes(graph, node, strategy)) { + transitiveClosure.putEdge(node, reachableNode); } } + return transitiveClosure.build(); + } + + /** + * Equivalent to {@code transitiveClosure(graph, ADD_SELF_LOOPS_ALWAYS)}. Callers should look at + * the different strategy options that the new method supports rather than simply migrating to the + * new method with the existing behavior; we believe that most callers will want to use the {@code + * ADD_SELF_LOOPS_FOR_CYCLES} strategy. + * + * @since 33.1.0 (present with return type {@code Graph} since 20.0) + * @deprecated Use {@link #transitiveClosure(Graph, TransitiveClosureSelfLoopStrategy)} instead. + */ + @SuppressWarnings("InlineMeSuggester") // We expect most users to want to change behavior. + @Deprecated + public static ImmutableGraph transitiveClosure(Graph graph) { + return transitiveClosure(graph, ADD_SELF_LOOPS_ALWAYS); + } + + /** + * Returns the nodes reachable from {@code node} in {@code graph}, according to the given {@code + * strategy}. + */ + private static Iterable getReachableNodes( + Graph graph, N node, TransitiveClosureSelfLoopStrategy strategy) { + Traverser traverser = Traverser.forGraph(graph); + switch (strategy) { + case ADD_SELF_LOOPS_ALWAYS: // always include 'node' + return traverser.breadthFirst(node); + case ADD_SELF_LOOPS_FOR_CYCLES: // include 'node' iff there's an incident cycle + // note that if 'node' has a self-loop, it will appear in its successors + return traverser.breadthFirst(graph.successors(node)); + } + throw new IllegalArgumentException("Unrecognized strategy: " + strategy); + } - return transitiveClosure; + /** + * A strategy for adding self-loops to {@linkplain #transitiveClosure(Graph, + * TransitiveClosureSelfLoopStrategy) the transitive closure graph}. All strategies preserve + * self-loops that are present in the original graph. + * + *

    The strategies differ based on how they define "cycle incident to a node". + * + * @since NEXT + */ + public enum TransitiveClosureSelfLoopStrategy { + /** + * Add a self-loop to each node in the original graph; this is based on a definition of "cycle + * incident to a node" that includes zero-length cycles. This matches the behavior of the + * now-deprecated {@link #transitiveClosure(Graph)} method. + */ + ADD_SELF_LOOPS_ALWAYS, + /** + * Add a self-loop to each node that is incident to a cycle of length one or greater in the + * original graph. + */ + ADD_SELF_LOOPS_FOR_CYCLES } /** - * Returns the set of nodes that are reachable from {@code node}. Node B is defined as reachable - * from node A if there exists a path (a sequence of adjacent outgoing edges) starting at node A - * and ending at node B. Note that a node is always reachable from itself via a zero-length path. + * Returns the set of nodes that are reachable from {@code node}. Specifically, it returns all + * nodes {@code v} such that there exists a path (a sequence of adjacent outgoing edges) starting + * at {@code node} and ending at {@code v}. This implementation includes {@code node} as the first + * element in the result. * - *

    This is a "snapshot" based on the current topology of {@code graph}, rather than a live view - * of the set of nodes reachable from {@code node}. In other words, the returned {@link Set} will - * not be updated after modifications to {@code graph}. + *

    If needed, the {@link Traverser} class provides more flexible and lighter-weight ways to + * list the nodes reachable from a given node or nodes. See the "Graph traversal" + * section of the Guava User's Guide for more information. + * + *

    The {@link Set} returned is a "snapshot" based on the current topology of {@code graph}, + * rather than a live view. In other words, modifications to {@code graph} made after this method + * returns will not be reflected in the set. * * @throws IllegalArgumentException if {@code node} is not present in {@code graph} + * @since 33.1.0 (present with return type {@code Set} since 20.0) */ - public static Set reachableNodes(Graph graph, N node) { + public static ImmutableSet reachableNodes(Graph graph, N node) { checkArgument(graph.nodes().contains(node), NODE_NOT_IN_GRAPH, node); return ImmutableSet.copyOf(Traverser.forGraph(graph).breadthFirst(node)); } @@ -213,7 +298,7 @@ public static Graph transpose(Graph graph) { return ((TransposedGraph) graph).graph; } - return new TransposedGraph(graph); + return new TransposedGraph<>(graph); } /** @@ -257,7 +342,7 @@ static EndpointPair transpose(EndpointPair endpoints) { // NOTE: this should work as long as the delegate graph's implementation of edges() (like that of // AbstractGraph) derives its behavior from calling successors(). - private static class TransposedGraph extends ForwardingGraph { + private static final class TransposedGraph extends ForwardingGraph { private final Graph graph; TransposedGraph(Graph graph) { @@ -281,17 +366,12 @@ public Set successors(N node) { @Override public Set> incidentEdges(N node) { - return new IncidentEdgeSet(this, node) { + return new IncidentEdgeSet(this, node, IncidentEdgeSet.EdgeType.BOTH) { @Override public Iterator> iterator() { return Iterators.transform( delegate().incidentEdges(node).iterator(), - new Function, EndpointPair>() { - @Override - public EndpointPair apply(EndpointPair edge) { - return EndpointPair.of(delegate(), edge.nodeV(), edge.nodeU()); - } - }); + edge -> EndpointPair.of(delegate(), edge.nodeV(), edge.nodeU())); } }; } @@ -319,7 +399,7 @@ public boolean hasEdgeConnecting(EndpointPair endpoints) { // NOTE: this should work as long as the delegate graph's implementation of edges() (like that of // AbstractValueGraph) derives its behavior from calling successors(). - private static class TransposedValueGraph extends ForwardingValueGraph { + private static final class TransposedValueGraph extends ForwardingValueGraph { private final ValueGraph graph; TransposedValueGraph(ValueGraph graph) { @@ -362,19 +442,17 @@ public boolean hasEdgeConnecting(EndpointPair endpoints) { } @Override - @NullableDecl - public V edgeValueOrDefault(N nodeU, N nodeV, @NullableDecl V defaultValue) { + public @Nullable V edgeValueOrDefault(N nodeU, N nodeV, @Nullable V defaultValue) { return delegate().edgeValueOrDefault(nodeV, nodeU, defaultValue); // transpose } @Override - @NullableDecl - public V edgeValueOrDefault(EndpointPair endpoints, @NullableDecl V defaultValue) { + public @Nullable V edgeValueOrDefault(EndpointPair endpoints, @Nullable V defaultValue) { return delegate().edgeValueOrDefault(transpose(endpoints), defaultValue); } } - private static class TransposedNetwork extends ForwardingNetwork { + private static final class TransposedNetwork extends ForwardingNetwork { private final Network network; TransposedNetwork(Network network) { @@ -433,12 +511,12 @@ public Set edgesConnecting(EndpointPair endpoints) { } @Override - public E edgeConnectingOrNull(N nodeU, N nodeV) { + public @Nullable E edgeConnectingOrNull(N nodeU, N nodeV) { return delegate().edgeConnectingOrNull(nodeV, nodeU); // transpose } @Override - public E edgeConnectingOrNull(EndpointPair endpoints) { + public @Nullable E edgeConnectingOrNull(EndpointPair endpoints) { return delegate().edgeConnectingOrNull(transpose(endpoints)); } @@ -500,8 +578,11 @@ public static MutableValueGraph inducedSubgraph( for (N node : subgraph.nodes()) { for (N successorNode : graph.successors(node)) { if (subgraph.nodes().contains(successorNode)) { + // requireNonNull is safe because the endpoint pair comes from the graph. subgraph.putEdgeValue( - node, successorNode, graph.edgeValueOrDefault(node, successorNode, null)); + node, + successorNode, + requireNonNull(graph.edgeValueOrDefault(node, successorNode, null))); } } } @@ -556,8 +637,11 @@ public static MutableValueGraph copyOf(ValueGraph graph) { copy.addNode(node); } for (EndpointPair edge : graph.edges()) { + // requireNonNull is safe because the endpoint pair comes from the graph. copy.putEdgeValue( - edge.nodeU(), edge.nodeV(), graph.edgeValueOrDefault(edge.nodeU(), edge.nodeV(), null)); + edge.nodeU(), + edge.nodeV(), + requireNonNull(graph.edgeValueOrDefault(edge.nodeU(), edge.nodeV(), null))); } return copy; } diff --git a/android/guava/src/com/google/common/graph/GraphsBridgeMethods.java b/android/guava/src/com/google/common/graph/GraphsBridgeMethods.java new file mode 100644 index 000000000000..5cbb790bfabf --- /dev/null +++ b/android/guava/src/com/google/common/graph/GraphsBridgeMethods.java @@ -0,0 +1,22 @@ +package com.google.common.graph; + +import com.google.common.annotations.Beta; +import java.util.Set; + +/** + * Supertype for {@link Graphs}, containing the old signatures of methods whose signatures we've + * changed. This provides binary compatibility for users who compiled against the old signatures. + */ +@Beta +abstract class GraphsBridgeMethods { + + @SuppressWarnings("PreferredInterfaceType") + public static Graph transitiveClosure(Graph graph) { + return Graphs.transitiveClosure(graph); + } + + @SuppressWarnings("PreferredInterfaceType") + public static Set reachableNodes(Graph graph, N node) { + return Graphs.reachableNodes(graph, node); + } +} diff --git a/android/guava/src/com/google/common/graph/ImmutableGraph.java b/android/guava/src/com/google/common/graph/ImmutableGraph.java index 6331632b5542..d9048f366537 100644 --- a/android/guava/src/com/google/common/graph/ImmutableGraph.java +++ b/android/guava/src/com/google/common/graph/ImmutableGraph.java @@ -26,6 +26,7 @@ import com.google.common.graph.GraphConstants.Presence; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.InlineMe; /** * A {@link Graph} whose elements and structural relationships will never change. Instances of this @@ -67,6 +68,9 @@ public static ImmutableGraph copyOf(Graph graph) { * * @deprecated no need to use this */ + @InlineMe( + replacement = "checkNotNull(graph)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated public static ImmutableGraph copyOf(ImmutableGraph graph) { return checkNotNull(graph); @@ -86,7 +90,7 @@ private static ImmutableMap> getNodeConnect for (N node : graph.nodes()) { nodeConnections.put(node, connectionsOf(graph, node)); } - return nodeConnections.build(); + return nodeConnections.buildOrThrow(); } @SuppressWarnings("unchecked") @@ -108,7 +112,7 @@ BaseGraph delegate() { * A builder for creating {@link ImmutableGraph} instances, especially {@code static final} * graphs. Example: * - *

    {@code
    +   * {@snippet :
        * static final ImmutableGraph COUNTRY_ADJACENCY_GRAPH =
        *     GraphBuilder.undirected()
        *         .immutable()
    @@ -117,7 +121,7 @@ BaseGraph delegate() {
        *         .putEdge(GERMANY, BELGIUM)
        *         .addNode(ICELAND)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple graphs in series. Each new graph contains all the elements of the ones created before diff --git a/android/guava/src/com/google/common/graph/ImmutableNetwork.java b/android/guava/src/com/google/common/graph/ImmutableNetwork.java index b35d7220817d..e210be0d5275 100644 --- a/android/guava/src/com/google/common/graph/ImmutableNetwork.java +++ b/android/guava/src/com/google/common/graph/ImmutableNetwork.java @@ -24,6 +24,7 @@ import com.google.common.collect.Maps; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.InlineMe; import java.util.Map; /** @@ -65,6 +66,9 @@ public static ImmutableNetwork copyOf(Network network) { * * @deprecated no need to use this */ + @InlineMe( + replacement = "checkNotNull(network)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated public static ImmutableNetwork copyOf(ImmutableNetwork network) { return checkNotNull(network); @@ -72,7 +76,7 @@ public static ImmutableNetwork copyOf(ImmutableNetwork networ @Override public ImmutableGraph asGraph() { - return new ImmutableGraph(super.asGraph()); // safe because the view is effectively immutable + return new ImmutableGraph<>(super.asGraph()); // safe because the view is effectively immutable } private static Map> getNodeConnections(Network network) { @@ -83,7 +87,7 @@ private static Map> getNodeConnections(Networ for (N node : network.nodes()) { nodeConnections.put(node, connectionsOf(network, node)); } - return nodeConnections.build(); + return nodeConnections.buildOrThrow(); } private static Map getEdgeToReferenceNode(Network network) { @@ -94,7 +98,7 @@ private static Map getEdgeToReferenceNode(Network network) { for (E edge : network.edges()) { edgeToReferenceNode.put(edge, network.incidentNodes(edge).nodeU()); } - return edgeToReferenceNode.build(); + return edgeToReferenceNode.buildOrThrow(); } private static NetworkConnections connectionsOf(Network network, N node) { @@ -114,38 +118,23 @@ private static NetworkConnections connectionsOf(Network netwo } } - private static Function sourceNodeFn(final Network network) { - return new Function() { - @Override - public N apply(E edge) { - return network.incidentNodes(edge).source(); - } - }; + private static Function sourceNodeFn(Network network) { + return (E edge) -> network.incidentNodes(edge).source(); } - private static Function targetNodeFn(final Network network) { - return new Function() { - @Override - public N apply(E edge) { - return network.incidentNodes(edge).target(); - } - }; + private static Function targetNodeFn(Network network) { + return (E edge) -> network.incidentNodes(edge).target(); } - private static Function adjacentNodeFn(final Network network, final N node) { - return new Function() { - @Override - public N apply(E edge) { - return network.incidentNodes(edge).adjacentNode(node); - } - }; + private static Function adjacentNodeFn(Network network, N node) { + return (E edge) -> network.incidentNodes(edge).adjacentNode(node); } /** * A builder for creating {@link ImmutableNetwork} instances, especially {@code static final} * networks. Example: * - *

    {@code
    +   * {@snippet :
        * static final ImmutableNetwork TRAIN_NETWORK =
        *     NetworkBuilder.undirected()
        *         .allowsParallelEdges(true)
    @@ -156,7 +145,7 @@ public N apply(E edge) {
        *         .addEdge(LONDON, BRUSSELS, Eurostar.trainNumber("4444"))
        *         .addNode(REYKJAVIK)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple networks in series. Each new network contains all the elements of the ones created diff --git a/android/guava/src/com/google/common/graph/ImmutableValueGraph.java b/android/guava/src/com/google/common/graph/ImmutableValueGraph.java index f2e2386340eb..8f09d6f9096e 100644 --- a/android/guava/src/com/google/common/graph/ImmutableValueGraph.java +++ b/android/guava/src/com/google/common/graph/ImmutableValueGraph.java @@ -17,6 +17,7 @@ package com.google.common.graph; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; import com.google.common.base.Function; @@ -24,6 +25,7 @@ import com.google.common.collect.Maps; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.InlineMe; /** * A {@link ValueGraph} whose elements and structural relationships will never change. Instances of @@ -61,6 +63,9 @@ public static ImmutableValueGraph copyOf(ValueGraph graph) { * * @deprecated no need to use this */ + @InlineMe( + replacement = "checkNotNull(graph)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated public static ImmutableValueGraph copyOf(ImmutableValueGraph graph) { return checkNotNull(graph); @@ -73,7 +78,7 @@ public ElementOrder incidentEdgeOrder() { @Override public ImmutableGraph asGraph() { - return new ImmutableGraph(this); // safe because the view is effectively immutable + return new ImmutableGraph<>(this); // safe because the view is effectively immutable } private static ImmutableMap> getNodeConnections( @@ -85,18 +90,14 @@ private static ImmutableMap> getNodeConnections for (N node : graph.nodes()) { nodeConnections.put(node, connectionsOf(graph, node)); } - return nodeConnections.build(); + return nodeConnections.buildOrThrow(); } - private static GraphConnections connectionsOf( - final ValueGraph graph, final N node) { + private static GraphConnections connectionsOf(ValueGraph graph, N node) { Function successorNodeToValueFn = - new Function() { - @Override - public V apply(N successorNode) { - return graph.edgeValueOrDefault(node, successorNode, null); - } - }; + (N successorNode) -> + // requireNonNull is safe because the endpoint pair comes from the graph. + requireNonNull(graph.edgeValueOrDefault(node, successorNode, null)); return graph.isDirected() ? DirectedGraphConnections.ofImmutable( node, graph.incidentEdges(node), successorNodeToValueFn) @@ -108,7 +109,7 @@ public V apply(N successorNode) { * A builder for creating {@link ImmutableValueGraph} instances, especially {@code static final} * graphs. Example: * - *

    {@code
    +   * {@snippet :
        * static final ImmutableValueGraph CITY_ROAD_DISTANCE_GRAPH =
        *     ValueGraphBuilder.undirected()
        *         .immutable()
    @@ -117,7 +118,7 @@ public V apply(N successorNode) {
        *         .putEdgeValue(BERLIN, BRUSSELS, kilometers(764))
        *         .addNode(REYKJAVIK)
        *         .build();
    -   * }
    + * } * *

    Builder instances can be reused; it is safe to call {@link #build} multiple times to build * multiple graphs in series. Each new graph contains all the elements of the ones created before diff --git a/android/guava/src/com/google/common/graph/IncidentEdgeSet.java b/android/guava/src/com/google/common/graph/IncidentEdgeSet.java index 0c318d9f6f2a..50153d8d24f9 100644 --- a/android/guava/src/com/google/common/graph/IncidentEdgeSet.java +++ b/android/guava/src/com/google/common/graph/IncidentEdgeSet.java @@ -16,9 +16,10 @@ package com.google.common.graph; +import com.google.common.collect.ImmutableSet; import java.util.AbstractSet; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Abstract base class for an incident edges set that allows different implementations of {@link @@ -26,49 +27,54 @@ */ abstract class IncidentEdgeSet extends AbstractSet> { final N node; - final BaseGraph graph; + final ArchetypeGraph graph; + final EdgeType edgeType; - IncidentEdgeSet(BaseGraph graph, N node) { + enum EdgeType { + INCOMING, // incoming incident edges only + OUTGOING, // outgoing incident edges only + BOTH // both incoming and outgoing incident edges + } + + IncidentEdgeSet(ArchetypeGraph graph, N node, EdgeType edgeType) { this.graph = graph; this.node = node; + this.edgeType = edgeType; } @Override - public boolean remove(Object o) { + public boolean remove(@Nullable Object o) { throw new UnsupportedOperationException(); } @Override public int size() { if (graph.isDirected()) { - return graph.inDegree(node) - + graph.outDegree(node) - - (graph.successors(node).contains(node) ? 1 : 0); + return predecessorsOrEmpty(node).size() + + successorsOrEmpty(node).size() + - (edgeType == EdgeType.BOTH && predecessorsOrEmpty(node).contains(node) ? 1 : 0); } else { return graph.adjacentNodes(node).size(); } } @Override - public boolean contains(@NullableDecl Object obj) { + public boolean contains(@Nullable Object obj) { if (!(obj instanceof EndpointPair)) { return false; } EndpointPair endpointPair = (EndpointPair) obj; - if (graph.isDirected()) { - if (!endpointPair.isOrdered()) { - return false; - } + if (graph.isDirected() != endpointPair.isOrdered()) { + return false; + } + if (graph.isDirected()) { Object source = endpointPair.source(); Object target = endpointPair.target(); - return (node.equals(source) && graph.successors(node).contains(target)) - || (node.equals(target) && graph.predecessors(node).contains(source)); + return (node.equals(source) && successorsOrEmpty(node).contains(target)) + || (node.equals(target) && predecessorsOrEmpty(node).contains(source)); } else { - if (endpointPair.isOrdered()) { - return false; - } Set adjacent = graph.adjacentNodes(node); Object nodeU = endpointPair.nodeU(); Object nodeV = endpointPair.nodeV(); @@ -77,4 +83,28 @@ public boolean contains(@NullableDecl Object obj) { || (node.equals(nodeU) && adjacent.contains(nodeV)); } } + + /** + * Returns the predecessors of the given node, or an empty set if this set does not represent + * incoming edges. + */ + private Set predecessorsOrEmpty(N node) { + if (edgeType == EdgeType.INCOMING || edgeType == EdgeType.BOTH) { + return graph.predecessors(node); + } else { + return ImmutableSet.of(); + } + } + + /** + * Returns the successors of the given node, or an empty set if this set does not represent + * outgoing edges. + */ + private Set successorsOrEmpty(N node) { + if (edgeType == EdgeType.OUTGOING || edgeType == EdgeType.BOTH) { + return graph.successors(node); + } else { + return ImmutableSet.of(); + } + } } diff --git a/android/guava/src/com/google/common/graph/InvalidatableSet.java b/android/guava/src/com/google/common/graph/InvalidatableSet.java new file mode 100644 index 000000000000..80e4a943b9f5 --- /dev/null +++ b/android/guava/src/com/google/common/graph/InvalidatableSet.java @@ -0,0 +1,53 @@ +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Supplier; +import com.google.common.collect.ForwardingSet; +import java.util.Set; + +/** + * A subclass of `ForwardingSet` that throws `IllegalStateException` on invocation of any method + * (except `hashCode` and `equals`) if the provided `Supplier` returns false. + */ +final class InvalidatableSet extends ForwardingSet { + private final Supplier validator; + private final Set delegate; + private final Supplier errorMessage; + + static InvalidatableSet of( + Set delegate, Supplier validator, Supplier errorMessage) { + return new InvalidatableSet<>( + checkNotNull(delegate), checkNotNull(validator), checkNotNull(errorMessage)); + } + + @Override + protected Set delegate() { + validate(); + return delegate; + } + + private InvalidatableSet( + Set delegate, Supplier validator, Supplier errorMessage) { + this.delegate = delegate; + this.validator = validator; + this.errorMessage = errorMessage; + } + + // Override hashCode() to access delegate directly (so that it doesn't trigger the validate() call + // via delegate()); it seems inappropriate to throw ISE on this method. + @Override + public int hashCode() { + return delegate.hashCode(); + } + + private void validate() { + // Don't use checkState(), because we don't want the overhead of generating the error message + // unless it's actually going to be used; validate() is called for all set method calls, so it + // needs to be fast. + // (We could instead generate the message once, when the set is created, but zero is better.) + if (!validator.get()) { + throw new IllegalStateException(errorMessage.get()); + } + } +} diff --git a/android/guava/src/com/google/common/graph/MapIteratorCache.java b/android/guava/src/com/google/common/graph/MapIteratorCache.java index 61bf0bcc8252..b376ef92b4b4 100644 --- a/android/guava/src/com/google/common/graph/MapIteratorCache.java +++ b/android/guava/src/com/google/common/graph/MapIteratorCache.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A map-like data structure that wraps a backing map and caches values while iterating through @@ -53,47 +53,57 @@ class MapIteratorCache { * while writing to it in another. All it does is help with _reading_ from multiple threads * concurrently. For more information, see AbstractNetworkTest.concurrentIteration. */ - @NullableDecl private transient volatile Entry cacheEntry; + private transient volatile @Nullable Entry cacheEntry; MapIteratorCache(Map backingMap) { this.backingMap = checkNotNull(backingMap); } @CanIgnoreReturnValue - public final V put(@NullableDecl K key, @NullableDecl V value) { + final @Nullable V put(K key, V value) { + checkNotNull(key); + checkNotNull(value); clearCache(); return backingMap.put(key, value); } @CanIgnoreReturnValue - public final V remove(@NullableDecl Object key) { + final @Nullable V remove(Object key) { + checkNotNull(key); clearCache(); return backingMap.remove(key); } - public final void clear() { + final void clear() { clearCache(); backingMap.clear(); } - public V get(@NullableDecl Object key) { + @Nullable V get(Object key) { + checkNotNull(key); V value = getIfCached(key); - return (value != null) ? value : getWithoutCaching(key); + // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker. + if (value == null) { + return getWithoutCaching(key); + } else { + return value; + } } - public final V getWithoutCaching(@NullableDecl Object key) { + final @Nullable V getWithoutCaching(Object key) { + checkNotNull(key); return backingMap.get(key); } - public final boolean containsKey(@NullableDecl Object key) { + final boolean containsKey(@Nullable Object key) { return getIfCached(key) != null || backingMap.containsKey(key); } - public final Set unmodifiableKeySet() { + final Set unmodifiableKeySet() { return new AbstractSet() { @Override public UnmodifiableIterator iterator() { - final Iterator> entryIterator = backingMap.entrySet().iterator(); + Iterator> entryIterator = backingMap.entrySet().iterator(); return new UnmodifiableIterator() { @Override @@ -116,7 +126,7 @@ public int size() { } @Override - public boolean contains(@NullableDecl Object key) { + public boolean contains(@Nullable Object key) { return containsKey(key); } }; @@ -124,7 +134,7 @@ public boolean contains(@NullableDecl Object key) { // Internal methods (package-visible, but treat as only subclass-visible) - V getIfCached(@NullableDecl Object key) { + @Nullable V getIfCached(@Nullable Object key) { Entry entry = cacheEntry; // store local reference for thread-safety // Check cache. We use == on purpose because it's cheaper and a cache miss is ok. diff --git a/android/guava/src/com/google/common/graph/MapRetrievalCache.java b/android/guava/src/com/google/common/graph/MapRetrievalCache.java index 2e3700859f42..6e3b8abf4188 100644 --- a/android/guava/src/com/google/common/graph/MapRetrievalCache.java +++ b/android/guava/src/com/google/common/graph/MapRetrievalCache.java @@ -16,8 +16,10 @@ package com.google.common.graph; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@link MapIteratorCache} that adds additional caching. In addition to the caching provided by @@ -25,10 +27,10 @@ * * @author James Sexton */ -class MapRetrievalCache extends MapIteratorCache { +final class MapRetrievalCache extends MapIteratorCache { // See the note about volatile in the superclass. - @NullableDecl private transient volatile CacheEntry cacheEntry1; - @NullableDecl private transient volatile CacheEntry cacheEntry2; + private transient volatile @Nullable CacheEntry cacheEntry1; + private transient volatile @Nullable CacheEntry cacheEntry2; MapRetrievalCache(Map backingMap) { super(backingMap); @@ -36,7 +38,8 @@ class MapRetrievalCache extends MapIteratorCache { @SuppressWarnings("unchecked") // Safe because we only cast if key is found in map. @Override - public V get(@NullableDecl Object key) { + @Nullable V get(Object key) { + checkNotNull(key); V value = getIfCached(key); if (value != null) { return value; @@ -52,7 +55,7 @@ public V get(@NullableDecl Object key) { // Internal methods (package-visible, but treat as only subclass-visible) @Override - V getIfCached(@NullableDecl Object key) { + @Nullable V getIfCached(@Nullable Object key) { V value = super.getIfCached(key); if (value != null) { return value; diff --git a/android/guava/src/com/google/common/graph/MultiEdgesConnecting.java b/android/guava/src/com/google/common/graph/MultiEdgesConnecting.java index 916c6dd09a9e..2d24508dfb00 100644 --- a/android/guava/src/com/google/common/graph/MultiEdgesConnecting.java +++ b/android/guava/src/com/google/common/graph/MultiEdgesConnecting.java @@ -24,7 +24,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A class to represent the set of edges connecting an (implicit) origin node to a target node. @@ -47,10 +47,10 @@ abstract class MultiEdgesConnecting extends AbstractSet { @Override public UnmodifiableIterator iterator() { - final Iterator> entries = outEdgeToNode.entrySet().iterator(); + Iterator> entries = outEdgeToNode.entrySet().iterator(); return new AbstractIterator() { @Override - protected E computeNext() { + protected @Nullable E computeNext() { while (entries.hasNext()) { Entry entry = entries.next(); if (targetNode.equals(entry.getValue())) { @@ -63,7 +63,7 @@ protected E computeNext() { } @Override - public boolean contains(@NullableDecl Object edge) { + public boolean contains(@Nullable Object edge) { return targetNode.equals(outEdgeToNode.get(edge)); } } diff --git a/android/guava/src/com/google/common/graph/MutableValueGraph.java b/android/guava/src/com/google/common/graph/MutableValueGraph.java index 70b286dee185..829f774ae0ae 100644 --- a/android/guava/src/com/google/common/graph/MutableValueGraph.java +++ b/android/guava/src/com/google/common/graph/MutableValueGraph.java @@ -18,6 +18,7 @@ import com.google.common.annotations.Beta; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.jspecify.annotations.Nullable; /** * A subinterface of {@link ValueGraph} which adds mutation methods. When mutation is not required, @@ -59,7 +60,7 @@ public interface MutableValueGraph extends ValueGraph { * #allowsSelfLoops()} */ @CanIgnoreReturnValue - V putEdgeValue(N nodeU, N nodeV, V value); + @Nullable V putEdgeValue(N nodeU, N nodeV, V value); /** * Adds an edge connecting {@code endpoints} if one is not already present, and sets a value for @@ -83,7 +84,7 @@ public interface MutableValueGraph extends ValueGraph { * @since 27.1 */ @CanIgnoreReturnValue - V putEdgeValue(EndpointPair endpoints, V value); + @Nullable V putEdgeValue(EndpointPair endpoints, V value); /** * Removes {@code node} if it is present; all edges incident to {@code node} will also be removed. @@ -100,7 +101,7 @@ public interface MutableValueGraph extends ValueGraph { * nodeV}, or null if there was no such edge. */ @CanIgnoreReturnValue - V removeEdge(N nodeU, N nodeV); + @Nullable V removeEdge(N nodeU, N nodeV); /** * Removes the edge connecting {@code endpoints}, if it is present. @@ -112,5 +113,5 @@ public interface MutableValueGraph extends ValueGraph { * @since 27.1 */ @CanIgnoreReturnValue - V removeEdge(EndpointPair endpoints); + @Nullable V removeEdge(EndpointPair endpoints); } diff --git a/android/guava/src/com/google/common/graph/Network.java b/android/guava/src/com/google/common/graph/Network.java index a7c90865b1c6..42fec77e34d5 100644 --- a/android/guava/src/com/google/common/graph/Network.java +++ b/android/guava/src/com/google/common/graph/Network.java @@ -19,12 +19,13 @@ import com.google.common.annotations.Beta; import com.google.errorprone.annotations.DoNotMock; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An interface for graph-structured data, - * whose edges are unique objects. + * whose edges are unique objects. * *

    A graph is composed of a set of nodes and a set of edges connecting pairs of nodes. * @@ -46,7 +47,8 @@ *

  • graphs that do/don't allow parallel edges *
  • graphs that do/don't allow self-loops *
  • graphs whose nodes/edges are insertion-ordered, sorted, or unordered - *
  • graphs whose edges are unique objects + *
  • graphs whose edges are unique objects * * *

    Building a {@code Network}

    @@ -55,22 +57,22 @@ * create an instance of one of the built-in implementations of {@code Network}, use the {@link * NetworkBuilder} class: * - *
    {@code
    - * MutableNetwork graph = NetworkBuilder.directed().build();
    - * }
    + * {@snippet : + * MutableNetwork network = NetworkBuilder.directed().build(); + * } * *

    {@link NetworkBuilder#build()} returns an instance of {@link MutableNetwork}, which is a * subtype of {@code Network} that provides methods for adding and removing nodes and edges. If you - * do not need to mutate a graph (e.g. if you write a method than runs a read-only algorithm on the - * graph), you should use the non-mutating {@link Network} interface, or an {@link + * do not need to mutate a network (e.g. if you write a method than runs a read-only algorithm on + * the network), you should use the non-mutating {@link Network} interface, or an {@link * ImmutableNetwork}. * *

    You can create an immutable copy of an existing {@code Network} using {@link * ImmutableNetwork#copyOf(Network)}: * - *

    {@code
    - * ImmutableNetwork immutableGraph = ImmutableNetwork.copyOf(graph);
    - * }
    + * {@snippet : + * ImmutableNetwork immutableGraph = ImmutableNetwork.copyOf(network); + * } * *

    Instances of {@link ImmutableNetwork} do not implement {@link MutableNetwork} (obviously!) and * are contractually guaranteed to be unmodifiable and thread-safe. @@ -103,14 +105,11 @@ */ @Beta @DoNotMock("Use NetworkBuilder to create a real instance") -public interface Network extends SuccessorsFunction, PredecessorsFunction { +public interface Network extends ArchetypeGraph { // // Network-level accessors // - /** Returns all nodes in this network, in the order specified by {@link #nodeOrder()}. */ - Set nodes(); - /** Returns all edges in this network, in the order specified by {@link #edgeOrder()}. */ Set edges(); @@ -128,29 +127,12 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti // Network properties // - /** - * Returns true if the edges in this network are directed. Directed edges connect a {@link - * EndpointPair#source() source node} to a {@link EndpointPair#target() target node}, while - * undirected edges connect a pair of nodes to each other. - */ - boolean isDirected(); - /** * Returns true if this network allows parallel edges. Attempting to add a parallel edge to a * network that does not allow them will throw an {@link IllegalArgumentException}. */ boolean allowsParallelEdges(); - /** - * Returns true if this network allows self-loops (edges that connect a node to itself). - * Attempting to add a self-loop to a network that does not allow them will throw an {@link - * IllegalArgumentException}. - */ - boolean allowsSelfLoops(); - - /** Returns the order of iteration for the elements of {@link #nodes()}. */ - ElementOrder nodeOrder(); - /** Returns the order of iteration for the elements of {@link #edges()}. */ ElementOrder edgeOrder(); @@ -159,69 +141,72 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti // /** - * Returns the nodes which have an incident edge in common with {@code node} in this network. - * - *

    This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. - * - * @throws IllegalArgumentException if {@code node} is not an element of this network - */ - Set adjacentNodes(N node); - - /** - * Returns all nodes in this network adjacent to {@code node} which can be reached by traversing - * {@code node}'s incoming edges against the direction (if any) of the edge. + * Returns a live view of the edges whose {@link #incidentNodes(Object) incident nodes} in this + * network include {@code node}. * - *

    In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}. - * - * @throws IllegalArgumentException if {@code node} is not an element of this network - */ - @Override - Set predecessors(N node); - - /** - * Returns all nodes in this network adjacent to {@code node} which can be reached by traversing - * {@code node}'s outgoing edges in the direction (if any) of the edge. - * - *

    In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}. - * - *

    This is not the same as "all nodes reachable from {@code node} by following outgoing - * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. + *

    This is equal to the union of {@link #inEdges(Object)} and {@link #outEdges(Object)}. * - * @throws IllegalArgumentException if {@code node} is not an element of this network - */ - @Override - Set successors(N node); - - /** - * Returns the edges whose {@link #incidentNodes(Object) incident nodes} in this network include - * {@code node}. + *

    If {@code node} is removed from the network after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: * - *

    This is equal to the union of {@link #inEdges(Object)} and {@link #outEdges(Object)}. + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the network after having been removed, {@code view}'s + * behavior is undefined + *
    * * @throws IllegalArgumentException if {@code node} is not an element of this network + * @since 24.0 */ Set incidentEdges(N node); /** - * Returns all edges in this network which can be traversed in the direction (if any) of the edge - * to end at {@code node}. + * Returns a live view of all edges in this network which can be traversed in the direction (if + * any) of the edge to end at {@code node}. * *

    In a directed network, an incoming edge's {@link EndpointPair#target()} equals {@code node}. * *

    In an undirected network, this is equivalent to {@link #incidentEdges(Object)}. * + *

    If {@code node} is removed from the network after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the network after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this network */ Set inEdges(N node); /** - * Returns all edges in this network which can be traversed in the direction (if any) of the edge - * starting from {@code node}. + * Returns a live view of all edges in this network which can be traversed in the direction (if + * any) of the edge starting from {@code node}. * *

    In a directed network, an outgoing edge's {@link EndpointPair#source()} equals {@code node}. * *

    In an undirected network, this is equivalent to {@link #incidentEdges(Object)}. * + *

    If {@code node} is removed from the network after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the network after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this network */ Set outEdges(N node); @@ -239,6 +224,7 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti * * @throws IllegalArgumentException if {@code node} is not an element of this network */ + @Override int degree(N node); /** @@ -249,6 +235,7 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti * * @throws IllegalArgumentException if {@code node} is not an element of this network */ + @Override int inDegree(N node); /** @@ -259,6 +246,7 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti * * @throws IllegalArgumentException if {@code node} is not an element of this network */ + @Override int outDegree(N node); /** @@ -269,21 +257,48 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti EndpointPair incidentNodes(E edge); /** - * Returns the edges which have an {@link #incidentNodes(Object) incident node} in common with - * {@code edge}. An edge is not considered adjacent to itself. + * Returns a live view of the edges which have an {@link #incidentNodes(Object) incident node} in + * common with {@code edge}. An edge is not considered adjacent to itself. + * + *

    If {@code edge} is removed from the network after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code edge} is re-added to the network after having been removed, {@code view}'s + * behavior is undefined + *
    * * @throws IllegalArgumentException if {@code edge} is not an element of this network */ Set adjacentEdges(E edge); /** - * Returns the set of edges that each directly connect {@code nodeU} to {@code nodeV}. + * Returns a live view of the set of edges that each directly connect {@code nodeU} to {@code + * nodeV}. * *

    In an undirected network, this is equal to {@code edgesConnecting(nodeV, nodeU)}. * - *

    The resulting set of edges will be parallel (i.e. have equal {@link #incidentNodes(Object)}. - * If this network does not {@link #allowsParallelEdges() allow parallel edges}, the resulting set - * will contain at most one edge (equivalent to {@code edgeConnecting(nodeU, nodeV).asSet()}). + *

    The resulting set of edges will be parallel (i.e. have equal {@link + * #incidentNodes(Object)}). If this network does not {@link #allowsParallelEdges() allow parallel + * edges}, the resulting set will contain at most one edge (equivalent to {@code + * edgeConnecting(nodeU, nodeV).asSet()}). + * + *

    If either {@code nodeU} or {@code nodeV} are removed from the network after this method is + * called, the {@code Set} {@code view} returned by this method will be invalidated, and will + * throw {@code IllegalStateException} if it is accessed in any way, with the following + * exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code nodeU} or {@code nodeV} are re-added to the network after having been removed, + * {@code view}'s behavior is undefined + *
    * * @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this * network @@ -291,17 +306,31 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti Set edgesConnecting(N nodeU, N nodeV); /** - * Returns the set of edges that each directly connect {@code endpoints} (in the order, if any, - * specified by {@code endpoints}). + * Returns a live view of the set of edges that each directly connect {@code endpoints} (in the + * order, if any, specified by {@code endpoints}). * - *

    The resulting set of edges will be parallel (i.e. have equal {@link #incidentNodes(Object)}. - * If this network does not {@link #allowsParallelEdges() allow parallel edges}, the resulting set - * will contain at most one edge (equivalent to {@code edgeConnecting(endpoints).asSet()}). + *

    The resulting set of edges will be parallel (i.e. have equal {@link + * #incidentNodes(Object)}). If this network does not {@link #allowsParallelEdges() allow parallel + * edges}, the resulting set will contain at most one edge (equivalent to {@code + * edgeConnecting(endpoints).asSet()}). * *

    If this network is directed, {@code endpoints} must be ordered. * + *

    If either element of {@code endpoints} is removed from the network after this method is + * called, the {@code Set} {@code view} returned by this method will be invalidated, and will + * throw {@code IllegalStateException} if it is accessed in any way, with the following + * exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if either endpoint is re-added to the network after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if either endpoint is not an element of this network - * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @throws IllegalArgumentException if the endpoints are unordered and the network is directed * @since 27.1 */ Set edgesConnecting(EndpointPair endpoints); @@ -318,33 +347,32 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti * network * @since 23.0 */ - @NullableDecl - E edgeConnectingOrNull(N nodeU, N nodeV); + @Nullable E edgeConnectingOrNull(N nodeU, N nodeV); /** * Returns the single edge that directly connects {@code endpoints} (in the order, if any, * specified by {@code endpoints}), if one is present, or {@code null} if no such edge exists. * - *

    If this graph is directed, the endpoints must be ordered. + *

    If this network is directed, the endpoints must be ordered. * * @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU} * to {@code nodeV} * @throws IllegalArgumentException if either endpoint is not an element of this network - * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @throws IllegalArgumentException if the endpoints are unordered and the network is directed * @since 27.1 */ - @NullableDecl - E edgeConnectingOrNull(EndpointPair endpoints); + @Nullable E edgeConnectingOrNull(EndpointPair endpoints); /** * Returns true if there is an edge that directly connects {@code nodeU} to {@code nodeV}. This is * equivalent to {@code nodes().contains(nodeU) && successors(nodeU).contains(nodeV)}, and to * {@code edgeConnectingOrNull(nodeU, nodeV) != null}. * - *

    In an undirected graph, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}. + *

    In an undirected network, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}. * * @since 23.0 */ + @Override boolean hasEdgeConnecting(N nodeU, N nodeV); /** @@ -352,12 +380,13 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti * any, specified by {@code endpoints}). * *

    Unlike the other {@code EndpointPair}-accepting methods, this method does not throw if the - * endpoints are unordered and the graph is directed; it simply returns {@code false}. This is for - * consistency with {@link Graph#hasEdgeConnecting(EndpointPair)} and {@link + * endpoints are unordered and the network is directed; it simply returns {@code false}. This is + * for consistency with {@link Graph#hasEdgeConnecting(EndpointPair)} and {@link * ValueGraph#hasEdgeConnecting(EndpointPair)}. * * @since 27.1 */ + @Override boolean hasEdgeConnecting(EndpointPair endpoints); // @@ -385,7 +414,7 @@ public interface Network extends SuccessorsFunction, PredecessorsFuncti *

    A reference implementation of this is provided by {@link AbstractNetwork#equals(Object)}. */ @Override - boolean equals(@NullableDecl Object object); + boolean equals(@Nullable Object object); /** * Returns the hash code for this network. The hash code of a network is defined as the hash code diff --git a/android/guava/src/com/google/common/graph/NetworkBuilder.java b/android/guava/src/com/google/common/graph/NetworkBuilder.java index d289ee2d53d6..c0dbcc181928 100644 --- a/android/guava/src/com/google/common/graph/NetworkBuilder.java +++ b/android/guava/src/com/google/common/graph/NetworkBuilder.java @@ -21,23 +21,29 @@ import com.google.common.annotations.Beta; import com.google.common.base.Optional; +import com.google.errorprone.annotations.CanIgnoreReturnValue; /** * A builder for constructing instances of {@link MutableNetwork} or {@link ImmutableNetwork} with * user-defined properties. * - *

    A network built by this class will have the following properties by default: + *

    A {@code Network} built by this class has the following default properties: * *

      *
    • does not allow parallel edges *
    • does not allow self-loops *
    • orders {@link Network#nodes()} and {@link Network#edges()} in the order in which the - * elements were added + * elements were added (insertion order) *
    * + *

    {@code Network}s built by this class also guarantee that each collection-returning accessor + * returns a (live) unmodifiable view; see the external + * documentation for details. + * *

    Examples of use: * - *

    {@code
    + * {@snippet :
      * // Building a mutable network
      * MutableNetwork network =
      *     NetworkBuilder.directed().allowsParallelEdges(true).build();
    @@ -54,7 +60,7 @@
      *         .addEdge("LAX", "ATL", 1598)
      *         .addEdge("ATL", "LAX", 2450)
      *         .build();
    - * }
    + * } * * @author James Sexton * @author Joshua O'Madadhain @@ -121,6 +127,7 @@ public ImmutableNetwork.Builder immutable() * *

    The default value is {@code false}. */ + @CanIgnoreReturnValue public NetworkBuilder allowsParallelEdges(boolean allowsParallelEdges) { this.allowsParallelEdges = allowsParallelEdges; return this; @@ -133,6 +140,7 @@ public NetworkBuilder allowsParallelEdges(boolean allowsParallelEdges) { * *

    The default value is {@code false}. */ + @CanIgnoreReturnValue public NetworkBuilder allowsSelfLoops(boolean allowsSelfLoops) { this.allowsSelfLoops = allowsSelfLoops; return this; @@ -143,6 +151,7 @@ public NetworkBuilder allowsSelfLoops(boolean allowsSelfLoops) { * * @throws IllegalArgumentException if {@code expectedNodeCount} is negative */ + @CanIgnoreReturnValue public NetworkBuilder expectedNodeCount(int expectedNodeCount) { this.expectedNodeCount = Optional.of(checkNonNegative(expectedNodeCount)); return this; @@ -153,6 +162,7 @@ public NetworkBuilder expectedNodeCount(int expectedNodeCount) { * * @throws IllegalArgumentException if {@code expectedEdgeCount} is negative */ + @CanIgnoreReturnValue public NetworkBuilder expectedEdgeCount(int expectedEdgeCount) { this.expectedEdgeCount = Optional.of(checkNonNegative(expectedEdgeCount)); return this; diff --git a/android/guava/src/com/google/common/graph/NetworkConnections.java b/android/guava/src/com/google/common/graph/NetworkConnections.java index 16a68d6cb830..940d6c2074b5 100644 --- a/android/guava/src/com/google/common/graph/NetworkConnections.java +++ b/android/guava/src/com/google/common/graph/NetworkConnections.java @@ -18,6 +18,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * An interface for representing and manipulating an origin node's adjacent nodes and incident edges @@ -60,7 +61,7 @@ interface NetworkConnections { *

    In the undirected case, returns {@code null} if {@code isSelfLoop} is true. */ @CanIgnoreReturnValue - N removeInEdge(E edge, boolean isSelfLoop); + @Nullable N removeInEdge(E edge, boolean isSelfLoop); /** Remove {@code edge} from the set of outgoing edges. Returns the former successor node. */ @CanIgnoreReturnValue diff --git a/android/guava/src/com/google/common/graph/ParametricNullness.java b/android/guava/src/com/google/common/graph/ParametricNullness.java new file mode 100644 index 000000000000..67db8773c3d0 --- /dev/null +++ b/android/guava/src/com/google/common/graph/ParametricNullness.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. + */ +@GwtCompatible +@Retention(CLASS) +@Target({FIELD, METHOD, PARAMETER}) +@interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/graph/PredecessorsFunction.java b/android/guava/src/com/google/common/graph/PredecessorsFunction.java index f9ca48ae77b9..4265d71be56f 100644 --- a/android/guava/src/com/google/common/graph/PredecessorsFunction.java +++ b/android/guava/src/com/google/common/graph/PredecessorsFunction.java @@ -30,18 +30,18 @@ * * Given an algorithm, for example: * - *

    {@code
    + * {@snippet :
      * public  someGraphAlgorithm(N startNode, PredecessorsFunction predecessorsFunction);
    - * }
    + * } * * you will invoke it depending on the graph representation you're using. * *

    If you have an instance of one of the primary {@code common.graph} types ({@link Graph}, * {@link ValueGraph}, and {@link Network}): * - *

    {@code
    + * {@snippet :
      * someGraphAlgorithm(startNode, graph);
    - * }
    + * } * * This works because those types each implement {@code PredecessorsFunction}. It will also work * with any other implementation of this interface. @@ -49,17 +49,17 @@ *

    If you have your own graph implementation based around a custom node type {@code MyNode}, * which has a method {@code getParents()} that retrieves its predecessors in a graph: * - *

    {@code
    + * {@snippet :
      * someGraphAlgorithm(startNode, MyNode::getParents);
    - * }
    + * } * *

    If you have some other mechanism for returning the predecessors of a node, or one that doesn't * return a {@code Iterable}, then you can use a lambda to perform a more general * transformation: * - *

    {@code
    + * {@snippet :
      * someGraphAlgorithm(startNode, node -> ImmutableList.of(node.mother(), node.father()));
    - * }
    + * } * *

    Graph algorithms that need additional capabilities (accessing both predecessors and * successors, iterating over the edges, etc.) should declare their input to be of a type that diff --git a/android/guava/src/com/google/common/graph/StandardMutableNetwork.java b/android/guava/src/com/google/common/graph/StandardMutableNetwork.java index 8cfe9c526021..23512b6f97d5 100644 --- a/android/guava/src/com/google/common/graph/StandardMutableNetwork.java +++ b/android/guava/src/com/google/common/graph/StandardMutableNetwork.java @@ -22,6 +22,7 @@ import static com.google.common.graph.GraphConstants.PARALLEL_EDGES_NOT_ALLOWED; import static com.google.common.graph.GraphConstants.REUSING_EDGE; import static com.google.common.graph.GraphConstants.SELF_LOOPS_NOT_ALLOWED; +import static java.util.Objects.requireNonNull; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -152,9 +153,10 @@ public boolean removeEdge(E edge) { return false; } - NetworkConnections connectionsU = nodeConnections.get(nodeU); + // requireNonNull is safe because of the edgeToReferenceNode check above. + NetworkConnections connectionsU = requireNonNull(nodeConnections.get(nodeU)); N nodeV = connectionsU.adjacentNode(edge); - NetworkConnections connectionsV = nodeConnections.get(nodeV); + NetworkConnections connectionsV = requireNonNull(nodeConnections.get(nodeV)); connectionsU.removeOutEdge(edge); connectionsV.removeInEdge(edge, allowsSelfLoops() && nodeU.equals(nodeV)); edgeToReferenceNode.remove(edge); diff --git a/android/guava/src/com/google/common/graph/StandardMutableValueGraph.java b/android/guava/src/com/google/common/graph/StandardMutableValueGraph.java index 558d8d60976e..bad4eb7626bd 100644 --- a/android/guava/src/com/google/common/graph/StandardMutableValueGraph.java +++ b/android/guava/src/com/google/common/graph/StandardMutableValueGraph.java @@ -22,8 +22,11 @@ import static com.google.common.graph.GraphConstants.SELF_LOOPS_NOT_ALLOWED; import static com.google.common.graph.Graphs.checkNonNegative; import static com.google.common.graph.Graphs.checkPositive; +import static java.util.Objects.requireNonNull; +import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.jspecify.annotations.Nullable; /** * Standard implementation of {@link MutableValueGraph} that supports both directed and undirected @@ -81,7 +84,7 @@ private GraphConnections addNodeInternal(N node) { @Override @CanIgnoreReturnValue - public V putEdgeValue(N nodeU, N nodeV, V value) { + public @Nullable V putEdgeValue(N nodeU, N nodeV, V value) { checkNotNull(nodeU, "nodeU"); checkNotNull(nodeV, "nodeV"); checkNotNull(value, "value"); @@ -108,7 +111,7 @@ public V putEdgeValue(N nodeU, N nodeV, V value) { @Override @CanIgnoreReturnValue - public V putEdgeValue(EndpointPair endpoints, V value) { + public @Nullable V putEdgeValue(EndpointPair endpoints, V value) { validateEndpoints(endpoints); return putEdgeValue(endpoints.nodeU(), endpoints.nodeV(), value); } @@ -131,13 +134,21 @@ public boolean removeNode(N node) { } } - for (N successor : connections.successors()) { - nodeConnections.getWithoutCaching(successor).removePredecessor(node); + for (N successor : ImmutableList.copyOf(connections.successors())) { + // requireNonNull is safe because the node is a successor. + requireNonNull(nodeConnections.getWithoutCaching(successor)).removePredecessor(node); + requireNonNull(connections.removeSuccessor(successor)); --edgeCount; } if (isDirected()) { // In undirected graphs, the successor and predecessor sets are equal. - for (N predecessor : connections.predecessors()) { - checkState(nodeConnections.getWithoutCaching(predecessor).removeSuccessor(node) != null); + // Since views are returned, we need to copy the predecessors that will be removed. + // Thus we avoid modifying the underlying view while iterating over it. + for (N predecessor : ImmutableList.copyOf(connections.predecessors())) { + // requireNonNull is safe because the node is a predecessor. + checkState( + requireNonNull(nodeConnections.getWithoutCaching(predecessor)).removeSuccessor(node) + != null); + connections.removePredecessor(predecessor); --edgeCount; } } @@ -148,7 +159,7 @@ public boolean removeNode(N node) { @Override @CanIgnoreReturnValue - public V removeEdge(N nodeU, N nodeV) { + public @Nullable V removeEdge(N nodeU, N nodeV) { checkNotNull(nodeU, "nodeU"); checkNotNull(nodeV, "nodeV"); @@ -168,7 +179,7 @@ public V removeEdge(N nodeU, N nodeV) { @Override @CanIgnoreReturnValue - public V removeEdge(EndpointPair endpoints) { + public @Nullable V removeEdge(EndpointPair endpoints) { validateEndpoints(endpoints); return removeEdge(endpoints.nodeU(), endpoints.nodeV()); } diff --git a/android/guava/src/com/google/common/graph/StandardNetwork.java b/android/guava/src/com/google/common/graph/StandardNetwork.java index 9163b4b4fb83..19f9e47887ff 100644 --- a/android/guava/src/com/google/common/graph/StandardNetwork.java +++ b/android/guava/src/com/google/common/graph/StandardNetwork.java @@ -22,12 +22,12 @@ import static com.google.common.graph.GraphConstants.DEFAULT_NODE_COUNT; import static com.google.common.graph.GraphConstants.EDGE_NOT_IN_GRAPH; import static com.google.common.graph.GraphConstants.NODE_NOT_IN_GRAPH; +import static java.util.Objects.requireNonNull; import com.google.common.collect.ImmutableSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; /** * Standard implementation of {@link Network} that supports the options supplied by {@link @@ -129,19 +129,20 @@ public ElementOrder edgeOrder() { @Override public Set incidentEdges(N node) { - return checkedConnections(node).incidentEdges(); + return nodeInvalidatableSet(checkedConnections(node).incidentEdges(), node); } @Override public EndpointPair incidentNodes(E edge) { N nodeU = checkedReferenceNode(edge); - N nodeV = nodeConnections.get(nodeU).adjacentNode(edge); + // requireNonNull is safe because checkedReferenceNode made sure the edge is in the network. + N nodeV = requireNonNull(nodeConnections.get(nodeU)).adjacentNode(edge); return EndpointPair.of(this, nodeU, nodeV); } @Override public Set adjacentNodes(N node) { - return checkedConnections(node).adjacentNodes(); + return nodeInvalidatableSet(checkedConnections(node).adjacentNodes(), node); } @Override @@ -151,27 +152,27 @@ public Set edgesConnecting(N nodeU, N nodeV) { return ImmutableSet.of(); } checkArgument(containsNode(nodeV), NODE_NOT_IN_GRAPH, nodeV); - return connectionsU.edgesConnecting(nodeV); + return nodePairInvalidatableSet(connectionsU.edgesConnecting(nodeV), nodeU, nodeV); } @Override public Set inEdges(N node) { - return checkedConnections(node).inEdges(); + return nodeInvalidatableSet(checkedConnections(node).inEdges(), node); } @Override public Set outEdges(N node) { - return checkedConnections(node).outEdges(); + return nodeInvalidatableSet(checkedConnections(node).outEdges(), node); } @Override public Set predecessors(N node) { - return checkedConnections(node).predecessors(); + return nodeInvalidatableSet(checkedConnections(node).predecessors(), node); } @Override public Set successors(N node) { - return checkedConnections(node).successors(); + return nodeInvalidatableSet(checkedConnections(node).successors(), node); } final NetworkConnections checkedConnections(N node) { @@ -192,11 +193,11 @@ final N checkedReferenceNode(E edge) { return referenceNode; } - final boolean containsNode(@NullableDecl N node) { + final boolean containsNode(N node) { return nodeConnections.containsKey(node); } - final boolean containsEdge(@NullableDecl E edge) { + final boolean containsEdge(E edge) { return edgeToReferenceNode.containsKey(edge); } } diff --git a/android/guava/src/com/google/common/graph/StandardValueGraph.java b/android/guava/src/com/google/common/graph/StandardValueGraph.java index f30ca67bba6c..12a5183d1827 100644 --- a/android/guava/src/com/google/common/graph/StandardValueGraph.java +++ b/android/guava/src/com/google/common/graph/StandardValueGraph.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Standard implementation of {@link ValueGraph} that supports the options supplied by {@link @@ -102,54 +102,53 @@ public ElementOrder nodeOrder() { @Override public Set adjacentNodes(N node) { - return checkedConnections(node).adjacentNodes(); + return nodeInvalidatableSet(checkedConnections(node).adjacentNodes(), node); } @Override public Set predecessors(N node) { - return checkedConnections(node).predecessors(); + return nodeInvalidatableSet(checkedConnections(node).predecessors(), node); } @Override public Set successors(N node) { - return checkedConnections(node).successors(); + return nodeInvalidatableSet(checkedConnections(node).successors(), node); } @Override public Set> incidentEdges(N node) { - final GraphConnections connections = checkedConnections(node); - - return new IncidentEdgeSet(this, node) { - @Override - public Iterator> iterator() { - return connections.incidentEdgeIterator(node); - } - }; + GraphConnections connections = checkedConnections(node); + IncidentEdgeSet incident = + new IncidentEdgeSet(this, node, IncidentEdgeSet.EdgeType.BOTH) { + @Override + public Iterator> iterator() { + return connections.incidentEdgeIterator(node); + } + }; + return nodeInvalidatableSet(incident, node); } @Override public boolean hasEdgeConnecting(N nodeU, N nodeV) { - return hasEdgeConnecting_internal(checkNotNull(nodeU), checkNotNull(nodeV)); + return hasEdgeConnectingInternal(checkNotNull(nodeU), checkNotNull(nodeV)); } @Override public boolean hasEdgeConnecting(EndpointPair endpoints) { checkNotNull(endpoints); return isOrderingCompatible(endpoints) - && hasEdgeConnecting_internal(endpoints.nodeU(), endpoints.nodeV()); + && hasEdgeConnectingInternal(endpoints.nodeU(), endpoints.nodeV()); } @Override - @NullableDecl - public V edgeValueOrDefault(N nodeU, N nodeV, @NullableDecl V defaultValue) { - return edgeValueOrDefault_internal(checkNotNull(nodeU), checkNotNull(nodeV), defaultValue); + public @Nullable V edgeValueOrDefault(N nodeU, N nodeV, @Nullable V defaultValue) { + return edgeValueOrDefaultInternal(checkNotNull(nodeU), checkNotNull(nodeV), defaultValue); } @Override - @NullableDecl - public V edgeValueOrDefault(EndpointPair endpoints, @NullableDecl V defaultValue) { + public @Nullable V edgeValueOrDefault(EndpointPair endpoints, @Nullable V defaultValue) { validateEndpoints(endpoints); - return edgeValueOrDefault_internal(endpoints.nodeU(), endpoints.nodeV(), defaultValue); + return edgeValueOrDefaultInternal(endpoints.nodeU(), endpoints.nodeV(), defaultValue); } @Override @@ -157,7 +156,7 @@ protected long edgeCount() { return edgeCount; } - final GraphConnections checkedConnections(N node) { + private final GraphConnections checkedConnections(N node) { GraphConnections connections = nodeConnections.get(node); if (connections == null) { checkNotNull(node); @@ -166,18 +165,23 @@ final GraphConnections checkedConnections(N node) { return connections; } - final boolean containsNode(@NullableDecl N node) { + final boolean containsNode(@Nullable N node) { return nodeConnections.containsKey(node); } - final boolean hasEdgeConnecting_internal(N nodeU, N nodeV) { + private final boolean hasEdgeConnectingInternal(N nodeU, N nodeV) { GraphConnections connectionsU = nodeConnections.get(nodeU); return (connectionsU != null) && connectionsU.successors().contains(nodeV); } - final V edgeValueOrDefault_internal(N nodeU, N nodeV, V defaultValue) { + private final @Nullable V edgeValueOrDefaultInternal(N nodeU, N nodeV, @Nullable V defaultValue) { GraphConnections connectionsU = nodeConnections.get(nodeU); V value = (connectionsU == null) ? null : connectionsU.value(nodeV); - return value == null ? defaultValue : value; + // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker. + if (value == null) { + return defaultValue; + } else { + return value; + } } } diff --git a/android/guava/src/com/google/common/graph/SuccessorsFunction.java b/android/guava/src/com/google/common/graph/SuccessorsFunction.java index f74f437c2937..9b71e8d54c14 100644 --- a/android/guava/src/com/google/common/graph/SuccessorsFunction.java +++ b/android/guava/src/com/google/common/graph/SuccessorsFunction.java @@ -30,18 +30,18 @@ * * Given an algorithm, for example: * - *

    {@code
    + * {@snippet :
      * public  someGraphAlgorithm(N startNode, SuccessorsFunction successorsFunction);
    - * }
    + * } * * you will invoke it depending on the graph representation you're using. * *

    If you have an instance of one of the primary {@code common.graph} types ({@link Graph}, * {@link ValueGraph}, and {@link Network}): * - *

    {@code
    + * {@snippet :
      * someGraphAlgorithm(startNode, graph);
    - * }
    + * } * * This works because those types each implement {@code SuccessorsFunction}. It will also work with * any other implementation of this interface. @@ -49,17 +49,17 @@ *

    If you have your own graph implementation based around a custom node type {@code MyNode}, * which has a method {@code getChildren()} that retrieves its successors in a graph: * - *

    {@code
    + * {@snippet :
      * someGraphAlgorithm(startNode, MyNode::getChildren);
    - * }
    + * } * *

    If you have some other mechanism for returning the successors of a node, or one that doesn't * return an {@code Iterable}, then you can use a lambda to perform a more general * transformation: * - *

    {@code
    + * {@snippet :
      * someGraphAlgorithm(startNode, node -> ImmutableList.of(node.leftChild(), node.rightChild()));
    - * }
    + * } * *

    Graph algorithms that need additional capabilities (accessing both predecessors and * successors, iterating over the edges, etc.) should declare their input to be of a type that diff --git a/android/guava/src/com/google/common/graph/Traverser.java b/android/guava/src/com/google/common/graph/Traverser.java index 0d8e6f97fa32..2013f161f4d2 100644 --- a/android/guava/src/com/google/common/graph/Traverser.java +++ b/android/guava/src/com/google/common/graph/Traverser.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; import com.google.common.collect.AbstractIterator; @@ -28,7 +29,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An object that can traverse the nodes that are reachable from a specified (set of) start node(s) @@ -94,7 +95,7 @@ private Traverser(SuccessorsFunction successorFunction) { * * @param graph {@link SuccessorsFunction} representing a general graph that may have cycles. */ - public static Traverser forGraph(final SuccessorsFunction graph) { + public static Traverser forGraph(SuccessorsFunction graph) { return new Traverser(graph) { @Override Traversal newTraversal() { @@ -112,8 +113,8 @@ Traversal newTraversal() { * structure being traversed is, in addition to being a tree/forest, also defined recursively. * This is because the {@code forTree()}-based implementations don't keep track of visited nodes, - * and therefore don't need to call `equals()` or `hashCode()` on the node objects; this saves - * both time and space versus traversing the same graph using {@code forGraph()}. + * and therefore don't need to call {@code equals()} or {@code hashCode()} on the node objects; + * this saves both time and space versus traversing the same graph using {@code forGraph()}. * *

    Providing a graph to be traversed for which there is more than one path from the start * node(s) to any node may lead to: @@ -139,7 +140,7 @@ Traversal newTraversal() { * b} were also a start node, then there would be multiple paths to reach {@code e} and * {@code h}. * - *

    {@code
    +   * {@snippet :
        *    a     b      c
        *   / \   / \     |
        *  /   \ /   \    |
    @@ -147,14 +148,14 @@ Traversal newTraversal() {
        *       |
        *       |
        *       h
    -   * }
    + * } * *

    . * *

    The graph below would be a valid input with start nodes of {@code a, f}. However, if {@code * b} were a start node, there would be multiple paths to {@code f}. * - *

    {@code
    +   * {@snippet :
        *    a     b
        *   / \   / \
        *  /   \ /   \
    @@ -162,21 +163,21 @@ Traversal newTraversal() {
        *        \   /
        *         \ /
        *          f
    -   * }
    + * } * *

    Note on binary trees * *

    This method can be used to traverse over a binary tree. Given methods {@code * leftChild(node)} and {@code rightChild(node)}, this method can be called as * - *

    {@code
    +   * {@snippet :
        * Traverser.forTree(node -> ImmutableList.of(leftChild(node), rightChild(node)));
    -   * }
    + * } * * @param tree {@link SuccessorsFunction} representing a directed acyclic graph that has at most * one path between any two nodes */ - public static Traverser forTree(final SuccessorsFunction tree) { + public static Traverser forTree(SuccessorsFunction tree) { if (tree instanceof BaseGraph) { checkArgument(((BaseGraph) tree).isDirected(), "Undirected graphs can never be trees."); } @@ -199,12 +200,12 @@ Traversal newTraversal() { *

    Example: The following graph with {@code startNode} {@code a} would return nodes in * the order {@code abcdef} (assuming successors are returned in alphabetical order). * - *

    {@code
    +   * {@snippet :
        * b ---- a ---- d
        * |      |
        * |      |
        * e ---- c ---- f
    -   * }
    + * } * *

    The behavior of this method is undefined if the nodes, or the topology of the graph, change * while iteration is in progress. @@ -213,9 +214,9 @@ Traversal newTraversal() { * compute its next element on the fly. It is thus possible to limit the traversal to a certain * number of nodes as follows: * - *

    {@code
    +   * {@snippet :
        * Iterables.limit(Traverser.forGraph(graph).breadthFirst(node), maxNumberOfNodes);
    -   * }
    + * } * *

    See Wikipedia for more * info. @@ -237,13 +238,8 @@ public final Iterable breadthFirst(N startNode) { * @since 24.1 */ public final Iterable breadthFirst(Iterable startNodes) { - final ImmutableSet validated = validate(startNodes); - return new Iterable() { - @Override - public Iterator iterator() { - return newTraversal().breadthFirst(validated.iterator()); - } - }; + ImmutableSet validated = validate(startNodes); + return () -> newTraversal().breadthFirst(validated.iterator()); } /** @@ -254,12 +250,12 @@ public Iterator iterator() { *

    Example: The following graph with {@code startNode} {@code a} would return nodes in * the order {@code abecfd} (assuming successors are returned in alphabetical order). * - *

    {@code
    +   * {@snippet :
        * b ---- a ---- d
        * |      |
        * |      |
        * e ---- c ---- f
    -   * }
    + * } * *

    The behavior of this method is undefined if the nodes, or the topology of the graph, change * while iteration is in progress. @@ -268,10 +264,10 @@ public Iterator iterator() { * compute its next element on the fly. It is thus possible to limit the traversal to a certain * number of nodes as follows: * - *

    {@code
    +   * {@snippet :
        * Iterables.limit(
        *     Traverser.forGraph(graph).depthFirstPreOrder(node), maxNumberOfNodes);
    -   * }
    + * } * *

    See Wikipedia for more info. * @@ -292,13 +288,8 @@ public final Iterable depthFirstPreOrder(N startNode) { * @since 24.1 */ public final Iterable depthFirstPreOrder(Iterable startNodes) { - final ImmutableSet validated = validate(startNodes); - return new Iterable() { - @Override - public Iterator iterator() { - return newTraversal().preOrder(validated.iterator()); - } - }; + ImmutableSet validated = validate(startNodes); + return () -> newTraversal().preOrder(validated.iterator()); } /** @@ -309,12 +300,12 @@ public Iterator iterator() { *

    Example: The following graph with {@code startNode} {@code a} would return nodes in * the order {@code fcebda} (assuming successors are returned in alphabetical order). * - *

    {@code
    +   * {@snippet :
        * b ---- a ---- d
        * |      |
        * |      |
        * e ---- c ---- f
    -   * }
    + * } * *

    The behavior of this method is undefined if the nodes, or the topology of the graph, change * while iteration is in progress. @@ -323,10 +314,10 @@ public Iterator iterator() { * compute its next element on the fly. It is thus possible to limit the traversal to a certain * number of nodes as follows: * - *

    {@code
    +   * {@snippet :
        * Iterables.limit(
        *     Traverser.forGraph(graph).depthFirstPostOrder(node), maxNumberOfNodes);
    -   * }
    + * } * *

    See Wikipedia for more info. * @@ -347,13 +338,8 @@ public final Iterable depthFirstPostOrder(N startNode) { * @since 24.1 */ public final Iterable depthFirstPostOrder(Iterable startNodes) { - final ImmutableSet validated = validate(startNodes); - return new Iterable() { - @Override - public Iterator iterator() { - return newTraversal().postOrder(validated.iterator()); - } - }; + ImmutableSet validated = validate(startNodes); + return () -> newTraversal().postOrder(validated.iterator()); } abstract Traversal newTraversal(); @@ -380,13 +366,22 @@ private abstract static class Traversal { } static Traversal inGraph(SuccessorsFunction graph) { - final Set visited = new HashSet<>(); + Set visited = new HashSet<>(); return new Traversal(graph) { @Override - N visitNext(Deque> horizon) { + @Nullable N visitNext(Deque> horizon) { Iterator top = horizon.getFirst(); while (top.hasNext()) { - N element = checkNotNull(top.next()); + N element = top.next(); + // requireNonNull is safe because horizon contains only graph nodes. + /* + * TODO(cpovirk): Replace these two statements with one (`N element = + * requireNonNull(top.next())`) once our checker supports it. + * + * (The problem is likely + * https://github.com/jspecify/jspecify-reference-checker/blob/61aafa4ae52594830cfc2d61c8b113009dbdb045/src/main/java/com/google/jspecify/nullness/NullSpecAnnotatedTypeFactory.java#L896) + */ + requireNonNull(element); if (visited.add(element)) { return element; } @@ -400,7 +395,7 @@ N visitNext(Deque> horizon) { static Traversal inTree(SuccessorsFunction tree) { return new Traversal(tree) { @Override - N visitNext(Deque> horizon) { + @Nullable N visitNext(Deque> horizon) { Iterator top = horizon.getFirst(); if (top.hasNext()) { return checkNotNull(top.next()); @@ -425,12 +420,12 @@ final Iterator preOrder(Iterator startNodes) { * determined by the {@code InsertionOrder} parameter: nieces are placed at the FRONT before * aunts for pre-order; while in BFS they are placed at the BACK after aunts. */ - private Iterator topDown(Iterator startNodes, final InsertionOrder order) { - final Deque> horizon = new ArrayDeque<>(); + private Iterator topDown(Iterator startNodes, InsertionOrder order) { + Deque> horizon = new ArrayDeque<>(); horizon.add(startNodes); return new AbstractIterator() { @Override - protected N computeNext() { + protected @Nullable N computeNext() { do { N next = visitNext(horizon); if (next != null) { @@ -449,12 +444,12 @@ protected N computeNext() { } final Iterator postOrder(Iterator startNodes) { - final Deque ancestorStack = new ArrayDeque<>(); - final Deque> horizon = new ArrayDeque<>(); + Deque ancestorStack = new ArrayDeque<>(); + Deque> horizon = new ArrayDeque<>(); horizon.add(startNodes); return new AbstractIterator() { @Override - protected N computeNext() { + protected @Nullable N computeNext() { for (N next = visitNext(horizon); next != null; next = visitNext(horizon)) { Iterator successors = successorFunction.successors(next).iterator(); if (!successors.hasNext()) { @@ -463,7 +458,11 @@ protected N computeNext() { horizon.addFirst(successors); ancestorStack.push(next); } - return ancestorStack.isEmpty() ? endOfData() : ancestorStack.pop(); + // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker. + if (!ancestorStack.isEmpty()) { + return ancestorStack.pop(); + } + return endOfData(); } }; } @@ -478,8 +477,7 @@ protected N computeNext() { * into {@code horizon} between calls to {@code visitNext()}. This causes them to receive * additional values interleaved with those shown above.) */ - @NullableDecl - abstract N visitNext(Deque> horizon); + abstract @Nullable N visitNext(Deque> horizon); } /** Poor man's method reference for {@code Deque::addFirst} and {@code Deque::addLast}. */ diff --git a/android/guava/src/com/google/common/graph/UndirectedGraphConnections.java b/android/guava/src/com/google/common/graph/UndirectedGraphConnections.java index 49689f9d8aec..ca3e880ea194 100644 --- a/android/guava/src/com/google/common/graph/UndirectedGraphConnections.java +++ b/android/guava/src/com/google/common/graph/UndirectedGraphConnections.java @@ -20,7 +20,6 @@ import static com.google.common.graph.GraphConstants.INNER_CAPACITY; import static com.google.common.graph.GraphConstants.INNER_LOAD_FACTOR; -import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; import java.util.Collections; @@ -29,6 +28,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; /** * An implementation of {@link GraphConnections} for undirected graphs. @@ -77,19 +77,14 @@ public Set successors() { } @Override - public Iterator> incidentEdgeIterator(final N thisNode) { + public Iterator> incidentEdgeIterator(N thisNode) { return Iterators.transform( adjacentNodeValues.keySet().iterator(), - new Function>() { - @Override - public EndpointPair apply(N incidentNode) { - return EndpointPair.unordered(thisNode, incidentNode); - } - }); + (N incidentNode) -> EndpointPair.unordered(thisNode, incidentNode)); } @Override - public V value(N node) { + public @Nullable V value(N node) { return adjacentNodeValues.get(node); } @@ -100,7 +95,7 @@ public void removePredecessor(N node) { } @Override - public V removeSuccessor(N node) { + public @Nullable V removeSuccessor(N node) { return adjacentNodeValues.remove(node); } @@ -111,7 +106,7 @@ public void addPredecessor(N node, V value) { } @Override - public V addSuccessor(N node, V value) { + public @Nullable V addSuccessor(N node, V value) { return adjacentNodeValues.put(node, value); } } diff --git a/android/guava/src/com/google/common/graph/UndirectedMultiNetworkConnections.java b/android/guava/src/com/google/common/graph/UndirectedMultiNetworkConnections.java index a3913799a11d..a93d04d8faab 100644 --- a/android/guava/src/com/google/common/graph/UndirectedMultiNetworkConnections.java +++ b/android/guava/src/com/google/common/graph/UndirectedMultiNetworkConnections.java @@ -30,7 +30,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An implementation of {@link NetworkConnections} for undirected networks with parallel edges. @@ -55,7 +55,7 @@ static UndirectedMultiNetworkConnections ofImmutable(Map inci return new UndirectedMultiNetworkConnections<>(ImmutableMap.copyOf(incidentEdges)); } - @LazyInit private transient Reference> adjacentNodesReference; + @LazyInit private transient @Nullable Reference> adjacentNodesReference; @Override public Set adjacentNodes() { @@ -72,7 +72,7 @@ private Multiset adjacentNodesMultiset() { } @Override - public Set edgesConnecting(final N node) { + public Set edgesConnecting(N node) { return new MultiEdgesConnecting(incidentEdgeMap, node) { @Override public int size() { @@ -82,7 +82,7 @@ public int size() { } @Override - public N removeInEdge(E edge, boolean isSelfLoop) { + public @Nullable N removeInEdge(E edge, boolean isSelfLoop) { if (!isSelfLoop) { return removeOutEdge(edge); } @@ -115,8 +115,7 @@ public void addOutEdge(E edge, N node) { } } - @NullableDecl - private static T getReference(@NullableDecl Reference reference) { + private static @Nullable T getReference(@Nullable Reference reference) { return (reference == null) ? null : reference.get(); } } diff --git a/android/guava/src/com/google/common/graph/UndirectedNetworkConnections.java b/android/guava/src/com/google/common/graph/UndirectedNetworkConnections.java index e38694ab6491..5d3473c20ece 100644 --- a/android/guava/src/com/google/common/graph/UndirectedNetworkConnections.java +++ b/android/guava/src/com/google/common/graph/UndirectedNetworkConnections.java @@ -53,6 +53,6 @@ public Set adjacentNodes() { @Override public Set edgesConnecting(N node) { - return new EdgesConnecting(((BiMap) incidentEdgeMap).inverse(), node); + return new EdgesConnecting<>(((BiMap) incidentEdgeMap).inverse(), node); } } diff --git a/android/guava/src/com/google/common/graph/ValueGraph.java b/android/guava/src/com/google/common/graph/ValueGraph.java index b7dd61c92b83..a740e3edc76d 100644 --- a/android/guava/src/com/google/common/graph/ValueGraph.java +++ b/android/guava/src/com/google/common/graph/ValueGraph.java @@ -19,7 +19,7 @@ import com.google.common.annotations.Beta; import java.util.Collection; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An interface for {@code + * {@snippet : * MutableValueGraph graph = ValueGraphBuilder.directed().build(); - * } + * } * *

    {@link ValueGraphBuilder#build()} returns an instance of {@link MutableValueGraph}, which is a * subtype of {@code ValueGraph} that provides methods for adding and removing nodes and edges. If @@ -73,9 +73,9 @@ *

    You can create an immutable copy of an existing {@code ValueGraph} using {@link * ImmutableValueGraph#copyOf(ValueGraph)}: * - *

    {@code
    + * {@snippet :
      * ImmutableValueGraph immutableGraph = ImmutableValueGraph.copyOf(graph);
    - * }
    + * } * *

    Instances of {@link ImmutableValueGraph} do not implement {@link MutableValueGraph} * (obviously!) and are contractually guaranteed to be unmodifiable and thread-safe. @@ -165,45 +165,86 @@ public interface ValueGraph extends BaseGraph { // /** - * Returns the nodes which have an incident edge in common with {@code node} in this graph. + * Returns a live view of the nodes which have an incident edge in common with {@code node} in + * this graph. * *

    This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this graph */ @Override Set adjacentNodes(N node); /** - * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing - * {@code node}'s incoming edges against the direction (if any) of the edge. + * Returns a live view of all nodes in this graph adjacent to {@code node} which can be reached by + * traversing {@code node}'s incoming edges against the direction (if any) of the edge. * *

    In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * returned by this method will be invalidated, and will throw {@code IllegalStateException} if it + * is accessed in any way. + * * @throws IllegalArgumentException if {@code node} is not an element of this graph */ @Override Set predecessors(N node); /** - * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing - * {@code node}'s outgoing edges in the direction (if any) of the edge. + * Returns a live view of all nodes in this graph adjacent to {@code node} which can be reached by + * traversing {@code node}'s outgoing edges in the direction (if any) of the edge. * *

    In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. * *

    This is not the same as "all nodes reachable from {@code node} by following outgoing * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this graph */ @Override Set successors(N node); /** - * Returns the edges in this graph whose endpoints include {@code node}. + * Returns a live view of the edges in this graph whose endpoints include {@code node}. * *

    This is equal to the union of incoming and outgoing edges. * + *

    If {@code node} is removed from the graph after this method is called, the {@code Set} + * {@code view} returned by this method will be invalidated, and will throw {@code + * IllegalStateException} if it is accessed in any way, with the following exceptions: + * + *

      + *
    • {@code view.equals(view)} evaluates to {@code true} (but any other {@code equals()} + * expression involving {@code view} will throw) + *
    • {@code hashCode()} does not throw + *
    • if {@code node} is re-added to the graph after having been removed, {@code view}'s + * behavior is undefined + *
    + * * @throws IllegalArgumentException if {@code node} is not an element of this graph * @since 24.0 */ @@ -285,8 +326,7 @@ public interface ValueGraph extends BaseGraph { * @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this * graph */ - @NullableDecl - V edgeValueOrDefault(N nodeU, N nodeV, @NullableDecl V defaultValue); + @Nullable V edgeValueOrDefault(N nodeU, N nodeV, @Nullable V defaultValue); /** * Returns the value of the edge that connects {@code endpoints} (in the order, if any, specified @@ -298,8 +338,7 @@ public interface ValueGraph extends BaseGraph { * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed * @since 27.1 */ - @NullableDecl - V edgeValueOrDefault(EndpointPair endpoints, @NullableDecl V defaultValue); + @Nullable V edgeValueOrDefault(EndpointPair endpoints, @Nullable V defaultValue); // // ValueGraph identity @@ -315,7 +354,8 @@ public interface ValueGraph extends BaseGraph { *
  • A and B have equal {@link #isDirected() directedness}. *
  • A and B have equal {@link #nodes() node sets}. *
  • A and B have equal {@link #edges() edge sets}. - *
  • The {@link #edgeValue(Object, Object) value} of a given edge is the same in both A and B. + *
  • The {@link #edgeValueOrDefault(N, N, V) value} of a given edge is the same in both A and + * B. * * *

    Graph properties besides {@link #isDirected() directedness} do not affect equality. @@ -326,12 +366,12 @@ public interface ValueGraph extends BaseGraph { *

    A reference implementation of this is provided by {@link AbstractValueGraph#equals(Object)}. */ @Override - boolean equals(@NullableDecl Object object); + boolean equals(@Nullable Object object); /** * Returns the hash code for this graph. The hash code of a graph is defined as the hash code of a - * map from each of its {@link #edges() edges} to the associated {@link #edgeValue(Object, Object) - * edge value}. + * map from each of its {@link #edges() edges} to the associated {@link #edgeValueOrDefault(N, N, + * V) edge value}. * *

    A reference implementation of this is provided by {@link AbstractValueGraph#hashCode()}. */ diff --git a/android/guava/src/com/google/common/graph/ValueGraphBuilder.java b/android/guava/src/com/google/common/graph/ValueGraphBuilder.java index 4fc752da1f4a..a8eeb87437e4 100644 --- a/android/guava/src/com/google/common/graph/ValueGraphBuilder.java +++ b/android/guava/src/com/google/common/graph/ValueGraphBuilder.java @@ -22,21 +22,28 @@ import com.google.common.annotations.Beta; import com.google.common.base.Optional; +import com.google.errorprone.annotations.CanIgnoreReturnValue; /** * A builder for constructing instances of {@link MutableValueGraph} or {@link ImmutableValueGraph} * with user-defined properties. * - *

    A graph built by this class will have the following properties by default: + *

    A {@code ValueGraph} built by this class has the following default properties: * *

      *
    • does not allow self-loops - *
    • orders {@link Graph#nodes()} in the order in which the elements were added + *
    • orders {@link ValueGraph#nodes()} in the order in which the elements were added (insertion + * order) *
    * + *

    {@code ValueGraph}s built by this class also guarantee that each collection-returning accessor + * returns a (live) unmodifiable view; see the external + * documentation for details. + * *

    Examples of use: * - *

    {@code
    + * {@snippet :
      * // Building a mutable value graph
      * MutableValueGraph graph =
      *     ValueGraphBuilder.undirected().allowsSelfLoops(true).build();
    @@ -53,7 +60,7 @@
      *         .putEdgeValue("San Jose", "San Jose", 0.0)
      *         .putEdgeValue("San Francisco", "San Jose", 48.4)
      *         .build();
    - * }
    + * } * * @author James Sexton * @author Joshua O'Madadhain @@ -121,6 +128,7 @@ public ImmutableValueGraph.Builder immutabl * *

    The default value is {@code false}. */ + @CanIgnoreReturnValue public ValueGraphBuilder allowsSelfLoops(boolean allowsSelfLoops) { this.allowsSelfLoops = allowsSelfLoops; return this; @@ -131,6 +139,7 @@ public ValueGraphBuilder allowsSelfLoops(boolean allowsSelfLoops) { * * @throws IllegalArgumentException if {@code expectedNodeCount} is negative */ + @CanIgnoreReturnValue public ValueGraphBuilder expectedNodeCount(int expectedNodeCount) { this.expectedNodeCount = Optional.of(checkNonNegative(expectedNodeCount)); return this; @@ -172,6 +181,7 @@ public ValueGraphBuilder incidentEdgeOrder( newBuilder.incidentEdgeOrder = checkNotNull(incidentEdgeOrder); return newBuilder; } + /** * Returns an empty {@link MutableValueGraph} with the properties of this {@link * ValueGraphBuilder}. diff --git a/android/guava/src/com/google/common/graph/package-info.java b/android/guava/src/com/google/common/graph/package-info.java index 32d8b0157bb3..7e97756afabf 100644 --- a/android/guava/src/com/google/common/graph/package-info.java +++ b/android/guava/src/com/google/common/graph/package-info.java @@ -22,8 +22,8 @@ * library. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.graph; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/hash/AbstractByteHasher.java b/android/guava/src/com/google/common/hash/AbstractByteHasher.java index 9f7e041909f5..9bb10245f023 100644 --- a/android/guava/src/com/google/common/hash/AbstractByteHasher.java +++ b/android/guava/src/com/google/common/hash/AbstractByteHasher.java @@ -24,6 +24,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import org.jspecify.annotations.Nullable; /** * Abstract {@link Hasher} that handles converting primitives to bytes using a scratch {@code @@ -31,10 +32,8 @@ * * @author Colin Decker */ -@CanIgnoreReturnValue -@ElementTypesAreNonnullByDefault abstract class AbstractByteHasher extends AbstractHasher { - private final ByteBuffer scratch = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + private @Nullable ByteBuffer scratch; /** Updates this hasher with the given byte. */ protected abstract void update(byte b); @@ -64,7 +63,9 @@ protected void update(ByteBuffer b) { } /** Updates the sink with the given number of bytes from the buffer. */ - private Hasher update(int bytes) { + @SuppressWarnings("ByteBufferBackingArray") // We created the array with ByteBuffer.allocate(). + @CanIgnoreReturnValue + private Hasher update(ByteBuffer scratch, int bytes) { try { update(scratch.array(), 0, bytes); } finally { @@ -74,12 +75,14 @@ private Hasher update(int bytes) { } @Override + @CanIgnoreReturnValue public Hasher putByte(byte b) { update(b); return this; } @Override + @CanIgnoreReturnValue public Hasher putBytes(byte[] bytes) { checkNotNull(bytes); update(bytes); @@ -87,6 +90,7 @@ public Hasher putBytes(byte[] bytes) { } @Override + @CanIgnoreReturnValue public Hasher putBytes(byte[] bytes, int off, int len) { checkPositionIndexes(off, off + len, bytes.length); update(bytes, off, len); @@ -94,32 +98,48 @@ public Hasher putBytes(byte[] bytes, int off, int len) { } @Override + @CanIgnoreReturnValue public Hasher putBytes(ByteBuffer bytes) { update(bytes); return this; } @Override + @CanIgnoreReturnValue public Hasher putShort(short s) { + ByteBuffer scratch = scratch(); scratch.putShort(s); - return update(Shorts.BYTES); + return update(scratch, Shorts.BYTES); } @Override + @CanIgnoreReturnValue public Hasher putInt(int i) { + ByteBuffer scratch = scratch(); scratch.putInt(i); - return update(Ints.BYTES); + return update(scratch, Ints.BYTES); } @Override + @CanIgnoreReturnValue public Hasher putLong(long l) { + ByteBuffer scratch = scratch(); scratch.putLong(l); - return update(Longs.BYTES); + return update(scratch, Longs.BYTES); } @Override + @CanIgnoreReturnValue public Hasher putChar(char c) { + ByteBuffer scratch = scratch(); scratch.putChar(c); - return update(Chars.BYTES); + return update(scratch, Chars.BYTES); + } + + private ByteBuffer scratch() { + if (scratch == null) { + scratch = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + } + return scratch; } } diff --git a/android/guava/src/com/google/common/hash/AbstractCompositeHashFunction.java b/android/guava/src/com/google/common/hash/AbstractCompositeHashFunction.java index 4b69bb721ca7..15ce417441df 100644 --- a/android/guava/src/com/google/common/hash/AbstractCompositeHashFunction.java +++ b/android/guava/src/com/google/common/hash/AbstractCompositeHashFunction.java @@ -20,7 +20,7 @@ import com.google.errorprone.annotations.Immutable; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * An abstract composition of multiple hash functions. {@linkplain #newHasher()} delegates to the @@ -30,7 +30,6 @@ * @author Dimitris Andreou */ @Immutable -@ElementTypesAreNonnullByDefault abstract class AbstractCompositeHashFunction extends AbstractHashFunction { @SuppressWarnings("Immutable") // array not modified after creation @@ -70,7 +69,7 @@ public Hasher newHasher(int expectedInputSize) { return fromHashers(hashers); } - private Hasher fromHashers(final Hasher[] hashers) { + private Hasher fromHashers(Hasher[] hashers) { return new Hasher() { @Override public Hasher putByte(byte b) { diff --git a/android/guava/src/com/google/common/hash/AbstractHashFunction.java b/android/guava/src/com/google/common/hash/AbstractHashFunction.java index 73085560024f..2479b29a502a 100644 --- a/android/guava/src/com/google/common/hash/AbstractHashFunction.java +++ b/android/guava/src/com/google/common/hash/AbstractHashFunction.java @@ -20,7 +20,7 @@ import com.google.errorprone.annotations.Immutable; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Skeleton implementation of {@link HashFunction} in terms of {@link #newHasher()}. @@ -28,7 +28,6 @@ *

    TODO(lowasser): make public */ @Immutable -@ElementTypesAreNonnullByDefault abstract class AbstractHashFunction implements HashFunction { @Override public HashCode hashObject( diff --git a/android/guava/src/com/google/common/hash/AbstractHasher.java b/android/guava/src/com/google/common/hash/AbstractHasher.java index c72e05be05e3..4136b231b99d 100644 --- a/android/guava/src/com/google/common/hash/AbstractHasher.java +++ b/android/guava/src/com/google/common/hash/AbstractHasher.java @@ -18,7 +18,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * An abstract implementation of {@link Hasher}, which only requires subtypes to implement {@link @@ -26,25 +26,27 @@ * * @author Dimitris Andreou */ -@CanIgnoreReturnValue -@ElementTypesAreNonnullByDefault abstract class AbstractHasher implements Hasher { @Override + @CanIgnoreReturnValue public final Hasher putBoolean(boolean b) { return putByte(b ? (byte) 1 : (byte) 0); } @Override + @CanIgnoreReturnValue public final Hasher putDouble(double d) { return putLong(Double.doubleToRawLongBits(d)); } @Override + @CanIgnoreReturnValue public final Hasher putFloat(float f) { return putInt(Float.floatToRawIntBits(f)); } @Override + @CanIgnoreReturnValue public Hasher putUnencodedChars(CharSequence charSequence) { for (int i = 0, len = charSequence.length(); i < len; i++) { putChar(charSequence.charAt(i)); @@ -53,16 +55,19 @@ public Hasher putUnencodedChars(CharSequence charSequence) { } @Override + @CanIgnoreReturnValue public Hasher putString(CharSequence charSequence, Charset charset) { return putBytes(charSequence.toString().getBytes(charset)); } @Override + @CanIgnoreReturnValue public Hasher putBytes(byte[] bytes) { return putBytes(bytes, 0, bytes.length); } @Override + @CanIgnoreReturnValue public Hasher putBytes(byte[] bytes, int off, int len) { Preconditions.checkPositionIndexes(off, off + len, bytes.length); for (int i = 0; i < len; i++) { @@ -72,6 +77,7 @@ public Hasher putBytes(byte[] bytes, int off, int len) { } @Override + @CanIgnoreReturnValue public Hasher putBytes(ByteBuffer b) { if (b.hasArray()) { putBytes(b.array(), b.arrayOffset() + b.position(), b.remaining()); @@ -85,6 +91,7 @@ public Hasher putBytes(ByteBuffer b) { } @Override + @CanIgnoreReturnValue public Hasher putShort(short s) { putByte((byte) s); putByte((byte) (s >>> 8)); @@ -92,6 +99,7 @@ public Hasher putShort(short s) { } @Override + @CanIgnoreReturnValue public Hasher putInt(int i) { putByte((byte) i); putByte((byte) (i >>> 8)); @@ -101,6 +109,7 @@ public Hasher putInt(int i) { } @Override + @CanIgnoreReturnValue public Hasher putLong(long l) { for (int i = 0; i < 64; i += 8) { putByte((byte) (l >>> i)); @@ -109,6 +118,7 @@ public Hasher putLong(long l) { } @Override + @CanIgnoreReturnValue public Hasher putChar(char c) { putByte((byte) c); putByte((byte) (c >>> 8)); @@ -116,6 +126,7 @@ public Hasher putChar(char c) { } @Override + @CanIgnoreReturnValue public Hasher putObject( @ParametricNullness T instance, Funnel funnel) { funnel.funnel(instance, this); diff --git a/android/guava/src/com/google/common/hash/AbstractNonStreamingHashFunction.java b/android/guava/src/com/google/common/hash/AbstractNonStreamingHashFunction.java index 4969e35b22ba..54c76de19564 100644 --- a/android/guava/src/com/google/common/hash/AbstractNonStreamingHashFunction.java +++ b/android/guava/src/com/google/common/hash/AbstractNonStreamingHashFunction.java @@ -30,7 +30,6 @@ * @author Dimitris Andreou */ @Immutable -@ElementTypesAreNonnullByDefault abstract class AbstractNonStreamingHashFunction extends AbstractHashFunction { @Override public Hasher newHasher() { diff --git a/android/guava/src/com/google/common/hash/AbstractStreamingHasher.java b/android/guava/src/com/google/common/hash/AbstractStreamingHasher.java index a987b48c35f7..e28520d12701 100644 --- a/android/guava/src/com/google/common/hash/AbstractStreamingHasher.java +++ b/android/guava/src/com/google/common/hash/AbstractStreamingHasher.java @@ -28,8 +28,6 @@ * @author Dimitris Andreou */ // TODO(kevinb): this class still needs some design-and-document-for-inheritance love -@CanIgnoreReturnValue -@ElementTypesAreNonnullByDefault abstract class AbstractStreamingHasher extends AbstractHasher { /** Buffer via which we pass data to the hash algorithm (the implementor) */ private final ByteBuffer buffer; @@ -92,11 +90,13 @@ protected void processRemaining(ByteBuffer bb) { } @Override + @CanIgnoreReturnValue public final Hasher putBytes(byte[] bytes, int off, int len) { return putBytesInternal(ByteBuffer.wrap(bytes, off, len).order(ByteOrder.LITTLE_ENDIAN)); } @Override + @CanIgnoreReturnValue public final Hasher putBytes(ByteBuffer readBuffer) { ByteOrder order = readBuffer.order(); try { @@ -107,6 +107,7 @@ public final Hasher putBytes(ByteBuffer readBuffer) { } } + @CanIgnoreReturnValue private Hasher putBytesInternal(ByteBuffer readBuffer) { // If we have room for all of it, this is easy if (readBuffer.remaining() <= buffer.remaining()) { @@ -143,6 +144,7 @@ private Hasher putBytesInternal(ByteBuffer readBuffer) { */ @Override + @CanIgnoreReturnValue public final Hasher putByte(byte b) { buffer.put(b); munchIfFull(); @@ -150,6 +152,7 @@ public final Hasher putByte(byte b) { } @Override + @CanIgnoreReturnValue public final Hasher putShort(short s) { buffer.putShort(s); munchIfFull(); @@ -157,6 +160,7 @@ public final Hasher putShort(short s) { } @Override + @CanIgnoreReturnValue public final Hasher putChar(char c) { buffer.putChar(c); munchIfFull(); @@ -164,6 +168,7 @@ public final Hasher putChar(char c) { } @Override + @CanIgnoreReturnValue public final Hasher putInt(int i) { buffer.putInt(i); munchIfFull(); @@ -171,6 +176,7 @@ public final Hasher putInt(int i) { } @Override + @CanIgnoreReturnValue public final Hasher putLong(long l) { buffer.putLong(l); munchIfFull(); diff --git a/android/guava/src/com/google/common/hash/BloomFilter.java b/android/guava/src/com/google/common/hash/BloomFilter.java index 336f47b74af3..054d0f2ce5db 100644 --- a/android/guava/src/com/google/common/hash/BloomFilter.java +++ b/android/guava/src/com/google/common/hash/BloomFilter.java @@ -16,25 +16,30 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.Byte.toUnsignedInt; +import static java.lang.Math.max; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.hash.BloomFilterStrategies.LockFreeBitArray; import com.google.common.math.DoubleMath; import com.google.common.primitives.SignedBytes; import com.google.common.primitives.UnsignedBytes; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.OutputStream; import java.io.Serializable; import java.math.RoundingMode; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Objects; +import java.util.stream.Collector; +import org.jspecify.annotations.Nullable; /** * A Bloom filter for instances of {@code T}. A Bloom filter offers an approximate containment test @@ -42,7 +47,7 @@ * but if it claims that an element is not contained in it, then this is definitely true. * *

    If you are unfamiliar with Bloom filters, this nice tutorial may help you understand how + * href="http://llimllib.github.io/bloomfilter-tutorial/">tutorial may help you understand how * they work. * *

    The false positive probability ({@code FPP}) of a Bloom filter is defined as the probability @@ -64,14 +69,13 @@ * @since 11.0 (thread-safe since 23.0) */ @Beta -@ElementTypesAreNonnullByDefault public final class BloomFilter implements Predicate, Serializable { /** * A strategy to translate T instances, to {@code numHashFunctions} bit indexes. * *

    Implementations should be collections of pure functions (i.e. stateless). */ - interface Strategy extends java.io.Serializable { + interface Strategy extends Serializable { /** * Sets {@code numHashFunctions} bits of the given bit array, by hashing a user element. @@ -116,6 +120,12 @@ interface Strategy extends java.io.Serializable { /** The strategy we employ to map an element T to {@code numHashFunctions} bit indexes. */ private final Strategy strategy; + /** Natural logarithm of 2, used to optimize calculations in Bloom filter sizing. */ + private static final double LOG_TWO = Math.log(2); + + /** Square of the natural logarithm of 2, reused to optimize the bit size calculation. */ + private static final double SQUARED_LOG_TWO = LOG_TWO * LOG_TWO; + /** Creates a BloomFilter. */ private BloomFilter( LockFreeBitArray bits, int numHashFunctions, Funnel funnel, Strategy strategy) { @@ -135,7 +145,7 @@ private BloomFilter( * @since 12.0 */ public BloomFilter copy() { - return new BloomFilter(bits.copy(), numHashFunctions, funnel, strategy); + return new BloomFilter<>(bits.copy(), numHashFunctions, funnel, strategy); } /** @@ -150,6 +160,7 @@ public boolean mightContain(@ParametricNullness T object) { * @deprecated Provided only to satisfy the {@link Predicate} interface; use {@link #mightContain} * instead. */ + @InlineMe(replacement = "this.mightContain(input)") @Deprecated @Override public boolean apply(@ParametricNullness T input) { @@ -198,7 +209,7 @@ public long approximateElementCount() { long bitSize = bits.bitSize(); long bitCount = bits.bitCount(); - /** + /* * Each insertion is expected to reduce the # of clear bits by a factor of * `numHashFunctions/bitSize`. So, after n insertions, expected bitCount is `bitSize * (1 - (1 - * numHashFunctions/bitSize)^n)`. Solving that for n, and approximating `ln x` as `x - 1` when x @@ -275,7 +286,7 @@ public void putAll(BloomFilter that) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -291,7 +302,75 @@ public boolean equals(@CheckForNull Object object) { @Override public int hashCode() { - return Objects.hashCode(numHashFunctions, funnel, strategy, bits); + return Objects.hash(numHashFunctions, funnel, strategy, bits); + } + + /** + * Returns a {@code Collector} expecting the specified number of insertions, and yielding a {@link + * BloomFilter} with false positive probability 3%. + * + *

    Note that if the {@code Collector} receives significantly more elements than specified, the + * resulting {@code BloomFilter} will suffer a sharp deterioration of its false positive + * probability. + * + *

    The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel} + * is. + * + *

    It is recommended that the funnel be implemented as a Java enum. This has the benefit of + * ensuring proper serialization and deserialization, which is important since {@link #equals} + * also relies on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed {@code + * BloomFilter}; must be positive + * @return a {@code Collector} generating a {@code BloomFilter} of the received elements + * @since 33.4.0 (but since 23.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toBloomFilter( + Funnel funnel, long expectedInsertions) { + return toBloomFilter(funnel, expectedInsertions, 0.03); + } + + /** + * Returns a {@code Collector} expecting the specified number of insertions, and yielding a {@link + * BloomFilter} with the specified expected false positive probability. + * + *

    Note that if the {@code Collector} receives significantly more elements than specified, the + * resulting {@code BloomFilter} will suffer a sharp deterioration of its false positive + * probability. + * + *

    The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel} + * is. + * + *

    It is recommended that the funnel be implemented as a Java enum. This has the benefit of + * ensuring proper serialization and deserialization, which is important since {@link #equals} + * also relies on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed {@code + * BloomFilter}; must be positive + * @param fpp the desired false positive probability (must be positive and less than 1.0) + * @return a {@code Collector} generating a {@code BloomFilter} of the received elements + * @since 33.4.0 (but since 23.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector> toBloomFilter( + Funnel funnel, long expectedInsertions, double fpp) { + checkNotNull(funnel); + checkArgument( + expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions); + checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp); + checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp); + return Collector.of( + () -> BloomFilter.create(funnel, expectedInsertions, fpp), + BloomFilter::put, + (bf1, bf2) -> { + bf1.putAll(bf2); + return bf1; + }, + Collector.Characteristics.UNORDERED, + Collector.Characteristics.CONCURRENT); } /** @@ -364,9 +443,9 @@ public int hashCode() { * optimalM(1000, 0.0000000000000001) = 76680 which is less than 10kb. Who cares! */ long numBits = optimalNumOfBits(expectedInsertions, fpp); - int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits); + int numHashFunctions = optimalNumOfHashFunctions(fpp); try { - return new BloomFilter(new LockFreeBitArray(numBits), numHashFunctions, funnel, strategy); + return new BloomFilter<>(new LockFreeBitArray(numBits), numHashFunctions, funnel, strategy); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e); } @@ -434,18 +513,16 @@ public int hashCode() { // 4) For optimal k: m = -nlnp / ((ln2) ^ 2) /** - * Computes the optimal k (number of hashes per element inserted in Bloom filter), given the - * expected insertions and total number of bits in the Bloom filter. + * Computes the optimal number of hash functions (k) for a given false positive probability (p). * *

    See http://en.wikipedia.org/wiki/File:Bloom_filter_fp_probability.svg for the formula. * - * @param n expected insertions (must be positive) - * @param m total number of bits in Bloom filter (must be positive) + * @param p desired false positive probability (must be between 0 and 1, exclusive) */ @VisibleForTesting - static int optimalNumOfHashFunctions(long n, long m) { - // (m / n) * log(2), but avoid truncation due to division! - return Math.max(1, (int) Math.round((double) m / n * Math.log(2))); + static int optimalNumOfHashFunctions(double p) { + // -log(p) / log(2), ensuring the result is rounded to avoid truncation. + return max(1, (int) Math.round(-Math.log(p) / LOG_TWO)); } /** @@ -463,14 +540,18 @@ static long optimalNumOfBits(long n, double p) { if (p == 0) { p = Double.MIN_VALUE; } - return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2))); + return (long) (-n * Math.log(p) / SQUARED_LOG_TWO); } - private Object writeReplace() { + private Object writeReplace() { return new SerialForm(this); } - private static class SerialForm implements Serializable { + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + private static final class SerialForm implements Serializable { final long[] data; final int numHashFunctions; final Funnel funnel; @@ -523,6 +604,7 @@ public void writeTo(OutputStream out) throws IOException { * @throws IOException if the InputStream throws an {@code IOException}, or if its data does not * appear to be a BloomFilter serialized using the {@linkplain #writeTo(OutputStream)} method. */ + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception public static BloomFilter readFrom( InputStream in, Funnel funnel) throws IOException { checkNotNull(in, "InputStream"); @@ -536,16 +618,25 @@ public void writeTo(OutputStream out) throws IOException { // add non-stateless strategies (for which we've reserved negative ordinals; see // Strategy.ordinal()). strategyOrdinal = din.readByte(); - numHashFunctions = UnsignedBytes.toInt(din.readByte()); + numHashFunctions = toUnsignedInt(din.readByte()); dataLength = din.readInt(); + /* + * We document in BloomFilterStrategies that we must not change the ordering, and we have a + * test that verifies that we don't do so. + */ + @SuppressWarnings("EnumOrdinal") Strategy strategy = BloomFilterStrategies.values()[strategyOrdinal]; - long[] data = new long[dataLength]; - for (int i = 0; i < data.length; i++) { - data[i] = din.readLong(); + + LockFreeBitArray dataArray = new LockFreeBitArray(Math.multiplyExact(dataLength, 64L)); + for (int i = 0; i < dataLength; i++) { + dataArray.putData(i, din.readLong()); } - return new BloomFilter(new LockFreeBitArray(data), numHashFunctions, funnel, strategy); - } catch (RuntimeException e) { + + return new BloomFilter<>(dataArray, numHashFunctions, funnel, strategy); + } catch (IOException e) { + throw e; + } catch (Exception e) { // sneaky checked exception String message = "Unable to deserialize BloomFilter from InputStream." + " strategyOrdinal: " @@ -557,4 +648,6 @@ public void writeTo(OutputStream out) throws IOException { throw new IOException(message, e); } } + + private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/hash/BloomFilterStrategies.java b/android/guava/src/com/google/common/hash/BloomFilterStrategies.java index 3a012f35885d..a2aa6b51df9d 100644 --- a/android/guava/src/com/google/common/hash/BloomFilterStrategies.java +++ b/android/guava/src/com/google/common/hash/BloomFilterStrategies.java @@ -22,8 +22,7 @@ import java.math.RoundingMode; import java.util.Arrays; import java.util.concurrent.atomic.AtomicLongArray; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Collections of strategies of generating the k * log(M) bits required for an element to be mapped @@ -37,7 +36,6 @@ * @author Dimitris Andreou * @author Kurt Alfred Kluever */ -@ElementTypesAreNonnullByDefault enum BloomFilterStrategies implements BloomFilter.Strategy { /** * See "Less Hashing, Same Performance: Building a Better Bloom Filter" by Adam Kirsch and Michael @@ -94,7 +92,7 @@ enum BloomFilterStrategies implements BloomFilter.Strategy { }, /** * This strategy uses all 128 bits of {@link Hashing#murmur3_128} when hashing. It looks different - * than the implementation in MURMUR128_MITZ_32 because we're avoiding the multiplication in the + * from the implementation in MURMUR128_MITZ_32 because we're avoiding the multiplication in the * loop and doing a (much simpler) += hash2. We're also changing the index to a positive number by * AND'ing with Long.MAX_VALUE instead of flipping the bits. */ @@ -263,29 +261,40 @@ void putAll(LockFreeBitArray other) { data.length(), other.data.length()); for (int i = 0; i < data.length(); i++) { - long otherLong = other.data.get(i); - - long ourLongOld; - long ourLongNew; - boolean changedAnyBits = true; - do { - ourLongOld = data.get(i); - ourLongNew = ourLongOld | otherLong; - if (ourLongOld == ourLongNew) { - changedAnyBits = false; - break; - } - } while (!data.compareAndSet(i, ourLongOld, ourLongNew)); + putData(i, other.data.get(i)); + } + } - if (changedAnyBits) { - int bitsAdded = Long.bitCount(ourLongNew) - Long.bitCount(ourLongOld); - bitCount.add(bitsAdded); + /** + * ORs the bits encoded in the {@code i}th {@code long} in the underlying {@link + * AtomicLongArray} with the given value. + */ + void putData(int i, long longValue) { + long ourLongOld; + long ourLongNew; + boolean changedAnyBits = true; + do { + ourLongOld = data.get(i); + ourLongNew = ourLongOld | longValue; + if (ourLongOld == ourLongNew) { + changedAnyBits = false; + break; } + } while (!data.compareAndSet(i, ourLongOld, ourLongNew)); + + if (changedAnyBits) { + int bitsAdded = Long.bitCount(ourLongNew) - Long.bitCount(ourLongOld); + bitCount.add(bitsAdded); } } + /** Returns the number of {@code long}s in the underlying {@link AtomicLongArray}. */ + int dataLength() { + return data.length(); + } + @Override - public boolean equals(@CheckForNull Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof LockFreeBitArray) { LockFreeBitArray lockFreeBitArray = (LockFreeBitArray) o; // TODO(lowasser): avoid allocation here diff --git a/android/guava/src/com/google/common/hash/ChecksumHashFunction.java b/android/guava/src/com/google/common/hash/ChecksumHashFunction.java index 159adbb8194b..380c3a39ab6e 100644 --- a/android/guava/src/com/google/common/hash/ChecksumHashFunction.java +++ b/android/guava/src/com/google/common/hash/ChecksumHashFunction.java @@ -27,7 +27,6 @@ * @author Colin Decker */ @Immutable -@ElementTypesAreNonnullByDefault final class ChecksumHashFunction extends AbstractHashFunction implements Serializable { private final ImmutableSupplier checksumSupplier; private final int bits; diff --git a/android/guava/src/com/google/common/hash/Crc32cHashFunction.java b/android/guava/src/com/google/common/hash/Crc32cHashFunction.java index 8e17e6538c21..679c47852062 100644 --- a/android/guava/src/com/google/common/hash/Crc32cHashFunction.java +++ b/android/guava/src/com/google/common/hash/Crc32cHashFunction.java @@ -24,7 +24,6 @@ * @author Kurt Alfred Kluever */ @Immutable -@ElementTypesAreNonnullByDefault final class Crc32cHashFunction extends AbstractHashFunction { static final HashFunction CRC_32_C = new Crc32cHashFunction(); @@ -50,8 +49,8 @@ static final class Crc32cHasher extends AbstractStreamingHasher { * CRC(x ^ y) == CRC(x) ^ CRC(y). The approach we take is to break the message as follows, * with each letter representing a 4-byte word: ABCDABCDABCDABCD... and to calculate * CRC(A000A000A000...), CRC(0B000B000B...), CRC(00C000C000C...), CRC(000D000D000D...) - * and then to XOR them together. The STRIDE_TABLE enables us to hash an int followed by 12 - * zero bytes (3 ints), while the BYTE_TABLE is for advancing one byte at a time. + * and then to XOR them together. The strideTable enables us to hash an int followed by 12 + * zero bytes (3 ints), while the byteTable is for advancing one byte at a time. * This algorithm is due to the paper "Everything we know about CRC but [are] afraid to forget" * by Kadatch and Jenkins, 2010. */ @@ -107,7 +106,7 @@ protected void processRemaining(ByteBuffer bb) { crc0 = combine(crc0, crc2); crc0 = combine(crc0, crc3); while (bb.hasRemaining()) { - crc0 = (crc0 >>> 8) ^ BYTE_TABLE[(bb.get() ^ crc0) & 0xFF]; + crc0 = (crc0 >>> 8) ^ byteTable[(bb.get() ^ crc0) & 0xFF]; } finished = true; } @@ -122,7 +121,7 @@ protected HashCode makeHash() { return HashCode.fromInt(~crc0); } - static final int[] BYTE_TABLE = { + static final int[] byteTable = { 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c, @@ -168,7 +167,7 @@ protected HashCode makeHash() { 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351 }; - static final int[][] STRIDE_TABLE = { + static final int[][] strideTable = { { 0x00000000, 0x30d23865, 0x61a470ca, 0x517648af, 0xc348e194, 0xf39ad9f1, 0xa2ec915e, 0x923ea93b, 0x837db5d9, 0xb3af8dbc, 0xe2d9c513, 0xd20bfd76, @@ -355,16 +354,16 @@ protected HashCode makeHash() { static final int INVERSE_COMPUTE_FOR_WORD_OF_ALL_1S = 0xeee3ddcd; static int computeForWord(int word) { - return STRIDE_TABLE[3][word & 0xFF] - ^ STRIDE_TABLE[2][(word >>> 8) & 0xFF] - ^ STRIDE_TABLE[1][(word >>> 16) & 0xFF] - ^ STRIDE_TABLE[0][word >>> 24]; + return strideTable[3][word & 0xFF] + ^ strideTable[2][(word >>> 8) & 0xFF] + ^ strideTable[1][(word >>> 16) & 0xFF] + ^ strideTable[0][word >>> 24]; } static int combine(int csum, int crc) { csum ^= crc; for (int i = 0; i < 4; i++) { - csum = (csum >>> 8) ^ BYTE_TABLE[csum & 0xFF]; + csum = (csum >>> 8) ^ byteTable[csum & 0xFF]; } return csum; } diff --git a/android/guava/src/com/google/common/hash/FarmHashFingerprint64.java b/android/guava/src/com/google/common/hash/FarmHashFingerprint64.java index 7d6a3981d79c..b908772d128a 100644 --- a/android/guava/src/com/google/common/hash/FarmHashFingerprint64.java +++ b/android/guava/src/com/google/common/hash/FarmHashFingerprint64.java @@ -38,7 +38,6 @@ * @author Kyle Maddison * @author Geoff Pike */ -@ElementTypesAreNonnullByDefault final class FarmHashFingerprint64 extends AbstractNonStreamingHashFunction { static final HashFunction FARMHASH_FINGERPRINT_64 = new FarmHashFingerprint64(); @@ -86,9 +85,9 @@ private static long shiftMix(long val) { private static long hashLength16(long u, long v, long mul) { long a = (u ^ v) * mul; - a ^= (a >>> 47); + a ^= a >>> 47; long b = (v ^ a) * mul; - b ^= (b >>> 47); + b ^= b >>> 47; b *= mul; return b; } @@ -117,7 +116,7 @@ private static void weakHashLength32WithSeeds( private static long hashLength0to16(byte[] bytes, int offset, int length) { if (length >= 8) { - long mul = K2 + length * 2; + long mul = K2 + length * 2L; long a = load64(bytes, offset) + K2; long b = load64(bytes, offset + length - 8); long c = rotateRight(b, 37) * mul + a; @@ -141,7 +140,7 @@ private static long hashLength0to16(byte[] bytes, int offset, int length) { } private static long hashLength17to32(byte[] bytes, int offset, int length) { - long mul = K2 + length * 2; + long mul = K2 + length * 2L; long a = load64(bytes, offset) * K1; long b = load64(bytes, offset + 8); long c = load64(bytes, offset + length - 8) * mul; @@ -151,7 +150,7 @@ private static long hashLength17to32(byte[] bytes, int offset, int length) { } private static long hashLength33To64(byte[] bytes, int offset, int length) { - long mul = K2 + length * 2; + long mul = K2 + length * 2L; long a = load64(bytes, offset) * K2; long b = load64(bytes, offset + 8); long c = load64(bytes, offset + length - 8) * mul; @@ -170,7 +169,7 @@ private static long hashLength33To64(byte[] bytes, int offset, int length) { * Compute an 8-byte hash of a byte array of length greater than 64 bytes. */ private static long hashLength65Plus(byte[] bytes, int offset, int length) { - final int seed = 81; + int seed = 81; // For strings over 64 bytes we loop. Internal state consists of 56 bytes: v, w, x, y, and z. long x = seed; @SuppressWarnings("ConstantOverflow") @@ -199,7 +198,7 @@ private static long hashLength65Plus(byte[] bytes, int offset, int length) { long mul = K1 + ((z & 0xFF) << 1); // Operate on the last 64 bytes of input. offset = last64offset; - w[0] += ((length - 1) & 63); + w[0] += (length - 1) & 63; v[0] += w[0]; w[0] += v[0]; x = rotateRight(x + y + v[0] + load64(bytes, offset + 8), 37) * mul; diff --git a/android/guava/src/com/google/common/hash/Fingerprint2011.java b/android/guava/src/com/google/common/hash/Fingerprint2011.java new file mode 100644 index 000000000000..74fac1230e88 --- /dev/null +++ b/android/guava/src/com/google/common/hash/Fingerprint2011.java @@ -0,0 +1,197 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.hash.LittleEndianByteArray.load64; +import static com.google.common.hash.LittleEndianByteArray.load64Safely; +import static java.lang.Long.rotateRight; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Implementation of Geoff Pike's fingerprint2011 hash function. See {@link Hashing#fingerprint2011} + * for information on the behaviour of the algorithm. + * + *

    On Intel Core2 2.66, on 1000 bytes, fingerprint2011 takes 0.9 microseconds compared to + * fingerprint at 4.0 microseconds and md5 at 4.5 microseconds. + * + *

    Note to maintainers: This implementation relies on signed arithmetic being bit-wise equivalent + * to unsigned arithmetic in all cases except: + * + *

      + *
    • comparisons (signed values can be negative) + *
    • division (avoided here) + *
    • shifting (right shift must be unsigned) + *
    + * + * @author kylemaddison@google.com (Kyle Maddison) + * @author gpike@google.com (Geoff Pike) + */ +final class Fingerprint2011 extends AbstractNonStreamingHashFunction { + static final HashFunction FINGERPRINT_2011 = new Fingerprint2011(); + + // Some primes between 2^63 and 2^64 for various uses. + private static final long K0 = 0xa5b85c5e198ed849L; + private static final long K1 = 0x8d58ac26afe12e47L; + private static final long K2 = 0xc47b6e9e3a970ed3L; + private static final long K3 = 0xc6a4a7935bd1e995L; + + @Override + public HashCode hashBytes(byte[] input, int off, int len) { + checkPositionIndexes(off, off + len, input.length); + return HashCode.fromLong(fingerprint(input, off, len)); + } + + @Override + public int bits() { + return 64; + } + + @Override + public String toString() { + return "Hashing.fingerprint2011()"; + } + + // End of public functions. + + @VisibleForTesting + static long fingerprint(byte[] bytes, int offset, int length) { + long result; + + if (length <= 32) { + result = murmurHash64WithSeed(bytes, offset, length, K0 ^ K1 ^ K2); + } else if (length <= 64) { + result = hashLength33To64(bytes, offset, length); + } else { + result = fullFingerprint(bytes, offset, length); + } + + long u = length >= 8 ? load64(bytes, offset) : K0; + long v = length >= 9 ? load64(bytes, offset + length - 8) : K0; + result = hash128to64(result + v, u); + return result == 0 || result == 1 ? result + ~1 : result; + } + + private static long shiftMix(long val) { + return val ^ (val >>> 47); + } + + /** Implementation of Hash128to64 from util/hash/hash128to64.h */ + @VisibleForTesting + static long hash128to64(long high, long low) { + long a = (low ^ high) * K3; + a ^= a >>> 47; + long b = (high ^ a) * K3; + b ^= b >>> 47; + b *= K3; + return b; + } + + /** + * Computes intermediate hash of 32 bytes of byte array from the given offset. Results are + * returned in the output array - this is 12% faster than allocating new arrays every time. + */ + private static void weakHashLength32WithSeeds( + byte[] bytes, int offset, long seedA, long seedB, long[] output) { + long part1 = load64(bytes, offset); + long part2 = load64(bytes, offset + 8); + long part3 = load64(bytes, offset + 16); + long part4 = load64(bytes, offset + 24); + + seedA += part1; + seedB = rotateRight(seedB + seedA + part4, 51); + long c = seedA; + seedA += part2; + seedA += part3; + seedB += rotateRight(seedA, 23); + output[0] = seedA + part4; + output[1] = seedB + c; + } + + /* + * Compute an 8-byte hash of a byte array of length greater than 64 bytes. + */ + private static long fullFingerprint(byte[] bytes, int offset, int length) { + // For lengths over 64 bytes we hash the end first, and then as we + // loop we keep 56 bytes of state: v, w, x, y, and z. + long x = load64(bytes, offset); + long y = load64(bytes, offset + length - 16) ^ K1; + long z = load64(bytes, offset + length - 56) ^ K0; + long[] v = new long[2]; + long[] w = new long[2]; + weakHashLength32WithSeeds(bytes, offset + length - 64, length, y, v); + weakHashLength32WithSeeds(bytes, offset + length - 32, length * K1, K0, w); + z += shiftMix(v[1]) * K1; + x = rotateRight(z + x, 39) * K1; + y = rotateRight(y, 33) * K1; + + // Decrease length to the nearest multiple of 64, and operate on 64-byte chunks. + length = (length - 1) & ~63; + do { + x = rotateRight(x + y + v[0] + load64(bytes, offset + 16), 37) * K1; + y = rotateRight(y + v[1] + load64(bytes, offset + 48), 42) * K1; + x ^= w[1]; + y ^= v[0]; + z = rotateRight(z ^ w[0], 33); + weakHashLength32WithSeeds(bytes, offset, v[1] * K1, x + w[0], v); + weakHashLength32WithSeeds(bytes, offset + 32, z + w[1], y, w); + long tmp = z; + z = x; + x = tmp; + offset += 64; + length -= 64; + } while (length != 0); + return hash128to64(hash128to64(v[0], w[0]) + shiftMix(y) * K1 + z, hash128to64(v[1], w[1]) + x); + } + + private static long hashLength33To64(byte[] bytes, int offset, int length) { + long z = load64(bytes, offset + 24); + long a = load64(bytes, offset) + (length + load64(bytes, offset + length - 16)) * K0; + long b = rotateRight(a + z, 52); + long c = rotateRight(a, 37); + a += load64(bytes, offset + 8); + c += rotateRight(a, 7); + a += load64(bytes, offset + 16); + long vf = a + z; + long vs = b + rotateRight(a, 31) + c; + a = load64(bytes, offset + 16) + load64(bytes, offset + length - 32); + z = load64(bytes, offset + length - 8); + b = rotateRight(a + z, 52); + c = rotateRight(a, 37); + a += load64(bytes, offset + length - 24); + c += rotateRight(a, 7); + a += load64(bytes, offset + length - 16); + long wf = a + z; + long ws = b + rotateRight(a, 31) + c; + long r = shiftMix((vf + ws) * K2 + (wf + vs) * K0); + return shiftMix(r * K0 + vs) * K2; + } + + @VisibleForTesting + static long murmurHash64WithSeed(byte[] bytes, int offset, int length, long seed) { + long mul = K3; + int topBit = 0x7; + + int lengthAligned = length & ~topBit; + int lengthRemainder = length & topBit; + long hash = seed ^ (length * mul); + + for (int i = 0; i < lengthAligned; i += 8) { + long loaded = load64(bytes, offset + i); + long data = shiftMix(loaded * mul) * mul; + hash ^= data; + hash *= mul; + } + + if (lengthRemainder != 0) { + long data = load64Safely(bytes, offset + lengthAligned, lengthRemainder); + hash ^= data; + hash *= mul; + } + + hash = shiftMix(hash) * mul; + hash = shiftMix(hash); + return hash; + } +} diff --git a/android/guava/src/com/google/common/hash/Funnel.java b/android/guava/src/com/google/common/hash/Funnel.java index 9d80dabcf6ed..29a5222739a2 100644 --- a/android/guava/src/com/google/common/hash/Funnel.java +++ b/android/guava/src/com/google/common/hash/Funnel.java @@ -17,7 +17,7 @@ import com.google.common.annotations.Beta; import com.google.errorprone.annotations.DoNotMock; import java.io.Serializable; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * An object which can send data from an object of type {@code T} into a {@code PrimitiveSink}. @@ -28,7 +28,7 @@ * single-element enum to maintain serialization guarantees. See Effective Java (2nd Edition), Item * 3: "Enforce the singleton property with a private constructor or an enum type". For example: * - *
    {@code
    + * {@snippet :
      * public enum PersonFunnel implements Funnel {
      *   INSTANCE;
      *   public void funnel(Person person, PrimitiveSink into) {
    @@ -37,14 +37,13 @@
      *         .putInt(person.getAge());
      *   }
      * }
    - * }
    + * } * * @author Dimitris Andreou * @since 11.0 */ @Beta @DoNotMock("Implement with a lambda") -@ElementTypesAreNonnullByDefault public interface Funnel extends Serializable { /** diff --git a/android/guava/src/com/google/common/hash/Funnels.java b/android/guava/src/com/google/common/hash/Funnels.java index 66738361cdfd..387cd87f54d2 100644 --- a/android/guava/src/com/google/common/hash/Funnels.java +++ b/android/guava/src/com/google/common/hash/Funnels.java @@ -16,11 +16,12 @@ import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.OutputStream; import java.io.Serializable; import java.nio.charset.Charset; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Funnels for common types. All implementations are serializable. @@ -29,7 +30,6 @@ * @since 11.0 */ @Beta -@ElementTypesAreNonnullByDefault public final class Funnels { private Funnels() {} @@ -87,7 +87,7 @@ public static Funnel stringFunnel(Charset charset) { return new StringCharsetFunnel(charset); } - private static class StringCharsetFunnel implements Funnel, Serializable { + private static final class StringCharsetFunnel implements Funnel { private final Charset charset; StringCharsetFunnel(Charset charset) { @@ -105,7 +105,7 @@ public String toString() { } @Override - public boolean equals(@CheckForNull Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof StringCharsetFunnel) { StringCharsetFunnel funnel = (StringCharsetFunnel) o; return this.charset.equals(funnel.charset); @@ -122,7 +122,11 @@ Object writeReplace() { return new SerializedForm(charset); } - private static class SerializedForm implements Serializable { + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + private static final class SerializedForm implements Serializable { private final String charsetCanonicalName; SerializedForm(Charset charset) { @@ -168,11 +172,11 @@ public String toString() { */ public static Funnel> sequentialFunnel( Funnel elementFunnel) { - return new SequentialFunnel(elementFunnel); + return new SequentialFunnel<>(elementFunnel); } - private static class SequentialFunnel - implements Funnel>, Serializable { + private static final class SequentialFunnel + implements Funnel> { private final Funnel elementFunnel; SequentialFunnel(Funnel elementFunnel) { @@ -192,7 +196,7 @@ public String toString() { } @Override - public boolean equals(@CheckForNull Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof SequentialFunnel) { SequentialFunnel funnel = (SequentialFunnel) o; return elementFunnel.equals(funnel.elementFunnel); @@ -243,7 +247,7 @@ public static OutputStream asOutputStream(PrimitiveSink sink) { return new SinkAsStream(sink); } - private static class SinkAsStream extends OutputStream { + private static final class SinkAsStream extends OutputStream { final PrimitiveSink sink; SinkAsStream(PrimitiveSink sink) { diff --git a/android/guava/src/com/google/common/hash/HashCode.java b/android/guava/src/com/google/common/hash/HashCode.java index fde2a86d6466..c80832508b12 100644 --- a/android/guava/src/com/google/common/hash/HashCode.java +++ b/android/guava/src/com/google/common/hash/HashCode.java @@ -17,13 +17,13 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.lang.Math.min; import com.google.common.base.Preconditions; -import com.google.common.primitives.Ints; import com.google.common.primitives.UnsignedInts; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Serializable; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * An immutable hash code of arbitrary bit length. @@ -32,7 +32,6 @@ * @author Kurt Alfred Kluever * @since 11.0 */ -@ElementTypesAreNonnullByDefault public abstract class HashCode { HashCode() {} @@ -83,7 +82,7 @@ public abstract class HashCode { */ @CanIgnoreReturnValue public int writeBytesTo(byte[] dest, int offset, int maxLength) { - maxLength = Ints.min(maxLength, bits() / 8); + maxLength = min(maxLength, bits() / 8); Preconditions.checkPositionIndexes(offset, offset + maxLength, dest.length); writeBytesToImpl(dest, offset, maxLength); return maxLength; @@ -288,8 +287,8 @@ public long asLong() { @Override public long padToLong() { - long retVal = (bytes[0] & 0xFF); - for (int i = 1; i < Math.min(bytes.length, 8); i++) { + long retVal = bytes[0] & 0xFF; + for (int i = 1; i < min(bytes.length, 8); i++) { retVal |= (bytes[i] & 0xFFL) << (i * 8); } return retVal; @@ -315,7 +314,7 @@ boolean equalsSameBits(HashCode that) { boolean areEqual = true; for (int i = 0; i < this.bytes.length; i++) { - areEqual &= (this.bytes[i] == that.getBytesInternal()[i]); + areEqual &= this.bytes[i] == that.getBytesInternal()[i]; } return areEqual; } @@ -368,7 +367,7 @@ private static int decode(char ch) { * to protect against timing attacks. */ @Override - public final boolean equals(@CheckForNull Object object) { + public final boolean equals(@Nullable Object object) { if (object instanceof HashCode) { HashCode that = (HashCode) object; return bits() == that.bits() && equalsSameBits(that); @@ -390,9 +389,9 @@ public final int hashCode() { } // If we have less than 4 bytes, use them all. byte[] bytes = getBytesInternal(); - int val = (bytes[0] & 0xFF); + int val = bytes[0] & 0xFF; for (int i = 1; i < bytes.length; i++) { - val |= ((bytes[i] & 0xFF) << (i * 8)); + val |= (bytes[i] & 0xFF) << (i * 8); } return val; } diff --git a/android/guava/src/com/google/common/hash/HashFunction.java b/android/guava/src/com/google/common/hash/HashFunction.java index 2712dfce1e59..34d64b395d8e 100644 --- a/android/guava/src/com/google/common/hash/HashFunction.java +++ b/android/guava/src/com/google/common/hash/HashFunction.java @@ -14,12 +14,11 @@ package com.google.common.hash; -import com.google.common.annotations.Beta; import com.google.common.primitives.Ints; import com.google.errorprone.annotations.Immutable; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A hash function is a collision-averse pure function that maps an arbitrary block of data to a @@ -116,21 +115,19 @@ * @author Kevin Bourrillion * @since 11.0 */ -@Beta @Immutable -@ElementTypesAreNonnullByDefault public interface HashFunction { /** * Begins a new hash code computation by returning an initialized, stateful {@code Hasher} * instance that is ready to receive data. Example: * - *
    {@code
    +   * {@snippet :
        * HashFunction hf = Hashing.md5();
        * HashCode hc = hf.newHasher()
        *     .putLong(id)
        *     .putBoolean(isActive)
        *     .hash();
    -   * }
    + * } */ Hasher newHasher(); diff --git a/android/guava/src/com/google/common/hash/Hasher.java b/android/guava/src/com/google/common/hash/Hasher.java index b3f24fa282b9..f76080255f34 100644 --- a/android/guava/src/com/google/common/hash/Hasher.java +++ b/android/guava/src/com/google/common/hash/Hasher.java @@ -18,7 +18,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * A {@link PrimitiveSink} that can compute a hash code after reading the input. Each hasher should @@ -38,11 +38,11 @@ * were inserted, not how those bytes were chunked into discrete put() operations. For example, the * following three expressions all generate colliding hash codes: * - *
    {@code
    + * {@snippet :
      * newHasher().putByte(b1).putByte(b2).putByte(b3).hash()
      * newHasher().putByte(b1).putBytes(new byte[] { b2, b3 }).hash()
      * newHasher().putBytes(new byte[] { b1, b2, b3 }).hash()
    - * }
    + * } * *

    If you wish to avoid this, you should either prepend or append the size of each chunk. Keep in * mind that when dealing with char sequences, the encoded form of two concatenated char sequences @@ -54,42 +54,51 @@ * @since 11.0 */ @Beta -@CanIgnoreReturnValue -@ElementTypesAreNonnullByDefault public interface Hasher extends PrimitiveSink { + @CanIgnoreReturnValue @Override Hasher putByte(byte b); + @CanIgnoreReturnValue @Override Hasher putBytes(byte[] bytes); + @CanIgnoreReturnValue @Override Hasher putBytes(byte[] bytes, int off, int len); + @CanIgnoreReturnValue @Override Hasher putBytes(ByteBuffer bytes); + @CanIgnoreReturnValue @Override Hasher putShort(short s); + @CanIgnoreReturnValue @Override Hasher putInt(int i); + @CanIgnoreReturnValue @Override Hasher putLong(long l); /** Equivalent to {@code putInt(Float.floatToRawIntBits(f))}. */ + @CanIgnoreReturnValue @Override Hasher putFloat(float f); /** Equivalent to {@code putLong(Double.doubleToRawLongBits(d))}. */ + @CanIgnoreReturnValue @Override Hasher putDouble(double d); /** Equivalent to {@code putByte(b ? (byte) 1 : (byte) 0)}. */ + @CanIgnoreReturnValue @Override Hasher putBoolean(boolean b); + @CanIgnoreReturnValue @Override Hasher putChar(char c); @@ -106,6 +115,7 @@ public interface Hasher extends PrimitiveSink { * * @since 15.0 (since 11.0 as putString(CharSequence)). */ + @CanIgnoreReturnValue @Override Hasher putUnencodedChars(CharSequence charSequence); @@ -117,10 +127,12 @@ public interface Hasher extends PrimitiveSink { * faster, produces the same output across Java releases, and hashes every {@code char} in the * input, even if some are invalid. */ + @CanIgnoreReturnValue @Override Hasher putString(CharSequence charSequence, Charset charset); /** A simple convenience for {@code funnel.funnel(object, this)}. */ + @CanIgnoreReturnValue Hasher putObject( @ParametricNullness T instance, Funnel funnel); diff --git a/android/guava/src/com/google/common/hash/Hashing.java b/android/guava/src/com/google/common/hash/Hashing.java index f53c9d3a7b0f..ca2e693a1bb8 100644 --- a/android/guava/src/com/google/common/hash/Hashing.java +++ b/android/guava/src/com/google/common/hash/Hashing.java @@ -17,33 +17,31 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.errorprone.annotations.Immutable; import java.security.Key; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.zip.Adler32; import java.util.zip.CRC32; import java.util.zip.Checksum; -import javax.annotation.CheckForNull; import javax.crypto.spec.SecretKeySpec; +import org.jspecify.annotations.Nullable; /** * Static methods to obtain {@link HashFunction} instances, and other static hashing-related * utilities. * *

    A comparison of the various hash functions can be found here. + * href="https://docs.google.com/spreadsheets/d/1_q2EVcxA2HjcrlVMbaqXwMj31h9M5-Bqj_m8vITOwwk/">here. * * @author Kevin Bourrillion * @author Dimitris Andreou * @author Kurt Alfred Kluever * @since 11.0 */ -@Beta -@ElementTypesAreNonnullByDefault public final class Hashing { /** * Returns a general-purpose, temporary-use, non-cryptographic hash function. The algorithm @@ -58,7 +56,8 @@ public final class Hashing { *

    Repeated calls to this method on the same loaded {@code Hashing} class, using the same value * for {@code minimumBits}, will return identically-behaving {@link HashFunction} instances. * - * @param minimumBits a positive integer (can be arbitrarily large) + * @param minimumBits a positive integer. This can be arbitrarily large. The returned {@link + * HashFunction} instance may use memory proportional to this integer. * @return a hash function, described above, that produces hash codes of length {@code * minimumBits} or greater */ @@ -91,15 +90,59 @@ public static HashFunction goodFastHash(int minimumBits) { @SuppressWarnings("GoodTime") // reading system time without TimeSource static final int GOOD_FAST_HASH_SEED = (int) System.currentTimeMillis(); + /** + * Returns a hash function implementing the 32-bit murmur3 + * algorithm, x86 variant (little-endian variant), using the given seed value, with a known + * bug as described in the deprecation text. + * + *

    The C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A), which however does not + * have the bug. + * + * @deprecated This implementation produces incorrect hash values from the {@link + * HashFunction#hashString} method if the string contains non-BMP characters. Use {@link + * #murmur3_32_fixed(int)} instead. + */ + @Deprecated + @SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks + public static HashFunction murmur3_32(int seed) { + return new Murmur3_32HashFunction(seed, /* supplementaryPlaneFix= */ false); + } + + /** + * Returns a hash function implementing the 32-bit murmur3 + * algorithm, x86 variant (little-endian variant), using the given seed value, with a known + * bug as described in the deprecation text. + * + *

    The C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A), which however does not + * have the bug. + * + * @deprecated This implementation produces incorrect hash values from the {@link + * HashFunction#hashString} method if the string contains non-BMP characters. Use {@link + * #murmur3_32_fixed()} instead. + */ + @Deprecated + @SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks + public static HashFunction murmur3_32() { + return Murmur3_32HashFunction.MURMUR3_32; + } + /** * Returns a hash function implementing the 32-bit murmur3 * algorithm, x86 variant (little-endian variant), using the given seed value. * *

    The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A). + * + *

    This method is called {@code murmur3_32_fixed} because it fixes a bug in the {@code + * HashFunction} returned by the original {@code murmur3_32} method. + * + * @since 31.0 */ - public static HashFunction murmur3_32(int seed) { - return new Murmur3_32HashFunction(seed); + @SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks + public static HashFunction murmur3_32_fixed(int seed) { + return new Murmur3_32HashFunction(seed, /* supplementaryPlaneFix= */ true); } /** @@ -108,9 +151,15 @@ public static HashFunction murmur3_32(int seed) { * algorithm, x86 variant (little-endian variant), using a seed value of zero. * *

    The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A). + * + *

    This method is called {@code murmur3_32_fixed} because it fixes a bug in the {@code + * HashFunction} returned by the original {@code murmur3_32} method. + * + * @since 31.0 */ - public static HashFunction murmur3_32() { - return Murmur3_32HashFunction.MURMUR3_32; + @SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks + public static HashFunction murmur3_32_fixed() { + return Murmur3_32HashFunction.MURMUR3_32_FIXED; } /** @@ -120,6 +169,7 @@ public static HashFunction murmur3_32() { * *

    The exact C++ equivalent is the MurmurHash3_x64_128 function (Murmur3F). */ + @SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks public static HashFunction murmur3_128(int seed) { return new Murmur3_128HashFunction(seed); } @@ -131,6 +181,7 @@ public static HashFunction murmur3_128(int seed) { * *

    The exact C++ equivalent is the MurmurHash3_x64_128 function (Murmur3F). */ + @SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks public static HashFunction murmur3_128() { return Murmur3_128HashFunction.MURMUR3_128; } @@ -172,7 +223,7 @@ public static HashFunction md5() { return Md5Holder.MD5; } - private static class Md5Holder { + private static final class Md5Holder { static final HashFunction MD5 = new MessageDigestHashFunction("MD5", "Hashing.md5()"); } @@ -193,7 +244,7 @@ public static HashFunction sha1() { return Sha1Holder.SHA_1; } - private static class Sha1Holder { + private static final class Sha1Holder { static final HashFunction SHA_1 = new MessageDigestHashFunction("SHA-1", "Hashing.sha1()"); } @@ -202,7 +253,7 @@ public static HashFunction sha256() { return Sha256Holder.SHA_256; } - private static class Sha256Holder { + private static final class Sha256Holder { static final HashFunction SHA_256 = new MessageDigestHashFunction("SHA-256", "Hashing.sha256()"); } @@ -216,7 +267,7 @@ public static HashFunction sha384() { return Sha384Holder.SHA_384; } - private static class Sha384Holder { + private static final class Sha384Holder { static final HashFunction SHA_384 = new MessageDigestHashFunction("SHA-384", "Hashing.sha384()"); } @@ -226,7 +277,7 @@ public static HashFunction sha512() { return Sha512Holder.SHA_512; } - private static class Sha512Holder { + private static final class Sha512Holder { static final HashFunction SHA_512 = new MessageDigestHashFunction("SHA-512", "Hashing.sha512()"); } @@ -235,6 +286,10 @@ private static class Sha512Holder { * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the * MD5 (128 hash bits) hash function and the given secret key. * + *

    If you are designing a new system that needs HMAC, prefer {@link #hmacSha256} or other + * future-proof algorithms over {@code hmacMd5}. + * * @param key the secret key * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC * @since 20.0 @@ -248,6 +303,10 @@ public static HashFunction hmacMd5(Key key) { * MD5 (128 hash bits) hash function and a {@link SecretKeySpec} created from the given byte array * and the MD5 algorithm. * + *

    If you are designing a new system that needs HMAC, prefer {@link #hmacSha256} or other + * future-proof algorithms over {@code hmacMd5}. + * * @param key the key material of the secret key * @since 20.0 */ @@ -328,9 +387,13 @@ public static HashFunction hmacSha512(byte[] key) { } private static String hmacToString(String methodName, Key key) { - return String.format( - "Hashing.%s(Key[algorithm=%s, format=%s])", - methodName, key.getAlgorithm(), key.getFormat()); + return "Hashing." + + methodName + + "(Key[algorithm=" + + key.getAlgorithm() + + ", format=" + + key.getFormat() + + "])"; } /** @@ -423,6 +486,30 @@ public static HashFunction farmHashFingerprint64() { return FarmHashFingerprint64.FARMHASH_FINGERPRINT_64; } + /** + * Returns a hash function implementing the Fingerprint2011 hashing function (64 hash bits). + * + *

    This is designed for generating persistent fingerprints of strings. It isn't + * cryptographically secure, but it produces a high-quality hash with few collisions. Fingerprints + * generated using this are byte-wise identical to those created using the C++ version, but note + * that this uses unsigned integers (see {@link com.google.common.primitives.UnsignedInts}). + * Comparisons between the two should take this into account. + * + *

    Fingerprint2011() is a form of Murmur2 on strings up to 32 bytes and a form of CityHash for + * longer strings. It could have been one or the other throughout. The main advantage of the + * combination is that CityHash has a bunch of special cases for short strings that don't need to + * be replicated here. The result will never be 0 or 1. + * + *

    This function is best understood as a fingerprint rather than a true + * hash function. + * + * @since 31.1 + */ + public static HashFunction fingerprint2011() { + return Fingerprint2011.FINGERPRINT_2011; + } + /** * Assigns to {@code hashCode} a "bucket" in the range {@code [0, buckets)}, in a uniform manner * that minimizes the need for remapping as {@code buckets} grows. That is, {@code @@ -575,7 +662,7 @@ public static HashFunction concatenating( List list = new ArrayList<>(); list.add(first); list.add(second); - list.addAll(Arrays.asList(rest)); + Collections.addAll(list, rest); return new ConcatenatedHashFunction(list.toArray(new HashFunction[0])); } @@ -596,7 +683,7 @@ public static HashFunction concatenating(Iterable hashFunctions) { for (HashFunction hashFunction : hashFunctions) { list.add(hashFunction); } - checkArgument(list.size() > 0, "number of hash functions (%s) must be > 0", list.size()); + checkArgument(!list.isEmpty(), "number of hash functions (%s) must be > 0", list.size()); return new ConcatenatedHashFunction(list.toArray(new HashFunction[0])); } @@ -634,7 +721,7 @@ public int bits() { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof ConcatenatedHashFunction) { ConcatenatedHashFunction other = (ConcatenatedHashFunction) object; return Arrays.equals(functions, other.functions); @@ -655,11 +742,11 @@ public int hashCode() { private static final class LinearCongruentialGenerator { private long state; - public LinearCongruentialGenerator(long seed) { + LinearCongruentialGenerator(long seed) { this.state = seed; } - public double nextDouble() { + double nextDouble() { state = 2862933555777941757L * state + 1; return ((double) ((int) (state >>> 33) + 1)) / 0x1.0p31; } diff --git a/android/guava/src/com/google/common/hash/HashingInputStream.java b/android/guava/src/com/google/common/hash/HashingInputStream.java index bf9464ce5573..f49dfd62daa5 100644 --- a/android/guava/src/com/google/common/hash/HashingInputStream.java +++ b/android/guava/src/com/google/common/hash/HashingInputStream.java @@ -29,7 +29,6 @@ * @since 16.0 */ @Beta -@ElementTypesAreNonnullByDefault public final class HashingInputStream extends FilterInputStream { private final Hasher hasher; diff --git a/android/guava/src/com/google/common/hash/HashingOutputStream.java b/android/guava/src/com/google/common/hash/HashingOutputStream.java index f138bba15050..20f1316a558c 100644 --- a/android/guava/src/com/google/common/hash/HashingOutputStream.java +++ b/android/guava/src/com/google/common/hash/HashingOutputStream.java @@ -24,11 +24,10 @@ /** * An {@link OutputStream} that maintains a hash of the data written to it. * - * @author Nick Piepmeier + * @author Zoe Piepmeier * @since 16.0 */ @Beta -@ElementTypesAreNonnullByDefault public final class HashingOutputStream extends FilterOutputStream { private final Hasher hasher; diff --git a/android/guava/src/com/google/common/hash/IgnoreJRERequirement.java b/android/guava/src/com/google/common/hash/IgnoreJRERequirement.java new file mode 100644 index 000000000000..0693b2d6f7e2 --- /dev/null +++ b/android/guava/src/com/google/common/hash/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/hash/ImmutableSupplier.java b/android/guava/src/com/google/common/hash/ImmutableSupplier.java index 24f711a313fa..b6a74a77b719 100644 --- a/android/guava/src/com/google/common/hash/ImmutableSupplier.java +++ b/android/guava/src/com/google/common/hash/ImmutableSupplier.java @@ -23,5 +23,4 @@ */ // TODO(cpovirk): Should we just use ChecksumType directly instead of defining this type? @Immutable -@ElementTypesAreNonnullByDefault interface ImmutableSupplier extends Supplier {} diff --git a/android/guava/src/com/google/common/hash/Java8Compatibility.java b/android/guava/src/com/google/common/hash/Java8Compatibility.java index c15f2b3cc575..52f71e788558 100644 --- a/android/guava/src/com/google/common/hash/Java8Compatibility.java +++ b/android/guava/src/com/google/common/hash/Java8Compatibility.java @@ -22,7 +22,6 @@ * https://github.com/google/guava/issues/3990 */ @GwtIncompatible -@ElementTypesAreNonnullByDefault final class Java8Compatibility { static void clear(Buffer b) { b.clear(); diff --git a/android/guava/src/com/google/common/hash/LittleEndianByteArray.java b/android/guava/src/com/google/common/hash/LittleEndianByteArray.java index 15d8b2cfb4c8..e0e83f22eae2 100644 --- a/android/guava/src/com/google/common/hash/LittleEndianByteArray.java +++ b/android/guava/src/com/google/common/hash/LittleEndianByteArray.java @@ -14,8 +14,16 @@ package com.google.common.hash; +import static java.lang.Math.min; + +import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Longs; +import java.lang.reflect.Field; import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Objects; import sun.misc.Unsafe; /** @@ -24,11 +32,13 @@ * @author Kevin Damm * @author Kyle Maddison */ -@ElementTypesAreNonnullByDefault final class LittleEndianByteArray { - /** The instance that actually does the work; delegates to Unsafe or a pure-Java fallback. */ - private static final LittleEndianBytes byteArray; + /** + * The instance that actually does the work; delegates to VarHandle, Unsafe, or a Java-8 + * compatible pure-Java fallback. + */ + private static final LittleEndianBytes byteArray = makeGetter(); /** * Load 8 bytes into long in a little endian manner, from the substring between position and @@ -61,7 +71,7 @@ static long load64Safely(byte[] input, int offset, int length) { // of the result already being filled with zeros. // This loop is critical to performance, so please check HashBenchmark if altering it. - int limit = Math.min(length, 8); + int limit = min(length, 8); for (int i = 0; i < limit; i++) { // Shift value left while iterating logically through the array. result |= (input[offset + i] & 0xFFL) << (i * 8); @@ -99,12 +109,12 @@ static int load32(byte[] source, int offset) { } /** - * Indicates that the loading of Unsafe was successful and the load and store operations will be - * very efficient. May be useful for calling code to fall back on an alternative implementation - * that is slower than Unsafe.get/store but faster than the pure-Java mask-and-shift. + * Indicates that the load and store operations will be very efficient because of use of VarHandle + * or Unsafe. May be useful for calling code to fall back on an alternative implementation that is + * slower than those implementations but faster than the pure-Java mask-and-shift. */ - static boolean usingUnsafe() { - return (byteArray instanceof UnsafeByteArray); + static boolean usingFastPath() { + return byteArray.usesFastPath(); } /** @@ -117,6 +127,8 @@ private interface LittleEndianBytes { long getLongLittleEndian(byte[] array, int offset); void putLongLittleEndian(byte[] array, int offset, long value); + + boolean usesFastPath(); } /** @@ -124,7 +136,9 @@ private interface LittleEndianBytes { * Unsafe.theUnsafe is inaccessible, the attempt to load the nested class fails, and the outer * class's static initializer can fall back on a non-Unsafe version. */ - private enum UnsafeByteArray implements LittleEndianBytes { + @SuppressWarnings("SunApi") // b/345822163 + @VisibleForTesting + enum UnsafeByteArray implements LittleEndianBytes { // Do *not* change the order of these constants! UNSAFE_LITTLE_ENDIAN { @Override @@ -153,6 +167,11 @@ public void putLongLittleEndian(byte[] array, int offset, long value) { } }; + @Override + public boolean usesFastPath() { + return true; + } + // Provides load and store operations that use native instructions to get better performance. private static final Unsafe theUnsafe; @@ -160,34 +179,32 @@ public void putLongLittleEndian(byte[] array, int offset, long value) { private static final int BYTE_ARRAY_BASE_OFFSET; /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. Replace with a simple - * call to Unsafe.getUnsafe when integrating into a jdk. + * Returns an Unsafe. Suitable for use in a 3rd party package. Replace with a simple call to + * Unsafe.getUnsafe when integrating into a JDK. * - * @return a sun.misc.Unsafe instance if successful + * @return an Unsafe instance if successful */ - private static sun.misc.Unsafe getUnsafe() { + private static Unsafe getUnsafe() { try { - return sun.misc.Unsafe.getUnsafe(); + return Unsafe.getUnsafe(); } catch (SecurityException tryReflectionInstead) { // We'll try reflection instead. } try { - return java.security.AccessController.doPrivileged( - new java.security.PrivilegedExceptionAction() { - @Override - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) { - return k.cast(x); + return AccessController.doPrivileged( + (PrivilegedExceptionAction) + () -> { + Class k = Unsafe.class; + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } } - } - throw new NoSuchFieldError("the Unsafe"); - } - }); - } catch (java.security.PrivilegedActionException e) { + throw new NoSuchFieldError("the Unsafe"); + }); + } catch (PrivilegedActionException e) { throw new RuntimeException("Could not initialize intrinsics", e.getCause()); } } @@ -203,7 +220,10 @@ public sun.misc.Unsafe run() throws Exception { } } - /** Fallback implementation for when Unsafe is not available in our current environment. */ + /** + * Fallback implementation for when VarHandle and Unsafe are not available in our current + * environment. + */ private enum JavaLittleEndianBytes implements LittleEndianBytes { INSTANCE { @Override @@ -226,11 +246,15 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) { sink[offset + i] = (byte) ((value & mask) >> (i * 8)); } } - }; + + @Override + public boolean usesFastPath() { + return false; + } + } } - static { - LittleEndianBytes theGetter = JavaLittleEndianBytes.INSTANCE; + static LittleEndianBytes makeGetter() { try { /* * UnsafeByteArray uses Unsafe.getLong() in an unsupported way, which is known to cause @@ -243,17 +267,17 @@ public void putLongLittleEndian(byte[] sink, int offset, long value) { * which will have an efficient native implementation in JDK 9. * */ - final String arch = System.getProperty("os.arch"); - if ("amd64".equals(arch)) { - theGetter = - ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) - ? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN - : UnsafeByteArray.UNSAFE_BIG_ENDIAN; + String arch = System.getProperty("os.arch"); + if (Objects.equals(arch, "amd64")) { + return ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN) + ? UnsafeByteArray.UNSAFE_LITTLE_ENDIAN + : UnsafeByteArray.UNSAFE_BIG_ENDIAN; } } catch (Throwable t) { // ensure we really catch *everything* } - byteArray = theGetter; + + return JavaLittleEndianBytes.INSTANCE; } /** Deter instantiation of this class. */ diff --git a/android/guava/src/com/google/common/hash/LongAddable.java b/android/guava/src/com/google/common/hash/LongAddable.java index 5c6a7f0c7db8..a95eece2e1ff 100644 --- a/android/guava/src/com/google/common/hash/LongAddable.java +++ b/android/guava/src/com/google/common/hash/LongAddable.java @@ -14,13 +14,11 @@ package com.google.common.hash; - /** * Abstract interface for objects that can concurrently add longs. * * @author Louis Wasserman */ -@ElementTypesAreNonnullByDefault interface LongAddable { void increment(); diff --git a/android/guava/src/com/google/common/hash/LongAddables.java b/android/guava/src/com/google/common/hash/LongAddables.java index 370030dab5d8..5ae9ba0b138b 100644 --- a/android/guava/src/com/google/common/hash/LongAddables.java +++ b/android/guava/src/com/google/common/hash/LongAddables.java @@ -22,14 +22,14 @@ * * @author Louis Wasserman */ -@ElementTypesAreNonnullByDefault final class LongAddables { private static final Supplier SUPPLIER; static { Supplier supplier; try { - new LongAdder(); // trigger static initialization of the LongAdder class, which may fail + // trigger static initialization of the LongAdder class, which may fail + LongAdder unused = new LongAdder(); supplier = new Supplier() { @Override diff --git a/android/guava/src/com/google/common/hash/LongAdder.java b/android/guava/src/com/google/common/hash/LongAdder.java index dc864aa9a417..78697205cdd8 100644 --- a/android/guava/src/com/google/common/hash/LongAdder.java +++ b/android/guava/src/com/google/common/hash/LongAdder.java @@ -11,6 +11,8 @@ package com.google.common.hash; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -38,9 +40,8 @@ * @since 1.8 * @author Doug Lea */ -@ElementTypesAreNonnullByDefault final class LongAdder extends Striped64 implements Serializable, LongAddable { - private static final long serialVersionUID = 7249069246863182397L; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 7249069246863182397L; /** Version of plus for use in retryUpdate */ @Override diff --git a/android/guava/src/com/google/common/hash/MacHashFunction.java b/android/guava/src/com/google/common/hash/MacHashFunction.java index 031b1c017b60..390b49c30234 100644 --- a/android/guava/src/com/google/common/hash/MacHashFunction.java +++ b/android/guava/src/com/google/common/hash/MacHashFunction.java @@ -30,7 +30,6 @@ * @author Kurt Alfred Kluever */ @Immutable -@ElementTypesAreNonnullByDefault final class MacHashFunction extends AbstractHashFunction { @SuppressWarnings("Immutable") // cloned before each use @@ -58,7 +57,7 @@ public int bits() { private static boolean supportsClone(Mac mac) { try { - mac.clone(); + Object unused = mac.clone(); return true; } catch (CloneNotSupportedException e) { return false; diff --git a/android/guava/src/com/google/common/hash/MessageDigestHashFunction.java b/android/guava/src/com/google/common/hash/MessageDigestHashFunction.java index 48b47b0f0bc1..9a435b9edc13 100644 --- a/android/guava/src/com/google/common/hash/MessageDigestHashFunction.java +++ b/android/guava/src/com/google/common/hash/MessageDigestHashFunction.java @@ -19,6 +19,8 @@ import static com.google.common.base.Preconditions.checkState; import com.google.errorprone.annotations.Immutable; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.nio.ByteBuffer; import java.security.MessageDigest; @@ -32,7 +34,6 @@ * @author Dimitris Andreou */ @Immutable -@ElementTypesAreNonnullByDefault final class MessageDigestHashFunction extends AbstractHashFunction implements Serializable { @SuppressWarnings("Immutable") // cloned before each use @@ -61,7 +62,7 @@ final class MessageDigestHashFunction extends AbstractHashFunction implements Se private static boolean supportsClone(MessageDigest digest) { try { - digest.clone(); + Object unused = digest.clone(); return true; } catch (CloneNotSupportedException e) { return false; @@ -120,6 +121,10 @@ Object writeReplace() { return new SerializedForm(prototype.getAlgorithm(), bytes, toString); } + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + /** Hasher that updates a message digest. */ private static final class MessageDigestHasher extends AbstractByteHasher { private final MessageDigest digest; diff --git a/android/guava/src/com/google/common/hash/Murmur3_128HashFunction.java b/android/guava/src/com/google/common/hash/Murmur3_128HashFunction.java index d1304f8273c5..4aeec9ce9f72 100644 --- a/android/guava/src/com/google/common/hash/Murmur3_128HashFunction.java +++ b/android/guava/src/com/google/common/hash/Murmur3_128HashFunction.java @@ -25,13 +25,13 @@ package com.google.common.hash; -import static com.google.common.primitives.UnsignedBytes.toInt; +import static java.lang.Byte.toUnsignedInt; import com.google.errorprone.annotations.Immutable; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * See MurmurHash3_x64_128 in the @@ -41,7 +41,7 @@ * @author Dimitris Andreou */ @Immutable -@ElementTypesAreNonnullByDefault +@SuppressWarnings("IdentifierName") // the best we could do for adjacent digit blocks final class Murmur3_128HashFunction extends AbstractHashFunction implements Serializable { static final HashFunction MURMUR3_128 = new Murmur3_128HashFunction(0); @@ -71,7 +71,7 @@ public String toString() { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof Murmur3_128HashFunction) { Murmur3_128HashFunction other = (Murmur3_128HashFunction) object; return seed == other.seed; @@ -128,36 +128,36 @@ protected void processRemaining(ByteBuffer bb) { length += bb.remaining(); switch (bb.remaining()) { case 15: - k2 ^= (long) toInt(bb.get(14)) << 48; // fall through + k2 ^= (long) toUnsignedInt(bb.get(14)) << 48; // fall through case 14: - k2 ^= (long) toInt(bb.get(13)) << 40; // fall through + k2 ^= (long) toUnsignedInt(bb.get(13)) << 40; // fall through case 13: - k2 ^= (long) toInt(bb.get(12)) << 32; // fall through + k2 ^= (long) toUnsignedInt(bb.get(12)) << 32; // fall through case 12: - k2 ^= (long) toInt(bb.get(11)) << 24; // fall through + k2 ^= (long) toUnsignedInt(bb.get(11)) << 24; // fall through case 11: - k2 ^= (long) toInt(bb.get(10)) << 16; // fall through + k2 ^= (long) toUnsignedInt(bb.get(10)) << 16; // fall through case 10: - k2 ^= (long) toInt(bb.get(9)) << 8; // fall through + k2 ^= (long) toUnsignedInt(bb.get(9)) << 8; // fall through case 9: - k2 ^= (long) toInt(bb.get(8)); // fall through + k2 ^= (long) toUnsignedInt(bb.get(8)); // fall through case 8: k1 ^= bb.getLong(); break; case 7: - k1 ^= (long) toInt(bb.get(6)) << 48; // fall through + k1 ^= (long) toUnsignedInt(bb.get(6)) << 48; // fall through case 6: - k1 ^= (long) toInt(bb.get(5)) << 40; // fall through + k1 ^= (long) toUnsignedInt(bb.get(5)) << 40; // fall through case 5: - k1 ^= (long) toInt(bb.get(4)) << 32; // fall through + k1 ^= (long) toUnsignedInt(bb.get(4)) << 32; // fall through case 4: - k1 ^= (long) toInt(bb.get(3)) << 24; // fall through + k1 ^= (long) toUnsignedInt(bb.get(3)) << 24; // fall through case 3: - k1 ^= (long) toInt(bb.get(2)) << 16; // fall through + k1 ^= (long) toUnsignedInt(bb.get(2)) << 16; // fall through case 2: - k1 ^= (long) toInt(bb.get(1)) << 8; // fall through + k1 ^= (long) toUnsignedInt(bb.get(1)) << 8; // fall through case 1: - k1 ^= (long) toInt(bb.get(0)); + k1 ^= (long) toUnsignedInt(bb.get(0)); break; default: throw new AssertionError("Should never get here."); diff --git a/android/guava/src/com/google/common/hash/Murmur3_32HashFunction.java b/android/guava/src/com/google/common/hash/Murmur3_32HashFunction.java index 44a332d8efb5..5165378671ee 100644 --- a/android/guava/src/com/google/common/hash/Murmur3_32HashFunction.java +++ b/android/guava/src/com/google/common/hash/Murmur3_32HashFunction.java @@ -27,9 +27,9 @@ import static com.google.common.base.Preconditions.checkPositionIndexes; import static com.google.common.base.Preconditions.checkState; -import static com.google.common.primitives.UnsignedBytes.toInt; +import static java.lang.Byte.toUnsignedInt; +import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Charsets; import com.google.common.primitives.Chars; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; @@ -39,7 +39,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * See MurmurHash3_x86_32 in >> 18)) & 0xFF) + // codePoint has at most 21 bits + return ((0xFL << 4) | (codePoint >>> 18)) | ((0x80L | (0x3F & (codePoint >>> 12))) << 8) | ((0x80L | (0x3F & (codePoint >>> 6))) << 16) | ((0x80L | (0x3F & codePoint)) << 24); } private static long charToThreeUtf8Bytes(char c) { - return (((0xF << 5) | (c >>> 12)) & 0xFF) + return ((0x7L << 5) | (c >>> 12)) | ((0x80 | (0x3F & (c >>> 6))) << 8) | ((0x80 | (0x3F & c)) << 16); } private static long charToTwoUtf8Bytes(char c) { - return (((0xF << 6) | (c >>> 6)) & 0xFF) | ((0x80 | (0x3F & c)) << 8); + // c has at most 11 bits + return ((0x3L << 6) | (c >>> 6)) | ((0x80 | (0x3F & c)) << 8); } private static final long serialVersionUID = 0L; diff --git a/android/guava/src/com/google/common/hash/ParametricNullness.java b/android/guava/src/com/google/common/hash/ParametricNullness.java old mode 100755 new mode 100644 index 2ae8d4200e94..1aee79c6a64b --- a/android/guava/src/com/google/common/hash/ParametricNullness.java +++ b/android/guava/src/com/google/common/hash/ParametricNullness.java @@ -19,25 +19,54 @@ import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static javax.annotation.meta.When.UNKNOWN; +import static java.lang.annotation.RetentionPolicy.CLASS; import com.google.common.annotations.GwtCompatible; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierNickname; /** - * Marks a "top-level" type-variable usage as (a) a Kotlin platform type when the type argument is - * non-nullable and (b) nullable when the type argument is nullable. This is the closest we can get - * to "non-nullable when non-nullable; nullable when nullable" (like the Android {@code - * NullFromTypeParam}). We use this to "undo" {@link ElementTypesAreNonnullByDefault}. + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. */ @GwtCompatible -@Retention(RUNTIME) +@Retention(CLASS) @Target({FIELD, METHOD, PARAMETER}) -@TypeQualifierNickname -@Nonnull(when = UNKNOWN) @interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/hash/PrimitiveSink.java b/android/guava/src/com/google/common/hash/PrimitiveSink.java index a29ba4e13609..71c5eceb7bff 100644 --- a/android/guava/src/com/google/common/hash/PrimitiveSink.java +++ b/android/guava/src/com/google/common/hash/PrimitiveSink.java @@ -26,8 +26,6 @@ * @since 12.0 (in 11.0 as {@code Sink}) */ @Beta -@CanIgnoreReturnValue -@ElementTypesAreNonnullByDefault public interface PrimitiveSink { /** * Puts a byte into this sink. @@ -35,6 +33,7 @@ public interface PrimitiveSink { * @param b a byte * @return this instance */ + @CanIgnoreReturnValue PrimitiveSink putByte(byte b); /** @@ -43,6 +42,7 @@ public interface PrimitiveSink { * @param bytes a byte array * @return this instance */ + @CanIgnoreReturnValue PrimitiveSink putBytes(byte[] bytes); /** @@ -56,6 +56,7 @@ public interface PrimitiveSink { * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > bytes.length} or * {@code len < 0} */ + @CanIgnoreReturnValue PrimitiveSink putBytes(byte[] bytes, int off, int len); /** @@ -67,27 +68,35 @@ public interface PrimitiveSink { * @return this instance * @since 23.0 */ + @CanIgnoreReturnValue PrimitiveSink putBytes(ByteBuffer bytes); /** Puts a short into this sink. */ + @CanIgnoreReturnValue PrimitiveSink putShort(short s); /** Puts an int into this sink. */ + @CanIgnoreReturnValue PrimitiveSink putInt(int i); /** Puts a long into this sink. */ + @CanIgnoreReturnValue PrimitiveSink putLong(long l); /** Puts a float into this sink. */ + @CanIgnoreReturnValue PrimitiveSink putFloat(float f); /** Puts a double into this sink. */ + @CanIgnoreReturnValue PrimitiveSink putDouble(double d); /** Puts a boolean into this sink. */ + @CanIgnoreReturnValue PrimitiveSink putBoolean(boolean b); /** Puts a character into this sink. */ + @CanIgnoreReturnValue PrimitiveSink putChar(char c); /** @@ -99,6 +108,7 @@ public interface PrimitiveSink { * * @since 15.0 (since 11.0 as putString(CharSequence)) */ + @CanIgnoreReturnValue PrimitiveSink putUnencodedChars(CharSequence charSequence); /** @@ -109,5 +119,6 @@ public interface PrimitiveSink { * is faster, produces the same output across Java releases, and processes every {@code char} in * the input, even if some are invalid. */ + @CanIgnoreReturnValue PrimitiveSink putString(CharSequence charSequence, Charset charset); } diff --git a/android/guava/src/com/google/common/hash/SipHashFunction.java b/android/guava/src/com/google/common/hash/SipHashFunction.java index a226b61a50b9..a5f328c3f3fc 100644 --- a/android/guava/src/com/google/common/hash/SipHashFunction.java +++ b/android/guava/src/com/google/common/hash/SipHashFunction.java @@ -24,7 +24,7 @@ import com.google.errorprone.annotations.Immutable; import java.io.Serializable; import java.nio.ByteBuffer; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * {@link HashFunction} implementation of SipHash-c-d. @@ -34,7 +34,6 @@ * @author Daniel J. Bernstein */ @Immutable -@ElementTypesAreNonnullByDefault final class SipHashFunction extends AbstractHashFunction implements Serializable { static final HashFunction SIP_HASH_24 = new SipHashFunction(2, 4, 0x0706050403020100L, 0x0f0e0d0c0b0a0908L); @@ -82,7 +81,7 @@ public String toString() { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof SipHashFunction) { SipHashFunction other = (SipHashFunction) object; return (c == other.c) && (d == other.d) && (k0 == other.k0) && (k1 == other.k1); diff --git a/android/guava/src/com/google/common/hash/SneakyThrows.java b/android/guava/src/com/google/common/hash/SneakyThrows.java new file mode 100644 index 000000000000..ead0cd086adc --- /dev/null +++ b/android/guava/src/com/google/common/hash/SneakyThrows.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +/** Static utility method for unchecked throwing of any {@link Throwable}. */ +@GwtCompatible +final class SneakyThrows { + /** + * Throws {@code t} as if it were an unchecked {@link Throwable}. + * + *

    This method is useful primarily when we make a reflective call to a method with no {@code + * throws} clause: Java forces us to handle an arbitrary {@link Throwable} from that method, + * rather than just the {@link RuntimeException} or {@link Error} that should be possible. (And in + * fact the static type of {@link Throwable} is occasionally justified even for a method with no + * {@code throws} clause: Some such methods can in fact throw a checked exception (e.g., by + * calling code written in Kotlin).) Typically, we want to let a {@link Throwable} from such a + * method propagate untouched, just as we'd typically let it do for a non-reflective call. + * However, we can't usually write {@code throw t;} when {@code t} has a static type of {@link + * Throwable}. But we can write {@code sneakyThrow(t);}. + * + *

    We sometimes also use {@code sneakyThrow} for testing how our code responds to + * sneaky checked exception. + * + * @return never; this method declares a return type of {@link Error} only so that callers can + * write {@code throw sneakyThrow(t);} to convince the compiler that the statement will always + * throw. + */ + @CanIgnoreReturnValue + static Error sneakyThrow(Throwable t) { + throw new SneakyThrows().throwIt(t); + } + + @SuppressWarnings("unchecked") // not really safe, but that's the point + private Error throwIt(Throwable t) throws T { + throw (T) t; + } + + private SneakyThrows() {} +} diff --git a/android/guava/src/com/google/common/hash/Striped64.java b/android/guava/src/com/google/common/hash/Striped64.java index 1a0671c9d7c5..aa7185509f13 100644 --- a/android/guava/src/com/google/common/hash/Striped64.java +++ b/android/guava/src/com/google/common/hash/Striped64.java @@ -12,9 +12,13 @@ package com.google.common.hash; import com.google.common.annotations.GwtIncompatible; +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.Random; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; +import sun.misc.Unsafe; /** * A package-local class holding common representation and mechanics for classes supporting dynamic @@ -22,7 +26,7 @@ * so. */ @GwtIncompatible -@ElementTypesAreNonnullByDefault +@SuppressWarnings("SunApi") // b/345822163 abstract class Striped64 extends Number { /* * This class maintains a lazily-initialized table of atomically @@ -104,18 +108,18 @@ static final class Cell { } final boolean cas(long cmp, long val) { - return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); + return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, cmp, val); } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long valueOffset; + private static final Unsafe UNSAFE; + private static final long VALUE_OFFSET; static { try { UNSAFE = getUnsafe(); Class ak = Cell.class; - valueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField("value")); + VALUE_OFFSET = UNSAFE.objectFieldOffset(ak.getDeclaredField("value")); } catch (Exception e) { throw new Error(e); } @@ -136,7 +140,7 @@ final boolean cas(long cmp, long val) { static final int NCPU = Runtime.getRuntime().availableProcessors(); /** Table of cells. When non-null, size is a power of 2. */ - @CheckForNull transient volatile Cell[] cells; + transient volatile Cell @Nullable [] cells; /** * Base value, used mainly when there is no contention, but also as a fallback during table @@ -152,12 +156,12 @@ final boolean cas(long cmp, long val) { /** CASes the base field. */ final boolean casBase(long cmp, long val) { - return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); + return UNSAFE.compareAndSwapLong(this, BASE_OFFSET, cmp, val); } /** CASes the busy field from 0 to 1 to acquire lock. */ final boolean casBusy() { - return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); + return UNSAFE.compareAndSwapInt(this, BUSY_OFFSET, 0, 1); } /** @@ -179,7 +183,7 @@ final boolean casBusy() { * @param hc the hash code holder * @param wasUncontended false if CAS failed before call */ - final void retryUpdate(long x, @CheckForNull int[] hc, boolean wasUncontended) { + final void retryUpdate(long x, int @Nullable [] hc, boolean wasUncontended) { int h; if (hc == null) { threadHashCode.set(hc = new int[1]); // Initialize randomly @@ -266,16 +270,16 @@ final void internalReset(long initialValue) { } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long baseOffset; - private static final long busyOffset; + private static final Unsafe UNSAFE; + private static final long BASE_OFFSET; + private static final long BUSY_OFFSET; static { try { UNSAFE = getUnsafe(); Class sk = Striped64.class; - baseOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField("base")); - busyOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField("busy")); + BASE_OFFSET = UNSAFE.objectFieldOffset(sk.getDeclaredField("base")); + BUSY_OFFSET = UNSAFE.objectFieldOffset(sk.getDeclaredField("busy")); } catch (Exception e) { throw new Error(e); } @@ -287,18 +291,18 @@ final void internalReset(long initialValue) { * * @return a sun.misc.Unsafe */ - private static sun.misc.Unsafe getUnsafe() { + private static Unsafe getUnsafe() { try { - return sun.misc.Unsafe.getUnsafe(); + return Unsafe.getUnsafe(); } catch (SecurityException tryReflectionInstead) { } try { - return java.security.AccessController.doPrivileged( - new java.security.PrivilegedExceptionAction() { + return AccessController.doPrivileged( + new PrivilegedExceptionAction() { @Override - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { + public Unsafe run() throws Exception { + Class k = Unsafe.class; + for (Field f : k.getDeclaredFields()) { f.setAccessible(true); Object x = f.get(null); if (k.isInstance(x)) return k.cast(x); @@ -306,7 +310,7 @@ public sun.misc.Unsafe run() throws Exception { throw new NoSuchFieldError("the Unsafe"); } }); - } catch (java.security.PrivilegedActionException e) { + } catch (PrivilegedActionException e) { throw new RuntimeException("Could not initialize intrinsics", e.getCause()); } } diff --git a/android/guava/src/com/google/common/hash/package-info.java b/android/guava/src/com/google/common/hash/package-info.java index d210f7ef7b46..b5405d48129c 100644 --- a/android/guava/src/com/google/common/hash/package-info.java +++ b/android/guava/src/com/google/common/hash/package-info.java @@ -20,8 +20,8 @@ * href="https://github.com/google/guava/wiki/HashingExplained">hashing. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.hash; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/html/ElementTypesAreNonnullByDefault.java b/android/guava/src/com/google/common/html/ElementTypesAreNonnullByDefault.java deleted file mode 100644 index a28b716632d4..000000000000 --- a/android/guava/src/com/google/common/html/ElementTypesAreNonnullByDefault.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.html; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import com.google.common.annotations.GwtCompatible; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; - -/** - * Marks all "top-level" types as non-null in a way that is recognized by Kotlin. Note that this - * unfortunately includes type-variable usages, so we also provide {@link ParametricNullness} to - * "undo" it as best we can. - */ -@GwtCompatible -@Retention(RUNTIME) -@Target(TYPE) -@TypeQualifierDefault({FIELD, METHOD, PARAMETER}) -@Nonnull -@interface ElementTypesAreNonnullByDefault {} diff --git a/android/guava/src/com/google/common/html/HtmlEscapers.java b/android/guava/src/com/google/common/html/HtmlEscapers.java index c42638871507..6da547570bdc 100644 --- a/android/guava/src/com/google/common/html/HtmlEscapers.java +++ b/android/guava/src/com/google/common/html/HtmlEscapers.java @@ -25,17 +25,17 @@ * One Google-authored templating system available for external use is Closure Templates. * - *

    HTML escaping is particularly tricky: For example, some - * elements' text contents must not be HTML escaped. As a result, it is impossible to escape an - * HTML document correctly without domain-specific knowledge beyond what {@code HtmlEscapers} - * provides. We strongly encourage the use of HTML templating systems. + *

    HTML escaping is particularly tricky: For example, some elements' text contents must not be HTML + * escaped. As a result, it is impossible to escape an HTML document correctly without + * domain-specific knowledge beyond what {@code HtmlEscapers} provides. We strongly encourage the + * use of HTML templating systems. * * @author Sven Mawson * @author David Beaumont * @since 15.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public final class HtmlEscapers { /** * Returns an {@link Escaper} instance that escapes HTML metacharacters as specified by {@code - * NullFromTypeParam}). We use this to "undo" {@link ElementTypesAreNonnullByDefault}. + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. */ @GwtCompatible -@Retention(RUNTIME) +@Retention(CLASS) @Target({FIELD, METHOD, PARAMETER}) -@TypeQualifierNickname -@Nonnull(when = UNKNOWN) @interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/html/package-info.java b/android/guava/src/com/google/common/html/package-info.java index f84d7f23d0ca..ccfd5b5693f9 100644 --- a/android/guava/src/com/google/common/html/package-info.java +++ b/android/guava/src/com/google/common/html/package-info.java @@ -17,12 +17,12 @@ * for * HTML. * - *

    This package is a part of the open-source Guava + *

    This package is a part of the open-source Guava * library. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.html; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/io/AppendableWriter.java b/android/guava/src/com/google/common/io/AppendableWriter.java index 6090bd30da77..4f230f2f4632 100644 --- a/android/guava/src/com/google/common/io/AppendableWriter.java +++ b/android/guava/src/com/google/common/io/AppendableWriter.java @@ -17,11 +17,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Closeable; import java.io.Flushable; import java.io.IOException; import java.io.Writer; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Writer that places all output on an {@link Appendable} target. If the target is {@link Flushable} @@ -31,8 +32,9 @@ * @author Sebastian Kanthak * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible -class AppendableWriter extends Writer { +final class AppendableWriter extends Writer { private final Appendable target; private boolean closed; @@ -68,13 +70,15 @@ public void write(int c) throws IOException { } @Override - public void write(@NullableDecl String str) throws IOException { + public void write(String str) throws IOException { + checkNotNull(str); checkNotClosed(); target.append(str); } @Override - public void write(@NullableDecl String str, int off, int len) throws IOException { + public void write(String str, int off, int len) throws IOException { + checkNotNull(str); checkNotClosed(); // tricky: append takes start, end pair... target.append(str, off, off + len); @@ -104,14 +108,14 @@ public Writer append(char c) throws IOException { } @Override - public Writer append(@NullableDecl CharSequence charSeq) throws IOException { + public Writer append(@Nullable CharSequence charSeq) throws IOException { checkNotClosed(); target.append(charSeq); return this; } @Override - public Writer append(@NullableDecl CharSequence charSeq, int start, int end) throws IOException { + public Writer append(@Nullable CharSequence charSeq, int start, int end) throws IOException { checkNotClosed(); target.append(charSeq, start, end); return this; diff --git a/android/guava/src/com/google/common/io/BaseEncoding.java b/android/guava/src/com/google/common/io/BaseEncoding.java index 16e406dedbac..a12d7519f55f 100644 --- a/android/guava/src/com/google/common/io/BaseEncoding.java +++ b/android/guava/src/com/google/common/io/BaseEncoding.java @@ -20,14 +20,16 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.math.IntMath.divide; import static com.google.common.math.IntMath.log2; +import static java.lang.Math.max; +import static java.lang.Math.min; import static java.math.RoundingMode.CEILING; import static java.math.RoundingMode.FLOOR; import static java.math.RoundingMode.UNNECESSARY; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Ascii; -import com.google.common.base.Objects; import com.google.errorprone.annotations.concurrent.LazyInit; import java.io.IOException; import java.io.InputStream; @@ -35,22 +37,23 @@ import java.io.Reader; import java.io.Writer; import java.util.Arrays; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * A binary encoding scheme for reversibly translating between byte sequences and printable ASCII * strings. This class includes several constants for encoding schemes specified by RFC 4648. For example, the expression: * - *

    {@code
    - * BaseEncoding.base32().encode("foo".getBytes(Charsets.US_ASCII))
    - * }
    + * {@snippet : + * BaseEncoding.base32().encode("foo".getBytes(US_ASCII)) + * } * *

    returns the string {@code "MZXW6==="}, and * - *

    {@code
    + * {@snippet :
      * byte[] decoded = BaseEncoding.base32().decode("MZXW6===");
    - * }
    + * } * *

    ...returns the ASCII bytes of the string {@code "foo"}. * @@ -59,19 +62,19 @@ * encoding and decoding behavior, use configuration methods to obtain a new encoding with modified * behavior: * - *

    {@code
    + * {@snippet :
      * BaseEncoding.base16().lowerCase().decode("deadbeef");
    - * }
    + * } * *

    Warning: BaseEncoding instances are immutable. Invoking a configuration method has no effect * on the receiving instance; you must store and use the new encoding instance it returns, instead. * - *

    {@code
    + * {@snippet :
      * // Do NOT do this
      * BaseEncoding hex = BaseEncoding.base16();
      * hex.lowerCase(); // does nothing!
      * return hex.decode("deadbeef"); // throws an IllegalArgumentException
    - * }
    + * } * *

    It is guaranteed that {@code encoding.decode(encoding.encode(x))} is always equal to {@code * x}, but the reverse does not necessarily hold. @@ -121,7 +124,7 @@ * @author Louis Wasserman * @since 14.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public abstract class BaseEncoding { // TODO(lowasser): consider making encodeTo(Appendable, byte[], int, int) public. @@ -134,13 +137,9 @@ public abstract class BaseEncoding { * @since 15.0 */ public static final class DecodingException extends IOException { - DecodingException(String message) { + DecodingException(@Nullable String message) { super(message); } - - DecodingException(Throwable cause) { - super(cause); - } } /** Encodes the specified byte array, and returns the encoded {@code String}. */ @@ -168,14 +167,16 @@ public final String encode(byte[] bytes, int off, int len) { * {@code Writer}. When the returned {@code OutputStream} is closed, so is the backing {@code * Writer}. */ + @J2ktIncompatible @GwtIncompatible // Writer,OutputStream public abstract OutputStream encodingStream(Writer writer); /** * Returns a {@code ByteSink} that writes base-encoded bytes to the specified {@code CharSink}. */ + @J2ktIncompatible @GwtIncompatible // ByteSink,CharSink - public final ByteSink encodingSink(final CharSink encodedSink) { + public final ByteSink encodingSink(CharSink encodedSink) { checkNotNull(encodedSink); return new ByteSink() { @Override @@ -238,6 +239,7 @@ final byte[] decodeChecked(CharSequence chars) * Returns an {@code InputStream} that decodes base-encoded input from the specified {@code * Reader}. The returned stream throws a {@link DecodingException} upon decoding-specific errors. */ + @J2ktIncompatible @GwtIncompatible // Reader,InputStream public abstract InputStream decodingStream(Reader reader); @@ -245,8 +247,9 @@ final byte[] decodeChecked(CharSequence chars) * Returns a {@code ByteSource} that reads base-encoded bytes from the specified {@code * CharSource}. */ + @J2ktIncompatible @GwtIncompatible // ByteSource,CharSource - public final ByteSource decodingSource(final CharSource encodedSource) { + public final ByteSource decodingSource(CharSource encodedSource) { checkNotNull(encodedSource); return new ByteSource() { @Override @@ -317,6 +320,16 @@ CharSequence trimTrailingPadding(CharSequence chars) { */ public abstract BaseEncoding lowerCase(); + /** + * Returns an encoding that behaves equivalently to this encoding, but decodes letters without + * regard to case. + * + * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and + * lower-case characters + * @since 32.0.0 + */ + public abstract BaseEncoding ignoreCase(); + private static final BaseEncoding BASE64 = new Base64Encoding( "base64()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", '='); @@ -412,12 +425,14 @@ public static BaseEncoding base32Hex() { *

    No line feeds are added by default, as per RFC 4648 section 3.1, Line Feeds in * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. + * + *

    Java 17+ users: Consider using the {@link java.util.HexFormat} API instead. */ public static BaseEncoding base16() { return BASE16; } - private static final class Alphabet { + static final class Alphabet { private final String name; // this is meant to be immutable -- don't modify it! private final char[] chars; @@ -427,8 +442,13 @@ private static final class Alphabet { final int bytesPerChunk; private final byte[] decodabet; private final boolean[] validPadding; + private final boolean ignoreCase; Alphabet(String name, char[] chars) { + this(name, chars, decodabetFor(chars), /* ignoreCase= */ false); + } + + private Alphabet(String name, char[] chars, byte[] decodabet, boolean ignoreCase) { this.name = checkNotNull(name); this.chars = checkNotNull(chars); try { @@ -437,20 +457,30 @@ private static final class Alphabet { throw new IllegalArgumentException("Illegal alphabet length " + chars.length, e); } - /* - * e.g. for base64, bitsPerChar == 6, charsPerChunk == 4, and bytesPerChunk == 3. This makes - * for the smallest chunk size that still has charsPerChunk * bitsPerChar be a multiple of 8. - */ - int gcd = Math.min(8, Integer.lowestOneBit(bitsPerChar)); - try { - this.charsPerChunk = 8 / gcd; - this.bytesPerChunk = bitsPerChar / gcd; - } catch (ArithmeticException e) { - throw new IllegalArgumentException("Illegal alphabet " + new String(chars), e); - } + // Compute how input bytes are chunked. For example, with base64 we chunk every 3 bytes into + // 4 characters. We have bitsPerChar == 6, charsPerChunk == 4, and bytesPerChunk == 3. + // We're looking for the smallest charsPerChunk such that bitsPerChar * charsPerChunk is a + // multiple of 8. A multiple of 8 has 3 low zero bits, so we just need to figure out how many + // extra zero bits we need to add to the end of bitsPerChar to get 3 in total. + // The logic here would be wrong for bitsPerChar > 8, but since we require distinct ASCII + // characters that can't happen. + int zeroesInBitsPerChar = Integer.numberOfTrailingZeros(bitsPerChar); + this.charsPerChunk = 1 << (3 - zeroesInBitsPerChar); + this.bytesPerChunk = bitsPerChar >> zeroesInBitsPerChar; this.mask = chars.length - 1; + this.decodabet = decodabet; + + boolean[] validPadding = new boolean[charsPerChunk]; + for (int i = 0; i < bytesPerChunk; i++) { + validPadding[divide(i * 8, bitsPerChar, CEILING)] = true; + } + this.validPadding = validPadding; + this.ignoreCase = ignoreCase; + } + + private static byte[] decodabetFor(char[] chars) { byte[] decodabet = new byte[Ascii.MAX + 1]; Arrays.fill(decodabet, (byte) -1); for (int i = 0; i < chars.length; i++) { @@ -459,13 +489,33 @@ private static final class Alphabet { checkArgument(decodabet[c] == -1, "Duplicate character: %s", c); decodabet[c] = (byte) i; } - this.decodabet = decodabet; + return decodabet; + } - boolean[] validPadding = new boolean[charsPerChunk]; - for (int i = 0; i < bytesPerChunk; i++) { - validPadding[divide(i * 8, bitsPerChar, CEILING)] = true; + /** Returns an equivalent {@code Alphabet} except it ignores case. */ + Alphabet ignoreCase() { + if (ignoreCase) { + return this; } - this.validPadding = validPadding; + + // We can't use .clone() because of GWT. + byte[] newDecodabet = Arrays.copyOf(decodabet, decodabet.length); + for (int upper = 'A'; upper <= 'Z'; upper++) { + int lower = upper | 0x20; + byte decodeUpper = decodabet[upper]; + byte decodeLower = decodabet[lower]; + if (decodeUpper == -1) { + newDecodabet[upper] = decodeLower; + } else { + checkState( + decodeLower == -1, + "Can't ignoreCase() since '%s' and '%s' encode different values", + (char) upper, + (char) lower); + newDecodabet[lower] = decodeUpper; + } + } + return new Alphabet(name + ".ignoreCase()", chars, newDecodabet, /* ignoreCase= */ true); } char encode(int bits) { @@ -522,7 +572,8 @@ Alphabet upperCase() { for (int i = 0; i < chars.length; i++) { upperCased[i] = Ascii.toUpperCase(chars[i]); } - return new Alphabet(name + ".upperCase()", upperCased); + Alphabet upperCase = new Alphabet(name + ".upperCase()", upperCased); + return ignoreCase ? upperCase.ignoreCase() : upperCase; } Alphabet lowerCase() { @@ -534,7 +585,8 @@ Alphabet lowerCase() { for (int i = 0; i < chars.length; i++) { lowerCased[i] = Ascii.toLowerCase(chars[i]); } - return new Alphabet(name + ".lowerCase()", lowerCased); + Alphabet lowerCase = new Alphabet(name + ".lowerCase()", lowerCased); + return ignoreCase ? lowerCase.ignoreCase() : lowerCase; } public boolean matches(char c) { @@ -547,31 +599,30 @@ public String toString() { } @Override - public boolean equals(@NullableDecl Object other) { + public boolean equals(@Nullable Object other) { if (other instanceof Alphabet) { Alphabet that = (Alphabet) other; - return Arrays.equals(this.chars, that.chars); + return this.ignoreCase == that.ignoreCase && Arrays.equals(this.chars, that.chars); } return false; } @Override public int hashCode() { - return Arrays.hashCode(chars); + return Arrays.hashCode(chars) + (ignoreCase ? 1231 : 1237); } } - static class StandardBaseEncoding extends BaseEncoding { - // TODO(lowasser): provide a useful toString + private static class StandardBaseEncoding extends BaseEncoding { final Alphabet alphabet; - @NullableDecl final Character paddingChar; + final @Nullable Character paddingChar; - StandardBaseEncoding(String name, String alphabetChars, @NullableDecl Character paddingChar) { + StandardBaseEncoding(String name, String alphabetChars, @Nullable Character paddingChar) { this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar); } - StandardBaseEncoding(Alphabet alphabet, @NullableDecl Character paddingChar) { + StandardBaseEncoding(Alphabet alphabet, @Nullable Character paddingChar) { this.alphabet = checkNotNull(alphabet); checkArgument( paddingChar == null || !alphabet.matches(paddingChar), @@ -585,9 +636,10 @@ int maxEncodedSize(int bytes) { return alphabet.charsPerChunk * divide(bytes, alphabet.bytesPerChunk, CEILING); } + @J2ktIncompatible @GwtIncompatible // Writer,OutputStream @Override - public OutputStream encodingStream(final Writer out) { + public OutputStream encodingStream(Writer out) { checkNotNull(out); return new OutputStream() { int bitBuffer = 0; @@ -635,7 +687,7 @@ void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOExcept checkNotNull(target); checkPositionIndexes(off, off + len, bytes.length); for (int i = 0; i < len; i += alphabet.bytesPerChunk) { - encodeChunkTo(target, bytes, off + i, Math.min(alphabet.bytesPerChunk, len - i)); + encodeChunkTo(target, bytes, off + i, min(alphabet.bytesPerChunk, len - i)); } } @@ -649,7 +701,7 @@ void encodeChunkTo(Appendable target, byte[] bytes, int off, int len) throws IOE bitBuffer <<= 8; // Add additional zero byte in the end. } // Position of first character is length of bitBuffer minus bitsPerChar. - final int bitOffset = (len + 1) * 8 - alphabet.bitsPerChar; + int bitOffset = (len + 1) * 8 - alphabet.bitsPerChar; int bitsProcessed = 0; while (bitsProcessed < len * 8) { int charIndex = (int) (bitBuffer >>> (bitOffset - bitsProcessed)) & alphabet.mask; @@ -717,7 +769,7 @@ int decodeTo(byte[] target, CharSequence chars) throws DecodingException { chunk |= alphabet.decode(chars.charAt(charIdx + charsProcessed++)); } } - final int minOffset = alphabet.bytesPerChunk * 8 - charsProcessed * alphabet.bitsPerChar; + int minOffset = alphabet.bytesPerChunk * 8 - charsProcessed * alphabet.bitsPerChar; for (int offset = (alphabet.bytesPerChunk - 1) * 8; offset >= minOffset; offset -= 8) { target[bytesWritten++] = (byte) ((chunk >>> offset) & 0xFF); } @@ -726,8 +778,9 @@ int decodeTo(byte[] target, CharSequence chars) throws DecodingException { } @Override + @J2ktIncompatible @GwtIncompatible // Reader,InputStream - public InputStream decodingStream(final Reader reader) { + public InputStream decodingStream(Reader reader) { checkNotNull(reader); return new InputStream() { int bitBuffer = 0; @@ -829,8 +882,9 @@ public BaseEncoding withSeparator(String separator, int afterEveryChars) { return new SeparatedBaseEncoding(this, separator, afterEveryChars); } - @LazyInit @NullableDecl private transient BaseEncoding upperCase; - @LazyInit @NullableDecl private transient BaseEncoding lowerCase; + @LazyInit private volatile @Nullable BaseEncoding upperCase; + @LazyInit private volatile @Nullable BaseEncoding lowerCase; + @LazyInit private volatile @Nullable BaseEncoding ignoreCase; @Override public BaseEncoding upperCase() { @@ -852,14 +906,24 @@ public BaseEncoding lowerCase() { return result; } - BaseEncoding newInstance(Alphabet alphabet, @NullableDecl Character paddingChar) { + @Override + public BaseEncoding ignoreCase() { + BaseEncoding result = ignoreCase; + if (result == null) { + Alphabet ignore = alphabet.ignoreCase(); + result = ignoreCase = (ignore == alphabet) ? this : newInstance(ignore, paddingChar); + } + return result; + } + + BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) { return new StandardBaseEncoding(alphabet, paddingChar); } @Override public String toString() { StringBuilder builder = new StringBuilder("BaseEncoding."); - builder.append(alphabet.toString()); + builder.append(alphabet); if (8 % alphabet.bitsPerChar != 0) { if (paddingChar == null) { builder.append(".omitPadding()"); @@ -871,11 +935,11 @@ public String toString() { } @Override - public boolean equals(@NullableDecl Object other) { + public boolean equals(@Nullable Object other) { if (other instanceof StandardBaseEncoding) { StandardBaseEncoding that = (StandardBaseEncoding) other; return this.alphabet.equals(that.alphabet) - && Objects.equal(this.paddingChar, that.paddingChar); + && Objects.equals(this.paddingChar, that.paddingChar); } return false; } @@ -886,7 +950,7 @@ public int hashCode() { } } - static final class Base16Encoding extends StandardBaseEncoding { + private static final class Base16Encoding extends StandardBaseEncoding { final char[] encoding = new char[512]; Base16Encoding(String name, String alphabetChars) { @@ -928,17 +992,17 @@ int decodeTo(byte[] target, CharSequence chars) throws DecodingException { } @Override - BaseEncoding newInstance(Alphabet alphabet, @NullableDecl Character paddingChar) { + BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) { return new Base16Encoding(alphabet); } } - static final class Base64Encoding extends StandardBaseEncoding { - Base64Encoding(String name, String alphabetChars, @NullableDecl Character paddingChar) { + private static final class Base64Encoding extends StandardBaseEncoding { + Base64Encoding(String name, String alphabetChars, @Nullable Character paddingChar) { this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar); } - private Base64Encoding(Alphabet alphabet, @NullableDecl Character paddingChar) { + private Base64Encoding(Alphabet alphabet, @Nullable Character paddingChar) { super(alphabet, paddingChar); checkArgument(alphabet.chars.length == 64); } @@ -949,7 +1013,7 @@ void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOExcept checkPositionIndexes(off, off + len, bytes.length); int i = off; for (int remaining = len; remaining >= 3; remaining -= 3) { - int chunk = (bytes[i++] & 0xFF) << 16 | (bytes[i++] & 0xFF) << 8 | bytes[i++] & 0xFF; + int chunk = (bytes[i++] & 0xFF) << 16 | (bytes[i++] & 0xFF) << 8 | (bytes[i++] & 0xFF); target.append(alphabet.encode(chunk >>> 18)); target.append(alphabet.encode((chunk >>> 12) & 0x3F)); target.append(alphabet.encode((chunk >>> 6) & 0x3F)); @@ -985,13 +1049,14 @@ int decodeTo(byte[] target, CharSequence chars) throws DecodingException { } @Override - BaseEncoding newInstance(Alphabet alphabet, @NullableDecl Character paddingChar) { + BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) { return new Base64Encoding(alphabet, paddingChar); } } + @J2ktIncompatible @GwtIncompatible - static Reader ignoringReader(final Reader delegate, final String toIgnore) { + static Reader ignoringReader(Reader delegate, String toIgnore) { checkNotNull(delegate); checkNotNull(toIgnore); return new Reader() { @@ -1017,7 +1082,7 @@ public void close() throws IOException { } static Appendable separatingAppendable( - final Appendable delegate, final String separator, final int afterEveryChars) { + Appendable delegate, String separator, int afterEveryChars) { checkNotNull(delegate); checkNotNull(separator); checkArgument(afterEveryChars > 0); @@ -1036,23 +1101,21 @@ public Appendable append(char c) throws IOException { } @Override - public Appendable append(@NullableDecl CharSequence chars, int off, int len) - throws IOException { + public Appendable append(@Nullable CharSequence chars, int off, int len) { throw new UnsupportedOperationException(); } @Override - public Appendable append(@NullableDecl CharSequence chars) throws IOException { + public Appendable append(@Nullable CharSequence chars) { throw new UnsupportedOperationException(); } }; } + @J2ktIncompatible @GwtIncompatible // Writer - static Writer separatingWriter( - final Writer delegate, final String separator, final int afterEveryChars) { - final Appendable separatingAppendable = - separatingAppendable(delegate, separator, afterEveryChars); + static Writer separatingWriter(Writer delegate, String separator, int afterEveryChars) { + Appendable separatingAppendable = separatingAppendable(delegate, separator, afterEveryChars); return new Writer() { @Override public void write(int c) throws IOException { @@ -1098,12 +1161,13 @@ CharSequence trimTrailingPadding(CharSequence chars) { int maxEncodedSize(int bytes) { int unseparatedSize = delegate.maxEncodedSize(bytes); return unseparatedSize - + separator.length() * divide(Math.max(0, unseparatedSize - 1), afterEveryChars, FLOOR); + + separator.length() * divide(max(0, unseparatedSize - 1), afterEveryChars, FLOOR); } + @J2ktIncompatible @GwtIncompatible // Writer,OutputStream @Override - public OutputStream encodingStream(final Writer output) { + public OutputStream encodingStream(Writer output) { return delegate.encodingStream(separatingWriter(output, separator, afterEveryChars)); } @@ -1142,8 +1206,9 @@ int decodeTo(byte[] target, CharSequence chars) throws DecodingException { } @Override + @J2ktIncompatible @GwtIncompatible // Reader,InputStream - public InputStream decodingStream(final Reader reader) { + public InputStream decodingStream(Reader reader) { return delegate.decodingStream(ignoringReader(reader, separator)); } @@ -1172,6 +1237,11 @@ public BaseEncoding lowerCase() { return delegate.lowerCase().withSeparator(separator, afterEveryChars); } + @Override + public BaseEncoding ignoreCase() { + return delegate.ignoreCase().withSeparator(separator, afterEveryChars); + } + @Override public String toString() { return delegate + ".withSeparator(\"" + separator + "\", " + afterEveryChars + ")"; diff --git a/android/guava/src/com/google/common/io/ByteArrayDataInput.java b/android/guava/src/com/google/common/io/ByteArrayDataInput.java index bef1431e2b23..375f07cd67f4 100644 --- a/android/guava/src/com/google/common/io/ByteArrayDataInput.java +++ b/android/guava/src/com/google/common/io/ByteArrayDataInput.java @@ -15,9 +15,11 @@ package com.google.common.io; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.DataInput; import java.io.IOException; +import org.jspecify.annotations.Nullable; /** * An extension of {@code DataInput} for reading from in-memory byte arrays; its methods offer @@ -31,13 +33,14 @@ * @author Kevin Bourrillion * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public interface ByteArrayDataInput extends DataInput { @Override - void readFully(byte b[]); + void readFully(byte[] b); @Override - void readFully(byte b[], int off, int len); + void readFully(byte[] b, int off, int len); // not guaranteed to skip n bytes so result should NOT be ignored // use ByteStreams.skipFully or one of the read methods instead @@ -86,7 +89,7 @@ public interface ByteArrayDataInput extends DataInput { @CanIgnoreReturnValue // to skip a line @Override - String readLine(); + @Nullable String readLine(); @CanIgnoreReturnValue // to skip a field @Override diff --git a/android/guava/src/com/google/common/io/ByteArrayDataOutput.java b/android/guava/src/com/google/common/io/ByteArrayDataOutput.java index e1ad6ab2f5f7..32c9e2fca9bc 100644 --- a/android/guava/src/com/google/common/io/ByteArrayDataOutput.java +++ b/android/guava/src/com/google/common/io/ByteArrayDataOutput.java @@ -15,6 +15,7 @@ package com.google.common.io; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.DataOutput; import java.io.IOException; @@ -25,16 +26,17 @@ * @author Jayaprabhakar Kadarkarai * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public interface ByteArrayDataOutput extends DataOutput { @Override void write(int b); @Override - void write(byte b[]); + void write(byte[] b); @Override - void write(byte b[], int off, int len); + void write(byte[] b, int off, int len); @Override void writeBoolean(boolean v); diff --git a/android/guava/src/com/google/common/io/ByteProcessor.java b/android/guava/src/com/google/common/io/ByteProcessor.java index 115c73571555..5a2a667650ba 100644 --- a/android/guava/src/com/google/common/io/ByteProcessor.java +++ b/android/guava/src/com/google/common/io/ByteProcessor.java @@ -14,11 +14,12 @@ package com.google.common.io; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotMock; import java.io.IOException; +import org.jspecify.annotations.Nullable; /** * A callback interface to process bytes from a stream. @@ -29,10 +30,10 @@ * @author Chris Nokleberg * @since 1.0 */ -@Beta @DoNotMock("Implement it normally") +@J2ktIncompatible @GwtIncompatible -public interface ByteProcessor { +public interface ByteProcessor { /** * This method will be called for each chunk of bytes in an input stream. The implementation * should process the bytes from {@code buf[off]} through {@code buf[off + len - 1]} (inclusive). @@ -46,5 +47,6 @@ public interface ByteProcessor { boolean processBytes(byte[] buf, int off, int len) throws IOException; /** Return the result of processing all the bytes. */ + @ParametricNullness T getResult(); } diff --git a/android/guava/src/com/google/common/io/ByteSink.java b/android/guava/src/com/google/common/io/ByteSink.java index ffba6e0a928c..d3013cb1ffb3 100644 --- a/android/guava/src/com/google/common/io/ByteSink.java +++ b/android/guava/src/com/google/common/io/ByteSink.java @@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.BufferedOutputStream; import java.io.IOException; @@ -45,6 +46,7 @@ * @since 14.0 * @author Colin Decker */ +@J2ktIncompatible @GwtIncompatible public abstract class ByteSink { @@ -96,15 +98,8 @@ public OutputStream openBufferedStream() throws IOException { public void write(byte[] bytes) throws IOException { checkNotNull(bytes); - Closer closer = Closer.create(); - try { - OutputStream out = closer.register(openStream()); + try (OutputStream out = openStream()) { out.write(bytes); - out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 - } catch (Throwable e) { - throw closer.rethrow(e); - } finally { - closer.close(); } } @@ -119,16 +114,8 @@ public void write(byte[] bytes) throws IOException { public long writeFrom(InputStream input) throws IOException { checkNotNull(input); - Closer closer = Closer.create(); - try { - OutputStream out = closer.register(openStream()); - long written = ByteStreams.copy(input, out); - out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 - return written; - } catch (Throwable e) { - throw closer.rethrow(e); - } finally { - closer.close(); + try (OutputStream out = openStream()) { + return ByteStreams.copy(input, out); } } diff --git a/android/guava/src/com/google/common/io/ByteSource.java b/android/guava/src/com/google/common/io/ByteSource.java index 3e5912f84055..e0695afe4e0f 100644 --- a/android/guava/src/com/google/common/io/ByteSource.java +++ b/android/guava/src/com/google/common/io/ByteSource.java @@ -16,11 +16,11 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.io.ByteStreams.createBuffer; import static com.google.common.io.ByteStreams.skipUpTo; +import static java.lang.Math.min; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Ascii; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; @@ -40,6 +40,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import org.jspecify.annotations.Nullable; /** * A readable source of bytes, such as a file. Unlike an {@link InputStream}, a {@code ByteSource} @@ -72,6 +73,7 @@ * @since 14.0 * @author Colin Decker */ +@J2ktIncompatible @GwtIncompatible public abstract class ByteSource { @@ -176,7 +178,6 @@ public boolean isEmpty() throws IOException { * * @since 19.0 */ - @Beta public Optional sizeIfKnown() { return Optional.absent(); } @@ -228,7 +229,7 @@ public long size() throws IOException { } /** Counts the bytes in the given input stream using skip if possible. */ - private long countBySkipping(InputStream in) throws IOException { + private static long countBySkipping(InputStream in) throws IOException { long count = 0; long skipped; while ((skipped = skipUpTo(in, Integer.MAX_VALUE)) > 0) { @@ -312,9 +313,9 @@ public byte[] read() throws IOException { * processor} throws an {@code IOException} * @since 16.0 */ - @Beta @CanIgnoreReturnValue // some processors won't return a useful result - public T read(ByteProcessor processor) throws IOException { + @ParametricNullness + public T read(ByteProcessor processor) throws IOException { checkNotNull(processor); Closer closer = Closer.create(); @@ -348,22 +349,10 @@ public HashCode hash(HashFunction hashFunction) throws IOException { public boolean contentEquals(ByteSource other) throws IOException { checkNotNull(other); - byte[] buf1 = createBuffer(); - byte[] buf2 = createBuffer(); - Closer closer = Closer.create(); try { - InputStream in1 = closer.register(openStream()); - InputStream in2 = closer.register(other.openStream()); - while (true) { - int read1 = ByteStreams.read(in1, buf1, 0, buf1.length); - int read2 = ByteStreams.read(in2, buf2, 0, buf2.length); - if (read1 != read2 || !Arrays.equals(buf1, buf2)) { - return false; - } else if (read1 != buf1.length) { - return true; - } - } + return ByteStreams.contentsEqual( + closer.register(openStream()), closer.register(other.openStream())); } catch (Throwable e) { throw closer.rethrow(e); } finally { @@ -544,7 +533,7 @@ public ByteSource slice(long offset, long length) { long maxLength = this.length - offset; return maxLength <= 0 ? ByteSource.empty() - : ByteSource.this.slice(this.offset + offset, Math.min(length, maxLength)); + : ByteSource.this.slice(this.offset + offset, min(length, maxLength)); } @Override @@ -557,8 +546,8 @@ public Optional sizeIfKnown() { Optional optionalUnslicedSize = ByteSource.this.sizeIfKnown(); if (optionalUnslicedSize.isPresent()) { long unslicedSize = optionalUnslicedSize.get(); - long off = Math.min(offset, unslicedSize); - return Optional.of(Math.min(length, unslicedSize - off)); + long off = min(offset, unslicedSize); + return Optional.of(min(length, unslicedSize - off)); } return Optional.absent(); } @@ -569,7 +558,9 @@ public String toString() { } } - private static class ByteArrayByteSource extends ByteSource { + private static class ByteArrayByteSource extends + ByteSource + { final byte[] bytes; final int offset; @@ -592,7 +583,7 @@ public InputStream openStream() { } @Override - public InputStream openBufferedStream() throws IOException { + public InputStream openBufferedStream() { return openStream(); } @@ -618,7 +609,8 @@ public byte[] read() { @SuppressWarnings("CheckReturnValue") // it doesn't matter what processBytes returns here @Override - public T read(ByteProcessor processor) throws IOException { + @ParametricNullness + public T read(ByteProcessor processor) throws IOException { processor.processBytes(bytes, offset, length); return processor.getResult(); } @@ -639,8 +631,8 @@ public ByteSource slice(long offset, long length) { checkArgument(offset >= 0, "offset (%s) may not be negative", offset); checkArgument(length >= 0, "length (%s) may not be negative", length); - offset = Math.min(offset, this.length); - length = Math.min(length, this.length - offset); + offset = min(offset, this.length); + length = min(length, this.length - offset); int newOffset = this.offset + (int) offset; return new ByteArrayByteSource(bytes, newOffset, (int) length); } diff --git a/android/guava/src/com/google/common/io/ByteStreams.java b/android/guava/src/com/google/common/io/ByteStreams.java index bdb24dbcc7d3..c41eac5d6f96 100644 --- a/android/guava/src/com/google/common/io/ByteStreams.java +++ b/android/guava/src/com/google/common/io/ByteStreams.java @@ -18,9 +18,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndex; import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Math.max; +import static java.lang.Math.min; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.math.IntMath; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.ByteArrayInputStream; @@ -41,6 +43,7 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.Queue; +import org.jspecify.annotations.Nullable; /** * Provides utility methods for working with byte arrays and I/O streams. @@ -94,6 +97,9 @@ private ByteStreams() {} * Copies all bytes from the input stream to the output stream. Does not close or flush either * stream. * + *

    Java 9 users and later: this method should be treated as deprecated; use the + * equivalent {@link InputStream#transferTo} method instead. + * * @param from the input stream to read from * @param to the output stream to write to * @return the number of bytes copied @@ -125,6 +131,7 @@ public static long copy(InputStream from, OutputStream to) throws IOException { * @return the number of bytes copied * @throws IOException if an I/O error occurs */ + @J2ktIncompatible @CanIgnoreReturnValue public static long copy(ReadableByteChannel from, WritableByteChannel to) throws IOException { checkNotNull(from); @@ -167,13 +174,18 @@ public static long copy(ReadableByteChannel from, WritableByteChannel to) throws */ private static byte[] toByteArrayInternal(InputStream in, Queue bufs, int totalLen) throws IOException { - // Starting with an 8k buffer, double the size of each successive buffer. Buffers are retained - // in a deque so that there's no copying between buffers while reading and so all of the bytes - // in each new allocated buffer are available for reading from the stream. - for (int bufSize = BUFFER_SIZE; + // Roughly size to match what has been read already. Some file systems, such as procfs, return 0 + // as their length. These files are very small, so it's wasteful to allocate an 8KB buffer. + int initialBufferSize = min(BUFFER_SIZE, max(128, Integer.highestOneBit(totalLen) * 2)); + // Starting with an 8k buffer, double the size of each successive buffer. Smaller buffers + // quadruple in size until they reach 8k, to minimize the number of small reads for longer + // streams. Buffers are retained in a deque so that there's no copying between buffers while + // reading and so all of the bytes in each new allocated buffer are available for reading from + // the stream. + for (int bufSize = initialBufferSize; totalLen < MAX_ARRAY_LEN; - bufSize = IntMath.saturatedMultiply(bufSize, 2)) { - byte[] buf = new byte[Math.min(bufSize, MAX_ARRAY_LEN - totalLen)]; + bufSize = IntMath.saturatedMultiply(bufSize, bufSize < 4096 ? 4 : 2)) { + byte[] buf = new byte[min(bufSize, MAX_ARRAY_LEN - totalLen)]; bufs.add(buf); int off = 0; while (off < buf.length) { @@ -197,11 +209,18 @@ private static byte[] toByteArrayInternal(InputStream in, Queue bufs, in } private static byte[] combineBuffers(Queue bufs, int totalLen) { - byte[] result = new byte[totalLen]; - int remaining = totalLen; + if (bufs.isEmpty()) { + return new byte[0]; + } + byte[] result = bufs.remove(); + if (result.length == totalLen) { + return result; + } + int remaining = totalLen - result.length; + result = Arrays.copyOf(result, totalLen); while (remaining > 0) { byte[] buf = bufs.remove(); - int bytesToCopy = Math.min(remaining, buf.length); + int bytesToCopy = min(remaining, buf.length); int resultOffset = totalLen - remaining; System.arraycopy(buf, 0, result, resultOffset, bytesToCopy); remaining -= bytesToCopy; @@ -212,6 +231,8 @@ private static byte[] combineBuffers(Queue bufs, int totalLen) { /** * Reads all bytes from an input stream into a byte array. Does not close the stream. * + *

    Java 9+ users: use {@code in#readAllBytes()} instead. + * * @param in the input stream to read from * @return a byte array containing all the bytes from the stream * @throws IOException if an I/O error occurs @@ -253,7 +274,7 @@ static byte[] toByteArray(InputStream in, long expectedSize) throws IOException } // the stream was longer, so read the rest normally - Queue bufs = new ArrayDeque(TO_BYTE_ARRAY_DEQUE_SIZE + 2); + Queue bufs = new ArrayDeque<>(TO_BYTE_ARRAY_DEQUE_SIZE + 2); bufs.add(bytes); bufs.add(new byte[] {(byte) b}); return toByteArrayInternal(in, bufs, bytes.length + 1); @@ -266,7 +287,6 @@ static byte[] toByteArray(InputStream in, long expectedSize) throws IOException * @since 20.0 */ @CanIgnoreReturnValue - @Beta public static long exhaust(InputStream in) throws IOException { long total = 0; long read; @@ -281,7 +301,7 @@ public static long exhaust(InputStream in) throws IOException { * Returns a new {@link ByteArrayDataInput} instance to read from the {@code bytes} array from the * beginning. */ - @Beta + @J2ktIncompatible public static ByteArrayDataInput newDataInput(byte[] bytes) { return newDataInput(new ByteArrayInputStream(bytes)); } @@ -293,7 +313,7 @@ public static ByteArrayDataInput newDataInput(byte[] bytes) { * @throws IndexOutOfBoundsException if {@code start} is negative or greater than the length of * the array */ - @Beta + @J2ktIncompatible public static ByteArrayDataInput newDataInput(byte[] bytes, int start) { checkPositionIndex(start, bytes.length); return newDataInput(new ByteArrayInputStream(bytes, start, bytes.length - start)); @@ -306,12 +326,13 @@ public static ByteArrayDataInput newDataInput(byte[] bytes, int start) { * * @since 17.0 */ - @Beta + @J2ktIncompatible public static ByteArrayDataInput newDataInput(ByteArrayInputStream byteArrayInputStream) { return new ByteArrayDataInputStream(checkNotNull(byteArrayInputStream)); } - private static class ByteArrayDataInputStream implements ByteArrayDataInput { + @J2ktIncompatible + private static final class ByteArrayDataInputStream implements ByteArrayDataInput { final DataInput input; ByteArrayDataInputStream(ByteArrayInputStream byteArrayInputStream) { @@ -319,7 +340,7 @@ private static class ByteArrayDataInputStream implements ByteArrayDataInput { } @Override - public void readFully(byte b[]) { + public void readFully(byte[] b) { try { input.readFully(b); } catch (IOException e) { @@ -328,7 +349,7 @@ public void readFully(byte b[]) { } @Override - public void readFully(byte b[], int off, int len) { + public void readFully(byte[] b, int off, int len) { try { input.readFully(b, off, len); } catch (IOException e) { @@ -438,7 +459,7 @@ public double readDouble() { } @Override - public String readLine() { + public @Nullable String readLine() { try { return input.readLine(); } catch (IOException e) { @@ -457,7 +478,7 @@ public String readUTF() { } /** Returns a new {@link ByteArrayDataOutput} instance with a default size. */ - @Beta + @J2ktIncompatible public static ByteArrayDataOutput newDataOutput() { return newDataOutput(new ByteArrayOutputStream()); } @@ -468,7 +489,7 @@ public static ByteArrayDataOutput newDataOutput() { * * @throws IllegalArgumentException if {@code size} is negative */ - @Beta + @J2ktIncompatible public static ByteArrayDataOutput newDataOutput(int size) { // When called at high frequency, boxing size generates too much garbage, // so avoid doing that if we can. @@ -490,12 +511,13 @@ public static ByteArrayDataOutput newDataOutput(int size) { * * @since 17.0 */ - @Beta + @J2ktIncompatible public static ByteArrayDataOutput newDataOutput(ByteArrayOutputStream byteArrayOutputStream) { return new ByteArrayDataOutputStream(checkNotNull(byteArrayOutputStream)); } - private static class ByteArrayDataOutputStream implements ByteArrayDataOutput { + @J2ktIncompatible + private static final class ByteArrayDataOutputStream implements ByteArrayDataOutput { final DataOutput output; final ByteArrayOutputStream byteArrayOutputStream; @@ -653,6 +675,7 @@ public void write(byte[] b) { @Override public void write(byte[] b, int off, int len) { checkNotNull(b); + checkPositionIndexes(off, off + len, b.length); } @Override @@ -664,9 +687,13 @@ public String toString() { /** * Returns an {@link OutputStream} that simply discards written bytes. * + *

    Java 11+ users: use {@link OutputStream#nullOutputStream()} instead. Note that the + * {@link ByteStreams} method returns a singleton stream whose {@code close} method has no effect, + * while the {@link OutputStream} method returns a new instance whose {@code write} methods throw + * if called on a closed stream. + * * @since 14.0 (since 1.0 as com.google.common.io.NullOutputStream) */ - @Beta public static OutputStream nullOutputStream() { return NULL_OUTPUT_STREAM; } @@ -679,11 +706,12 @@ public static OutputStream nullOutputStream() { * @return a length-limited {@link InputStream} * @since 14.0 (since 1.0 as com.google.common.io.LimitInputStream) */ - @Beta + @J2ktIncompatible public static InputStream limit(InputStream in, long limit) { return new LimitedInputStream(in, limit); } + @J2ktIncompatible private static final class LimitedInputStream extends FilterInputStream { private long left; @@ -698,7 +726,7 @@ private static final class LimitedInputStream extends FilterInputStream { @Override public int available() throws IOException { - return (int) Math.min(in.available(), left); + return (int) min(in.available(), left); } // it's okay to mark even if mark isn't supported, as reset won't work @@ -727,7 +755,7 @@ public int read(byte[] b, int off, int len) throws IOException { return -1; } - len = (int) Math.min(len, left); + len = (int) min(len, left); int result = in.read(b, off, len); if (result != -1) { left -= result; @@ -750,7 +778,7 @@ public synchronized void reset() throws IOException { @Override public long skip(long n) throws IOException { - n = Math.min(n, left); + n = min(n, left); long skipped = in.skip(n); left -= skipped; return skipped; @@ -766,7 +794,6 @@ public long skip(long n) throws IOException { * @throws EOFException if this stream reaches the end before reading all the bytes. * @throws IOException if an I/O error occurs. */ - @Beta public static void readFully(InputStream in, byte[] b) throws IOException { readFully(in, b, 0, b.length); } @@ -783,7 +810,6 @@ public static void readFully(InputStream in, byte[] b) throws IOException { * @throws EOFException if this stream reaches the end before reading all the bytes. * @throws IOException if an I/O error occurs. */ - @Beta public static void readFully(InputStream in, byte[] b, int off, int len) throws IOException { int read = read(in, b, off, len); if (read != len) { @@ -801,7 +827,6 @@ public static void readFully(InputStream in, byte[] b, int off, int len) throws * @throws EOFException if this stream reaches the end before skipping all the bytes * @throws IOException if an I/O error occurs, or the stream does not support skipping */ - @Beta public static void skipFully(InputStream in, long n) throws IOException { long skipped = skipUpTo(in, n); if (skipped < n) { @@ -815,7 +840,7 @@ public static void skipFully(InputStream in, long n) throws IOException { * either the full amount has been skipped or until the end of the stream is reached, whichever * happens first. Returns the total number of bytes skipped. */ - static long skipUpTo(InputStream in, final long n) throws IOException { + static long skipUpTo(InputStream in, long n) throws IOException { long totalSkipped = 0; // A buffer is allocated if skipSafely does not skip any bytes. byte[] buf = null; @@ -827,7 +852,7 @@ static long skipUpTo(InputStream in, final long n) throws IOException { if (skipped == 0) { // Do a buffered read since skipSafely could return 0 repeatedly, for example if // in.available() always returns 0 (the default). - int skip = (int) Math.min(remaining, BUFFER_SIZE); + int skip = (int) min(remaining, BUFFER_SIZE); if (buf == null) { // Allocate a buffer bounded by the maximum size that can be requested, for // example an array of BUFFER_SIZE is unnecessary when the value of remaining @@ -855,7 +880,7 @@ static long skipUpTo(InputStream in, final long n) throws IOException { */ private static long skipSafely(InputStream in, long n) throws IOException { int available = in.available(); - return available == 0 ? 0 : in.skip(Math.min(available, n)); + return available == 0 ? 0 : in.skip(min(available, n)); } /** @@ -867,9 +892,11 @@ private static long skipSafely(InputStream in, long n) throws IOException { * @throws IOException if an I/O error occurs * @since 14.0 */ - @Beta @CanIgnoreReturnValue // some processors won't return a useful result - public static T readBytes(InputStream input, ByteProcessor processor) throws IOException { + @ParametricNullness + @J2ktIncompatible + public static T readBytes( + InputStream input, ByteProcessor processor) throws IOException { checkNotNull(input); checkNotNull(processor); @@ -905,7 +932,6 @@ public static T readBytes(InputStream input, ByteProcessor processor) thr * @throws IndexOutOfBoundsException if {@code off} is negative, if {@code len} is negative, or if * {@code off + len} is greater than {@code b.length} */ - @Beta @CanIgnoreReturnValue // Sometimes you don't care how many bytes you actually read, I guess. // (You know that it's either going to read len bytes or stop at EOF.) @@ -926,4 +952,32 @@ public static int read(InputStream in, byte[] b, int off, int len) throws IOExce } return total; } + + /** Compares the contents of the two {@link InputStream}s for equality. */ + static boolean contentsEqual(InputStream in1, InputStream in2) throws IOException { + byte[] buf1 = createBuffer(); + byte[] buf2 = createBuffer(); + while (true) { + int read1 = read(in1, buf1, 0, BUFFER_SIZE); + int read2 = read(in2, buf2, 0, BUFFER_SIZE); + if (read1 != read2 || !arraysEqual(buf1, buf2, read1)) { + return false; + } else if (read1 != BUFFER_SIZE) { + return true; + } + } + } + + // The Arrays.equals(, int, int, , int, int) methods were not added until + // Java 9. This function is just returns the same result that + // Arrays.equals(array1, 0, count, array2, 0, count) would. It assumes that both arrays have a + // length of at least count. + private static boolean arraysEqual(byte[] array1, byte[] array2, int count) { + for (int i = 0; i < count; i++) { + if (array1[i] != array2[i]) { + return false; + } + } + return true; + } } diff --git a/android/guava/src/com/google/common/io/CharSequenceReader.java b/android/guava/src/com/google/common/io/CharSequenceReader.java index 4cbeda1fb365..819abd1134f5 100644 --- a/android/guava/src/com/google/common/io/CharSequenceReader.java +++ b/android/guava/src/com/google/common/io/CharSequenceReader.java @@ -17,11 +17,15 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.IOException; import java.io.Reader; import java.nio.CharBuffer; +import org.jspecify.annotations.Nullable; /** * A {@link Reader} that reads the characters in a {@link CharSequence}. Like {@code StringReader}, @@ -30,10 +34,11 @@ * @author Colin Decker */ // TODO(cgdecker): make this public? as a type, or a method in CharStreams? +@J2ktIncompatible @GwtIncompatible final class CharSequenceReader extends Reader { - private CharSequence seq; + private @Nullable CharSequence seq; private int pos; private int mark; @@ -53,17 +58,31 @@ private boolean hasRemaining() { } private int remaining() { + requireNonNull(seq); // safe as long as we call this only after checkOpen return seq.length() - pos; } + /* + * To avoid the need to call requireNonNull so much, we could consider more clever approaches, + * such as: + * + * - Make checkOpen return the non-null `seq`. Then callers can assign that to a local variable or + * even back to `this.seq`. However, that may suggest that we're defending against concurrent + * mutation, which is not an actual risk because we use `synchronized`. + * - Make `remaining` require a non-null `seq` argument. But this is a bit weird because the + * method, while it would avoid the instance field `seq` would still access the instance field + * `pos`. + */ + @Override public synchronized int read(CharBuffer target) throws IOException { checkNotNull(target); checkOpen(); + requireNonNull(seq); // safe because of checkOpen if (!hasRemaining()) { return -1; } - int charsToRead = Math.min(target.remaining(), remaining()); + int charsToRead = min(target.remaining(), remaining()); for (int i = 0; i < charsToRead; i++) { target.put(seq.charAt(pos++)); } @@ -73,6 +92,7 @@ public synchronized int read(CharBuffer target) throws IOException { @Override public synchronized int read() throws IOException { checkOpen(); + requireNonNull(seq); // safe because of checkOpen return hasRemaining() ? seq.charAt(pos++) : -1; } @@ -80,10 +100,11 @@ public synchronized int read() throws IOException { public synchronized int read(char[] cbuf, int off, int len) throws IOException { checkPositionIndexes(off, off + len, cbuf.length); checkOpen(); + requireNonNull(seq); // safe because of checkOpen if (!hasRemaining()) { return -1; } - int charsToRead = Math.min(len, remaining()); + int charsToRead = min(len, remaining()); for (int i = 0; i < charsToRead; i++) { cbuf[off + i] = seq.charAt(pos++); } @@ -94,7 +115,7 @@ public synchronized int read(char[] cbuf, int off, int len) throws IOException { public synchronized long skip(long n) throws IOException { checkArgument(n >= 0, "n (%s) may not be negative", n); checkOpen(); - int charsToSkip = (int) Math.min(remaining(), n); // safe because remaining is an int + int charsToSkip = (int) min(remaining(), n); // safe because remaining is an int pos += charsToSkip; return charsToSkip; } diff --git a/android/guava/src/com/google/common/io/CharSink.java b/android/guava/src/com/google/common/io/CharSink.java index e615662d336d..a0ce2263b5e2 100644 --- a/android/guava/src/com/google/common/io/CharSink.java +++ b/android/guava/src/com/google/common/io/CharSink.java @@ -15,14 +15,18 @@ package com.google.common.io; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.BufferedWriter; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.stream.Stream; /** * A destination to which characters can be written, such as a text file. Unlike a {@link Writer}, a @@ -47,6 +51,7 @@ * @since 14.0 * @author Colin Decker */ +@J2ktIncompatible @GwtIncompatible public abstract class CharSink { @@ -89,15 +94,8 @@ public Writer openBufferedStream() throws IOException { public void write(CharSequence charSequence) throws IOException { checkNotNull(charSequence); - Closer closer = Closer.create(); - try { - Writer out = closer.register(openStream()); + try (Writer out = openStream()) { out.append(charSequence); - out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 - } catch (Throwable e) { - throw closer.rethrow(e); - } finally { - closer.close(); } } @@ -120,20 +118,43 @@ public void writeLines(Iterable lines) throws IOExceptio */ public void writeLines(Iterable lines, String lineSeparator) throws IOException { - checkNotNull(lines); + writeLines(lines.iterator(), lineSeparator); + } + + /** + * Writes the given lines of text to this sink with each line (including the last) terminated with + * the operating system's default line separator. This method is equivalent to {@code + * writeLines(lines, System.getProperty("line.separator"))}. + * + * @throws IOException if an I/O error occurs while writing to this sink + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using Stream. + public void writeLines(Stream lines) throws IOException { + writeLines(lines, LINE_SEPARATOR.value()); + } + + /** + * Writes the given lines of text to this sink with each line (including the last) terminated with + * the given line separator. + * + * @throws IOException if an I/O error occurs while writing to this sink + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using Stream. + public void writeLines(Stream lines, String lineSeparator) + throws IOException { + writeLines(lines.iterator(), lineSeparator); + } + + private void writeLines(Iterator lines, String lineSeparator) + throws IOException { checkNotNull(lineSeparator); - Closer closer = Closer.create(); - try { - Writer out = closer.register(openBufferedStream()); - for (CharSequence line : lines) { - out.append(line).append(lineSeparator); + try (Writer out = openBufferedStream()) { + while (lines.hasNext()) { + out.append(lines.next()).append(lineSeparator); } - out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 - } catch (Throwable e) { - throw closer.rethrow(e); - } finally { - closer.close(); } } @@ -149,16 +170,8 @@ public void writeLines(Iterable lines, String lineSepara public long writeFrom(Readable readable) throws IOException { checkNotNull(readable); - Closer closer = Closer.create(); - try { - Writer out = closer.register(openStream()); - long written = CharStreams.copy(readable, out); - out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 - return written; - } catch (Throwable e) { - throw closer.rethrow(e); - } finally { - closer.close(); + try (Writer out = openStream()) { + return CharStreams.copy(readable, out); } } } diff --git a/android/guava/src/com/google/common/io/CharSource.java b/android/guava/src/com/google/common/io/CharSource.java index 29e1ccba9a61..49d0d47cbdbf 100644 --- a/android/guava/src/com/google/common/io/CharSource.java +++ b/android/guava/src/com/google/common/io/CharSource.java @@ -15,26 +15,32 @@ package com.google.common.io; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Streams.stream; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Ascii; import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.MustBeClosed; import java.io.BufferedReader; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; +import java.io.UncheckedIOException; import java.io.Writer; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; /** * A readable source of characters, such as a text file. Unlike a {@link Reader}, a {@code @@ -75,6 +81,7 @@ * @since 14.0 * @author Colin Decker */ +@J2ktIncompatible @GwtIncompatible public abstract class CharSource { @@ -92,7 +99,6 @@ protected CharSource() {} * * @since 20.0 */ - @Beta public ByteSource asByteSource(Charset charset) { return new AsByteSource(charset); } @@ -122,6 +128,53 @@ public BufferedReader openBufferedStream() throws IOException { : new BufferedReader(reader); } + /** + * Opens a new {@link Stream} for reading text one line at a time from this source. This method + * returns a new, independent stream each time it is called. + * + *

    The returned stream is lazy and only reads from the source in the terminal operation. If an + * I/O error occurs while the stream is reading from the source or when the stream is closed, an + * {@link UncheckedIOException} is thrown. + * + *

    Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + *

    The caller is responsible for ensuring that the returned stream is closed. For example: + * + * {@snippet : + * try (Stream lines = source.lines()) { + * lines.map(...) + * .filter(...) + * .forEach(...); + * } + * } + * + * @throws IOException if an I/O error occurs while opening the stream + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @MustBeClosed + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Stream calls. + @IgnoreJRERequirement + public Stream lines() throws IOException { + BufferedReader reader = openBufferedStream(); + return reader.lines().onClose(() -> closeUnchecked(reader)); + } + + @IgnoreJRERequirement // helper for lines() + /* + * If we make these calls inline inside the lambda inside lines(), we get an Animal Sniffer error, + * despite the @IgnoreJRERequirement annotation there. For details, see ImmutableSortedMultiset. + */ + private static void closeUnchecked(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + /** * Returns the size of this source in chars, if the size can be easily determined without actually * opening the data stream. @@ -136,7 +189,6 @@ public BufferedReader openBufferedStream() throws IOException { * * @since 19.0 */ - @Beta public Optional lengthIfKnown() { return Optional.absent(); } @@ -160,7 +212,6 @@ public Optional lengthIfKnown() { * @throws IOException if an I/O error occurs while reading the length of this source * @since 19.0 */ - @Beta public long length() throws IOException { Optional lengthIfKnown = lengthIfKnown(); if (lengthIfKnown.isPresent()) { @@ -178,7 +229,7 @@ public long length() throws IOException { } } - private long countBySkipping(Reader reader) throws IOException { + private static long countBySkipping(Reader reader) throws IOException { long count = 0; long read; while ((read = reader.skip(Long.MAX_VALUE)) != 0) { @@ -260,8 +311,7 @@ public String read() throws IOException { * * @throws IOException if an I/O error occurs while reading from this source */ - @NullableDecl - public String readFirstLine() throws IOException { + public @Nullable String readFirstLine() throws IOException { Closer closer = Closer.create(); try { BufferedReader reader = closer.register(openBufferedStream()); @@ -288,7 +338,7 @@ public ImmutableList readLines() throws IOException { Closer closer = Closer.create(); try { BufferedReader reader = closer.register(openBufferedStream()); - List result = Lists.newArrayList(); + List result = new ArrayList<>(); String line; while ((line = reader.readLine()) != null) { result.add(line); @@ -315,9 +365,9 @@ public ImmutableList readLines() throws IOException { * processor} throws an {@code IOException} * @since 16.0 */ - @Beta @CanIgnoreReturnValue // some processors won't return a useful result - public T readLines(LineProcessor processor) throws IOException { + @ParametricNullness + public T readLines(LineProcessor processor) throws IOException { checkNotNull(processor); Closer closer = Closer.create(); @@ -331,6 +381,33 @@ public T readLines(LineProcessor processor) throws IOException { } } + /** + * Reads all lines of text from this source, running the given {@code action} for each line as it + * is read. + * + *

    Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + * @throws IOException if an I/O error occurs while reading from this source or if {@code action} + * throws an {@code UncheckedIOException} + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + /* + * We have to rely on users not to call this without library desugaring, as NewApi won't flag + * Consumer creation. + */ + @IgnoreJRERequirement + public void forEachLine(Consumer action) throws IOException { + try (Stream lines = lines()) { + // The lines should be ordered regardless in most cases, but use forEachOrdered to be sure + lines.forEachOrdered(action); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + /** * Returns whether the source has zero chars. The default implementation first checks {@link * #lengthIfKnown}, returning true if it's known to be zero and false if it's known to be @@ -466,9 +543,9 @@ private static class CharSequenceCharSource extends CharSource { private static final Splitter LINE_SPLITTER = Splitter.onPattern("\r\n|\n|\r"); - protected final CharSequence seq; + final CharSequence seq; - protected CharSequenceCharSource(CharSequence seq) { + CharSequenceCharSource(CharSequence seq) { this.seq = checkNotNull(seq); } @@ -503,10 +580,10 @@ public Optional lengthIfKnown() { */ private Iterator linesIterator() { return new AbstractIterator() { - Iterator lines = LINE_SPLITTER.split(seq).iterator(); + final Iterator lines = LINE_SPLITTER.split(seq).iterator(); @Override - protected String computeNext() { + protected @Nullable String computeNext() { if (lines.hasNext()) { String next = lines.next(); // skip last line if it's empty @@ -520,7 +597,14 @@ protected String computeNext() { } @Override - public String readFirstLine() { + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Stream calls + @IgnoreJRERequirement + public Stream lines() { + return stream(linesIterator()); + } + + @Override + public @Nullable String readFirstLine() { Iterator lines = linesIterator(); return lines.hasNext() ? lines.next() : null; } @@ -531,7 +615,8 @@ public ImmutableList readLines() { } @Override - public T readLines(LineProcessor processor) throws IOException { + @ParametricNullness + public T readLines(LineProcessor processor) throws IOException { Iterator lines = linesIterator(); while (lines.hasNext()) { if (!processor.processLine(lines.next())) { @@ -563,7 +648,7 @@ public String toString() { * */ private static class StringCharSource extends CharSequenceCharSource { - protected StringCharSource(String seq) { + StringCharSource(String seq) { super(seq); } diff --git a/android/guava/src/com/google/common/io/CharStreams.java b/android/guava/src/com/google/common/io/CharStreams.java index c700691b1c11..f6686e1e97ea 100644 --- a/android/guava/src/com/google/common/io/CharStreams.java +++ b/android/guava/src/com/google/common/io/CharStreams.java @@ -17,9 +17,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.BufferedReader; import java.io.Closeable; import java.io.EOFException; import java.io.IOException; @@ -28,22 +29,17 @@ import java.nio.CharBuffer; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Provides utility methods for working with character streams. * - *

    All method parameters must be non-null unless documented otherwise. - * - *

    Some of the methods in this class take arguments with a generic type of {@code Readable & - * Closeable}. A {@link java.io.Reader} implements both of those interfaces. Similarly for {@code - * Appendable & Closeable} and {@link java.io.Writer}. - * * @author Chris Nokleberg * @author Bin Zhu * @author Colin Decker * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public final class CharStreams { @@ -156,6 +152,9 @@ static long copyReaderToWriter(Reader from, Writer to) throws IOException { * Reads all characters from a {@link Readable} object into a {@link String}. Does not close the * {@code Readable}. * + *

    Java 25+ users: If the input is a {@link Reader}, prefer {@link + * Reader#readAllAsString()}. + * * @param r the object to read from * @return a string containing all the characters * @throws IOException if an I/O error occurs @@ -189,11 +188,19 @@ private static StringBuilder toStringBuilder(Readable r) throws IOException { *

    Does not close the {@code Readable}. If reading files or resources you should use the {@link * Files#readLines} and {@link Resources#readLines} methods. * + *

    This method prioritizes convenience over performance: It reads the entire input into memory + * immediately. To instead read and process lines individually, use an alternative like {@link + * BufferedReader#lines()}. + * + *

    Java 25+ users: If the input is a {@link Reader}, you may prefer {@link + * Reader#readAllLines()}. The two may have different performance characteristics for different + * {@link Reader} classes and Java versions, but both methods read the entire input into memory + * immediately, so we discourage use of both when memory usage is a concern. + * * @param r the object to read from * @return a mutable {@link List} containing all the lines * @throws IOException if an I/O error occurs */ - @Beta public static List readLines(Readable r) throws IOException { List result = new ArrayList<>(); LineReader lineReader = new LineReader(r); @@ -210,12 +217,17 @@ public static List readLines(Readable r) throws IOException { * {@code readable}. Note that this method may not fully consume the contents of {@code readable} * if the processor stops processing early. * + *

    Users who can use {@code Stream}: If your input is a {@link Reader}, consider + * wrapping it with a {@link BufferedReader} and operating on the {@link java.util.stream.Stream} + * of lines returned by {@link BufferedReader#lines()}. + * * @throws IOException if an I/O error occurs * @since 14.0 */ - @Beta @CanIgnoreReturnValue // some processors won't return a useful result - public static T readLines(Readable readable, LineProcessor processor) throws IOException { + @ParametricNullness + public static T readLines( + Readable readable, LineProcessor processor) throws IOException { checkNotNull(readable); checkNotNull(processor); @@ -235,7 +247,6 @@ public static T readLines(Readable readable, LineProcessor processor) thr * * @since 20.0 */ - @Beta @CanIgnoreReturnValue public static long exhaust(Readable readable) throws IOException { long total = 0; @@ -257,7 +268,6 @@ public static long exhaust(Readable readable) throws IOException { * @throws EOFException if this stream reaches the end before skipping all the characters * @throws IOException if an I/O error occurs */ - @Beta public static void skipFully(Reader reader, long n) throws IOException { checkNotNull(reader); while (n > 0) { @@ -272,9 +282,13 @@ public static void skipFully(Reader reader, long n) throws IOException { /** * Returns a {@link Writer} that simply discards written chars. * + *

    Java 11+ users: use {@link Writer#nullWriter()} instead. Note that the {@link + * CharStreams} method returns a singleton writer whose {@code close} method has no effect, while + * the {@link Writer#nullWriter()} method returns a new instance whose methods throw after the + * instance is {@link Writer#close() closed}. + * * @since 15.0 */ - @Beta public static Writer nullWriter() { return NullWriter.INSTANCE; } @@ -307,12 +321,12 @@ public void write(String str, int off, int len) { } @Override - public Writer append(@NullableDecl CharSequence csq) { + public Writer append(@Nullable CharSequence csq) { return this; } @Override - public Writer append(@NullableDecl CharSequence csq, int start, int end) { + public Writer append(@Nullable CharSequence csq, int start, int end) { checkPositionIndexes(start, end, csq == null ? "null".length() : csq.length()); return this; } @@ -342,7 +356,6 @@ public String toString() { * @param target the object to which output will be sent * @return a new Writer object, unless target is a Writer, in which case the target is returned */ - @Beta public static Writer asWriter(Appendable target) { if (target instanceof Writer) { return (Writer) target; diff --git a/android/guava/src/com/google/common/io/Closeables.java b/android/guava/src/com/google/common/io/Closeables.java index e7489a7b3091..a2bfadc2d04c 100644 --- a/android/guava/src/com/google/common/io/Closeables.java +++ b/android/guava/src/com/google/common/io/Closeables.java @@ -14,8 +14,8 @@ package com.google.common.io; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import java.io.Closeable; import java.io.IOException; @@ -23,7 +23,7 @@ import java.io.Reader; import java.util.logging.Level; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Utility methods for working with {@link Closeable} objects. @@ -31,7 +31,7 @@ * @author Michael Lancaster * @since 1.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class Closeables { @VisibleForTesting static final Logger logger = Logger.getLogger(Closeables.class.getName()); @@ -48,7 +48,7 @@ private Closeables() {} * *

    Example: * - *

    {@code
    +   * {@snippet :
        * public void useStreamNicely() throws IOException {
        *   SomeStream stream = new SomeStream("foo");
        *   boolean threw = true;
    @@ -60,7 +60,7 @@ private Closeables() {}
        *     Closeables.close(stream, threw);
        *   }
        * }
    -   * }
    + * } * * @param closeable the {@code Closeable} object to be closed, or null, in which case this method * does nothing @@ -69,7 +69,17 @@ private Closeables() {} * @throws IOException if {@code swallowIOException} is false and {@code close} throws an {@code * IOException}. */ - public static void close(@NullableDecl Closeable closeable, boolean swallowIOException) + /* + * The proper capitalization would be "swallowIoException." However: + * + * - It might be preferable to be consistent with the JDK precedent (which they stuck with even + * for "UncheckedIOException"). + * + * - If we change the name, some of our callers break because our Android Lint ParameterName check + * doesn't make the exception for com.google.common that internal Error Prone does: b/386402967. + */ + @SuppressWarnings("IdentifierName") + public static void close(@Nullable Closeable closeable, boolean swallowIOException) throws IOException { if (closeable == null) { return; @@ -99,7 +109,7 @@ public static void close(@NullableDecl Closeable closeable, boolean swallowIOExc * does nothing * @since 17.0 */ - public static void closeQuietly(@NullableDecl InputStream inputStream) { + public static void closeQuietly(@Nullable InputStream inputStream) { try { close(inputStream, true); } catch (IOException impossible) { @@ -120,7 +130,7 @@ public static void closeQuietly(@NullableDecl InputStream inputStream) { * @param reader the reader to be closed, or {@code null} in which case this method does nothing * @since 17.0 */ - public static void closeQuietly(@NullableDecl Reader reader) { + public static void closeQuietly(@Nullable Reader reader) { try { close(reader, true); } catch (IOException impossible) { diff --git a/android/guava/src/com/google/common/io/Closer.java b/android/guava/src/com/google/common/io/Closer.java index ff5db89b233e..f5f38cd6497f 100644 --- a/android/guava/src/com/google/common/io/Closer.java +++ b/android/guava/src/com/google/common/io/Closer.java @@ -15,32 +15,30 @@ package com.google.common.io; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Throwables.throwIfInstanceOf; +import static com.google.common.base.Throwables.throwIfUnchecked; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Throwables; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.Closeable; import java.io.IOException; -import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.Deque; import java.util.logging.Level; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@link Closeable} that collects {@code Closeable} resources and closes them all when it is - * {@linkplain #close closed}. This is intended to approximately emulate the behavior of Java 7's try-with-resources statement in JDK6-compatible code. Running on Java 7, code using this - * should be approximately equivalent in behavior to the same code written with try-with-resources. - * Running on Java 6, exceptions that cannot be thrown must be logged rather than being added to the - * thrown exception as a suppressed exception. + * {@linkplain #close closed}. This was intended to approximately emulate the behavior of Java 7's + * try-with-resources statement in JDK6-compatible code. Code using this should be + * approximately equivalent in behavior to the same code written with try-with-resources. * *

    This class is intended to be used in the following pattern: * - *

    {@code
    + * {@snippet :
      * Closer closer = Closer.create();
      * try {
      *   InputStream in = closer.register(openInputStream());
    @@ -53,7 +51,7 @@
      * } finally {
      *   closer.close();
      * }
    - * }
    + * } * *

    Note that this try-catch-finally block is not equivalent to a try-catch-finally block using * try-with-resources. To get the equivalent of that, you must wrap the above code in another @@ -73,39 +71,26 @@ * another exception is already being thrown) is suppressed. * * - *

    An exception that is suppressed is not thrown. The method of suppression used depends on the - * version of Java the code is running on: - * - *

      - *
    • Java 7+: Exceptions are suppressed by adding them to the exception that will - * be thrown using {@code Throwable.addSuppressed(Throwable)}. - *
    • Java 6: Exceptions are suppressed by logging them instead. - *
    + *

    An exception that is suppressed is added to the exception that will be thrown using + * {@code Throwable.addSuppressed(Throwable)}. * * @author Colin Decker * @since 14.0 */ // Coffee's for {@link Closer closers} only. -@Beta +@J2ktIncompatible @GwtIncompatible public final class Closer implements Closeable { - - /** The suppressor implementation to use for the current Java version. */ - private static final Suppressor SUPPRESSOR = - SuppressingSuppressor.isAvailable() - ? SuppressingSuppressor.INSTANCE - : LoggingSuppressor.INSTANCE; - /** Creates a new {@link Closer}. */ public static Closer create() { - return new Closer(SUPPRESSOR); + return new Closer(SUPPRESSING_SUPPRESSOR); } @VisibleForTesting final Suppressor suppressor; // only need space for 2 elements in most cases, so try to use the smallest array possible private final Deque stack = new ArrayDeque<>(4); - @NullableDecl private Throwable thrown; + private @Nullable Throwable thrown; @VisibleForTesting Closer(Suppressor suppressor) { @@ -120,7 +105,8 @@ public static Closer create() { */ // close. this word no longer has any meaning to me. @CanIgnoreReturnValue - public C register(@NullableDecl C closeable) { + @ParametricNullness + public C register(@ParametricNullness C closeable) { if (closeable != null) { stack.addFirst(closeable); } @@ -144,7 +130,8 @@ public C register(@NullableDecl C closeable) { public RuntimeException rethrow(Throwable e) throws IOException { checkNotNull(e); thrown = e; - Throwables.propagateIfPossible(e, IOException.class); + throwIfInstanceOf(e, IOException.class); + throwIfUnchecked(e); throw new RuntimeException(e); } @@ -166,8 +153,9 @@ public RuntimeException rethrow(Throwable e, Class decl throws IOException, X { checkNotNull(e); thrown = e; - Throwables.propagateIfPossible(e, IOException.class); - Throwables.propagateIfPossible(e, declaredType); + throwIfInstanceOf(e, IOException.class); + throwIfInstanceOf(e, declaredType); + throwIfUnchecked(e); throw new RuntimeException(e); } @@ -190,8 +178,10 @@ public RuntimeException rethrow( Throwable e, Class declaredType1, Class declaredType2) throws IOException, X1, X2 { checkNotNull(e); thrown = e; - Throwables.propagateIfPossible(e, IOException.class); - Throwables.propagateIfPossible(e, declaredType1, declaredType2); + throwIfInstanceOf(e, IOException.class); + throwIfInstanceOf(e, declaredType1); + throwIfInstanceOf(e, declaredType2); + throwIfUnchecked(e); throw new RuntimeException(e); } @@ -221,7 +211,8 @@ public void close() throws IOException { } if (thrown == null && throwable != null) { - Throwables.propagateIfPossible(throwable, IOException.class); + throwIfInstanceOf(throwable, IOException.class); + throwIfUnchecked(throwable); throw new AssertionError(throwable); // not possible } } @@ -237,55 +228,27 @@ interface Suppressor { void suppress(Closeable closeable, Throwable thrown, Throwable suppressed); } - /** Suppresses exceptions by logging them. */ - @VisibleForTesting - static final class LoggingSuppressor implements Suppressor { - - static final LoggingSuppressor INSTANCE = new LoggingSuppressor(); - - @Override - public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { - // log to the same place as Closeables - Closeables.logger.log( - Level.WARNING, "Suppressing exception thrown when closing " + closeable, suppressed); - } - } - /** - * Suppresses exceptions by adding them to the exception that will be thrown using JDK7's + * Suppresses exceptions by adding them to the exception that will be thrown using the * addSuppressed(Throwable) mechanism. */ - @VisibleForTesting - static final class SuppressingSuppressor implements Suppressor { - - static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor(); - - static boolean isAvailable() { - return addSuppressed != null; - } - - static final Method addSuppressed = addSuppressedMethodOrNull(); - - private static Method addSuppressedMethodOrNull() { - try { - return Throwable.class.getMethod("addSuppressed", Throwable.class); - } catch (Throwable e) { - return null; - } - } - - @Override - public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { - // ensure no exceptions from addSuppressed - if (thrown == suppressed) { - return; - } - try { - addSuppressed.invoke(thrown, suppressed); - } catch (Throwable e) { - // if, somehow, IllegalAccessException or another exception is thrown, fall back to logging - LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed); - } - } - } + private static final Suppressor SUPPRESSING_SUPPRESSOR = + (closeable, thrown, suppressed) -> { + // ensure no exceptions from addSuppressed + if (thrown == suppressed) { + return; + } + try { + thrown.addSuppressed(suppressed); + } catch (Throwable e) { + /* + * A Throwable is very unlikely, but we really don't want to throw from a Suppressor, so + * we catch everything. (Any Exception is either a RuntimeException or + * sneaky checked exception.) With no better options, we log anything to the same + * place as Closeables logs. + */ + Closeables.logger.log( + Level.WARNING, "Suppressing exception thrown when closing " + closeable, suppressed); + } + }; } diff --git a/android/guava/src/com/google/common/io/CountingInputStream.java b/android/guava/src/com/google/common/io/CountingInputStream.java index b015aca4bf01..c2f73f5ff114 100644 --- a/android/guava/src/com/google/common/io/CountingInputStream.java +++ b/android/guava/src/com/google/common/io/CountingInputStream.java @@ -16,8 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -28,7 +28,7 @@ * @author Chris Nokleberg * @since 1.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class CountingInputStream extends FilterInputStream { diff --git a/android/guava/src/com/google/common/io/CountingOutputStream.java b/android/guava/src/com/google/common/io/CountingOutputStream.java index 5d67a093dd23..c2273f8c5e3f 100644 --- a/android/guava/src/com/google/common/io/CountingOutputStream.java +++ b/android/guava/src/com/google/common/io/CountingOutputStream.java @@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -27,6 +28,7 @@ * @author Chris Nokleberg * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public final class CountingOutputStream extends FilterOutputStream { diff --git a/android/guava/src/com/google/common/io/FileBackedOutputStream.java b/android/guava/src/com/google/common/io/FileBackedOutputStream.java index 8783c5408889..520926b0f486 100644 --- a/android/guava/src/com/google/common/io/FileBackedOutputStream.java +++ b/android/guava/src/com/google/common/io/FileBackedOutputStream.java @@ -14,11 +14,15 @@ package com.google.common.io; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.concurrent.GuardedBy; +import com.google.j2objc.annotations.J2ObjCIncompatible; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -27,12 +31,20 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An {@link OutputStream} that starts buffering to a byte array, but switches to file buffering * once the data reaches a configurable size. * + *

    When this stream creates a temporary file, it restricts the file's permissions to the current + * user or, in the case of Android, the current app. If that is not possible (as is the case under + * the very old Android Ice Cream Sandwich release), then this stream throws an exception instead of + * creating a file that would be more accessible. (This behavior is new in Guava 32.0.0. Previous + * versions would create a file that is more accessible, as discussed in Guava issue 2575. TODO: b/283778848 - Fill + * in CVE number once it's available.) + * *

    Temporary files created by this stream may live in the local filesystem until either: * *

      @@ -50,25 +62,25 @@ * @since 1.0 */ @Beta +@J2ktIncompatible @GwtIncompatible +@J2ObjCIncompatible public final class FileBackedOutputStream extends OutputStream { private final int fileThreshold; private final boolean resetOnFinalize; private final ByteSource source; - @NullableDecl private final File parentDirectory; @GuardedBy("this") private OutputStream out; @GuardedBy("this") - private MemoryOutput memory; + private @Nullable MemoryOutput memory; @GuardedBy("this") - @NullableDecl - private File file; + private @Nullable File file; /** ByteArrayOutputStream that exposes its internals. */ - private static class MemoryOutput extends ByteArrayOutputStream { + private static final class MemoryOutput extends ByteArrayOutputStream { byte[] getBuffer() { return buf; } @@ -80,7 +92,7 @@ int getCount() { /** Returns the file holding the data (possibly null). */ @VisibleForTesting - synchronized File getFile() { + synchronized @Nullable File getFile() { return file; } @@ -89,6 +101,7 @@ synchronized File getFile() { * {@link ByteSource} returned by {@link #asByteSource} is finalized. * * @param fileThreshold the number of bytes before the stream should switch to buffering to a file + * @throws IllegalArgumentException if {@code fileThreshold} is negative */ public FileBackedOutputStream(int fileThreshold) { this(fileThreshold, false); @@ -101,16 +114,13 @@ public FileBackedOutputStream(int fileThreshold) { * @param fileThreshold the number of bytes before the stream should switch to buffering to a file * @param resetOnFinalize if true, the {@link #reset} method will be called when the {@link * ByteSource} returned by {@link #asByteSource} is finalized. + * @throws IllegalArgumentException if {@code fileThreshold} is negative */ public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) { - this(fileThreshold, resetOnFinalize, null); - } - - private FileBackedOutputStream( - int fileThreshold, boolean resetOnFinalize, @NullableDecl File parentDirectory) { + checkArgument( + fileThreshold >= 0, "fileThreshold must be non-negative, but was %s", fileThreshold); this.fileThreshold = fileThreshold; this.resetOnFinalize = resetOnFinalize; - this.parentDirectory = parentDirectory; memory = new MemoryOutput(); out = memory; @@ -122,6 +132,7 @@ public InputStream openStream() throws IOException { return openInputStream(); } + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 @Override protected void finalize() { try { @@ -155,6 +166,8 @@ private synchronized InputStream openInputStream() throws IOException { if (file != null) { return new FileInputStream(file); } else { + // requireNonNull is safe because we always have either `file` or `memory`. + requireNonNull(memory); return new ByteArrayInputStream(memory.getBuffer(), 0, memory.getCount()); } } @@ -218,20 +231,28 @@ public synchronized void flush() throws IOException { */ @GuardedBy("this") private void update(int len) throws IOException { - if (file == null && (memory.getCount() + len > fileThreshold)) { - File temp = File.createTempFile("FileBackedOutputStream", null, parentDirectory); + if (memory != null && (memory.getCount() + len > fileThreshold)) { + File temp = TempFileCreator.INSTANCE.createTempFile("FileBackedOutputStream"); if (resetOnFinalize) { // Finalizers are not guaranteed to be called on system shutdown; // this is insurance. temp.deleteOnExit(); } + FileOutputStream transfer = null; try { - FileOutputStream transfer = new FileOutputStream(temp); + transfer = new FileOutputStream(temp); transfer.write(memory.getBuffer(), 0, memory.getCount()); transfer.flush(); // We've successfully transferred the data; switch to writing to file out = transfer; } catch (IOException e) { + if (transfer != null) { + try { + transfer.close(); + } catch (IOException closeException) { + e.addSuppressed(closeException); + } + } temp.delete(); throw e; } diff --git a/android/guava/src/com/google/common/io/FileWriteMode.java b/android/guava/src/com/google/common/io/FileWriteMode.java index 2c69a2edb63b..c253b00afe1f 100644 --- a/android/guava/src/com/google/common/io/FileWriteMode.java +++ b/android/guava/src/com/google/common/io/FileWriteMode.java @@ -15,6 +15,7 @@ package com.google.common.io; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; /** * Modes for opening a file for writing. The default when mode when none is specified is to truncate @@ -22,6 +23,7 @@ * * @author Colin Decker */ +@J2ktIncompatible @GwtIncompatible public enum FileWriteMode { /** Specifies that writes to the opened file should append to the end of the file. */ diff --git a/android/guava/src/com/google/common/io/Files.java b/android/guava/src/com/google/common/io/Files.java index 73eb656602df..293e1980d598 100644 --- a/android/guava/src/com/google/common/io/Files.java +++ b/android/guava/src/com/google/common/io/Files.java @@ -17,21 +17,24 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.io.FileWriteMode.APPEND; +import static java.util.Collections.unmodifiableList; import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import com.google.common.graph.SuccessorsFunction; import com.google.common.graph.Traverser; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import com.google.j2objc.annotations.J2ObjCIncompatible; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -50,8 +53,8 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import org.jspecify.annotations.Nullable; /** * Provides utility methods for working with {@linkplain File files}. @@ -63,12 +66,10 @@ * @author Colin Decker * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public final class Files { - /** Maximum loop count when creating temp directories. */ - private static final int TEMP_DIR_ATTEMPTS = 10000; - private Files() {} /** @@ -82,7 +83,6 @@ private Files() {} * helpful predefined constants * @return the buffered reader */ - @Beta public static BufferedReader newReader(File file, Charset charset) throws FileNotFoundException { checkNotNull(file); checkNotNull(charset); @@ -101,7 +101,6 @@ public static BufferedReader newReader(File file, Charset charset) throws FileNo * helpful predefined constants * @return the buffered writer */ - @Beta public static BufferedWriter newWriter(File file, Charset charset) throws FileNotFoundException { checkNotNull(file); checkNotNull(charset); @@ -117,7 +116,9 @@ public static ByteSource asByteSource(File file) { return new FileByteSource(file); } - private static final class FileByteSource extends ByteSource { + private static final class FileByteSource extends + ByteSource + { private final File file; @@ -232,7 +233,6 @@ public static CharSink asCharSink(File file, Charset charset, FileWriteMode... m * (2^31 - 1) * @throws IOException if an I/O error occurs */ - @Beta public static byte[] toByteArray(File file) throws IOException { return asByteSource(file).read(); } @@ -245,11 +245,12 @@ public static byte[] toByteArray(File file) throws IOException { * helpful predefined constants * @return a string containing all the characters from the file * @throws IOException if an I/O error occurs - * @deprecated Prefer {@code asCharSource(file, charset).read()}. This method is scheduled to be - * removed in October 2019. + * @deprecated Prefer {@code asCharSource(file, charset).read()}. */ - @Beta @Deprecated + @InlineMe( + replacement = "Files.asCharSource(file, charset).read()", + imports = "com.google.common.io.Files") public static String toString(File file, Charset charset) throws IOException { return asCharSource(file, charset).read(); } @@ -264,7 +265,6 @@ public static String toString(File file, Charset charset) throws IOException { * @param to the destination file * @throws IOException if an I/O error occurs */ - @Beta public static void write(byte[] from, File to) throws IOException { asByteSink(to).write(from); } @@ -277,11 +277,12 @@ public static void write(byte[] from, File to) throws IOException { * @param charset the charset used to encode the output stream; see {@link StandardCharsets} for * helpful predefined constants * @throws IOException if an I/O error occurs - * @deprecated Prefer {@code asCharSink(to, charset).write(from)}. This method is scheduled to be - * removed in October 2019. + * @deprecated Prefer {@code asCharSink(to, charset).write(from)}. */ - @Beta @Deprecated + @InlineMe( + replacement = "Files.asCharSink(to, charset).write(from)", + imports = "com.google.common.io.Files") public static void write(CharSequence from, File to, Charset charset) throws IOException { asCharSink(to, charset).write(from); } @@ -296,7 +297,6 @@ public static void write(CharSequence from, File to, Charset charset) throws IOE * @param to the output stream * @throws IOException if an I/O error occurs */ - @Beta public static void copy(File from, OutputStream to) throws IOException { asByteSource(from).copyTo(to); } @@ -320,7 +320,6 @@ public static void copy(File from, OutputStream to) throws IOException { * @throws IOException if an I/O error occurs * @throws IllegalArgumentException if {@code from.equals(to)} */ - @Beta public static void copy(File from, File to) throws IOException { checkArgument(!from.equals(to), "Source %s and destination %s must be different", from, to); asByteSource(from).copyTo(asByteSink(to)); @@ -334,11 +333,12 @@ public static void copy(File from, File to) throws IOException { * helpful predefined constants * @param to the appendable object * @throws IOException if an I/O error occurs - * @deprecated Prefer {@code asCharSource(from, charset).copyTo(to)}. This method is scheduled to - * be removed in October 2019. + * @deprecated Prefer {@code asCharSource(from, charset).copyTo(to)}. */ - @Beta @Deprecated + @InlineMe( + replacement = "Files.asCharSource(from, charset).copyTo(to)", + imports = "com.google.common.io.Files") public static void copy(File from, Charset charset, Appendable to) throws IOException { asCharSource(from, charset).copyTo(to); @@ -352,11 +352,12 @@ static void copy(File from, Charset charset, Appendable to) throws IOException { * @param charset the charset used to encode the output stream; see {@link StandardCharsets} for * helpful predefined constants * @throws IOException if an I/O error occurs - * @deprecated Prefer {@code asCharSink(to, charset, FileWriteMode.APPEND).write(from)}. This - * method is scheduled to be removed in October 2019. + * @deprecated Prefer {@code asCharSink(to, charset, FileWriteMode.APPEND).write(from)}. */ - @Beta @Deprecated + @InlineMe( + replacement = "Files.asCharSink(to, charset, FileWriteMode.APPEND).write(from)", + imports = {"com.google.common.io.FileWriteMode", "com.google.common.io.Files"}) public static void append(CharSequence from, File to, Charset charset) throws IOException { asCharSink(to, charset, FileWriteMode.APPEND).write(from); @@ -367,7 +368,6 @@ static void append(CharSequence from, File to, Charset charset) throws IOExcepti * * @throws IOException if an I/O error occurs */ - @Beta public static boolean equal(File file1, File file2) throws IOException { checkNotNull(file1); checkNotNull(file2); @@ -392,17 +392,19 @@ public static boolean equal(File file1, File file2) throws IOException { * Atomically creates a new directory somewhere beneath the system's temporary directory (as * defined by the {@code java.io.tmpdir} system property), and returns its name. * + *

      The temporary directory is created with permissions restricted to the current user or, in + * the case of Android, the current app. If that is not possible (as is the case under the very + * old Android Ice Cream Sandwich release), then this method throws an exception instead of + * creating a directory that would be more accessible. (This behavior is new in Guava 32.0.0. + * Previous versions would create a directory that is more accessible, as discussed in CVE-2020-8908.) + * *

      Use this method instead of {@link File#createTempFile(String, String)} when you wish to * create a directory, not a regular file. A common pitfall is to call {@code createTempFile}, * delete the file and create a directory in its place, but this leads a race condition which can * be exploited to create security vulnerabilities, especially when executable files are to be * written into the directory. * - *

      Depending on the environmment that this code is run in, the system temporary directory (and - * thus the directory this method creates) may be more visible that a program would like - files - * written to this directory may be read or overwritten by hostile programs running on the same - * machine. - * *

      This method assumes that the temporary volume is writable, has free inodes and free blocks, * and that it will not be called thousands of times per second. * @@ -410,36 +412,26 @@ public static boolean equal(File file1, File file2) throws IOException { * java.nio.file.Files#createTempDirectory}. * * @return the newly-created directory - * @throws IllegalStateException if the directory could not be created + * @throws IllegalStateException if the directory could not be created, such as if the system does + * not support creating temporary directories securely * @deprecated For Android users, see the Data and File * Storage overview to select an appropriate temporary directory (perhaps {@code - * context.getCacheDir()}). For developers on Java 7 or later, use {@link + * context.getCacheDir()}), and create your own directory under that. (For example, you might + * use {@code new File(context.getCacheDir(), "directoryname").mkdir()}, or, if you need an + * arbitrary number of temporary directories, you might have to generate multiple directory + * names in a loop until {@code mkdir()} returns {@code true}.) For JRE users, prefer {@link * java.nio.file.Files#createTempDirectory}, transforming it to a {@link File} using {@link - * java.nio.file.Path#toFile() toFile()} if needed. + * java.nio.file.Path#toFile() toFile()} if needed. To restrict permissions as this method + * does, pass {@code + * PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"))} to your + * call to {@code createTempDirectory}. */ @Beta @Deprecated + @J2ObjCIncompatible public static File createTempDir() { - File baseDir = new File(System.getProperty("java.io.tmpdir")); - @SuppressWarnings("GoodTime") // reading system time without TimeSource - String baseName = System.currentTimeMillis() + "-"; - - for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { - File tempDir = new File(baseDir, baseName + counter); - if (tempDir.mkdir()) { - return tempDir; - } - } - throw new IllegalStateException( - "Failed to create directory within " - + TEMP_DIR_ATTEMPTS - + " attempts (tried " - + baseName - + "0 to " - + baseName - + (TEMP_DIR_ATTEMPTS - 1) - + ')'); + return TempFileCreator.INSTANCE.createTempDir(); } /** @@ -449,7 +441,6 @@ public static File createTempDir() { * @param file the file to create or update * @throws IOException if an I/O error occurs */ - @Beta @SuppressWarnings("GoodTime") // reading system time without TimeSource public static void touch(File file) throws IOException { checkNotNull(file); @@ -467,7 +458,6 @@ public static void touch(File file) throws IOException { * directories of the specified file could not be created. * @since 4.0 */ - @Beta public static void createParentDirs(File file) throws IOException { checkNotNull(file); File parent = file.getCanonicalFile().getParentFile(); @@ -498,7 +488,6 @@ public static void createParentDirs(File file) throws IOException { * @throws IOException if an I/O error occurs * @throws IllegalArgumentException if {@code from.equals(to)} */ - @Beta public static void move(File from, File to) throws IOException { checkNotNull(from); checkNotNull(to); @@ -524,13 +513,14 @@ public static void move(File from, File to) throws IOException { * helpful predefined constants * @return the first line, or null if the file is empty * @throws IOException if an I/O error occurs - * @deprecated Prefer {@code asCharSource(file, charset).readFirstLine()}. This method is - * scheduled to be removed in October 2019. + * @deprecated Prefer {@code asCharSource(file, charset).readFirstLine()}. */ - @Beta @Deprecated + @InlineMe( + replacement = "Files.asCharSource(file, charset).readFirstLine()", + imports = "com.google.common.io.Files") public - static String readFirstLine(File file, Charset charset) throws IOException { + static @Nullable String readFirstLine(File file, Charset charset) throws IOException { return asCharSource(file, charset).readFirstLine(); } @@ -550,14 +540,13 @@ static String readFirstLine(File file, Charset charset) throws IOException { * @return a mutable {@link List} containing all the lines * @throws IOException if an I/O error occurs */ - @Beta public static List readLines(File file, Charset charset) throws IOException { // don't use asCharSource(file, charset).readLines() because that returns // an immutable list, which would change the behavior of this method return asCharSource(file, charset) .readLines( new LineProcessor>() { - final List result = Lists.newArrayList(); + final List result = new ArrayList<>(); @Override public boolean processLine(String line) { @@ -582,14 +571,17 @@ public List getResult() { * @param callback the {@link LineProcessor} to use to handle the lines * @return the output of processing the lines * @throws IOException if an I/O error occurs - * @deprecated Prefer {@code asCharSource(file, charset).readLines(callback)}. This method is - * scheduled to be removed in October 2019. + * @deprecated Prefer {@code asCharSource(file, charset).readLines(callback)}. */ - @Beta @Deprecated + @InlineMe( + replacement = "Files.asCharSource(file, charset).readLines(callback)", + imports = "com.google.common.io.Files") @CanIgnoreReturnValue // some processors won't return a useful result + @ParametricNullness public - static T readLines(File file, Charset charset, LineProcessor callback) throws IOException { + static T readLines( + File file, Charset charset, LineProcessor callback) throws IOException { return asCharSource(file, charset).readLines(callback); } @@ -602,14 +594,17 @@ static T readLines(File file, Charset charset, LineProcessor callback) th * @param processor the object to which the bytes of the file are passed. * @return the result of the byte processor * @throws IOException if an I/O error occurs - * @deprecated Prefer {@code asByteSource(file).read(processor)}. This method is scheduled to be - * removed in October 2019. + * @deprecated Prefer {@code asByteSource(file).read(processor)}. */ - @Beta @Deprecated + @InlineMe( + replacement = "Files.asByteSource(file).read(processor)", + imports = "com.google.common.io.Files") @CanIgnoreReturnValue // some processors won't return a useful result + @ParametricNullness public - static T readBytes(File file, ByteProcessor processor) throws IOException { + static T readBytes(File file, ByteProcessor processor) + throws IOException { return asByteSource(file).read(processor); } @@ -621,11 +616,12 @@ static T readBytes(File file, ByteProcessor processor) throws IOException * @return the {@link HashCode} of all of the bytes in the file * @throws IOException if an I/O error occurs * @since 12.0 - * @deprecated Prefer {@code asByteSource(file).hash(hashFunction)}. This method is scheduled to - * be removed in October 2019. + * @deprecated Prefer {@code asByteSource(file).hash(hashFunction)}. */ - @Beta @Deprecated + @InlineMe( + replacement = "Files.asByteSource(file).hash(hashFunction)", + imports = "com.google.common.io.Files") public static HashCode hash(File file, HashFunction hashFunction) throws IOException { return asByteSource(file).hash(hashFunction); @@ -646,7 +642,6 @@ static HashCode hash(File file, HashFunction hashFunction) throws IOException { * @see FileChannel#map(MapMode, long, long) * @since 2.0 */ - @Beta public static MappedByteBuffer map(File file) throws IOException { checkNotNull(file); return map(file, MapMode.READ_ONLY); @@ -669,7 +664,6 @@ public static MappedByteBuffer map(File file) throws IOException { * @see FileChannel#map(MapMode, long, long) * @since 2.0 */ - @Beta public static MappedByteBuffer map(File file, MapMode mode) throws IOException { return mapInternal(file, mode, -1); } @@ -693,7 +687,6 @@ public static MappedByteBuffer map(File file, MapMode mode) throws IOException { * @see FileChannel#map(MapMode, long, long) * @since 2.0 */ - @Beta public static MappedByteBuffer map(File file, MapMode mode, long size) throws IOException { checkArgument(size >= 0, "size (%s) may not be negative", size); return mapInternal(file, mode, size); @@ -737,7 +730,6 @@ private static MappedByteBuffer mapInternal(File file, MapMode mode, long size) * * @since 11.0 */ - @Beta public static String simplifyPath(String pathname) { checkNotNull(pathname); if (pathname.length() == 0) { @@ -777,7 +769,7 @@ public static String simplifyPath(String pathname) { } if (result.equals("/..")) { result = "/"; - } else if ("".equals(result)) { + } else if (result.isEmpty()) { result = "."; } @@ -794,11 +786,12 @@ public static String simplifyPath(String pathname) { * behavior that the {@link File} API does not already account for. For example, on NTFS it will * report {@code "txt"} as the extension for the filename {@code "foo.exe:.txt"} even though NTFS * will drop the {@code ":.txt"} part of the name when the file is actually created on the - * filesystem due to NTFS's Alternate Data Streams. + * filesystem due to NTFS's Alternate + * Data Streams. * * @since 11.0 */ - @Beta public static String getFileExtension(String fullName) { checkNotNull(fullName); String fileName = new File(fullName).getName(); @@ -816,7 +809,6 @@ public static String getFileExtension(String fullName) { * @return The file name without its path or extension. * @since 14.0 */ - @Beta public static String getNameWithoutExtension(String file) { checkNotNull(file); String fileName = new File(file).getName(); @@ -846,25 +838,21 @@ public static String getNameWithoutExtension(String file) { * * @since 23.5 */ - @Beta public static Traverser fileTraverser() { return Traverser.forTree(FILE_TREE); } private static final SuccessorsFunction FILE_TREE = - new SuccessorsFunction() { - @Override - public Iterable successors(File file) { - // check isDirectory() just because it may be faster than listFiles() on a non-directory - if (file.isDirectory()) { - File[] files = file.listFiles(); - if (files != null) { - return Collections.unmodifiableList(Arrays.asList(files)); - } + file -> { + // check isDirectory() just because it may be faster than listFiles() on a non-directory + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + return unmodifiableList(Arrays.asList(files)); } - - return ImmutableList.of(); } + + return ImmutableList.of(); }; /** @@ -872,7 +860,6 @@ public Iterable successors(File file) { * * @since 15.0 */ - @Beta public static Predicate isDirectory() { return FilePredicate.IS_DIRECTORY; } @@ -882,7 +869,6 @@ public static Predicate isDirectory() { * * @since 15.0 */ - @Beta public static Predicate isFile() { return FilePredicate.IS_FILE; } diff --git a/android/guava/src/com/google/common/io/Flushables.java b/android/guava/src/com/google/common/io/Flushables.java index 9b1d6a096136..7e5e11275c0a 100644 --- a/android/guava/src/com/google/common/io/Flushables.java +++ b/android/guava/src/com/google/common/io/Flushables.java @@ -16,6 +16,7 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.Flushable; import java.io.IOException; import java.util.logging.Level; @@ -27,7 +28,7 @@ * @author Michael Lancaster * @since 1.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class Flushables { private static final Logger logger = Logger.getLogger(Flushables.class.getName()); @@ -47,6 +48,7 @@ private Flushables() {} * an {@code IOException}. * @see Closeables#close */ + @SuppressWarnings("IdentifierName") // See Closeables.close public static void flush(Flushable flushable, boolean swallowIOException) throws IOException { try { flushable.flush(); @@ -65,6 +67,7 @@ public static void flush(Flushable flushable, boolean swallowIOException) throws * * @param flushable the {@code Flushable} object to be flushed. */ + @Beta public static void flushQuietly(Flushable flushable) { try { flush(flushable, true); diff --git a/android/guava/src/com/google/common/io/IgnoreJRERequirement.java b/android/guava/src/com/google/common/io/IgnoreJRERequirement.java new file mode 100644 index 000000000000..132eb65f08c3 --- /dev/null +++ b/android/guava/src/com/google/common/io/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

      Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/io/InsecureRecursiveDeleteException.java b/android/guava/src/com/google/common/io/InsecureRecursiveDeleteException.java new file mode 100644 index 000000000000..87c132580f1f --- /dev/null +++ b/android/guava/src/com/google/common/io/InsecureRecursiveDeleteException.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.nio.file.FileSystemException; +import java.nio.file.SecureDirectoryStream; +import org.jspecify.annotations.Nullable; + +/** + * Exception indicating that a recursive delete can't be performed because the file system does not + * have the support necessary to guarantee that it is not vulnerable to race conditions that would + * allow it to delete files and directories outside of the directory being deleted (i.e., {@link + * SecureDirectoryStream} is not supported). + * + *

      {@link RecursiveDeleteOption#ALLOW_INSECURE} can be used to force the recursive delete method + * to proceed anyway. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + * @author Colin Decker + */ +@J2ktIncompatible +@GwtIncompatible +@J2ObjCIncompatible // java.nio.file +// Users are unlikely to use this unless they're already interacting with MoreFiles and Path. +@IgnoreJRERequirement +public final class InsecureRecursiveDeleteException extends FileSystemException { + + public InsecureRecursiveDeleteException(@Nullable String file) { + super(file, null, "unable to guarantee security of recursive delete"); + } +} diff --git a/android/guava/src/com/google/common/io/Java8Compatibility.java b/android/guava/src/com/google/common/io/Java8Compatibility.java index 62b5c2ea5b7f..f1cd446ed330 100644 --- a/android/guava/src/com/google/common/io/Java8Compatibility.java +++ b/android/guava/src/com/google/common/io/Java8Compatibility.java @@ -15,12 +15,14 @@ package com.google.common.io; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.nio.Buffer; /** * Wrappers around {@link Buffer} methods that are covariantly overridden in Java 9+. See * https://github.com/google/guava/issues/3990 */ +@J2ktIncompatible @GwtIncompatible final class Java8Compatibility { static void clear(Buffer b) { @@ -35,9 +37,17 @@ static void limit(Buffer b, int limit) { b.limit(limit); } + static void mark(Buffer b) { + b.mark(); + } + static void position(Buffer b, int position) { b.position(position); } + static void reset(Buffer b) { + b.reset(); + } + private Java8Compatibility() {} } diff --git a/android/guava/src/com/google/common/io/LineBuffer.java b/android/guava/src/com/google/common/io/LineBuffer.java index a8e775c72bdf..ab376ee570a0 100644 --- a/android/guava/src/com/google/common/io/LineBuffer.java +++ b/android/guava/src/com/google/common/io/LineBuffer.java @@ -15,6 +15,7 @@ package com.google.common.io; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; @@ -29,10 +30,12 @@ * @author Chris Nokleberg * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible abstract class LineBuffer { /** Holds partial line contents. */ private StringBuilder line = new StringBuilder(); + /** Whether a line ending with a CR is pending processing. */ private boolean sawReturn; diff --git a/android/guava/src/com/google/common/io/LineProcessor.java b/android/guava/src/com/google/common/io/LineProcessor.java index 65ded53a4701..aab83ad16e1c 100644 --- a/android/guava/src/com/google/common/io/LineProcessor.java +++ b/android/guava/src/com/google/common/io/LineProcessor.java @@ -14,10 +14,11 @@ package com.google.common.io; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; +import org.jspecify.annotations.Nullable; /** * A callback to be used with the streaming {@code readLines} methods. @@ -28,9 +29,9 @@ * @author Miles Barr * @since 1.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible -public interface LineProcessor { +public interface LineProcessor { /** * This method will be called once for each line. @@ -42,5 +43,6 @@ public interface LineProcessor { boolean processLine(String line) throws IOException; /** Return the result of processing all the lines. */ + @ParametricNullness T getResult(); } diff --git a/android/guava/src/com/google/common/io/LineReader.java b/android/guava/src/com/google/common/io/LineReader.java index 55d00b14a4b4..b313a8c9880e 100644 --- a/android/guava/src/com/google/common/io/LineReader.java +++ b/android/guava/src/com/google/common/io/LineReader.java @@ -17,15 +17,15 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.io.CharStreams.createBuffer; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.io.Reader; import java.nio.CharBuffer; import java.util.ArrayDeque; import java.util.Queue; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A class for reading lines of text. Provides the same functionality as {@link @@ -35,11 +35,11 @@ * @author Chris Nokleberg * @since 1.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class LineReader { private final Readable readable; - @NullableDecl private final Reader reader; + private final @Nullable Reader reader; private final CharBuffer cbuf = createBuffer(); private final char[] buf = cbuf.array(); @@ -68,7 +68,7 @@ public LineReader(Readable readable) { * @throws IOException if an I/O error occurs */ @CanIgnoreReturnValue // to skip a line - public String readLine() throws IOException { + public @Nullable String readLine() throws IOException { while (lines.peek() == null) { Java8Compatibility.clear(cbuf); // The default implementation of Reader#read(CharBuffer) allocates a diff --git a/android/guava/src/com/google/common/io/LittleEndianDataInputStream.java b/android/guava/src/com/google/common/io/LittleEndianDataInputStream.java index 8f5f23da41ed..9e2f05e7413c 100644 --- a/android/guava/src/com/google/common/io/LittleEndianDataInputStream.java +++ b/android/guava/src/com/google/common/io/LittleEndianDataInputStream.java @@ -14,8 +14,8 @@ package com.google.common.io; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Preconditions; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; @@ -39,7 +39,7 @@ * @author Keith Bottner * @since 8.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class LittleEndianDataInputStream extends FilterInputStream implements DataInput { @@ -79,7 +79,7 @@ public int skipBytes(int n) throws IOException { @Override public int readUnsignedByte() throws IOException { int b1 = in.read(); - if (0 > b1) { + if (b1 < 0) { throw new EOFException(); } @@ -230,7 +230,7 @@ public boolean readBoolean() throws IOException { private byte readAndCheckByte() throws IOException, EOFException { int b1 = in.read(); - if (-1 == b1) { + if (b1 == -1) { throw new EOFException(); } diff --git a/android/guava/src/com/google/common/io/LittleEndianDataOutputStream.java b/android/guava/src/com/google/common/io/LittleEndianDataOutputStream.java index e5e398f615ba..dd3746cc6781 100644 --- a/android/guava/src/com/google/common/io/LittleEndianDataOutputStream.java +++ b/android/guava/src/com/google/common/io/LittleEndianDataOutputStream.java @@ -14,10 +14,9 @@ package com.google.common.io; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Preconditions; -import com.google.common.primitives.Longs; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.FilterOutputStream; @@ -35,7 +34,7 @@ * @author Keith Bottner * @since 8.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput { @@ -142,8 +141,7 @@ public void writeInt(int v) throws IOException { */ @Override public void writeLong(long v) throws IOException { - byte[] bytes = Longs.toByteArray(Long.reverseBytes(v)); - write(bytes, 0, bytes.length); + ((DataOutputStream) out).writeLong(Long.reverseBytes(v)); } /** diff --git a/android/guava/src/com/google/common/io/MoreFiles.java b/android/guava/src/com/google/common/io/MoreFiles.java new file mode 100644 index 000000000000..26f03f57a19a --- /dev/null +++ b/android/guava/src/com/google/common/io/MoreFiles.java @@ -0,0 +1,857 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; +import static java.nio.file.LinkOption.NOFOLLOW_LINKS; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.graph.Traverser; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.SeekableByteChannel; +import java.nio.charset.Charset; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystemException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.NotDirectoryException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.SecureDirectoryStream; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + +/** + * Static utilities for use with {@link Path} instances, intended to complement {@link Files}. + * + *

      Many methods provided by Guava's {@code Files} class for {@link java.io.File} instances are + * now available via the JDK's {@link java.nio.file.Files} class for {@code Path} - check the JDK's + * class if a sibling method from {@code Files} appears to be missing from this class. + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + * @author Colin Decker + */ +@J2ktIncompatible +@GwtIncompatible +@J2ObjCIncompatible // java.nio.file +@IgnoreJRERequirement // Users will use this only if they're already using Path. +public final class MoreFiles { + + private MoreFiles() {} + + /** + * Returns a view of the given {@code path} as a {@link ByteSource}. + * + *

      Any {@linkplain OpenOption open options} provided are used when opening streams to the file + * and may affect the behavior of the returned source and the streams it provides. See {@link + * StandardOpenOption} for the standard options that may be provided. Providing no options is + * equivalent to providing the {@link StandardOpenOption#READ READ} option. + */ + public static ByteSource asByteSource(Path path, OpenOption... options) { + return new PathByteSource(path, options); + } + + @IgnoreJRERequirement // *should* be redundant with the one on MoreFiles itself + private static final class PathByteSource extends + ByteSource + { + + private static final LinkOption[] FOLLOW_LINKS = {}; + + private final Path path; + private final OpenOption[] options; + private final boolean followLinks; + + private PathByteSource(Path path, OpenOption... options) { + this.path = checkNotNull(path); + this.options = options.clone(); + this.followLinks = followLinks(this.options); + // TODO(cgdecker): validate the provided options... for example, just WRITE seems wrong + } + + private static boolean followLinks(OpenOption[] options) { + for (OpenOption option : options) { + if (option == NOFOLLOW_LINKS) { + return false; + } + } + return true; + } + + @Override + public InputStream openStream() throws IOException { + return Files.newInputStream(path, options); + } + + private BasicFileAttributes readAttributes() throws IOException { + return Files.readAttributes( + path, + BasicFileAttributes.class, + followLinks ? FOLLOW_LINKS : new LinkOption[] {NOFOLLOW_LINKS}); + } + + @Override + public Optional sizeIfKnown() { + BasicFileAttributes attrs; + try { + attrs = readAttributes(); + } catch (IOException e) { + // Failed to get attributes; we don't know the size. + return Optional.absent(); + } + + // Don't return a size for directories or symbolic links; their sizes are implementation + // specific and they can't be read as bytes using the read methods anyway. + if (attrs.isDirectory() || attrs.isSymbolicLink()) { + return Optional.absent(); + } + + return Optional.of(attrs.size()); + } + + @Override + public long size() throws IOException { + BasicFileAttributes attrs = readAttributes(); + + // Don't return a size for directories or symbolic links; their sizes are implementation + // specific and they can't be read as bytes using the read methods anyway. + if (attrs.isDirectory()) { + throw new IOException("can't read: is a directory"); + } else if (attrs.isSymbolicLink()) { + throw new IOException("can't read: is a symbolic link"); + } + + return attrs.size(); + } + + @Override + public byte[] read() throws IOException { + try (SeekableByteChannel channel = Files.newByteChannel(path, options)) { + return ByteStreams.toByteArray(Channels.newInputStream(channel), channel.size()); + } + } + + @Override + public CharSource asCharSource(Charset charset) { + if (options.length == 0) { + // If no OpenOptions were passed, delegate to Files.lines, which could have performance + // advantages. (If OpenOptions were passed we can't, because Files.lines doesn't have an + // overload taking OpenOptions, meaning we can't guarantee the same behavior w.r.t. things + // like following/not following symlinks.) + return new AsCharSource(charset) { + @SuppressWarnings("FilesLinesLeak") // the user needs to close it in this case + @Override + public Stream lines() throws IOException { + return Files.lines(path, charset); + } + }; + } + + return super.asCharSource(charset); + } + + @Override + public String toString() { + return "MoreFiles.asByteSource(" + path + ", " + Arrays.toString(options) + ")"; + } + } + + /** + * Returns a view of the given {@code path} as a {@link ByteSink}. + * + *

      Any {@linkplain OpenOption open options} provided are used when opening streams to the file + * and may affect the behavior of the returned sink and the streams it provides. See {@link + * StandardOpenOption} for the standard options that may be provided. Providing no options is + * equivalent to providing the {@link StandardOpenOption#CREATE CREATE}, {@link + * StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING} and {@link StandardOpenOption#WRITE + * WRITE} options. + */ + public static ByteSink asByteSink(Path path, OpenOption... options) { + return new PathByteSink(path, options); + } + + @IgnoreJRERequirement // *should* be redundant with the one on MoreFiles itself + private static final class PathByteSink extends ByteSink { + + private final Path path; + private final OpenOption[] options; + + private PathByteSink(Path path, OpenOption... options) { + this.path = checkNotNull(path); + this.options = options.clone(); + // TODO(cgdecker): validate the provided options... for example, just READ seems wrong + } + + @Override + public OutputStream openStream() throws IOException { + return Files.newOutputStream(path, options); + } + + @Override + public String toString() { + return "MoreFiles.asByteSink(" + path + ", " + Arrays.toString(options) + ")"; + } + } + + /** + * Returns a view of the given {@code path} as a {@link CharSource} using the given {@code + * charset}. + * + *

      Any {@linkplain OpenOption open options} provided are used when opening streams to the file + * and may affect the behavior of the returned source and the streams it provides. See {@link + * StandardOpenOption} for the standard options that may be provided. Providing no options is + * equivalent to providing the {@link StandardOpenOption#READ READ} option. + */ + public static CharSource asCharSource(Path path, Charset charset, OpenOption... options) { + return asByteSource(path, options).asCharSource(charset); + } + + /** + * Returns a view of the given {@code path} as a {@link CharSink} using the given {@code charset}. + * + *

      Any {@linkplain OpenOption open options} provided are used when opening streams to the file + * and may affect the behavior of the returned sink and the streams it provides. See {@link + * StandardOpenOption} for the standard options that may be provided. Providing no options is + * equivalent to providing the {@link StandardOpenOption#CREATE CREATE}, {@link + * StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING} and {@link StandardOpenOption#WRITE + * WRITE} options. + */ + public static CharSink asCharSink(Path path, Charset charset, OpenOption... options) { + return asByteSink(path, options).asCharSink(charset); + } + + /** + * Returns an immutable list of paths to the files contained in the given directory. + * + * @throws NoSuchFileException if the file does not exist (optional specific exception) + * @throws NotDirectoryException if the file could not be opened because it is not a directory + * (optional specific exception) + * @throws IOException if an I/O error occurs + */ + public static ImmutableList listFiles(Path dir) throws IOException { + try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + return ImmutableList.copyOf(stream); + } catch (DirectoryIteratorException e) { + throw e.getCause(); + } + } + + /** + * Returns a {@link Traverser} instance for the file and directory tree. The returned traverser + * starts from a {@link Path} and will return all files and directories it encounters. + * + *

      The returned traverser attempts to avoid following symbolic links to directories. However, + * the traverser cannot guarantee that it will not follow symbolic links to directories as it is + * possible for a directory to be replaced with a symbolic link between checking if the file is a + * directory and actually reading the contents of that directory. + * + *

      If the {@link Path} passed to one of the traversal methods does not exist or is not a + * directory, no exception will be thrown and the returned {@link Iterable} will contain a single + * element: that path. + * + *

      {@link DirectoryIteratorException} may be thrown when iterating {@link Iterable} instances + * created by this traverser if an {@link IOException} is thrown by a call to {@link + * #listFiles(Path)}. + * + *

      Example: {@code MoreFiles.fileTraverser().depthFirstPreOrder(Paths.get("/"))} may return the + * following paths: {@code ["/", "/etc", "/etc/config.txt", "/etc/fonts", "/home", "/home/alice", + * ...]} + * + * @since 23.5 + */ + public static Traverser fileTraverser() { + return Traverser.forTree(MoreFiles::fileTreeChildren); + } + + private static Iterable fileTreeChildren(Path dir) { + if (Files.isDirectory(dir, NOFOLLOW_LINKS)) { + try { + return listFiles(dir); + } catch (IOException e) { + // the exception thrown when iterating a DirectoryStream if an I/O exception occurs + throw new DirectoryIteratorException(e); + } + } + return ImmutableList.of(); + } + + /** + * Returns a predicate that returns the result of {@link java.nio.file.Files#isDirectory(Path, + * LinkOption...)} on input paths with the given link options. + */ + public static Predicate isDirectory(LinkOption... options) { + LinkOption[] optionsCopy = options.clone(); + return new Predicate() { + @Override + public boolean apply(Path input) { + return Files.isDirectory(input, optionsCopy); + } + + @Override + public String toString() { + return "MoreFiles.isDirectory(" + Arrays.toString(optionsCopy) + ")"; + } + }; + } + + /** Returns whether or not the file with the given name in the given dir is a directory. */ + private static boolean isDirectory( + SecureDirectoryStream dir, Path name, LinkOption... options) throws IOException { + return dir.getFileAttributeView(name, BasicFileAttributeView.class, options) + .readAttributes() + .isDirectory(); + } + + /** + * Returns a predicate that returns the result of {@link java.nio.file.Files#isRegularFile(Path, + * LinkOption...)} on input paths with the given link options. + */ + public static Predicate isRegularFile(LinkOption... options) { + LinkOption[] optionsCopy = options.clone(); + return new Predicate() { + @Override + public boolean apply(Path input) { + return Files.isRegularFile(input, optionsCopy); + } + + @Override + public String toString() { + return "MoreFiles.isRegularFile(" + Arrays.toString(optionsCopy) + ")"; + } + }; + } + + /** + * Returns true if the files located by the given paths exist, are not directories, and contain + * the same bytes. + * + * @throws IOException if an I/O error occurs + * @since 22.0 + */ + public static boolean equal(Path path1, Path path2) throws IOException { + checkNotNull(path1); + checkNotNull(path2); + if (Files.isSameFile(path1, path2)) { + return true; + } + + /* + * Some operating systems may return zero as the length for files denoting system-dependent + * entities such as devices or pipes, in which case we must fall back on comparing the bytes + * directly. + */ + ByteSource source1 = asByteSource(path1); + ByteSource source2 = asByteSource(path2); + long len1 = source1.sizeIfKnown().or(0L); + long len2 = source2.sizeIfKnown().or(0L); + if (len1 != 0 && len2 != 0 && len1 != len2) { + return false; + } + return source1.contentEquals(source2); + } + + /** + * Like the unix command of the same name, creates an empty file or updates the last modified + * timestamp of the existing file at the given path to the current system time. + */ + @SuppressWarnings("GoodTime") // reading system time without TimeSource + public static void touch(Path path) throws IOException { + checkNotNull(path); + + try { + Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis())); + } catch (NoSuchFileException e) { + try { + Files.createFile(path); + } catch (FileAlreadyExistsException ignore) { + // The file didn't exist when we called setLastModifiedTime, but it did when we called + // createFile, so something else created the file in between. The end result is + // what we wanted: a new file that probably has its last modified time set to approximately + // now. Or it could have an arbitrary last modified time set by the creator, but that's no + // different than if another process set its last modified time to something else after we + // created it here. + } + } + } + + /** + * Creates any necessary but nonexistent parent directories of the specified path. Note that if + * this operation fails, it may have succeeded in creating some (but not all) of the necessary + * parent directories. The parent directory is created with the given {@code attrs}. + * + * @throws IOException if an I/O error occurs, or if any necessary but nonexistent parent + * directories of the specified file could not be created. + */ + public static void createParentDirectories(Path path, FileAttribute... attrs) + throws IOException { + // Interestingly, unlike File.getCanonicalFile(), Path/Files provides no way of getting the + // canonical (absolute, normalized, symlinks resolved, etc.) form of a path to a nonexistent + // file. getCanonicalFile() can at least get the canonical form of the part of the path which + // actually exists and then append the normalized remainder of the path to that. + Path normalizedAbsolutePath = path.toAbsolutePath().normalize(); + Path parent = normalizedAbsolutePath.getParent(); + if (parent == null) { + // The given directory is a filesystem root. All zero of its ancestors exist. This doesn't + // mean that the root itself exists -- consider x:\ on a Windows machine without such a + // drive -- or even that the caller can create it, but this method makes no such guarantees + // even for non-root files. + return; + } + + // Check if the parent is a directory first because createDirectories will fail if the parent + // exists and is a symlink to a directory... we'd like for this to succeed in that case. + // (I'm kind of surprised that createDirectories would fail in that case; doesn't seem like + // what you'd want to happen.) + if (!Files.isDirectory(parent)) { + Files.createDirectories(parent, attrs); + if (!Files.isDirectory(parent)) { + throw new IOException("Unable to create parent directories of " + path); + } + } + } + + /** + * Returns the file extension for + * the file at the given path, or the empty string if the file has no extension. The result does + * not include the '{@code .}'. + * + *

      Note: This method simply returns everything after the last '{@code .}' in the file's + * name as determined by {@link Path#getFileName}. It does not account for any filesystem-specific + * behavior that the {@link Path} API does not already account for. For example, on NTFS it will + * report {@code "txt"} as the extension for the filename {@code "foo.exe:.txt"} even though NTFS + * will drop the {@code ":.txt"} part of the name when the file is actually created on the + * filesystem due to NTFS's Alternate + * Data Streams. + */ + public static String getFileExtension(Path path) { + Path name = path.getFileName(); + + // null for empty paths and root-only paths + if (name == null) { + return ""; + } + + String fileName = name.toString(); + int dotIndex = fileName.lastIndexOf('.'); + return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1); + } + + /** + * Returns the file name without its file extension or path. This is + * similar to the {@code basename} unix command. The result does not include the '{@code .}'. + */ + public static String getNameWithoutExtension(Path path) { + Path name = path.getFileName(); + + // null for empty paths and root-only paths + if (name == null) { + return ""; + } + + String fileName = name.toString(); + int dotIndex = fileName.lastIndexOf('.'); + return dotIndex == -1 ? fileName : fileName.substring(0, dotIndex); + } + + /** + * Deletes the file or directory at the given {@code path} recursively. Deletes symbolic links, + * not their targets (subject to the caveat below). + * + *

      If an I/O exception occurs attempting to read, open or delete any file under the given + * directory, this method skips that file and continues. All such exceptions are collected and, + * after attempting to delete all files, an {@code IOException} is thrown containing those + * exceptions as {@linkplain Throwable#getSuppressed() suppressed exceptions}. + * + *

      Warning: Security of recursive deletes

      + * + *

      On a file system that supports symbolic links and does not support {@link + * SecureDirectoryStream}, it is possible for a recursive delete to delete files and directories + * that are outside the directory being deleted. This can happen if, after checking that a + * file is a directory (and not a symbolic link), that directory is replaced by a symbolic link to + * an outside directory before the call that opens the directory to read its entries. + * + *

      By default, this method throws {@link InsecureRecursiveDeleteException} if it can't + * guarantee the security of recursive deletes. If you wish to allow the recursive deletes anyway, + * pass {@link RecursiveDeleteOption#ALLOW_INSECURE} to this method to override that behavior. + * + * @throws NoSuchFileException if {@code path} does not exist (optional specific exception) + * @throws InsecureRecursiveDeleteException if the security of recursive deletes can't be + * guaranteed for the file system and {@link RecursiveDeleteOption#ALLOW_INSECURE} was not + * specified + * @throws IOException if {@code path} or any file in the subtree rooted at it can't be deleted + * for any reason + */ + public static void deleteRecursively(Path path, RecursiveDeleteOption... options) + throws IOException { + Path parentPath = getParentPath(path); + if (parentPath == null) { + throw new FileSystemException(path.toString(), null, "can't delete recursively"); + } + + Collection exceptions = null; // created lazily if needed + try { + boolean sdsSupported = false; + try (DirectoryStream parent = Files.newDirectoryStream(parentPath)) { + if (parent instanceof SecureDirectoryStream) { + sdsSupported = true; + exceptions = + deleteRecursivelySecure( + (SecureDirectoryStream) parent, + /* + * requireNonNull is safe because paths have file names when they have parents, + * and we checked for a parent at the beginning of the method. + */ + requireNonNull(path.getFileName())); + } + } + + if (!sdsSupported) { + checkAllowsInsecure(path, options); + exceptions = deleteRecursivelyInsecure(path); + } + } catch (IOException e) { + if (exceptions == null) { + throw e; + } else { + exceptions.add(e); + } + } + + if (exceptions != null) { + throwDeleteFailed(path, exceptions); + } + } + + /** + * Deletes all files within the directory at the given {@code path} {@linkplain #deleteRecursively + * recursively}. Does not delete the directory itself. Deletes symbolic links, not their targets + * (subject to the caveat below). If {@code path} itself is a symbolic link to a directory, that + * link is followed and the contents of the directory it targets are deleted. + * + *

      If an I/O exception occurs attempting to read, open or delete any file under the given + * directory, this method skips that file and continues. All such exceptions are collected and, + * after attempting to delete all files, an {@code IOException} is thrown containing those + * exceptions as {@linkplain Throwable#getSuppressed() suppressed exceptions}. + * + *

      Warning: Security of recursive deletes

      + * + *

      On a file system that supports symbolic links and does not support {@link + * SecureDirectoryStream}, it is possible for a recursive delete to delete files and directories + * that are outside the directory being deleted. This can happen if, after checking that a + * file is a directory (and not a symbolic link), that directory is replaced by a symbolic link to + * an outside directory before the call that opens the directory to read its entries. + * + *

      By default, this method throws {@link InsecureRecursiveDeleteException} if it can't + * guarantee the security of recursive deletes. If you wish to allow the recursive deletes anyway, + * pass {@link RecursiveDeleteOption#ALLOW_INSECURE} to this method to override that behavior. + * + * @throws NoSuchFileException if {@code path} does not exist (optional specific exception) + * @throws NotDirectoryException if the file at {@code path} is not a directory (optional + * specific exception) + * @throws InsecureRecursiveDeleteException if the security of recursive deletes can't be + * guaranteed for the file system and {@link RecursiveDeleteOption#ALLOW_INSECURE} was not + * specified + * @throws IOException if one or more files can't be deleted for any reason + */ + public static void deleteDirectoryContents(Path path, RecursiveDeleteOption... options) + throws IOException { + Collection exceptions = null; // created lazily if needed + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + if (stream instanceof SecureDirectoryStream) { + SecureDirectoryStream sds = (SecureDirectoryStream) stream; + exceptions = deleteDirectoryContentsSecure(sds); + } else { + checkAllowsInsecure(path, options); + exceptions = deleteDirectoryContentsInsecure(stream); + } + } catch (IOException e) { + if (exceptions == null) { + throw e; + } else { + exceptions.add(e); + } + } + + if (exceptions != null) { + throwDeleteFailed(path, exceptions); + } + } + + /** + * Secure recursive delete using {@code SecureDirectoryStream}. Returns a collection of exceptions + * that occurred or null if no exceptions were thrown. + */ + private static @Nullable Collection deleteRecursivelySecure( + SecureDirectoryStream dir, Path path) { + Collection exceptions = null; + try { + if (isDirectory(dir, path, NOFOLLOW_LINKS)) { + try (SecureDirectoryStream childDir = dir.newDirectoryStream(path, NOFOLLOW_LINKS)) { + exceptions = deleteDirectoryContentsSecure(childDir); + } + + // If exceptions is not null, something went wrong trying to delete the contents of the + // directory, so we shouldn't try to delete the directory as it will probably fail. + if (exceptions == null) { + dir.deleteDirectory(path); + } + } else { + dir.deleteFile(path); + } + + return exceptions; + } catch (IOException e) { + return addException(exceptions, e); + } + } + + /** + * Secure method for deleting the contents of a directory using {@code SecureDirectoryStream}. + * Returns a collection of exceptions that occurred or null if no exceptions were thrown. + */ + private static @Nullable Collection deleteDirectoryContentsSecure( + SecureDirectoryStream dir) { + Collection exceptions = null; + try { + for (Path path : dir) { + exceptions = concat(exceptions, deleteRecursivelySecure(dir, path.getFileName())); + } + + return exceptions; + } catch (DirectoryIteratorException e) { + return addException(exceptions, e.getCause()); + } + } + + /** + * Insecure recursive delete for file systems that don't support {@code SecureDirectoryStream}. + * Returns a collection of exceptions that occurred or null if no exceptions were thrown. + */ + private static @Nullable Collection deleteRecursivelyInsecure(Path path) { + Collection exceptions = null; + try { + if (Files.isDirectory(path, NOFOLLOW_LINKS)) { + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + exceptions = deleteDirectoryContentsInsecure(stream); + } + } + + // If exceptions is not null, something went wrong trying to delete the contents of the + // directory, so we shouldn't try to delete the directory as it will probably fail. + if (exceptions == null) { + Files.delete(path); + } + + return exceptions; + } catch (IOException e) { + return addException(exceptions, e); + } + } + + /** + * Simple, insecure method for deleting the contents of a directory for file systems that don't + * support {@code SecureDirectoryStream}. Returns a collection of exceptions that occurred or null + * if no exceptions were thrown. + */ + private static @Nullable Collection deleteDirectoryContentsInsecure( + DirectoryStream dir) { + Collection exceptions = null; + try { + for (Path entry : dir) { + exceptions = concat(exceptions, deleteRecursivelyInsecure(entry)); + } + + return exceptions; + } catch (DirectoryIteratorException e) { + return addException(exceptions, e.getCause()); + } + } + + /** + * Returns a path to the parent directory of the given path. If the path actually has a parent + * path, this is simple. Otherwise, we need to do some trickier things. Returns null if the path + * is a root or is the empty path. + */ + private static @Nullable Path getParentPath(Path path) { + Path parent = path.getParent(); + + // Paths that have a parent: + if (parent != null) { + // "/foo" ("/") + // "foo/bar" ("foo") + // "C:\foo" ("C:\") + // "\foo" ("\" - current drive for process on Windows) + // "C:foo" ("C:" - working dir of drive C on Windows) + return parent; + } + + // Paths that don't have a parent: + if (path.getNameCount() == 0) { + // "/", "C:\", "\" (no parent) + // "" (undefined, though typically parent of working dir) + // "C:" (parent of working dir of drive C on Windows) + // + // For working dir paths ("" and "C:"), return null because: + // A) it's not specified that "" is the path to the working directory. + // B) if we're getting this path for recursive delete, it's typically not possible to + // delete the working dir with a relative path anyway, so it's ok to fail. + // C) if we're getting it for opening a new SecureDirectoryStream, there's no need to get + // the parent path anyway since we can safely open a DirectoryStream to the path without + // worrying about a symlink. + return null; + } else { + // "foo" (working dir) + return path.getFileSystem().getPath("."); + } + } + + /** Checks that the given options allow an insecure delete, throwing an exception if not. */ + private static void checkAllowsInsecure(Path path, RecursiveDeleteOption[] options) + throws InsecureRecursiveDeleteException { + if (!Arrays.asList(options).contains(RecursiveDeleteOption.ALLOW_INSECURE)) { + throw new InsecureRecursiveDeleteException(path.toString()); + } + } + + /** + * Adds the given exception to the given collection, creating the collection if it's null. Returns + * the collection. + */ + private static Collection addException( + @Nullable Collection exceptions, IOException e) { + if (exceptions == null) { + exceptions = new ArrayList<>(); // don't need Set semantics + } + exceptions.add(e); + return exceptions; + } + + /** + * Concatenates the contents of the two given collections of exceptions. If either collection is + * null, the other collection is returned. Otherwise, the elements of {@code other} are added to + * {@code exceptions} and {@code exceptions} is returned. + */ + private static @Nullable Collection concat( + @Nullable Collection exceptions, @Nullable Collection other) { + if (exceptions == null) { + return other; + } else if (other != null) { + exceptions.addAll(other); + } + return exceptions; + } + + /** + * Throws an exception indicating that one or more files couldn't be deleted when deleting {@code + * path} or its contents. + * + *

      If there is only one exception in the collection, and it is a {@link NoSuchFileException} + * thrown because {@code path} itself didn't exist, then throws that exception. Otherwise, the + * thrown exception contains all the exceptions in the given collection as suppressed exceptions. + */ + private static void throwDeleteFailed(Path path, Collection exceptions) + throws FileSystemException { + NoSuchFileException pathNotFound = pathNotFound(path, exceptions); + if (pathNotFound != null) { + throw pathNotFound; + } + // TODO(cgdecker): Should there be a custom exception type for this? + // Also, should we try to include the Path of each file we may have failed to delete rather + // than just the exceptions that occurred? + FileSystemException deleteFailed = + new FileSystemException( + path.toString(), + null, + "failed to delete one or more files; see suppressed exceptions for details"); + for (IOException e : exceptions) { + deleteFailed.addSuppressed(e); + } + throw deleteFailed; + } + + private static @Nullable NoSuchFileException pathNotFound( + Path path, Collection exceptions) { + if (exceptions.size() != 1) { + return null; + } + IOException exception = getOnlyElement(exceptions); + if (!(exception instanceof NoSuchFileException)) { + return null; + } + NoSuchFileException noSuchFileException = (NoSuchFileException) exception; + String exceptionFile = noSuchFileException.getFile(); + if (exceptionFile == null) { + /* + * It's not clear whether this happens in practice, especially with the filesystem + * implementations that are built into java.nio. + */ + return null; + } + Path parentPath = getParentPath(path); + if (parentPath == null) { + /* + * This is probably impossible: + * + * - In deleteRecursively, we require the path argument to have a parent. + * + * - In deleteDirectoryContents, the path argument may have no parent. Fortunately, all the + * *other* paths we process will be descendants of that. That leaves only the original path + * argument for us to consider. And the only place we call pathNotFound is from + * throwDeleteFailed, and the other place that we call throwDeleteFailed inside + * deleteDirectoryContents is when an exception is thrown during the recursive steps. Any + * failure during the initial lookup of the path argument itself is rethrown directly. So + * any exception that we're seeing here is from a descendant, which naturally has a parent. + * I think. + * + * Still, if this can happen somehow (a weird filesystem implementation that lets callers + * change its working directly concurrently with a call to deleteDirectoryContents?), it makes + * more sense for us to fall back to a generic FileSystemException (by returning null here) + * than to dereference parentPath and end up producing NullPointerException. + */ + return null; + } + // requireNonNull is safe because paths have file names when they have parents. + Path pathResolvedFromParent = parentPath.resolve(requireNonNull(path.getFileName())); + if (exceptionFile.equals(pathResolvedFromParent.toString())) { + return noSuchFileException; + } + return null; + } +} diff --git a/android/guava/src/com/google/common/io/MultiInputStream.java b/android/guava/src/com/google/common/io/MultiInputStream.java index bae8e391964a..e2924cbe7367 100644 --- a/android/guava/src/com/google/common/io/MultiInputStream.java +++ b/android/guava/src/com/google/common/io/MultiInputStream.java @@ -17,10 +17,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An {@link InputStream} that concatenates multiple substreams. At most one stream will be open at @@ -29,11 +30,12 @@ * @author Chris Nokleberg * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible final class MultiInputStream extends InputStream { - private Iterator it; - @NullableDecl private InputStream in; + private final Iterator it; + private @Nullable InputStream in; /** * Creates a new instance. @@ -90,7 +92,8 @@ public int read() throws IOException { } @Override - public int read(@NullableDecl byte[] b, int off, int len) throws IOException { + public int read(byte[] b, int off, int len) throws IOException { + checkNotNull(b); while (in != null) { int result = in.read(b, off, len); if (result != -1) { diff --git a/android/guava/src/com/google/common/io/MultiReader.java b/android/guava/src/com/google/common/io/MultiReader.java index d075727ef08f..380445ad5f26 100644 --- a/android/guava/src/com/google/common/io/MultiReader.java +++ b/android/guava/src/com/google/common/io/MultiReader.java @@ -14,12 +14,15 @@ package com.google.common.io; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Preconditions; import java.io.IOException; import java.io.Reader; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A {@link Reader} that concatenates multiple readers. @@ -27,10 +30,11 @@ * @author Bin Zhu * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible -class MultiReader extends Reader { +final class MultiReader extends Reader { private final Iterator it; - @NullableDecl private Reader current; + private @Nullable Reader current; MultiReader(Iterator readers) throws IOException { this.it = readers; @@ -46,7 +50,8 @@ private void advance() throws IOException { } @Override - public int read(@NullableDecl char[] cbuf, int off, int len) throws IOException { + public int read(char[] cbuf, int off, int len) throws IOException { + checkNotNull(cbuf); if (current == null) { return -1; } diff --git a/android/guava/src/com/google/common/io/ParametricNullness.java b/android/guava/src/com/google/common/io/ParametricNullness.java new file mode 100644 index 000000000000..48773c8718a1 --- /dev/null +++ b/android/guava/src/com/google/common/io/ParametricNullness.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

        + *
      • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
      • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
      + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
        + *
      • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
      • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
      + * + *

      Consumers of this annotation include: + * + *

        + *
      • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
      • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
      + * + *

      This annotation is a temporary hack. We will remove it after tools no longer need + * it. + */ +@GwtCompatible +@Retention(CLASS) +@Target({FIELD, METHOD, PARAMETER}) +@interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/io/PatternFilenameFilter.java b/android/guava/src/com/google/common/io/PatternFilenameFilter.java index 43e4f30154fa..e92eb66528c6 100644 --- a/android/guava/src/com/google/common/io/PatternFilenameFilter.java +++ b/android/guava/src/com/google/common/io/PatternFilenameFilter.java @@ -14,14 +14,13 @@ package com.google.common.io; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Preconditions; import java.io.File; import java.io.FilenameFilter; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; /** * File name filter that only accepts files matching a regular expression. This class is thread-safe @@ -30,7 +29,7 @@ * @author Apple Chow * @since 1.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class PatternFilenameFilter implements FilenameFilter { @@ -55,8 +54,21 @@ public PatternFilenameFilter(Pattern pattern) { this.pattern = Preconditions.checkNotNull(pattern); } + /* + * Our implementation works fine with a null `dir`. However, there's nothing in the documentation + * of the supertype that suggests that implementations are expected to tolerate null. That said, I + * see calls in Google code that pass a null `dir` to a FilenameFilter.... So let's declare the + * parameter as non-nullable (since passing null to a FilenameFilter is unsafe in general), but if + * someone still manages to pass null, let's continue to have the method work. + * + * (PatternFilenameFilter is of course one of those classes that shouldn't be a publicly visible + * class to begin with but rather something returned from a static factory method whose declared + * return type is plain FilenameFilter. If we made such a change, then the annotation we choose + * here would have no significance to end users, who would be forced to conform to the signature + * used in FilenameFilter.) + */ @Override - public boolean accept(@NullableDecl File dir, String fileName) { + public boolean accept(File dir, String fileName) { return pattern.matcher(fileName).matches(); } } diff --git a/android/guava/src/com/google/common/io/ReaderInputStream.java b/android/guava/src/com/google/common/io/ReaderInputStream.java index eeb64e4e0d8c..8ee90b9350c9 100644 --- a/android/guava/src/com/google/common/io/ReaderInputStream.java +++ b/android/guava/src/com/google/common/io/ReaderInputStream.java @@ -17,9 +17,11 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Byte.toUnsignedInt; +import static java.lang.Math.min; import com.google.common.annotations.GwtIncompatible; -import com.google.common.primitives.UnsignedBytes; +import com.google.common.annotations.J2ktIncompatible; import java.io.IOException; import java.io.InputStream; import java.io.Reader; @@ -43,6 +45,7 @@ * * @author Chris Nokleberg */ +@J2ktIncompatible @GwtIncompatible final class ReaderInputStream extends InputStream { private final Reader reader; @@ -64,8 +67,10 @@ final class ReaderInputStream extends InputStream { /** Whether we've finished reading the reader. */ private boolean endOfInput; + /** Whether we're copying encoded bytes to the caller's buffer. */ private boolean draining; + /** Whether we've successfully flushed the encoder. */ private boolean doneFlushing; @@ -116,7 +121,7 @@ public void close() throws IOException { @Override public int read() throws IOException { - return (read(singleByte) == 1) ? UnsignedBytes.toInt(singleByte[0]) : -1; + return (read(singleByte) == 1) ? toUnsignedInt(singleByte[0]) : -1; } // TODO(chrisn): Consider trying to encode/flush directly to the argument byte @@ -197,8 +202,8 @@ private static CharBuffer grow(CharBuffer buf) { /** Handle the case of underflow caused by needing more input characters. */ private void readMoreChars() throws IOException { // Possibilities: - // 1) array has space available on right hand side (between limit and capacity) - // 2) array has space available on left hand side (before position) + // 1) array has space available on right-hand side (between limit and capacity) + // 2) array has space available on left-hand side (before position) // 3) array has no space available // // In case 2 we shift the existing chars to the left, and in case 3 we create a bigger @@ -248,7 +253,7 @@ private void startDraining(boolean overflow) { * number of characters copied. */ private int drain(byte[] b, int off, int len) { - int remaining = Math.min(len, byteBuffer.remaining()); + int remaining = min(len, byteBuffer.remaining()); byteBuffer.get(b, off, remaining); return remaining; } diff --git a/android/guava/src/com/google/common/io/RecursiveDeleteOption.java b/android/guava/src/com/google/common/io/RecursiveDeleteOption.java new file mode 100644 index 000000000000..c7cb270c8d44 --- /dev/null +++ b/android/guava/src/com/google/common/io/RecursiveDeleteOption.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.nio.file.SecureDirectoryStream; + +/** + * Options for use with recursive delete methods ({@link MoreFiles#deleteRecursively} and {@link + * MoreFiles#deleteDirectoryContents}). + * + * @since 33.4.0 (but since 21.0 in the JRE flavor) + * @author Colin Decker + */ +@J2ktIncompatible +@GwtIncompatible +@J2ObjCIncompatible // java.nio.file +public enum RecursiveDeleteOption { + /** + * Specifies that the recursive delete should not throw an exception when it can't be guaranteed + * that it can be done securely, without vulnerability to race conditions (i.e. when the file + * system does not support {@link SecureDirectoryStream}). + * + *

      Warning: On a file system that supports symbolic links, it is possible for an + * insecure recursive delete to delete files and directories that are outside the directory + * being deleted. This can happen if, after checking that a file is a directory (and not a + * symbolic link), that directory is deleted and replaced by a symbolic link to an outside + * directory before the call that opens the directory to read its entries. File systems that + * support {@code SecureDirectoryStream} do not have this vulnerability. + */ + ALLOW_INSECURE +} diff --git a/android/guava/src/com/google/common/io/Resources.java b/android/guava/src/com/google/common/io/Resources.java index d64bf3d51d65..b10484871229 100644 --- a/android/guava/src/com/google/common/io/Resources.java +++ b/android/guava/src/com/google/common/io/Resources.java @@ -17,32 +17,31 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Charsets; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.MoreObjects; -import com.google.common.collect.Lists; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; /** * Provides utility methods for working with resources in the classpath. Note that even though these * methods use {@link URL} parameters, they are usually not appropriate for HTTP or other * non-classpath resources. * - *

      All method parameters must be non-null unless documented otherwise. - * * @author Chris Nokleberg * @author Ben Yu * @author Colin Decker * @since 1.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class Resources { private Resources() {} @@ -100,8 +99,8 @@ public static byte[] toByteArray(URL url) throws IOException { * Reads all characters from a URL into a {@link String}, using the given character set. * * @param url the URL to read from - * @param charset the charset used to decode the input stream; see {@link Charsets} for helpful - * predefined constants + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants * @return a string containing all the characters from the URL * @throws IOException if an I/O error occurs. */ @@ -114,15 +113,16 @@ public static String toString(URL url, Charset charset) throws IOException { * lines. * * @param url the URL to read from - * @param charset the charset used to decode the input stream; see {@link Charsets} for helpful - * predefined constants + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants * @param callback the LineProcessor to use to handle the lines * @return the output of processing the lines * @throws IOException if an I/O error occurs */ @CanIgnoreReturnValue // some processors won't return a useful result - public static T readLines(URL url, Charset charset, LineProcessor callback) - throws IOException { + @ParametricNullness + public static T readLines( + URL url, Charset charset, LineProcessor callback) throws IOException { return asCharSource(url, charset).readLines(callback); } @@ -134,8 +134,8 @@ public static T readLines(URL url, Charset charset, LineProcessor callbac * Resources.asCharSource(url, charset).readLines()}. * * @param url the URL to read from - * @param charset the charset used to decode the input stream; see {@link Charsets} for helpful - * predefined constants + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants * @return a mutable {@link List} containing all the lines * @throws IOException if an I/O error occurs */ @@ -146,7 +146,7 @@ public static List readLines(URL url, Charset charset) throws IOExceptio url, charset, new LineProcessor>() { - final List result = Lists.newArrayList(); + final List result = new ArrayList<>(); @Override public boolean processLine(String line) { diff --git a/android/guava/src/com/google/common/io/TempFileCreator.java b/android/guava/src/com/google/common/io/TempFileCreator.java new file mode 100644 index 000000000000..6089cffece64 --- /dev/null +++ b/android/guava/src/com/google/common/io/TempFileCreator.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; +import static com.google.common.base.StandardSystemProperty.USER_NAME; +import static com.google.common.base.Throwables.throwIfUnchecked; +import static java.nio.file.attribute.AclEntryFlag.DIRECTORY_INHERIT; +import static java.nio.file.attribute.AclEntryFlag.FILE_INHERIT; +import static java.nio.file.attribute.AclEntryType.ALLOW; +import static java.nio.file.attribute.PosixFilePermissions.asFileAttribute; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.FileSystems; +import java.nio.file.Paths; +import java.nio.file.attribute.AclEntry; +import java.nio.file.attribute.AclEntryPermission; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermissions; +import java.nio.file.attribute.UserPrincipal; +import java.util.EnumSet; +import java.util.Set; + +/** + * Creates temporary files and directories whose permissions are restricted to the current user or, + * in the case of Android, the current app. If that is not possible (as is the case under the very + * old Android Ice Cream Sandwich release), then this class throws an exception instead of creating + * a file or directory that would be more accessible. + */ +@J2ktIncompatible +@GwtIncompatible +@J2ObjCIncompatible +abstract class TempFileCreator { + static final TempFileCreator INSTANCE = pickSecureCreator(); + + /** + * @throws IllegalStateException if the directory could not be created (to implement the contract + * of {@link Files#createTempDir()}, such as if the system does not support creating temporary + * directories securely + */ + abstract File createTempDir(); + + abstract File createTempFile(String prefix) throws IOException; + + private static TempFileCreator pickSecureCreator() { + try { + Class.forName("java.nio.file.Path"); + return new JavaNioCreator(); + } catch (ClassNotFoundException runningUnderAndroid) { + // Try another way. + } + + try { + int version = (int) Class.forName("android.os.Build$VERSION").getField("SDK_INT").get(null); + int jellyBean = + (int) Class.forName("android.os.Build$VERSION_CODES").getField("JELLY_BEAN").get(null); + /* + * I assume that this check can't fail because JELLY_BEAN will be present only if we're + * running under Jelly Bean or higher. But it seems safest to check. + */ + if (version < jellyBean) { + return new ThrowingCreator(); + } + } catch (ReflectiveOperationException e) { + // Should be impossible, but we want to return *something* so that class init succeeds. + return new ThrowingCreator(); + } + + // Android isolates apps' temporary directories since Jelly Bean: + // https://github.com/google/guava/issues/4011#issuecomment-770020802 + // So we can create files there with any permissions and still get security from the isolation. + return new JavaIoCreator(); + } + + /** + * Creates the permissions normally used for Windows filesystems, looking up the user afresh, even + * if previous calls have initialized the {@code PermissionSupplier} fields. + * + *

      This lets us test the effects of different values of the {@code user.name} system property + * without needing a separate VM or classloader. + */ + @IgnoreJRERequirement // used only when Path is available (and only from tests) + @VisibleForTesting + static void testMakingUserPermissionsFromScratch() throws IOException { + // All we're testing is whether it throws. + FileAttribute unused = JavaNioCreator.userPermissions().get(); + } + + @IgnoreJRERequirement // used only when Path is available + private static final class JavaNioCreator extends TempFileCreator { + @Override + File createTempDir() { + try { + return java.nio.file.Files.createTempDirectory( + Paths.get(JAVA_IO_TMPDIR.value()), /* prefix= */ null, directoryPermissions.get()) + .toFile(); + } catch (IOException e) { + throw new IllegalStateException("Failed to create directory", e); + } + } + + @Override + File createTempFile(String prefix) throws IOException { + return java.nio.file.Files.createTempFile( + Paths.get(JAVA_IO_TMPDIR.value()), + /* prefix= */ prefix, + /* suffix= */ null, + filePermissions.get()) + .toFile(); + } + + @IgnoreJRERequirement // see enclosing class (whose annotation Animal Sniffer ignores here...) + private interface PermissionSupplier { + FileAttribute get() throws IOException; + } + + private static final PermissionSupplier filePermissions; + private static final PermissionSupplier directoryPermissions; + + static { + Set views = FileSystems.getDefault().supportedFileAttributeViews(); + if (views.contains("posix")) { + filePermissions = () -> asFileAttribute(PosixFilePermissions.fromString("rw-------")); + directoryPermissions = () -> asFileAttribute(PosixFilePermissions.fromString("rwx------")); + } else if (views.contains("acl")) { + filePermissions = directoryPermissions = userPermissions(); + } else { + filePermissions = + directoryPermissions = + () -> { + throw new IOException("unrecognized FileSystem type " + FileSystems.getDefault()); + }; + } + } + + private static PermissionSupplier userPermissions() { + try { + UserPrincipal user = + FileSystems.getDefault() + .getUserPrincipalLookupService() + .lookupPrincipalByName(getUsername()); + ImmutableList acl = + ImmutableList.of( + AclEntry.newBuilder() + .setType(ALLOW) + .setPrincipal(user) + .setPermissions(EnumSet.allOf(AclEntryPermission.class)) + .setFlags(DIRECTORY_INHERIT, FILE_INHERIT) + .build()); + FileAttribute> attribute = + new FileAttribute>() { + @Override + public String name() { + return "acl:acl"; + } + + @Override + public ImmutableList value() { + return acl; + } + }; + return () -> attribute; + } catch (IOException e) { + // We throw a new exception each time so that the stack trace is right. + return () -> { + throw new IOException("Could not find user", e); + }; + } + } + + private static String getUsername() { + /* + * https://github.com/google/guava/issues/6634: ProcessHandle has more accurate information, + * but that class isn't available under all environments that we support. We use it if + * available and fall back if not. + */ + String fromSystemProperty = requireNonNull(USER_NAME.value()); + + try { + Class processHandleClass = Class.forName("java.lang.ProcessHandle"); + Class processHandleInfoClass = Class.forName("java.lang.ProcessHandle$Info"); + Class optionalClass = Class.forName("java.util.Optional"); + /* + * We don't *need* to use reflection to access Optional: It's available on all JDKs we + * support, and Android code won't get this far, anyway, because ProcessHandle is + * unavailable. But given how much other reflection we're using, we might as well use it + * here, too, so that we don't need to also suppress an AndroidApiChecker error. + */ + + Method currentMethod = processHandleClass.getMethod("current"); + Method infoMethod = processHandleClass.getMethod("info"); + Method userMethod = processHandleInfoClass.getMethod("user"); + Method orElseMethod = optionalClass.getMethod("orElse", Object.class); + + Object current = currentMethod.invoke(null); + Object info = infoMethod.invoke(current); + Object user = userMethod.invoke(info); + return (String) requireNonNull(orElseMethod.invoke(user, fromSystemProperty)); + } catch (ClassNotFoundException runningUnderAndroidOrJava8) { + /* + * I'm not sure that we could actually get here for *Android*: I would expect us to enter + * the POSIX code path instead. And if we tried this code path, we'd have trouble unless we + * were running under a new enough version of Android to support NIO. + * + * So this is probably just the "Windows Java 8" case. In that case, if we wanted *another* + * layer of fallback before consulting the system property, we could try + * com.sun.security.auth.module.NTSystem. + * + * But for now, we use the value from the system property as our best guess. + */ + return fromSystemProperty; + } catch (InvocationTargetException e) { + throwIfUnchecked(e.getCause()); // in case it's an Error or something + return fromSystemProperty; // should be impossible + } catch (NoSuchMethodException | IllegalAccessException shouldBeImpossible) { + return fromSystemProperty; + } + } + } + + private static final class JavaIoCreator extends TempFileCreator { + @Override + File createTempDir() { + File baseDir = new File(JAVA_IO_TMPDIR.value()); + @SuppressWarnings("GoodTime") // reading system time without TimeSource + String baseName = System.currentTimeMillis() + "-"; + + for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { + File tempDir = new File(baseDir, baseName + counter); + if (tempDir.mkdir()) { + return tempDir; + } + } + throw new IllegalStateException( + "Failed to create directory within " + + TEMP_DIR_ATTEMPTS + + " attempts (tried " + + baseName + + "0 to " + + baseName + + (TEMP_DIR_ATTEMPTS - 1) + + ')'); + } + + @Override + File createTempFile(String prefix) throws IOException { + return File.createTempFile( + /* prefix= */ prefix, + /* suffix= */ null, + /* directory= */ null /* defaults to java.io.tmpdir */); + } + + /** Maximum loop count when creating temp directories. */ + private static final int TEMP_DIR_ATTEMPTS = 10000; + } + + private static final class ThrowingCreator extends TempFileCreator { + private static final String MESSAGE = + "Guava cannot securely create temporary files or directories under SDK versions before" + + " Jelly Bean. You can create one yourself, either in the insecure default directory" + + " or in a more secure directory, such as context.getCacheDir(). For more information," + + " see the Javadoc for Files.createTempDir()."; + + @Override + File createTempDir() { + throw new IllegalStateException(MESSAGE); + } + + @Override + File createTempFile(String prefix) throws IOException { + throw new IOException(MESSAGE); + } + } + + private TempFileCreator() {} +} diff --git a/android/guava/src/com/google/common/io/package-info.java b/android/guava/src/com/google/common/io/package-info.java index f0666b26f4b2..30cff81d2fd2 100644 --- a/android/guava/src/com/google/common/io/package-info.java +++ b/android/guava/src/com/google/common/io/package-info.java @@ -13,24 +13,23 @@ */ /** - * This package contains utility methods and classes for working with Java I/O; for example input - * streams, output streams, readers, writers, and files. + * Utility methods and classes for I/O; for example input streams, output streams, readers, writers, + * and files. * - *

      At the core of this package are the Source/Sink types: {@link com.google.common.io.ByteSource - * ByteSource}, {@link com.google.common.io.CharSource CharSource}, {@link - * com.google.common.io.ByteSink ByteSink} and {@link com.google.common.io.CharSink CharSink}. They - * are factories for I/O streams that provide many convenience methods that handle both opening and + *

      At the core of this package are the Source/Sink types: {@link ByteSource ByteSource}, {@link + * CharSource CharSource}, {@link ByteSink ByteSink} and {@link CharSink CharSink}. They are + * factories for I/O streams that provide many convenience methods that handle both opening and * closing streams for you. * - *

      This package is a part of the open-source Guava + *

      This package is a part of the open-source Guava * library. For more information on Sources and Sinks as well as other features of this package, see * I/O Explained on the Guava wiki. * * @author Chris Nokleberg */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.io; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/math/BigDecimalMath.java b/android/guava/src/com/google/common/math/BigDecimalMath.java index b5c23f8dd993..6cfa8a39b430 100644 --- a/android/guava/src/com/google/common/math/BigDecimalMath.java +++ b/android/guava/src/com/google/common/math/BigDecimalMath.java @@ -15,6 +15,7 @@ package com.google.common.math; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.math.BigDecimal; import java.math.RoundingMode; @@ -24,6 +25,7 @@ * @author Louis Wasserman * @since 30.0 */ +@J2ktIncompatible @GwtIncompatible public class BigDecimalMath { private BigDecimalMath() {} @@ -53,7 +55,7 @@ public static double roundToDouble(BigDecimal x, RoundingMode mode) { return BigDecimalToDoubleRounder.INSTANCE.roundToDouble(x, mode); } - private static class BigDecimalToDoubleRounder extends ToDoubleRounder { + private static final class BigDecimalToDoubleRounder extends ToDoubleRounder { static final BigDecimalToDoubleRounder INSTANCE = new BigDecimalToDoubleRounder(); private BigDecimalToDoubleRounder() {} diff --git a/android/guava/src/com/google/common/math/BigIntegerMath.java b/android/guava/src/com/google/common/math/BigIntegerMath.java index bf443e9b145b..abf630ff3aaf 100644 --- a/android/guava/src/com/google/common/math/BigIntegerMath.java +++ b/android/guava/src/com/google/common/math/BigIntegerMath.java @@ -21,11 +21,8 @@ import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; import static java.math.RoundingMode.CEILING; import static java.math.RoundingMode.FLOOR; -import static java.math.RoundingMode.HALF_DOWN; import static java.math.RoundingMode.HALF_EVEN; -import static java.math.RoundingMode.UNNECESSARY; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; @@ -47,7 +44,7 @@ * @author Louis Wasserman * @since 11.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class BigIntegerMath { /** * Returns the smallest power of two greater than or equal to {@code x}. This is equivalent to @@ -56,7 +53,6 @@ public final class BigIntegerMath { * @throws IllegalArgumentException if {@code x <= 0} * @since 20.0 */ - @Beta public static BigInteger ceilingPowerOfTwo(BigInteger x) { return BigInteger.ZERO.setBit(log2(x, CEILING)); } @@ -68,7 +64,6 @@ public static BigInteger ceilingPowerOfTwo(BigInteger x) { * @throws IllegalArgumentException if {@code x <= 0} * @since 20.0 */ - @Beta public static BigInteger floorPowerOfTwo(BigInteger x) { return BigInteger.ZERO.setBit(log2(x, FLOOR)); } @@ -121,10 +116,8 @@ public static int log2(BigInteger x, RoundingMode mode) { BigInteger x2 = x.pow(2); int logX2Floor = x2.bitLength() - 1; return (logX2Floor < 2 * logFloor + 1) ? logFloor : logFloor + 1; - - default: - throw new AssertionError(); } + throw new AssertionError(); } /* @@ -192,7 +185,7 @@ public static int log10(BigInteger x, RoundingMode mode) { switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(floorCmp == 0); - // fall through + // fall through case FLOOR: case DOWN: return floorLog; @@ -208,9 +201,8 @@ public static int log10(BigInteger x, RoundingMode mode) { BigInteger x2 = x.pow(2); BigInteger halfPowerSquared = floorPow.pow(2).multiply(BigInteger.TEN); return (x2.compareTo(halfPowerSquared) <= 0) ? floorLog : floorLog + 1; - default: - throw new AssertionError(); } + throw new AssertionError(); } private static final double LN_10 = Math.log(10); @@ -254,9 +246,8 @@ public static BigInteger sqrt(BigInteger x, RoundingMode mode) { * halfSquare. */ return (halfSquare.compareTo(x) >= 0) ? sqrtFloor : sqrtFloor.add(BigInteger.ONE); - default: - throw new AssertionError(); } + throw new AssertionError(); } @GwtIncompatible // TODO @@ -335,7 +326,7 @@ public static double roundToDouble(BigInteger x, RoundingMode mode) { } @GwtIncompatible - private static class BigIntegerToDoubleRounder extends ToDoubleRounder { + private static final class BigIntegerToDoubleRounder extends ToDoubleRounder { static final BigIntegerToDoubleRounder INSTANCE = new BigIntegerToDoubleRounder(); private BigIntegerToDoubleRounder() {} diff --git a/android/guava/src/com/google/common/math/DoubleMath.java b/android/guava/src/com/google/common/math/DoubleMath.java index 05394408608f..c4e8dc8dfec2 100644 --- a/android/guava/src/com/google/common/math/DoubleMath.java +++ b/android/guava/src/com/google/common/math/DoubleMath.java @@ -33,7 +33,6 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.primitives.Booleans; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.math.BigInteger; import java.math.RoundingMode; @@ -45,7 +44,7 @@ * @author Louis Wasserman * @since 11.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class DoubleMath { /* * This method returns a value y such that rounding y DOWN (towards zero) gives the same result as @@ -107,10 +106,8 @@ static double roundIntermediate(double x, RoundingMode mode) { return z; } } - - default: - throw new AssertionError(); } + throw new AssertionError(); } /** @@ -128,6 +125,8 @@ static double roundIntermediate(double x, RoundingMode mode) { *

    */ @GwtIncompatible // #roundIntermediate + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static int roundToInt(double x, RoundingMode mode) { double z = roundIntermediate(x, mode); checkInRangeForRoundingInputs( @@ -153,6 +152,8 @@ public static int roundToInt(double x, RoundingMode mode) { * */ @GwtIncompatible // #roundIntermediate + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long roundToLong(double x, RoundingMode mode) { double z = roundIntermediate(x, mode); checkInRangeForRoundingInputs( @@ -180,6 +181,8 @@ public static long roundToLong(double x, RoundingMode mode) { */ // #roundIntermediate, java.lang.Math.getExponent, com.google.common.math.DoubleUtils @GwtIncompatible + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static BigInteger roundToBigInteger(double x, RoundingMode mode) { x = roundIntermediate(x, mode); if (MIN_LONG_AS_DOUBLE - x < 1.0 & x < MAX_LONG_AS_DOUBLE_PLUS_ONE) { @@ -234,7 +237,8 @@ public static double log2(double x) { * infinite */ @GwtIncompatible // java.lang.Math.getExponent, com.google.common.math.DoubleUtils - @SuppressWarnings("fallthrough") + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings({"fallthrough", "ShortCircuitBoolean"}) public static int log2(double x, RoundingMode mode) { checkArgument(x > 0.0 && isFinite(x), "x must be positive and finite"); int exponent = getExponent(x); @@ -247,7 +251,7 @@ public static int log2(double x, RoundingMode mode) { switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(isPowerOfTwo(x)); - // fall through + // fall through case FLOOR: increment = false; break; @@ -385,7 +389,7 @@ public static int fuzzyCompare(double a, double b, double tolerance) { } else if (a > b) { return 1; } else { - return Booleans.compare(Double.isNaN(a), Double.isNaN(b)); + return Boolean.compare(Double.isNaN(a), Double.isNaN(b)); } } diff --git a/android/guava/src/com/google/common/math/DoubleUtils.java b/android/guava/src/com/google/common/math/DoubleUtils.java index 4183195fc714..e2331a71c624 100644 --- a/android/guava/src/com/google/common/math/DoubleUtils.java +++ b/android/guava/src/com/google/common/math/DoubleUtils.java @@ -22,6 +22,7 @@ import static java.lang.Double.isNaN; import static java.lang.Double.longBitsToDouble; import static java.lang.Math.getExponent; +import static java.lang.Math.max; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; @@ -131,7 +132,7 @@ static double bigToDouble(BigInteger x) { /** Returns its argument if it is non-negative, zero if it is negative. */ static double ensureNonNegative(double value) { checkArgument(!isNaN(value)); - return Math.max(value, 0.0); + return max(value, 0.0); } @VisibleForTesting static final long ONE_BITS = 0x3ff0000000000000L; diff --git a/android/guava/src/com/google/common/math/IgnoreJRERequirement.java b/android/guava/src/com/google/common/math/IgnoreJRERequirement.java new file mode 100644 index 000000000000..fab5c826c782 --- /dev/null +++ b/android/guava/src/com/google/common/math/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/math/IntMath.java b/android/guava/src/com/google/common/math/IntMath.java index 78aedda98db4..ed7bafdafd89 100644 --- a/android/guava/src/com/google/common/math/IntMath.java +++ b/android/guava/src/com/google/common/math/IntMath.java @@ -25,11 +25,11 @@ import static java.math.RoundingMode.HALF_EVEN; import static java.math.RoundingMode.HALF_UP; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Ints; +import com.google.errorprone.annotations.InlineMe; import java.math.BigInteger; import java.math.RoundingMode; @@ -47,10 +47,8 @@ * @author Louis Wasserman * @since 11.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class IntMath { - // NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || - @VisibleForTesting static final int MAX_SIGNED_POWER_OF_TWO = 1 << (Integer.SIZE - 2); /** @@ -62,7 +60,6 @@ public final class IntMath { * int}, i.e. when {@code x > 2^30} * @since 20.0 */ - @Beta public static int ceilingPowerOfTwo(int x) { checkPositive("x", x); if (x > MAX_SIGNED_POWER_OF_TWO) { @@ -78,7 +75,6 @@ public static int ceilingPowerOfTwo(int x) { * @throws IllegalArgumentException if {@code x <= 0} * @since 20.0 */ - @Beta public static int floorPowerOfTwo(int x) { checkPositive("x", x); return Integer.highestOneBit(x); @@ -90,6 +86,8 @@ public static int floorPowerOfTwo(int x) { *

    This differs from {@code Integer.bitCount(x) == 1}, because {@code * Integer.bitCount(Integer.MIN_VALUE) == 1}, but {@link Integer#MIN_VALUE} is not a power of two. */ + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static boolean isPowerOfTwo(int x) { return x > 0 & (x & (x - 1)) == 0; } @@ -120,7 +118,7 @@ public static int log2(int x, RoundingMode mode) { switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(isPowerOfTwo(x)); - // fall through + // fall through case DOWN: case FLOOR: return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x); @@ -138,10 +136,8 @@ public static int log2(int x, RoundingMode mode) { // floor(2^(logFloor + 0.5)) int logFloor = (Integer.SIZE - 1) - leadingZeros; return logFloor + lessThanBranchFree(cmp, x); - - default: - throw new AssertionError(); } + throw new AssertionError(); } /** The biggest half power of two that can fit in an unsigned int. */ @@ -163,7 +159,7 @@ public static int log10(int x, RoundingMode mode) { switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(x == floorPow); - // fall through + // fall through case FLOOR: case DOWN: return logFloor; @@ -175,9 +171,8 @@ public static int log10(int x, RoundingMode mode) { case HALF_EVEN: // sqrt(10) is irrational, so log10(x) - logFloor is never exactly 0.5 return logFloor + lessThanBranchFree(halfPowersOf10[logFloor], x); - default: - throw new AssertionError(); } + throw new AssertionError(); } private static int log10Floor(int x) { @@ -231,11 +226,11 @@ public static int pow(int b, int k) { return (k == 0) ? 1 : 0; case 1: return 1; - case (-1): + case -1: return ((k & 1) == 0) ? 1 : -1; case 2: return (k < Integer.SIZE) ? (1 << k) : 0; - case (-2): + case -2: if (k < Integer.SIZE) { return ((k & 1) == 0) ? (1 << k) : -(1 << k); } else { @@ -294,9 +289,8 @@ public static int sqrt(int x, RoundingMode mode) { * signed int, so lessThanBranchFree is safe for use. */ return sqrtFloor + lessThanBranchFree(halfSquare, x); - default: - throw new AssertionError(); } + throw new AssertionError(); } private static int sqrtFloor(int x) { @@ -307,12 +301,15 @@ private static int sqrtFloor(int x) { /** * Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code - * RoundingMode}. + * RoundingMode}. If the {@code RoundingMode} is {@link RoundingMode#DOWN}, then this method is + * equivalent to regular Java division, {@code p / q}; and if it is {@link RoundingMode#FLOOR}, + * then this method is equivalent to {@link Math#floorDiv(int,int) Math.floorDiv}{@code (p, q)}. * * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} * is not an integer multiple of {@code b} */ - @SuppressWarnings("fallthrough") + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings({"fallthrough", "ShortCircuitBoolean"}) public static int divide(int p, int q, RoundingMode mode) { checkNotNull(mode); if (q == 0) { @@ -337,7 +334,7 @@ public static int divide(int p, int q, RoundingMode mode) { switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(rem == 0); - // fall through + // fall through case DOWN: increment = false; break; @@ -371,17 +368,19 @@ public static int divide(int p, int q, RoundingMode mode) { /** * Returns {@code x mod m}, a non-negative value less than {@code m}. This differs from {@code x % - * m}, which might be negative. + * m}, which might be negative. This method is equivalent to {@code Math.floorMod(x, m)} except + * that that method also allows negative {@code m}. {@code Math.floorMod} should be preferred when + * {@code m} is known to be positive. * *

    For example: * - *

    {@code
    +   * {@snippet :
        * mod(7, 4) == 3
        * mod(-7, 4) == 1
        * mod(-1, 4) == 3
        * mod(-8, 4) == 0
        * mod(8, 4) == 0
    -   * }
    + * } * * @throws ArithmeticException if {@code m <= 0} * @see @@ -391,8 +390,7 @@ public static int mod(int x, int m) { if (m <= 0) { throw new ArithmeticException("Modulus " + m + " must be > 0"); } - int result = x % m; - return (result >= 0) ? result : result + m; + return Math.floorMod(x, m); } /** @@ -449,34 +447,40 @@ public static int gcd(int a, int b) { /** * Returns the sum of {@code a} and {@code b}, provided it does not overflow. * + *

    Note: this method is now unnecessary and should be treated as deprecated; use {@link + * Math#addExact(int, int)} instead. + * * @throws ArithmeticException if {@code a + b} overflows in signed {@code int} arithmetic */ + @InlineMe(replacement = "Math.addExact(a, b)") public static int checkedAdd(int a, int b) { - long result = (long) a + b; - checkNoOverflow(result == (int) result, "checkedAdd", a, b); - return (int) result; + return Math.addExact(a, b); } /** * Returns the difference of {@code a} and {@code b}, provided it does not overflow. * + *

    Note: this method is now unnecessary and should be treated as deprecated; use {@link + * Math#subtractExact(int, int)} instead. + * * @throws ArithmeticException if {@code a - b} overflows in signed {@code int} arithmetic */ + @InlineMe(replacement = "Math.subtractExact(a, b)") public static int checkedSubtract(int a, int b) { - long result = (long) a - b; - checkNoOverflow(result == (int) result, "checkedSubtract", a, b); - return (int) result; + return Math.subtractExact(a, b); } /** * Returns the product of {@code a} and {@code b}, provided it does not overflow. * + *

    Note: this method is now unnecessary and should be treated as deprecated; use {@link + * Math#multiplyExact(int, int)} instead. + * * @throws ArithmeticException if {@code a * b} overflows in signed {@code int} arithmetic */ + @InlineMe(replacement = "Math.multiplyExact(a, b)") public static int checkedMultiply(int a, int b) { - long result = (long) a * b; - checkNoOverflow(result == (int) result, "checkedMultiply", a, b); - return (int) result; + return Math.multiplyExact(a, b); } /** @@ -487,6 +491,8 @@ public static int checkedMultiply(int a, int b) { * @throws ArithmeticException if {@code b} to the {@code k}th power overflows in signed {@code * int} arithmetic */ + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static int checkedPow(int b, int k) { checkNonNegative("exponent", k); switch (b) { @@ -494,12 +500,12 @@ public static int checkedPow(int b, int k) { return (k == 0) ? 1 : 0; case 1: return 1; - case (-1): + case -1: return ((k & 1) == 0) ? 1 : -1; case 2: checkNoOverflow(k < Integer.SIZE - 1, "checkedPow", b, k); return 1 << k; - case (-2): + case -2: checkNoOverflow(k < Integer.SIZE, "checkedPow", b, k); return ((k & 1) == 0) ? 1 << k : -1 << k; default: @@ -511,10 +517,10 @@ public static int checkedPow(int b, int k) { case 0: return accum; case 1: - return checkedMultiply(accum, b); + return Math.multiplyExact(accum, b); default: if ((k & 1) != 0) { - accum = checkedMultiply(accum, b); + accum = Math.multiplyExact(accum, b); } k >>= 1; if (k > 0) { @@ -531,7 +537,6 @@ public static int checkedPow(int b, int k) { * * @since 20.0 */ - @Beta public static int saturatedAdd(int a, int b) { return Ints.saturatedCast((long) a + b); } @@ -542,7 +547,6 @@ public static int saturatedAdd(int a, int b) { * * @since 20.0 */ - @Beta public static int saturatedSubtract(int a, int b) { return Ints.saturatedCast((long) a - b); } @@ -553,7 +557,6 @@ public static int saturatedSubtract(int a, int b) { * * @since 20.0 */ - @Beta public static int saturatedMultiply(int a, int b) { return Ints.saturatedCast((long) a * b); } @@ -564,7 +567,8 @@ public static int saturatedMultiply(int a, int b) { * * @since 20.0 */ - @Beta + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static int saturatedPow(int b, int k) { checkNonNegative("exponent", k); switch (b) { @@ -572,14 +576,14 @@ public static int saturatedPow(int b, int k) { return (k == 0) ? 1 : 0; case 1: return 1; - case (-1): + case -1: return ((k & 1) == 0) ? 1 : -1; case 2: if (k >= Integer.SIZE - 1) { return Integer.MAX_VALUE; } return 1 << k; - case (-2): + case -2: if (k >= Integer.SIZE) { return Integer.MAX_VALUE + (k & 1); } @@ -589,7 +593,7 @@ public static int saturatedPow(int b, int k) { } int accum = 1; // if b is negative and k is odd then the limit is MIN otherwise the limit is MAX - int limit = Integer.MAX_VALUE + ((b >>> Integer.SIZE - 1) & (k & 1)); + int limit = Integer.MAX_VALUE + ((b >>> (Integer.SIZE - 1)) & (k & 1)); while (true) { switch (k) { case 0: @@ -673,7 +677,7 @@ public static int binomial(int n, int k) { // binomial(biggestBinomials[k], k) fits in an int, but not binomial(biggestBinomials[k]+1,k). @VisibleForTesting - static int[] biggestBinomials = { + static final int[] biggestBinomials = { Integer.MAX_VALUE, Integer.MAX_VALUE, 65536, @@ -719,10 +723,40 @@ public static int mean(int x, int y) { * @since 20.0 */ @GwtIncompatible // TODO - @Beta public static boolean isPrime(int n) { return LongMath.isPrime(n); } + /** + * Returns the closest representable {@code int} to the absolute value of {@code x}. + * + *

    This is the same thing as the true absolute value of {@code x} except in the case when + * {@code x} is {@link Integer#MIN_VALUE}, in which case this returns {@link Integer#MAX_VALUE}. + * (Note that {@code Integer.MAX_VALUE} is mathematically equal to {@code -Integer.MIN_VALUE - + * 1}.) + * + *

    There are three common APIs for determining the absolute value of an integer, all of which + * behave identically except when passed {@code Integer.MIN_VALUE}. Those methods are: + * + *

      + *
    • {@link Math#abs(int)}, which returns {@code Integer.MIN_VALUE} when passed {@code + * Integer.MIN_VALUE} + *
    • {@link Math#absExact(int)}, which throws {@link ArithmeticException} when passed {@code + * Integer.MIN_VALUE} + *
    • this method, {@code IntMath.saturatedAbs(int)}, which returns {@code Integer.MAX_VALUE} + * when passed {@code Integer.MIN_VALUE} + *
    + * + *

    Note that if your only goal is to turn a well-distributed {@code int} (such as a random + * number or hash code) into a well-distributed nonnegative number, the most even distribution is + * achieved not by this method or other absolute value methods, but by {@code x & + * Integer.MAX_VALUE}. + * + * @since 33.5.0 + */ + public static int saturatedAbs(int x) { + return (x == Integer.MIN_VALUE) ? Integer.MAX_VALUE : Math.abs(x); + } + private IntMath() {} } diff --git a/android/guava/src/com/google/common/math/LinearTransformation.java b/android/guava/src/com/google/common/math/LinearTransformation.java index 485b04660902..2560d5cd71d0 100644 --- a/android/guava/src/com/google/common/math/LinearTransformation.java +++ b/android/guava/src/com/google/common/math/LinearTransformation.java @@ -18,9 +18,10 @@ import static com.google.common.math.DoubleUtils.isFinite; import static java.lang.Double.NaN; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.concurrent.LazyInit; +import org.jspecify.annotations.Nullable; /** * The representation of a linear transformation between real numbers {@code x} and {@code y}. @@ -33,9 +34,16 @@ * @author Pete Gillin * @since 20.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public abstract class LinearTransformation { + /** + * Constructor for use by subclasses inside Guava. + * + * @deprecated Create instances by using the static factory methods of the class. + */ + @Deprecated + public LinearTransformation() {} /** * Start building an instance which maps {@code x = x1} to {@code y = y1}. Both arguments must be @@ -161,7 +169,7 @@ private static final class RegularLinearTransformation extends LinearTransformat final double slope; final double yIntercept; - @LazyInit LinearTransformation inverse; + @LazyInit @Nullable LinearTransformation inverse; RegularLinearTransformation(double slope, double yIntercept) { this.slope = slope; @@ -182,7 +190,7 @@ public boolean isVertical() { @Override public boolean isHorizontal() { - return (slope == 0.0); + return slope == 0.0; } @Override @@ -219,7 +227,7 @@ private static final class VerticalLinearTransformation extends LinearTransforma final double x; - @LazyInit LinearTransformation inverse; + @LazyInit @Nullable LinearTransformation inverse; VerticalLinearTransformation(double x) { this.x = x; diff --git a/android/guava/src/com/google/common/math/LongMath.java b/android/guava/src/com/google/common/math/LongMath.java index 420b48a9b453..35b8de0f2d6d 100644 --- a/android/guava/src/com/google/common/math/LongMath.java +++ b/android/guava/src/com/google/common/math/LongMath.java @@ -25,12 +25,11 @@ import static java.math.RoundingMode.HALF_EVEN; import static java.math.RoundingMode.HALF_UP; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.primitives.Longs; import com.google.common.primitives.UnsignedLongs; +import com.google.errorprone.annotations.InlineMe; import java.math.BigInteger; import java.math.RoundingMode; @@ -48,10 +47,8 @@ * @author Louis Wasserman * @since 11.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class LongMath { - // NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || - @VisibleForTesting static final long MAX_SIGNED_POWER_OF_TWO = 1L << (Long.SIZE - 2); /** @@ -63,7 +60,6 @@ public final class LongMath { * long}, i.e. when {@code x > 2^62} * @since 20.0 */ - @Beta public static long ceilingPowerOfTwo(long x) { checkPositive("x", x); if (x > MAX_SIGNED_POWER_OF_TWO) { @@ -79,7 +75,6 @@ public static long ceilingPowerOfTwo(long x) { * @throws IllegalArgumentException if {@code x <= 0} * @since 20.0 */ - @Beta public static long floorPowerOfTwo(long x) { checkPositive("x", x); @@ -94,6 +89,8 @@ public static long floorPowerOfTwo(long x) { *

    This differs from {@code Long.bitCount(x) == 1}, because {@code * Long.bitCount(Long.MIN_VALUE) == 1}, but {@link Long#MIN_VALUE} is not a power of two. */ + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static boolean isPowerOfTwo(long x) { return x > 0 & (x & (x - 1)) == 0; } @@ -123,7 +120,7 @@ public static int log2(long x, RoundingMode mode) { switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(isPowerOfTwo(x)); - // fall through + // fall through case DOWN: case FLOOR: return (Long.SIZE - 1) - Long.numberOfLeadingZeros(x); @@ -141,10 +138,8 @@ public static int log2(long x, RoundingMode mode) { // floor(2^(logFloor + 0.5)) int logFloor = (Long.SIZE - 1) - leadingZeros; return logFloor + lessThanBranchFree(cmp, x); - - default: - throw new AssertionError("impossible"); } + throw new AssertionError("impossible"); } /** The biggest half power of two that fits into an unsigned long */ @@ -167,7 +162,7 @@ public static int log10(long x, RoundingMode mode) { switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(x == floorPow); - // fall through + // fall through case FLOOR: case DOWN: return logFloor; @@ -179,9 +174,8 @@ public static int log10(long x, RoundingMode mode) { case HALF_EVEN: // sqrt(10) is irrational, so log10(x)-logFloor is never exactly 0.5 return logFloor + lessThanBranchFree(halfPowersOf10[logFloor], x); - default: - throw new AssertionError(); } + throw new AssertionError(); } @GwtIncompatible // TODO @@ -274,11 +268,11 @@ public static long pow(long b, int k) { return (k == 0) ? 1 : 0; case 1: return 1; - case (-1): + case -1: return ((k & 1) == 0) ? 1 : -1; case 2: return (k < Long.SIZE) ? 1L << k : 0; - case (-2): + case -2: if (k < Long.SIZE) { return ((k & 1) == 0) ? 1L << k : -(1L << k); } else { @@ -309,7 +303,6 @@ public static long pow(long b, int k) { * sqrt(x)} is not an integer */ @GwtIncompatible // TODO - @SuppressWarnings("fallthrough") public static long sqrt(long x, RoundingMode mode) { checkNonNegative("x", x); if (fitsInInt(x)) { @@ -330,7 +323,7 @@ public static long sqrt(long x, RoundingMode mode) { * since (long) Math.sqrt(k * k) == k, as checked exhaustively in * {@link LongMathTest#testSqrtOfPerfectSquareAsDoubleIsPerfect} */ - long guess = (long) Math.sqrt(x); + long guess = (long) Math.sqrt((double) x); // Note: guess is always <= FLOOR_SQRT_MAX_LONG. long guessSquared = guess * guess; // Note (2013-2-26): benchmarks indicate that, inscrutably enough, using if statements is @@ -368,14 +361,15 @@ public static long sqrt(long x, RoundingMode mode) { * signed long, so lessThanBranchFree is safe for use. */ return sqrtFloor + lessThanBranchFree(halfSquare, x); - default: - throw new AssertionError(); } + throw new AssertionError(); } /** * Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code - * RoundingMode}. + * RoundingMode}. If the {@code RoundingMode} is {@link RoundingMode#DOWN}, then this method is + * equivalent to regular Java division, {@code p / q}; and if it is {@link RoundingMode#FLOOR}, + * then this method is equivalent to {@link Math#floorDiv(long,long) Math.floorDiv}{@code (p, q)}. * * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} * is not an integer multiple of {@code b} @@ -403,7 +397,7 @@ public static long divide(long p, long q, RoundingMode mode) { switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(rem == 0); - // fall through + // fall through case DOWN: increment = false; break; @@ -424,7 +418,7 @@ public static long divide(long p, long q, RoundingMode mode) { // subtracting two nonnegative longs can't overflow // cmpRemToHalfDivisor has the same sign as compare(abs(rem), abs(q) / 2). if (cmpRemToHalfDivisor == 0) { // exactly on the half mark - increment = (mode == HALF_UP | (mode == HALF_EVEN & (div & 1) != 0)); + increment = (mode == HALF_UP || (mode == HALF_EVEN && (div & 1) != 0)); } else { increment = cmpRemToHalfDivisor > 0; // closer to the UP value } @@ -441,13 +435,13 @@ public static long divide(long p, long q, RoundingMode mode) { * *

    For example: * - *

    {@code
    +   * {@snippet :
        * mod(7, 4) == 3
        * mod(-7, 4) == 1
        * mod(-1, 4) == 3
        * mod(-8, 4) == 0
        * mod(8, 4) == 0
    -   * }
    + * } * * @throws ArithmeticException if {@code m <= 0} * @see
    @@ -465,13 +459,13 @@ public static int mod(long x, int m) { * *

    For example: * - *

    {@code
    +   * {@snippet :
        * mod(7, 4) == 3
        * mod(-7, 4) == 1
        * mod(-1, 4) == 3
        * mod(-8, 4) == 0
        * mod(8, 4) == 0
    -   * }
    + * } * * @throws ArithmeticException if {@code m <= 0} * @see
    @@ -482,8 +476,7 @@ public static long mod(long x, long m) { if (m <= 0) { throw new ArithmeticException("Modulus must be positive"); } - long result = x % m; - return (result >= 0) ? result : result + m; + return Math.floorMod(x, m); } /** @@ -540,57 +533,51 @@ public static long gcd(long a, long b) { /** * Returns the sum of {@code a} and {@code b}, provided it does not overflow. * + *

    Note: this method is now unnecessary and should be treated as deprecated; use {@link + * Math#addExact(long, long)} instead. Note that if both arguments are {@code int} values, writing + * {@code Math.addExact(a, b)} will call the {@link Math#addExact(int, int)} overload, not {@link + * Math#addExact(long, long)}. Also note that adding two {@code int} values can never + * overflow a {@code long}, so you can just write {@code (long) a + b}. + * * @throws ArithmeticException if {@code a + b} overflows in signed {@code long} arithmetic */ - @GwtIncompatible // TODO + @InlineMe(replacement = "Math.addExact(a, b)") public static long checkedAdd(long a, long b) { - long result = a + b; - checkNoOverflow((a ^ b) < 0 | (a ^ result) >= 0, "checkedAdd", a, b); - return result; + return Math.addExact(a, b); } /** * Returns the difference of {@code a} and {@code b}, provided it does not overflow. * + *

    Note: this method is now unnecessary and should be treated as deprecated; use {@link + * Math#subtractExact(long, long)} instead. Note that if both arguments are {@code int} values, + * writing {@code Math.subtractExact(a, b)} will call the {@link Math#subtractExact(int, int)} + * overload, not {@link Math#subtractExact(long, long)}. Also note that subtracting two {@code + * int} values can never overflow a {@code long}, so you can just write {@code (long) a - + * b}. + * * @throws ArithmeticException if {@code a - b} overflows in signed {@code long} arithmetic */ - @GwtIncompatible // TODO + @InlineMe(replacement = "Math.subtractExact(a, b)") public static long checkedSubtract(long a, long b) { - long result = a - b; - checkNoOverflow((a ^ b) >= 0 | (a ^ result) >= 0, "checkedSubtract", a, b); - return result; + return Math.subtractExact(a, b); } /** * Returns the product of {@code a} and {@code b}, provided it does not overflow. * + *

    Note: this method is now unnecessary and should be treated as deprecated; use {@link + * Math#multiplyExact(long, long)} instead. Note that if both arguments are {@code int} values, + * writing {@code Math.multiplyExact(a, b)} will call the {@link Math#multiplyExact(int, int)} + * overload, not {@link Math#multiplyExact(long, long)}. Also note that multiplying two {@code + * int} values can never overflow a {@code long}, so you can just write {@code (long) a * + * b}. + * * @throws ArithmeticException if {@code a * b} overflows in signed {@code long} arithmetic */ + @InlineMe(replacement = "Math.multiplyExact(a, b)") public static long checkedMultiply(long a, long b) { - // Hacker's Delight, Section 2-12 - int leadingZeros = - Long.numberOfLeadingZeros(a) - + Long.numberOfLeadingZeros(~a) - + Long.numberOfLeadingZeros(b) - + Long.numberOfLeadingZeros(~b); - /* - * If leadingZeros > Long.SIZE + 1 it's definitely fine, if it's < Long.SIZE it's definitely - * bad. We do the leadingZeros check to avoid the division below if at all possible. - * - * Otherwise, if b == Long.MIN_VALUE, then the only allowed values of a are 0 and 1. We take - * care of all a < 0 with their own check, because in particular, the case a == -1 will - * incorrectly pass the division check below. - * - * In all other cases, we check that either a is 0 or the result is consistent with division. - */ - if (leadingZeros > Long.SIZE + 1) { - return a * b; - } - checkNoOverflow(leadingZeros >= Long.SIZE, "checkedMultiply", a, b); - checkNoOverflow(a >= 0 | b != Long.MIN_VALUE, "checkedMultiply", a, b); - long result = a * b; - checkNoOverflow(a == 0 || result / a == b, "checkedMultiply", a, b); - return result; + return Math.multiplyExact(a, b); } /** @@ -600,6 +587,8 @@ public static long checkedMultiply(long a, long b) { * long} arithmetic */ @GwtIncompatible // TODO + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long checkedPow(long b, int k) { checkNonNegative("exponent", k); if (b >= -2 & b <= 2) { @@ -608,12 +597,12 @@ public static long checkedPow(long b, int k) { return (k == 0) ? 1 : 0; case 1: return 1; - case (-1): + case -1: return ((k & 1) == 0) ? 1 : -1; case 2: checkNoOverflow(k < Long.SIZE - 1, "checkedPow", b, k); return 1L << k; - case (-2): + case -2: checkNoOverflow(k < Long.SIZE, "checkedPow", b, k); return ((k & 1) == 0) ? (1L << k) : (-1L << k); default: @@ -626,10 +615,10 @@ public static long checkedPow(long b, int k) { case 0: return accum; case 1: - return checkedMultiply(accum, b); + return Math.multiplyExact(accum, b); default: if ((k & 1) != 0) { - accum = checkedMultiply(accum, b); + accum = Math.multiplyExact(accum, b); } k >>= 1; if (k > 0) { @@ -647,7 +636,8 @@ public static long checkedPow(long b, int k) { * * @since 20.0 */ - @Beta + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long saturatedAdd(long a, long b) { long naiveSum = a + b; if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) { @@ -665,7 +655,8 @@ public static long saturatedAdd(long a, long b) { * * @since 20.0 */ - @Beta + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long saturatedSubtract(long a, long b) { long naiveDifference = a - b; if ((a ^ b) >= 0 | (a ^ naiveDifference) >= 0) { @@ -683,7 +674,8 @@ public static long saturatedSubtract(long a, long b) { * * @since 20.0 */ - @Beta + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long saturatedMultiply(long a, long b) { // see checkedMultiply for explanation int leadingZeros = @@ -713,7 +705,8 @@ public static long saturatedMultiply(long a, long b) { * * @since 20.0 */ - @Beta + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + @SuppressWarnings("ShortCircuitBoolean") public static long saturatedPow(long b, int k) { checkNonNegative("exponent", k); if (b >= -2 & b <= 2) { @@ -722,14 +715,14 @@ public static long saturatedPow(long b, int k) { return (k == 0) ? 1 : 0; case 1: return 1; - case (-1): + case -1: return ((k & 1) == 0) ? 1 : -1; case 2: if (k >= Long.SIZE - 1) { return Long.MAX_VALUE; } return 1L << k; - case (-2): + case -2: if (k >= Long.SIZE) { return Long.MAX_VALUE + (k & 1); } @@ -740,7 +733,7 @@ public static long saturatedPow(long b, int k) { } long accum = 1; // if b is negative and k is odd then the limit is MIN otherwise the limit is MAX - long limit = Long.MAX_VALUE + ((b >>> Long.SIZE - 1) & (k & 1)); + long limit = Long.MAX_VALUE + ((b >>> (Long.SIZE - 1)) & (k & 1)); while (true) { switch (k) { case 0: @@ -957,6 +950,7 @@ static long multiplyFraction(long x, long numerator, long denominator) { 61, 61 }; + // These values were generated by using checkedMultiply to see when the simple multiply/divide // algorithm would lead to an overflow. @@ -978,7 +972,7 @@ public static long mean(long x, long y) { } /* - * This bitmask is used as an optimization for cheaply testing for divisiblity by 2, 3, or 5. + * This bitmask is used as an optimization for cheaply testing for divisibility by 2, 3, or 5. * Each bit is set to 1 for all remainders that indicate divisibility by 2, 3, or 5, so * 1, 7, 11, 13, 17, 19, 23, 29 are set to 0. 30 and up don't matter because they won't be hit. */ @@ -999,7 +993,6 @@ public static long mean(long x, long y) { * @since 20.0 */ @GwtIncompatible // TODO - @Beta public static boolean isPrime(long n) { if (n < 2) { checkNonNegative("n", n); @@ -1115,7 +1108,7 @@ private long plusMod(long a, long b, long m) { private long times2ToThe32Mod(long a, long m) { int remainingPowersOf2 = 32; do { - int shift = Math.min(remainingPowersOf2, Long.numberOfLeadingZeros(a)); + int shift = min(remainingPowersOf2, Long.numberOfLeadingZeros(a)); // shift is either the number of powers of 2 left to multiply a by, or the biggest shift // possible while keeping a in an unsigned long. a = UnsignedLongs.remainder(a << shift, m); @@ -1241,7 +1234,6 @@ private boolean testWitness(long base, long n) { * is not precisely representable as a {@code double} * @since 30.0 */ - @SuppressWarnings("deprecation") @GwtIncompatible public static double roundToDouble(long x, RoundingMode mode) { // Logic adapted from ToDoubleRounder. @@ -1252,7 +1244,7 @@ public static double roundToDouble(long x, RoundingMode mode) { if (roundArbitrarilyAsLong == Long.MAX_VALUE) { /* * For most values, the conversion from roundArbitrarily to roundArbitrarilyAsLong is - * lossless. In that case we can compare x to roundArbitrarily using Longs.compare(x, + * lossless. In that case we can compare x to roundArbitrarily using Long.compare(x, * roundArbitrarilyAsLong). The exception is for values where the conversion to double rounds * up to give roundArbitrarily equal to 2^63, so the conversion back to long overflows and * roundArbitrarilyAsLong is Long.MAX_VALUE. (This is the only way this condition can occur as @@ -1262,7 +1254,7 @@ public static double roundToDouble(long x, RoundingMode mode) { */ cmpXToRoundArbitrarily = -1; } else { - cmpXToRoundArbitrarily = Longs.compare(x, roundArbitrarilyAsLong); + cmpXToRoundArbitrarily = Long.compare(x, roundArbitrarilyAsLong); } switch (mode) { @@ -1321,7 +1313,7 @@ public static double roundToDouble(long x, RoundingMode mode) { deltaToCeiling++; } - int diff = Longs.compare(deltaToFloor, deltaToCeiling); + int diff = Long.compare(deltaToFloor, deltaToCeiling); if (diff < 0) { // closer to floor return roundFloorAsDouble; } else if (diff > 0) { // closer to ceiling @@ -1345,5 +1337,34 @@ public static double roundToDouble(long x, RoundingMode mode) { throw new AssertionError("impossible"); } + /** + * Returns the closest representable {@code long} to the absolute value of {@code x}. + * + *

    This is the same thing as the true absolute value of {@code x} except in the case when + * {@code x} is {@link Long#MIN_VALUE}, in which case this returns {@link Long#MAX_VALUE}. (Note + * that {@code Long.MAX_VALUE} is mathematically equal to {@code -Long.MIN_VALUE - 1}.) + * + *

    There are three common APIs for determining the absolute value of a long, all of which + * behave identically except when passed {@code Long.MIN_VALUE}. Those methods are: + * + *

      + *
    • {@link Math#abs(long)}, which returns {@code Long.MIN_VALUE} when passed {@code + * Long.MIN_VALUE} + *
    • {@link Math#absExact(long)}, which throws {@link ArithmeticException} when passed {@code + * Long.MIN_VALUE} + *
    • this method, {@code LongMath.saturatedAbs(long)}, which returns {@code Long.MAX_VALUE} + * when passed {@code Long.MIN_VALUE} + *
    + * + *

    Note that if your only goal is to turn a well-distributed {@code long} (such as a random + * number) into a well-distributed nonnegative number, the most even distribution is achieved not + * by this method or other absolute value methods, but by {@code x & Long.MAX_VALUE}. + * + * @since 33.5.0 + */ + public static long saturatedAbs(long x) { + return (x == Long.MIN_VALUE) ? Long.MAX_VALUE : Math.abs(x); + } + private LongMath() {} } diff --git a/android/guava/src/com/google/common/math/MathPreconditions.java b/android/guava/src/com/google/common/math/MathPreconditions.java index 5f925d38d774..33e142d0b7e3 100644 --- a/android/guava/src/com/google/common/math/MathPreconditions.java +++ b/android/guava/src/com/google/common/math/MathPreconditions.java @@ -18,7 +18,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.math.BigInteger; import java.math.RoundingMode; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; /** * A collection of preconditions for math functions. @@ -26,51 +25,57 @@ * @author Louis Wasserman */ @GwtCompatible -@CanIgnoreReturnValue final class MathPreconditions { - static int checkPositive(@NullableDecl String role, int x) { + @CanIgnoreReturnValue + static int checkPositive(String role, int x) { if (x <= 0) { throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); } return x; } - static long checkPositive(@NullableDecl String role, long x) { + @CanIgnoreReturnValue + static long checkPositive(String role, long x) { if (x <= 0) { throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); } return x; } - static BigInteger checkPositive(@NullableDecl String role, BigInteger x) { + @CanIgnoreReturnValue + static BigInteger checkPositive(String role, BigInteger x) { if (x.signum() <= 0) { throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); } return x; } - static int checkNonNegative(@NullableDecl String role, int x) { + @CanIgnoreReturnValue + static int checkNonNegative(String role, int x) { if (x < 0) { throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } - static long checkNonNegative(@NullableDecl String role, long x) { + @CanIgnoreReturnValue + static long checkNonNegative(String role, long x) { if (x < 0) { throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } - static BigInteger checkNonNegative(@NullableDecl String role, BigInteger x) { + @CanIgnoreReturnValue + static BigInteger checkNonNegative(String role, BigInteger x) { if (x.signum() < 0) { throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } return x; } - static double checkNonNegative(@NullableDecl String role, double x) { + @CanIgnoreReturnValue + static double checkNonNegative(String role, double x) { if (!(x >= 0)) { // not x < 0, to work with NaN. throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); } diff --git a/android/guava/src/com/google/common/math/PairedStats.java b/android/guava/src/com/google/common/math/PairedStats.java index a636e51d3160..2798a62ed664 100644 --- a/android/guava/src/com/google/common/math/PairedStats.java +++ b/android/guava/src/com/google/common/math/PairedStats.java @@ -21,14 +21,14 @@ import static java.lang.Double.doubleToLongBits; import static java.lang.Double.isNaN; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * An immutable value object capturing some basic statistics about a collection of paired double @@ -37,7 +37,7 @@ * @author Pete Gillin * @since 20.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class PairedStats implements Serializable { @@ -213,7 +213,7 @@ public LinearTransformation leastSquaresFit() { * guarantees {@code strictfp}-like semantics.) */ @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == null) { return false; } @@ -234,7 +234,7 @@ public boolean equals(@NullableDecl Object obj) { */ @Override public int hashCode() { - return Objects.hashCode(xStats, yStats, sumOfProductsOfDeltas); + return Objects.hash(xStats, yStats, sumOfProductsOfDeltas); } @Override diff --git a/android/guava/src/com/google/common/math/PairedStatsAccumulator.java b/android/guava/src/com/google/common/math/PairedStatsAccumulator.java index a9884959a04a..30cc0ccb81e9 100644 --- a/android/guava/src/com/google/common/math/PairedStatsAccumulator.java +++ b/android/guava/src/com/google/common/math/PairedStatsAccumulator.java @@ -15,12 +15,12 @@ package com.google.common.math; import static com.google.common.base.Preconditions.checkState; -import static com.google.common.primitives.Doubles.isFinite; import static java.lang.Double.NaN; +import static java.lang.Double.isFinite; import static java.lang.Double.isNaN; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.primitives.Doubles; /** @@ -30,9 +30,11 @@ * @author Pete Gillin * @since 20.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class PairedStatsAccumulator { + /** Creates a new accumulator. */ + public PairedStatsAccumulator() {} // These fields must satisfy the requirements of PairedStats' constructor as well as those of the // stat methods of this class. @@ -228,7 +230,7 @@ public final LinearTransformation leastSquaresFit() { } } - private double ensurePositive(double value) { + private static double ensurePositive(double value) { if (value > 0.0) { return value; } else { diff --git a/android/guava/src/com/google/common/math/ParametricNullness.java b/android/guava/src/com/google/common/math/ParametricNullness.java new file mode 100644 index 000000000000..34901185ab04 --- /dev/null +++ b/android/guava/src/com/google/common/math/ParametricNullness.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.math; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. + */ +@GwtCompatible +@Retention(CLASS) +@Target({FIELD, METHOD, PARAMETER}) +@interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/math/Quantiles.java b/android/guava/src/com/google/common/math/Quantiles.java index 7aac58f077ea..ad1d47d5fc25 100644 --- a/android/guava/src/com/google/common/math/Quantiles.java +++ b/android/guava/src/com/google/common/math/Quantiles.java @@ -21,8 +21,8 @@ import static java.util.Arrays.sort; import static java.util.Collections.unmodifiableMap; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.primitives.Doubles; import com.google.common.primitives.Ints; import java.math.RoundingMode; @@ -38,26 +38,26 @@ * *

    To compute the median: * - *

    {@code
    + * {@snippet :
      * double myMedian = median().compute(myDataset);
    - * }
    + * } * * where {@link #median()} has been statically imported. * *

    To compute the 99th percentile: * - *

    {@code
    + * {@snippet :
      * double myPercentile99 = percentiles().index(99).compute(myDataset);
    - * }
    + * } * * where {@link #percentiles()} has been statically imported. * *

    To compute median and the 90th and 99th percentiles: * - *

    {@code
    + * {@snippet :
      * Map myPercentiles =
      *     percentiles().indexes(50, 90, 99).compute(myDataset);
    - * }
    + * } * * where {@link #percentiles()} has been statically imported: {@code myPercentiles} maps the keys * 50, 90, and 99, to their corresponding quantile values. @@ -126,9 +126,17 @@ * @author Pete Gillin * @since 20.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class Quantiles { + /** + * Constructor for a type that is not meant to be instantiated. + * + * @deprecated Use the static factory methods of the class. There is no reason to create an + * instance of {@link Quantiles}. + */ + @Deprecated + public Quantiles() {} /** Specifies the computation of a median (i.e. the 1st 2-quantile). */ public static ScaleAndIndex median() { diff --git a/android/guava/src/com/google/common/math/Stats.java b/android/guava/src/com/google/common/math/Stats.java index 94939383a430..084f222e83e3 100644 --- a/android/guava/src/com/google/common/math/Stats.java +++ b/android/guava/src/com/google/common/math/Stats.java @@ -19,20 +19,24 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.math.DoubleUtils.ensureNonNegative; import static com.google.common.math.StatsAccumulator.calculateNewMeanNonFinite; -import static com.google.common.primitives.Doubles.isFinite; import static java.lang.Double.NaN; import static java.lang.Double.doubleToLongBits; +import static java.lang.Double.isFinite; import static java.lang.Double.isNaN; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Iterator; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import java.util.stream.Collector; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import org.jspecify.annotations.Nullable; /** * A bundle of statistical summary values -- sum, count, mean/average, min and max, and several @@ -51,14 +55,14 @@ *

    Static convenience methods called {@code meanOf} are also provided for users who wish to * calculate only the mean. * - *

    Java 8 users: If you are not using any of the variance statistics, you may wish to use + *

    Java 8+ users: If you are not using any of the variance statistics, you may wish to use * built-in JDK libraries instead of this class. * * @author Pete Gillin * @author Kevin Bourrillion * @since 20.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class Stats implements Serializable { @@ -121,9 +125,9 @@ public static Stats of(Iterator values) { * @param values a series of values */ public static Stats of(double... values) { - StatsAccumulator acummulator = new StatsAccumulator(); - acummulator.addAll(values); - return acummulator.snapshot(); + StatsAccumulator accumulator = new StatsAccumulator(); + accumulator.addAll(values); + return accumulator.snapshot(); } /** @@ -132,9 +136,9 @@ public static Stats of(double... values) { * @param values a series of values */ public static Stats of(int... values) { - StatsAccumulator acummulator = new StatsAccumulator(); - acummulator.addAll(values); - return acummulator.snapshot(); + StatsAccumulator accumulator = new StatsAccumulator(); + accumulator.addAll(values); + return accumulator.snapshot(); } /** @@ -144,9 +148,85 @@ public static Stats of(int... values) { * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) */ public static Stats of(long... values) { - StatsAccumulator acummulator = new StatsAccumulator(); - acummulator.addAll(values); - return acummulator.snapshot(); + StatsAccumulator accumulator = new StatsAccumulator(); + accumulator.addAll(values); + return accumulator.snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. The stream will be completely + * consumed by this method. + * + *

    If you have a {@code Stream} rather than a {@code DoubleStream}, you should collect + * the values using {@link #toStats()} instead. + * + * @param values a series of values + * @since 33.4.0 (but since 28.2 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Stats of(DoubleStream values) { + return values + .collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll) + .snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. The stream will be completely + * consumed by this method. + * + *

    If you have a {@code Stream} rather than an {@code IntStream}, you should collect + * the values using {@link #toStats()} instead. + * + * @param values a series of values + * @since 33.4.0 (but since 28.2 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Stats of(IntStream values) { + return values + .collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll) + .snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. The stream will be completely + * consumed by this method. + * + *

    If you have a {@code Stream} rather than a {@code LongStream}, you should collect the + * values using {@link #toStats()} instead. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) + * @since 33.4.0 (but since 28.2 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Stats of(LongStream values) { + return values + .collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll) + .snapshot(); + } + + /** + * Returns a {@link Collector} which accumulates statistics from a {@link java.util.stream.Stream} + * of any type of boxed {@link Number} into a {@link Stats}. Use by calling {@code + * boxedNumericStream.collect(toStats())}. The numbers will be converted to {@code double} values + * (which may cause loss of precision). + * + *

    If you have any of the primitive streams {@code DoubleStream}, {@code IntStream}, or {@code + * LongStream}, you should use the factory method {@link #of} instead. + * + * @since 33.4.0 (but since 28.2 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static Collector toStats() { + return Collector.of( + StatsAccumulator::new, + (a, x) -> a.add(x.doubleValue()), + (l, r) -> { + l.addAll(r); + return l; + }, + StatsAccumulator::snapshot, + Collector.Characteristics.UNORDERED); } /** Returns the number of values. */ @@ -340,7 +420,7 @@ public double max() { * {@code strictfp}-like semantics.) */ @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == null) { return false; } @@ -363,7 +443,7 @@ && doubleToLongBits(min) == doubleToLongBits(other.min) */ @Override public int hashCode() { - return Objects.hashCode(count, mean, sumOfSquaresOfDeltas, min, max); + return Objects.hash(count, mean, sumOfSquaresOfDeltas, min, max); } @Override diff --git a/android/guava/src/com/google/common/math/StatsAccumulator.java b/android/guava/src/com/google/common/math/StatsAccumulator.java index 54468cf4f963..14c196fb55a6 100644 --- a/android/guava/src/com/google/common/math/StatsAccumulator.java +++ b/android/guava/src/com/google/common/math/StatsAccumulator.java @@ -16,13 +16,16 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.math.DoubleUtils.ensureNonNegative; -import static com.google.common.primitives.Doubles.isFinite; import static java.lang.Double.NaN; +import static java.lang.Double.isFinite; import static java.lang.Double.isNaN; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.Iterator; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; /** * A mutable object which accumulates double values and tracks some basic statistics over all the @@ -32,9 +35,11 @@ * @author Kevin Bourrillion * @since 20.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class StatsAccumulator { + /** Creates a new accumulator. */ + public StatsAccumulator() {} // These fields must satisfy the requirements of Stats' constructor as well as those of the stat // methods of this class. @@ -128,6 +133,40 @@ public void addAll(long... values) { } } + /** + * Adds the given values to the dataset. The stream will be completely consumed by this method. + * + * @param values a series of values + * @since 33.4.0 (but since 28.2 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public void addAll(DoubleStream values) { + addAll(values.collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll)); + } + + /** + * Adds the given values to the dataset. The stream will be completely consumed by this method. + * + * @param values a series of values + * @since 33.4.0 (but since 28.2 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public void addAll(IntStream values) { + addAll(values.collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll)); + } + + /** + * Adds the given values to the dataset. The stream will be completely consumed by this method. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) + * @since 33.4.0 (but since 28.2 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public void addAll(LongStream values) { + addAll(values.collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll)); + } + /** * Adds the given statistics to the dataset, as if the individual values used to compute the * statistics had been added directly. diff --git a/android/guava/src/com/google/common/math/package-info.java b/android/guava/src/com/google/common/math/package-info.java index 0408246e7452..663d3bced554 100644 --- a/android/guava/src/com/google/common/math/package-info.java +++ b/android/guava/src/com/google/common/math/package-info.java @@ -13,17 +13,18 @@ */ /** - * Arithmetic functions operating on primitive values and {@link java.math.BigInteger} instances. + * Arithmetic functions operating on primitive values and on {@link java.math.BigInteger} and {@link + * java.math.BigDecimal} instances. * - *

    This package is a part of the open-source Guava + *

    This package is a part of the open-source Guava * library. * *

    See the Guava User Guide article on math utilities. */ -@ParametersAreNonnullByDefault @CheckReturnValue +@NullMarked package com.google.common.math; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/net/HostAndPort.java b/android/guava/src/com/google/common/net/HostAndPort.java index df8ded405349..2fded49be32f 100644 --- a/android/guava/src/com/google/common/net/HostAndPort.java +++ b/android/guava/src/com/google/common/net/HostAndPort.java @@ -17,14 +17,17 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.isNullOrEmpty; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.base.Objects; -import com.google.common.base.Strings; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.primitives.Ints; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import java.io.Serializable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.Objects; +import org.jspecify.annotations.Nullable; /** * An immutable representation of a host and port. @@ -59,7 +62,6 @@ * @author Paul Marks * @since 10.0 */ -@Beta @Immutable @GwtCompatible public final class HostAndPort implements Serializable { @@ -162,6 +164,7 @@ public static HostAndPort fromHost(String host) { * @return if parsing was successful, a populated HostAndPort object. * @throws IllegalArgumentException if nothing meaningful could be parsed. */ + @CanIgnoreReturnValue // TODO(b/219820829): consider removing public static HostAndPort fromString(String hostPortString) { checkNotNull(hostPortString); String host; @@ -181,20 +184,16 @@ public static HostAndPort fromString(String hostPortString) { } else { // 0 or 2+ colons. Bare hostname or IPv6 literal. host = hostPortString; - hasBracketlessColons = (colonPos >= 0); + hasBracketlessColons = colonPos >= 0; } } - int port = NO_PORT; - if (!Strings.isNullOrEmpty(portString)) { - // Try to parse the whole port string as a number. - // JDK7 accepts leading plus signs. We don't want to. - checkArgument(!portString.startsWith("+"), "Unparseable port number: %s", hostPortString); - try { - port = Integer.parseInt(portString); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Unparseable port number: " + hostPortString); - } + Integer port; + if (isNullOrEmpty(portString)) { + port = NO_PORT; + } else { + port = Ints.tryParse(portString); + checkArgument(port != null, "Unparseable port number: %s", hostPortString); checkArgument(isValidPort(port), "Port number out of range: %s", hostPortString); } @@ -204,19 +203,17 @@ public static HostAndPort fromString(String hostPortString) { /** * Parses a bracketed host-port string, throwing IllegalArgumentException if parsing fails. * - * @param hostPortString the full bracketed host-port specification. Post might not be specified. + * @param hostPortString the full bracketed host-port specification. Port might not be specified. * @return an array with 2 strings: host and port, in that order. * @throws IllegalArgumentException if parsing the bracketed host-port string fails. */ private static String[] getHostAndPortFromBracketedHost(String hostPortString) { - int colonIndex = 0; - int closeBracketIndex = 0; checkArgument( hostPortString.charAt(0) == '[', "Bracketed host-port string must start with a bracket: %s", hostPortString); - colonIndex = hostPortString.indexOf(':'); - closeBracketIndex = hostPortString.lastIndexOf(']'); + int colonIndex = hostPortString.indexOf(':'); + int closeBracketIndex = hostPortString.lastIndexOf(']'); checkArgument( colonIndex > -1 && closeBracketIndex > colonIndex, "Invalid bracketed host/port: %s", @@ -271,26 +268,27 @@ public HostAndPort withDefaultPort(int defaultPort) { * @return {@code this}, to enable chaining of calls. * @throws IllegalArgumentException if bracketless IPv6 is detected. */ + @CanIgnoreReturnValue public HostAndPort requireBracketsForIPv6() { checkArgument(!hasBracketlessColons, "Possible bracketless IPv6 literal: %s", host); return this; } @Override - public boolean equals(@NullableDecl Object other) { + public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (other instanceof HostAndPort) { HostAndPort that = (HostAndPort) other; - return Objects.equal(this.host, that.host) && this.port == that.port; + return Objects.equals(this.host, that.host) && this.port == that.port; } return false; } @Override public int hashCode() { - return Objects.hashCode(host, port); + return Objects.hash(host, port); } /** Rebuild the host:port string, including brackets if necessary. */ @@ -314,5 +312,5 @@ private static boolean isValidPort(int port) { return port >= 0 && port <= 65535; } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/net/HostSpecifier.java b/android/guava/src/com/google/common/net/HostSpecifier.java index 3f6f5690961e..51d80fbcc218 100644 --- a/android/guava/src/com/google/common/net/HostSpecifier.java +++ b/android/guava/src/com/google/common/net/HostSpecifier.java @@ -14,12 +14,13 @@ package com.google.common.net; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.net.InetAddress; import java.text.ParseException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A syntactically valid host specifier, suitable for use in a URI. This may be either a numeric IP @@ -41,7 +42,7 @@ * @author Craig Berry * @since 5.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class HostSpecifier { @@ -70,9 +71,9 @@ private HostSpecifier(String canonicalForm) { public static HostSpecifier fromValid(String specifier) { // Verify that no port was specified, and strip optional brackets from // IPv6 literals. - final HostAndPort parsedHost = HostAndPort.fromString(specifier); + HostAndPort parsedHost = HostAndPort.fromString(specifier); Preconditions.checkArgument(!parsedHost.hasPort()); - final String host = parsedHost.getHost(); + String host = parsedHost.getHost(); // Try to interpret the specifier as an IP address. Note we build // the address rather than using the .is* methods because we want to @@ -92,7 +93,7 @@ public static HostSpecifier fromValid(String specifier) { // It is not any kind of IP address; must be a domain name or invalid. // TODO(user): different versions of this for different factories? - final InternetDomainName domain = InternetDomainName.from(host); + InternetDomainName domain = InternetDomainName.from(host); if (domain.hasPublicSuffix()) { return new HostSpecifier(domain.toString()); @@ -109,6 +110,7 @@ public static HostSpecifier fromValid(String specifier) { * * @throws ParseException if the specifier is not valid. */ + @CanIgnoreReturnValue // TODO(b/219820829): consider removing public static HostSpecifier from(String specifier) throws ParseException { try { return fromValid(specifier); @@ -129,7 +131,7 @@ public static HostSpecifier from(String specifier) throws ParseException { */ public static boolean isValid(String specifier) { try { - fromValid(specifier); + HostSpecifier unused = fromValid(specifier); return true; } catch (IllegalArgumentException e) { return false; @@ -137,13 +139,13 @@ public static boolean isValid(String specifier) { } @Override - public boolean equals(@NullableDecl Object other) { + public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (other instanceof HostSpecifier) { - final HostSpecifier that = (HostSpecifier) other; + HostSpecifier that = (HostSpecifier) other; return this.canonicalForm.equals(that.canonicalForm); } diff --git a/android/guava/src/com/google/common/net/HttpHeaders.java b/android/guava/src/com/google/common/net/HttpHeaders.java index 2755a8fc9d2c..4d2c0d0f5df4 100644 --- a/android/guava/src/com/google/common/net/HttpHeaders.java +++ b/android/guava/src/com/google/common/net/HttpHeaders.java @@ -14,7 +14,6 @@ package com.google.common.net; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; /** @@ -39,16 +38,22 @@ private HttpHeaders() {} /** The HTTP {@code Cache-Control} header field name. */ public static final String CACHE_CONTROL = "Cache-Control"; + /** The HTTP {@code Content-Length} header field name. */ public static final String CONTENT_LENGTH = "Content-Length"; + /** The HTTP {@code Content-Type} header field name. */ public static final String CONTENT_TYPE = "Content-Type"; + /** The HTTP {@code Date} header field name. */ public static final String DATE = "Date"; + /** The HTTP {@code Pragma} header field name. */ public static final String PRAGMA = "Pragma"; + /** The HTTP {@code Via} header field name. */ public static final String VIA = "Via"; + /** The HTTP {@code Warning} header field name. */ public static final String WARNING = "Warning"; @@ -56,22 +61,31 @@ private HttpHeaders() {} /** The HTTP {@code Accept} header field name. */ public static final String ACCEPT = "Accept"; + /** The HTTP {@code Accept-Charset} header field name. */ public static final String ACCEPT_CHARSET = "Accept-Charset"; + /** The HTTP {@code Accept-Encoding} header field name. */ public static final String ACCEPT_ENCODING = "Accept-Encoding"; + /** The HTTP {@code Accept-Language} header field name. */ public static final String ACCEPT_LANGUAGE = "Accept-Language"; + /** The HTTP {@code Access-Control-Request-Headers} header field name. */ public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + /** The HTTP {@code Access-Control-Request-Method} header field name. */ public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + /** The HTTP {@code Authorization} header field name. */ public static final String AUTHORIZATION = "Authorization"; + /** The HTTP {@code Connection} header field name. */ public static final String CONNECTION = "Connection"; + /** The HTTP {@code Cookie} header field name. */ public static final String COOKIE = "Cookie"; + /** * The HTTP {@code * Cross-Origin-Resource-Policy} header field name. @@ -79,55 +93,71 @@ private HttpHeaders() {} * @since 28.0 */ public static final String CROSS_ORIGIN_RESOURCE_POLICY = "Cross-Origin-Resource-Policy"; + /** - * The HTTP {@code Early-Data} header field - * name. + * The HTTP {@code Early-Data} header + * field name. * * @since 27.0 */ public static final String EARLY_DATA = "Early-Data"; + /** The HTTP {@code Expect} header field name. */ public static final String EXPECT = "Expect"; + /** The HTTP {@code From} header field name. */ public static final String FROM = "From"; + /** - * The HTTP {@code Forwarded} header field name. + * The HTTP {@code Forwarded} header + * field name. * * @since 20.0 */ public static final String FORWARDED = "Forwarded"; + /** * The HTTP {@code Follow-Only-When-Prerender-Shown} header field name. * * @since 17.0 */ - @Beta public static final String FOLLOW_ONLY_WHEN_PRERENDER_SHOWN = "Follow-Only-When-Prerender-Shown"; + /** The HTTP {@code Host} header field name. */ public static final String HOST = "Host"; + /** - * The HTTP {@code HTTP2-Settings} - * header field name. + * The HTTP {@code + * HTTP2-Settings} header field name. * * @since 24.0 */ public static final String HTTP2_SETTINGS = "HTTP2-Settings"; + /** The HTTP {@code If-Match} header field name. */ public static final String IF_MATCH = "If-Match"; + /** The HTTP {@code If-Modified-Since} header field name. */ public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + /** The HTTP {@code If-None-Match} header field name. */ public static final String IF_NONE_MATCH = "If-None-Match"; + /** The HTTP {@code If-Range} header field name. */ public static final String IF_RANGE = "If-Range"; + /** The HTTP {@code If-Unmodified-Since} header field name. */ public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + /** The HTTP {@code Last-Event-ID} header field name. */ public static final String LAST_EVENT_ID = "Last-Event-ID"; + /** The HTTP {@code Max-Forwards} header field name. */ public static final String MAX_FORWARDS = "Max-Forwards"; + /** The HTTP {@code Origin} header field name. */ public static final String ORIGIN = "Origin"; + /** * The HTTP {@code Origin-Isolation} header * field name. @@ -135,12 +165,16 @@ private HttpHeaders() {} * @since 30.1 */ public static final String ORIGIN_ISOLATION = "Origin-Isolation"; + /** The HTTP {@code Proxy-Authorization} header field name. */ public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + /** The HTTP {@code Range} header field name. */ public static final String RANGE = "Range"; + /** The HTTP {@code Referer} header field name. */ public static final String REFERER = "Referer"; + /** * The HTTP {@code Referrer-Policy} header * field name. @@ -175,10 +209,13 @@ private ReferrerPolicyValues() {} * @since 20.0 */ public static final String SERVICE_WORKER = "Service-Worker"; + /** The HTTP {@code TE} header field name. */ public static final String TE = "TE"; + /** The HTTP {@code Upgrade} header field name. */ public static final String UPGRADE = "Upgrade"; + /** * The HTTP {@code * Upgrade-Insecure-Requests} header field name. @@ -194,34 +231,58 @@ private ReferrerPolicyValues() {} /** The HTTP {@code Accept-Ranges} header field name. */ public static final String ACCEPT_RANGES = "Accept-Ranges"; + /** The HTTP {@code Access-Control-Allow-Headers} header field name. */ public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + /** The HTTP {@code Access-Control-Allow-Methods} header field name. */ public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + /** The HTTP {@code Access-Control-Allow-Origin} header field name. */ public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + + /** + * The HTTP {@code + * Access-Control-Allow-Private-Network} header field name. + * + * @since 31.1 + */ + public static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = + "Access-Control-Allow-Private-Network"; + /** The HTTP {@code Access-Control-Allow-Credentials} header field name. */ public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + /** The HTTP {@code Access-Control-Expose-Headers} header field name. */ public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + /** The HTTP {@code Access-Control-Max-Age} header field name. */ public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + /** The HTTP {@code Age} header field name. */ public static final String AGE = "Age"; + /** The HTTP {@code Allow} header field name. */ public static final String ALLOW = "Allow"; + /** The HTTP {@code Content-Disposition} header field name. */ public static final String CONTENT_DISPOSITION = "Content-Disposition"; + /** The HTTP {@code Content-Encoding} header field name. */ public static final String CONTENT_ENCODING = "Content-Encoding"; + /** The HTTP {@code Content-Language} header field name. */ public static final String CONTENT_LANGUAGE = "Content-Language"; + /** The HTTP {@code Content-Location} header field name. */ public static final String CONTENT_LOCATION = "Content-Location"; + /** The HTTP {@code Content-MD5} header field name. */ public static final String CONTENT_MD5 = "Content-MD5"; + /** The HTTP {@code Content-Range} header field name. */ public static final String CONTENT_RANGE = "Content-Range"; + /** * The HTTP {@code * Content-Security-Policy} header field name. @@ -229,6 +290,7 @@ private ReferrerPolicyValues() {} * @since 15.0 */ public static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy"; + /** * The HTTP * {@code Content-Security-Policy-Report-Only} header field name. @@ -237,6 +299,7 @@ private ReferrerPolicyValues() {} */ public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"; + /** * The HTTP nonstandard {@code X-Content-Security-Policy} header field name. It was introduced in * CSP v.1 and used by the Firefox until @@ -246,6 +309,7 @@ private ReferrerPolicyValues() {} * @since 20.0 */ public static final String X_CONTENT_SECURITY_POLICY = "X-Content-Security-Policy"; + /** * The HTTP nonstandard {@code X-Content-Security-Policy-Report-Only} header field name. It was * introduced in CSP v.1 and used by the @@ -256,6 +320,7 @@ private ReferrerPolicyValues() {} */ public static final String X_CONTENT_SECURITY_POLICY_REPORT_ONLY = "X-Content-Security-Policy-Report-Only"; + /** * The HTTP nonstandard {@code X-WebKit-CSP} header field name. It was introduced in CSP v.1 and used by the Chrome until @@ -264,6 +329,7 @@ private ReferrerPolicyValues() {} * @since 20.0 */ public static final String X_WEBKIT_CSP = "X-WebKit-CSP"; + /** * The HTTP nonstandard {@code X-WebKit-CSP-Report-Only} header field name. It was introduced in * CSP v.1 and used by the Chrome until @@ -272,6 +338,7 @@ private ReferrerPolicyValues() {} * @since 20.0 */ public static final String X_WEBKIT_CSP_REPORT_ONLY = "X-WebKit-CSP-Report-Only"; + /** * The HTTP {@code * Cross-Origin-Embedder-Policy} header field name. @@ -279,6 +346,7 @@ private ReferrerPolicyValues() {} * @since 30.0 */ public static final String CROSS_ORIGIN_EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy"; + /** * The HTTP {@code * Cross-Origin-Embedder-Policy-Report-Only} header field name. @@ -287,22 +355,44 @@ private ReferrerPolicyValues() {} */ public static final String CROSS_ORIGIN_EMBEDDER_POLICY_REPORT_ONLY = "Cross-Origin-Embedder-Policy-Report-Only"; + /** * The HTTP Cross-Origin-Opener-Policy header field name. * * @since 28.2 */ public static final String CROSS_ORIGIN_OPENER_POLICY = "Cross-Origin-Opener-Policy"; + /** The HTTP {@code ETag} header field name. */ public static final String ETAG = "ETag"; + /** The HTTP {@code Expires} header field name. */ public static final String EXPIRES = "Expires"; + /** The HTTP {@code Last-Modified} header field name. */ public static final String LAST_MODIFIED = "Last-Modified"; + /** The HTTP {@code Link} header field name. */ public static final String LINK = "Link"; + /** The HTTP {@code Location} header field name. */ public static final String LOCATION = "Location"; + + /** + * The HTTP {@code Keep-Alive} header field name. + * + * @since 31.0 + */ + public static final String KEEP_ALIVE = "Keep-Alive"; + + /** + * The HTTP {@code + * No-Vary-Seearch} header field name. + * + * @since 32.0.0 + */ + public static final String NO_VARY_SEARCH = "No-Vary-Search"; + /** * The HTTP {@code Origin-Trial} * header field name. @@ -310,22 +400,29 @@ private ReferrerPolicyValues() {} * @since 27.1 */ public static final String ORIGIN_TRIAL = "Origin-Trial"; + /** The HTTP {@code P3P} header field name. Limited browser support. */ public static final String P3P = "P3P"; + /** The HTTP {@code Proxy-Authenticate} header field name. */ public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + /** The HTTP {@code Refresh} header field name. Non-standard header supported by most browsers. */ public static final String REFRESH = "Refresh"; + /** * The HTTP {@code Report-To} header field name. * * @since 27.1 */ public static final String REPORT_TO = "Report-To"; + /** The HTTP {@code Retry-After} header field name. */ public static final String RETRY_AFTER = "Retry-After"; + /** The HTTP {@code Server} header field name. */ public static final String SERVER = "Server"; + /** * The HTTP {@code Server-Timing} header field * name. @@ -333,6 +430,7 @@ private ReferrerPolicyValues() {} * @since 23.6 */ public static final String SERVER_TIMING = "Server-Timing"; + /** * The HTTP {@code * Service-Worker-Allowed} header field name. @@ -340,17 +438,30 @@ private ReferrerPolicyValues() {} * @since 20.0 */ public static final String SERVICE_WORKER_ALLOWED = "Service-Worker-Allowed"; + /** The HTTP {@code Set-Cookie} header field name. */ public static final String SET_COOKIE = "Set-Cookie"; + /** The HTTP {@code Set-Cookie2} header field name. */ public static final String SET_COOKIE2 = "Set-Cookie2"; /** - * The HTTP {@code SourceMap} header field name. + * The HTTP {@code + * SourceMap} header field name. * * @since 27.1 */ - @Beta public static final String SOURCE_MAP = "SourceMap"; + public static final String SOURCE_MAP = "SourceMap"; + + /** + * The HTTP {@code + * Supports-Loading-Mode} header field name. This can be used to specify, for example, fenced + * frames. + * + * @since 32.0.0 + */ + public static final String SUPPORTS_LOADING_MODE = "Supports-Loading-Mode"; /** * The HTTP {@code @@ -359,6 +470,7 @@ private ReferrerPolicyValues() {} * @since 15.0 */ public static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"; + /** * The HTTP {@code * Timing-Allow-Origin} header field name. @@ -366,12 +478,16 @@ private ReferrerPolicyValues() {} * @since 15.0 */ public static final String TIMING_ALLOW_ORIGIN = "Timing-Allow-Origin"; + /** The HTTP {@code Trailer} header field name. */ public static final String TRAILER = "Trailer"; + /** The HTTP {@code Transfer-Encoding} header field name. */ public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + /** The HTTP {@code Vary} header field name. */ public static final String VARY = "Vary"; + /** The HTTP {@code WWW-Authenticate} header field name. */ public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; @@ -379,56 +495,119 @@ private ReferrerPolicyValues() {} /** The HTTP {@code DNT} header field name. */ public static final String DNT = "DNT"; + /** The HTTP {@code X-Content-Type-Options} header field name. */ public static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; + + /** + * The HTTP {@code + * X-Device-IP} header field name. Header used for VAST requests to provide the IP address of + * the device on whose behalf the request is being made. + * + * @since 31.0 + */ + public static final String X_DEVICE_IP = "X-Device-IP"; + + /** + * The HTTP {@code + * X-Device-Referer} header field name. Header used for VAST requests to provide the {@link + * #REFERER} header value that the on-behalf-of client would have used when making a request + * itself. + * + * @since 31.0 + */ + public static final String X_DEVICE_REFERER = "X-Device-Referer"; + + /** + * The HTTP {@code + * X-Device-Accept-Language} header field name. Header used for VAST requests to provide the + * {@link #ACCEPT_LANGUAGE} header value that the on-behalf-of client would have used when making + * a request itself. + * + * @since 31.0 + */ + public static final String X_DEVICE_ACCEPT_LANGUAGE = "X-Device-Accept-Language"; + + /** + * The HTTP {@code + * X-Device-Requested-With} header field name. Header used for VAST requests to provide the + * {@link #X_REQUESTED_WITH} header value that the on-behalf-of client would have used when making + * a request itself. + * + * @since 31.0 + */ + public static final String X_DEVICE_REQUESTED_WITH = "X-Device-Requested-With"; + /** The HTTP {@code X-Do-Not-Track} header field name. */ public static final String X_DO_NOT_TRACK = "X-Do-Not-Track"; + /** The HTTP {@code X-Forwarded-For} header field name (superseded by {@code Forwarded}). */ public static final String X_FORWARDED_FOR = "X-Forwarded-For"; + /** The HTTP {@code X-Forwarded-Proto} header field name. */ public static final String X_FORWARDED_PROTO = "X-Forwarded-Proto"; + /** - * The HTTP {@code X-Forwarded-Host} header field name. + * The HTTP {@code + * X-Forwarded-Host} header field name. * * @since 20.0 */ public static final String X_FORWARDED_HOST = "X-Forwarded-Host"; + /** - * The HTTP {@code X-Forwarded-Port} header field name. + * The HTTP {@code + * X-Forwarded-Port} header field name. * * @since 20.0 */ public static final String X_FORWARDED_PORT = "X-Forwarded-Port"; + /** The HTTP {@code X-Frame-Options} header field name. */ public static final String X_FRAME_OPTIONS = "X-Frame-Options"; + /** The HTTP {@code X-Powered-By} header field name. */ public static final String X_POWERED_BY = "X-Powered-By"; + /** * The HTTP {@code * Public-Key-Pins} header field name. * * @since 15.0 */ - @Beta public static final String PUBLIC_KEY_PINS = "Public-Key-Pins"; + public static final String PUBLIC_KEY_PINS = "Public-Key-Pins"; + /** * The HTTP {@code * Public-Key-Pins-Report-Only} header field name. * * @since 15.0 */ - @Beta public static final String PUBLIC_KEY_PINS_REPORT_ONLY = "Public-Key-Pins-Report-Only"; + public static final String PUBLIC_KEY_PINS_REPORT_ONLY = "Public-Key-Pins-Report-Only"; + /** * The HTTP {@code X-Request-ID} header field name. * * @since 30.1 */ public static final String X_REQUEST_ID = "X-Request-ID"; + /** The HTTP {@code X-Requested-With} header field name. */ public static final String X_REQUESTED_WITH = "X-Requested-With"; + /** The HTTP {@code X-User-IP} header field name. */ public static final String X_USER_IP = "X-User-IP"; + /** - * The HTTP {@code X-Download-Options} header field name. + * The HTTP {@code + * X-Download-Options} header field name. * *

    When the new X-Download-Options header is present with the value {@code noopen}, the user is * prevented from opening a file download directly; instead, they must first save the file @@ -436,9 +615,11 @@ private ReferrerPolicyValues() {} * * @since 24.1 */ - @Beta public static final String X_DOWNLOAD_OPTIONS = "X-Download-Options"; + public static final String X_DOWNLOAD_OPTIONS = "X-Download-Options"; + /** The HTTP {@code X-XSS-Protection} header field name. */ public static final String X_XSS_PROTECTION = "X-XSS-Protection"; + /** * The HTTP {@code @@ -446,6 +627,7 @@ private ReferrerPolicyValues() {} * By default, DNS prefetching is "on" for HTTP pages and "off" for HTTPS pages. */ public static final String X_DNS_PREFETCH_CONTROL = "X-DNS-Prefetch-Control"; + /** * The HTTP * {@code Ping-From} header field name. @@ -453,6 +635,7 @@ private ReferrerPolicyValues() {} * @since 19.0 */ public static final String PING_FROM = "Ping-From"; + /** * The HTTP * {@code Ping-To} header field name. @@ -469,6 +652,7 @@ private ReferrerPolicyValues() {} * @since 28.0 */ public static final String PURPOSE = "Purpose"; + /** * The HTTP {@code @@ -477,6 +661,7 @@ private ReferrerPolicyValues() {} * @since 28.0 */ public static final String X_PURPOSE = "X-Purpose"; + /** * The HTTP {@code @@ -486,6 +671,110 @@ private ReferrerPolicyValues() {} */ public static final String X_MOZ = "X-Moz"; + /** + * The HTTP {@code + * Device-Memory} header field name. + * + * @since 31.0 + */ + public static final String DEVICE_MEMORY = "Device-Memory"; + + /** + * The HTTP {@code + * Downlink} header field name. + * + * @since 31.0 + */ + public static final String DOWNLINK = "Downlink"; + + /** + * The HTTP {@code + * ECT} header field name. + * + * @since 31.0 + */ + public static final String ECT = "ECT"; + + /** + * The HTTP {@code + * RTT} header field name. + * + * @since 31.0 + */ + public static final String RTT = "RTT"; + + /** + * The HTTP {@code + * Save-Data} header field name. + * + * @since 31.0 + */ + public static final String SAVE_DATA = "Save-Data"; + + /** + * The HTTP {@code + * Viewport-Width} header field name. + * + * @since 31.0 + */ + public static final String VIEWPORT_WIDTH = "Viewport-Width"; + + /** + * The HTTP {@code + * Width} header field name. + * + * @since 31.0 + */ + public static final String WIDTH = "Width"; + + /** + * The HTTP {@code Permissions-Policy} + * header field name. + * + * @since 31.0 + */ + public static final String PERMISSIONS_POLICY = "Permissions-Policy"; + + /** + * The HTTP {@code + * Permissions-Policy-Report-Only} header field name. + * + * @since 33.2.0 + */ + public static final String PERMISSIONS_POLICY_REPORT_ONLY = "Permissions-Policy-Report-Only"; + + /** + * The HTTP {@code + * Sec-CH-Prefers-Color-Scheme} header field name. + * + *

    This header is experimental. + * + * @since 31.0 + */ + public static final String SEC_CH_PREFERS_COLOR_SCHEME = "Sec-CH-Prefers-Color-Scheme"; + + /** + * The HTTP {@code + * Accept-CH} header field name. + * + * @since 31.0 + */ + public static final String ACCEPT_CH = "Accept-CH"; + + /** + * The HTTP {@code + * Critical-CH} header field name. + * + * @since 31.0 + */ + public static final String CRITICAL_CH = "Critical-CH"; + /** * The HTTP {@code Sec-CH-UA} * header field name. @@ -493,49 +782,123 @@ private ReferrerPolicyValues() {} * @since 30.0 */ public static final String SEC_CH_UA = "Sec-CH-UA"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-CH-UA-Arch} header field name. * * @since 30.0 */ public static final String SEC_CH_UA_ARCH = "Sec-CH-UA-Arch"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-CH-UA-Model} header field name. * * @since 30.0 */ public static final String SEC_CH_UA_MODEL = "Sec-CH-UA-Model"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-CH-UA-Platform} header field name. * * @since 30.0 */ public static final String SEC_CH_UA_PLATFORM = "Sec-CH-UA-Platform"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-CH-UA-Platform-Version} header field name. * * @since 30.0 */ public static final String SEC_CH_UA_PLATFORM_VERSION = "Sec-CH-UA-Platform-Version"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-CH-UA-Full-Version} header field name. * + * @deprecated Prefer {@link SEC_CH_UA_FULL_VERSION_LIST}. * @since 30.0 */ - public static final String SEC_CH_UA_FULL_VERSION = "Sec-CH-UA-Full-Version"; + @Deprecated public static final String SEC_CH_UA_FULL_VERSION = "Sec-CH-UA-Full-Version"; + + /** + * The HTTP {@code + * Sec-CH-UA-Full-Version} header field name. + * + * @since 31.1 + */ + public static final String SEC_CH_UA_FULL_VERSION_LIST = "Sec-CH-UA-Full-Version-List"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-CH-UA-Mobile} header field name. * * @since 30.0 */ public static final String SEC_CH_UA_MOBILE = "Sec-CH-UA-Mobile"; + /** + * The HTTP {@code + * Sec-CH-UA-WoW64} header field name. + * + * @since 32.0.0 + */ + public static final String SEC_CH_UA_WOW64 = "Sec-CH-UA-WoW64"; + + /** + * The HTTP {@code + * Sec-CH-UA-Bitness} header field name. + * + * @since 31.0 + */ + public static final String SEC_CH_UA_BITNESS = "Sec-CH-UA-Bitness"; + + /** + * The HTTP {@code + * Sec-CH-UA-Form-Factor} header field name. + * + * @deprecated Prefer {@link SEC_CH_UA_FORM_FACTORS}. + * @since 32.0.0 + */ + @Deprecated public static final String SEC_CH_UA_FORM_FACTOR = "Sec-CH-UA-Form-Factor"; + + /** + * The HTTP {@code + * Sec-CH-UA-Form-Factors} header field name. + * + * @since 33.3.0 + */ + public static final String SEC_CH_UA_FORM_FACTORS = "Sec-CH-UA-Form-Factors"; + + /** + * The HTTP {@code + * Sec-CH-Viewport-Width} header field name. + * + * @since 32.0.0 + */ + public static final String SEC_CH_VIEWPORT_WIDTH = "Sec-CH-Viewport-Width"; + + /** + * The HTTP {@code + * Sec-CH-Viewport-Height} header field name. + * + * @since 32.0.0 + */ + public static final String SEC_CH_VIEWPORT_HEIGHT = "Sec-CH-Viewport-Height"; + + /** + * The HTTP {@code + * Sec-CH-DPR} header field name. + * + * @since 32.0.0 + */ + public static final String SEC_CH_DPR = "Sec-CH-DPR"; + /** * The HTTP {@code Sec-Fetch-Dest} * header field name. @@ -543,6 +906,7 @@ private ReferrerPolicyValues() {} * @since 27.1 */ public static final String SEC_FETCH_DEST = "Sec-Fetch-Dest"; + /** * The HTTP {@code Sec-Fetch-Mode} * header field name. @@ -550,6 +914,7 @@ private ReferrerPolicyValues() {} * @since 27.1 */ public static final String SEC_FETCH_MODE = "Sec-Fetch-Mode"; + /** * The HTTP {@code Sec-Fetch-Site} * header field name. @@ -557,6 +922,7 @@ private ReferrerPolicyValues() {} * @since 27.1 */ public static final String SEC_FETCH_SITE = "Sec-Fetch-Site"; + /** * The HTTP {@code Sec-Fetch-User} * header field name. @@ -564,6 +930,7 @@ private ReferrerPolicyValues() {} * @since 27.1 */ public static final String SEC_FETCH_USER = "Sec-Fetch-User"; + /** * The HTTP {@code Sec-Metadata} * header field name. @@ -571,66 +938,135 @@ private ReferrerPolicyValues() {} * @since 26.0 */ public static final String SEC_METADATA = "Sec-Metadata"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-Token-Binding} header field name. * * @since 25.1 */ public static final String SEC_TOKEN_BINDING = "Sec-Token-Binding"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-Provided-Token-Binding-ID} header field name. * * @since 25.1 */ public static final String SEC_PROVIDED_TOKEN_BINDING_ID = "Sec-Provided-Token-Binding-ID"; + /** - * The HTTP {@code + * The HTTP {@code * Sec-Referred-Token-Binding-ID} header field name. * * @since 25.1 */ public static final String SEC_REFERRED_TOKEN_BINDING_ID = "Sec-Referred-Token-Binding-ID"; + /** - * The HTTP {@code Sec-WebSocket-Accept} header - * field name. + * The HTTP {@code + * Sec-WebSocket-Accept} header field name. * * @since 28.0 */ public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; + /** - * The HTTP {@code Sec-WebSocket-Extensions} - * header field name. + * The HTTP {@code + * Sec-WebSocket-Extensions} header field name. * * @since 28.0 */ public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions"; + /** - * The HTTP {@code Sec-WebSocket-Key} header - * field name. + * The HTTP {@code Sec-WebSocket-Key} + * header field name. * * @since 28.0 */ public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; + /** - * The HTTP {@code Sec-WebSocket-Protocol} - * header field name. + * The HTTP {@code + * Sec-WebSocket-Protocol} header field name. * * @since 28.0 */ public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; + /** - * The HTTP {@code Sec-WebSocket-Version} header - * field name. + * The HTTP {@code + * Sec-WebSocket-Version} header field name. * * @since 28.0 */ public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; + + /** + * The HTTP {@code + * Sec-Browsing-Topics} header field name. + * + * @since 32.0.0 + */ + public static final String SEC_BROWSING_TOPICS = "Sec-Browsing-Topics"; + + /** + * The HTTP {@code + * Observe-Browsing-Topics} header field name. + * + * @since 32.0.0 + */ + public static final String OBSERVE_BROWSING_TOPICS = "Observe-Browsing-Topics"; + + /** + * The HTTP {@code + * Sec-Ad-Auction-Fetch} header field name. + * + * @since 33.0.0 + */ + public static final String SEC_AD_AUCTION_FETCH = "Sec-Ad-Auction-Fetch"; + + /** + * The HTTP {@code + * Sec-GPC} header field name. + * + * @since 33.2.0 + */ + public static final String SEC_GPC = "Sec-GPC"; + + /** + * The HTTP {@code + * Ad-Auction-Signals} header field name. + * + * @since 33.0.0 + */ + public static final String AD_AUCTION_SIGNALS = "Ad-Auction-Signals"; + + /** + * The HTTP {@code + * Ad-Auction-Allowed} header field name. + * + * @since 33.2.0 + */ + public static final String AD_AUCTION_ALLOWED = "Ad-Auction-Allowed"; + /** - * The HTTP {@code CDN-Loop} header field name. + * The HTTP {@code CDN-Loop} header + * field name. * * @since 28.0 */ public static final String CDN_LOOP = "CDN-Loop"; + + /** + * The HTTP {@code Alt-Svc} + * header field name. + * + * @since 33.4.0 + */ + public static final String ALT_SVC = "Alt-Svc"; } diff --git a/android/guava/src/com/google/common/net/InetAddresses.java b/android/guava/src/com/google/common/net/InetAddresses.java index 75699dfd06f8..c8cdda10c435 100644 --- a/android/guava/src/com/google/common/net/InetAddresses.java +++ b/android/guava/src/com/google/common/net/InetAddresses.java @@ -16,23 +16,28 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.Math.max; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.CharMatcher; import com.google.common.base.MoreObjects; import com.google.common.hash.Hashing; import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Locale; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@link InetAddress} instances. @@ -95,7 +100,7 @@ * @author Erik Kline * @since 5.0 */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class InetAddresses { private static final int IPV4_PART_COUNT = 4; @@ -123,7 +128,7 @@ private static Inet4Address getInet4Address(byte[] bytes) { bytes.length); // Given a 4-byte array, this cast should always succeed. - return (Inet4Address) bytesToInetAddress(bytes); + return (Inet4Address) bytesToInetAddress(bytes, null); } /** @@ -131,38 +136,63 @@ private static Inet4Address getInet4Address(byte[] bytes) { * *

    This deliberately avoids all nameservice lookups (e.g. no DNS). * - *

    Anything after a {@code %} in an IPv6 address is ignored (assumed to be a Scope ID). + *

    This method accepts non-ASCII digits, for example {@code "192.168.0.1"} (those are fullwidth + * characters). That is consistent with {@link InetAddress}, but not with various RFCs. If you + * want to accept ASCII digits only, you can use something like {@code + * CharMatcher.ascii().matchesAllOf(ipString)}. + * + *

    The scope ID is validated against the interfaces on the machine, which requires permissions + * under Android. + * + *

    Android users on API >= 29: Prefer {@code InetAddresses.parseNumericAddress}. * * @param ipString {@code String} containing an IPv4 or IPv6 string literal, e.g. {@code - * "192.168.0.1"} or {@code "2001:db8::1"} + * "192.168.0.1"} or {@code "2001:db8::1"} or with a scope ID, e.g. {@code "2001:db8::1%eth0"} * @return {@link InetAddress} representing the argument - * @throws IllegalArgumentException if the argument is not a valid IP string literal + * @throws IllegalArgumentException if the argument is not a valid IP string literal or if the + * address has a scope ID that fails validation against the interfaces on the machine (as + * required by Java's {@link InetAddress}) */ + @CanIgnoreReturnValue // TODO(b/219820829): consider removing public static InetAddress forString(String ipString) { - byte[] addr = ipStringToBytes(ipString); + Scope scope = new Scope(); + byte[] addr = ipStringToBytes(ipString, scope); // The argument was malformed, i.e. not an IP string literal. if (addr == null) { throw formatIllegalArgumentException("'%s' is not an IP string literal.", ipString); } - return bytesToInetAddress(addr); + return bytesToInetAddress(addr, scope.scope); } /** * Returns {@code true} if the supplied string is a valid IP string literal, {@code false} * otherwise. * + *

    This method accepts non-ASCII digits, for example {@code "192.168.0.1"} (those are fullwidth + * characters). That is consistent with {@link InetAddress}, but not with various RFCs. If you + * want to accept ASCII digits only, you can use something like {@code + * CharMatcher.ascii().matchesAllOf(ipString)}. + * + *

    Note that if this method returns {@code true}, a call to {@link #forString(String)} can + * still throw if the address has a scope ID that fails validation against the interfaces on the + * machine. + * * @param ipString {@code String} to evaluated as an IP string literal * @return {@code true} if the argument is a valid IP string literal */ public static boolean isInetAddress(String ipString) { - return ipStringToBytes(ipString) != null; + return ipStringToBytes(ipString, null) != null; + } + + private static final class Scope { + private String scope; } /** Returns {@code null} if unable to parse into a {@code byte[]}. */ - @NullableDecl - private static byte[] ipStringToBytes(String ipString) { + private static byte @Nullable [] ipStringToBytes(String ipStringParam, @Nullable Scope scope) { + String ipString = ipStringParam; // Make a first pass to categorize the characters in this string. boolean hasColon = false; boolean hasDot = false; @@ -178,7 +208,7 @@ private static byte[] ipStringToBytes(String ipString) { hasColon = true; } else if (c == '%') { percentIndex = i; - break; // everything after a '%' is ignored (it's a Scope ID): http://superuser.com/a/99753 + break; } else if (Character.digit(c, 16) == -1) { return null; // Everything else must be a decimal or hex digit. } @@ -193,6 +223,9 @@ private static byte[] ipStringToBytes(String ipString) { } } if (percentIndex != -1) { + if (scope != null) { + scope.scope = ipString.substring(percentIndex + 1); + } ipString = ipString.substring(0, percentIndex); } return textToNumericFormatV6(ipString); @@ -205,8 +238,7 @@ private static byte[] ipStringToBytes(String ipString) { return null; } - @NullableDecl - private static byte[] textToNumericFormatV4(String ipString) { + private static byte @Nullable [] textToNumericFormatV4(String ipString) { if (IPV4_DELIMITER_MATCHER.countIn(ipString) + 1 != IPV4_PART_COUNT) { return null; // Wrong number of parts } @@ -231,8 +263,7 @@ private static byte[] textToNumericFormatV4(String ipString) { return bytes; } - @NullableDecl - private static byte[] textToNumericFormatV6(String ipString) { + private static byte @Nullable [] textToNumericFormatV6(String ipString) { // An address can have [2..8] colons. int delimiterCount = IPV6_DELIMITER_MATCHER.countIn(ipString); if (delimiterCount < 2 || delimiterCount > IPV6_PART_COUNT) { @@ -302,8 +333,7 @@ private static byte[] textToNumericFormatV6(String ipString) { return rawBytes.array(); } - @NullableDecl - private static String convertDottedQuadToHex(String ipString) { + private static @Nullable String convertDottedQuadToHex(String ipString) { int lastColon = ipString.lastIndexOf(':'); String initialPart = ipString.substring(0, lastColon + 1); String dottedQuad = ipString.substring(lastColon + 1); @@ -343,6 +373,24 @@ private static byte parseOctet(String ipString, int start, int end) { return (byte) octet; } + /** Returns a -1 if unable to parse */ + private static int tryParseDecimal(String string, int start, int end) { + int decimal = 0; + int max = Integer.MAX_VALUE / 10; // for int overflow detection + for (int i = start; i < end; i++) { + if (decimal > max) { + return -1; + } + decimal *= 10; + int digit = Character.digit(string.charAt(i), 10); + if (digit < 0) { + return -1; + } + decimal += digit; + } + return decimal; + } + // Parse a hextet out of the ipString from start (inclusive) to end (exclusive) private static short parseHextet(String ipString, int start, int end) { // Note: we already verified that this string contains only hex digits. @@ -368,9 +416,30 @@ private static short parseHextet(String ipString, int start, int end) { * @param addr the raw 4-byte or 16-byte IP address in big-endian order * @return an InetAddress object created from the raw IP address */ - private static InetAddress bytesToInetAddress(byte[] addr) { + private static InetAddress bytesToInetAddress(byte[] addr, @Nullable String scope) { try { - return InetAddress.getByAddress(addr); + InetAddress address = InetAddress.getByAddress(addr); + if (scope == null) { + return address; + } + checkArgument( + address instanceof Inet6Address, "Unexpected state, scope should only appear for ipv6"); + Inet6Address v6Address = (Inet6Address) address; + int interfaceIndex = tryParseDecimal(scope, 0, scope.length()); + if (interfaceIndex != -1) { + return Inet6Address.getByAddress( + v6Address.getHostAddress(), v6Address.getAddress(), interfaceIndex); + } + try { + NetworkInterface asInterface = NetworkInterface.getByName(scope); + if (asInterface == null) { + throw formatIllegalArgumentException("No such interface: '%s'", scope); + } + return Inet6Address.getByAddress( + v6Address.getHostAddress(), v6Address.getAddress(), asInterface); + } catch (SocketException | UnknownHostException e) { + throw new IllegalArgumentException("No such interface: " + scope, e); + } } catch (UnknownHostException e) { throw new AssertionError(e); } @@ -382,10 +451,13 @@ private static InetAddress bytesToInetAddress(byte[] addr) { *

    For IPv4 addresses, this is identical to {@link InetAddress#getHostAddress()}, but for IPv6 * addresses, the output follows RFC 5952 section * 4. The main difference is that this method uses "::" for zero compression, while Java's version - * uses the uncompressed form. + * uses the uncompressed form (except on Android, where the zero compression is also done). The + * other difference is that this method outputs any scope ID in the format that it was provided at + * creation time, while Android may always output it as an interface name, even if it was supplied + * as a numeric ID. * *

    This method uses hexadecimal for all IPv6 addresses, including IPv4-mapped IPv6 addresses - * such as "::c000:201". The output does not include a Scope ID. + * such as "::c000:201". * * @param ip {@link InetAddress} to be converted to an address string * @return {@code String} containing the text-formatted IP address @@ -395,16 +467,32 @@ public static String toAddrString(InetAddress ip) { checkNotNull(ip); if (ip instanceof Inet4Address) { // For IPv4, Java's formatting is good enough. - return ip.getHostAddress(); + // requireNonNull accommodates Android's @RecentlyNullable annotation on getHostAddress + return requireNonNull(ip.getHostAddress()); } - checkArgument(ip instanceof Inet6Address); byte[] bytes = ip.getAddress(); int[] hextets = new int[IPV6_PART_COUNT]; for (int i = 0; i < hextets.length; i++) { hextets[i] = Ints.fromBytes((byte) 0, (byte) 0, bytes[2 * i], bytes[2 * i + 1]); } compressLongestRunOfZeroes(hextets); - return hextetsToIPv6String(hextets); + + return hextetsToIPv6String(hextets) + scopeWithDelimiter((Inet6Address) ip); + } + + private static String scopeWithDelimiter(Inet6Address ip) { + // getHostAddress on android sometimes maps the scope ID to an invalid interface name; if the + // mapped interface isn't present, fallback to use the scope ID (which has no validation against + // present interfaces) + NetworkInterface scopedInterface = ip.getScopedInterface(); + if (scopedInterface != null) { + return "%" + scopedInterface.getName(); + } + int scope = ip.getScopeId(); + if (scope != 0) { + return "%" + scope; + } + return ""; } /** @@ -500,18 +588,25 @@ public static String toUriString(InetAddress ip) { * Returns an InetAddress representing the literal IPv4 or IPv6 host portion of a URL, encoded in * the format specified by RFC 3986 section 3.2.2. * - *

    This function is similar to {@link InetAddresses#forString(String)}, however, it requires - * that IPv6 addresses are surrounded by square brackets. + *

    This method is similar to {@link InetAddresses#forString(String)}, however, it requires that + * IPv6 addresses are surrounded by square brackets. * - *

    This function is the inverse of {@link InetAddresses#toUriString(java.net.InetAddress)}. + *

    This method is the inverse of {@link InetAddresses#toUriString(java.net.InetAddress)}. * - * @param hostAddr A RFC 3986 section 3.2.2 encoded IPv4 or IPv6 address + *

    This method accepts non-ASCII digits, for example {@code "192.168.0.1"} (those are fullwidth + * characters). That is consistent with {@link InetAddress}, but not with various RFCs. If you + * want to accept ASCII digits only, you can use something like {@code + * CharMatcher.ascii().matchesAllOf(ipString)}. + * + * @param hostAddr an RFC 3986 section 3.2.2 encoded IPv4 or IPv6 address * @return an InetAddress representing the address in {@code hostAddr} * @throws IllegalArgumentException if {@code hostAddr} is not a valid IPv4 address, or IPv6 - * address surrounded by square brackets + * address surrounded by square brackets, or if the address has a scope ID that fails + * validation against the interfaces on the machine (as required by Java's {@link + * InetAddress}) */ public static InetAddress forUriString(String hostAddr) { - InetAddress addr = forUriStringNoThrow(hostAddr); + InetAddress addr = forUriStringOrNull(hostAddr, /* parseScope= */ true); if (addr == null) { throw formatIllegalArgumentException("Not a valid URI IP literal: '%s'", hostAddr); } @@ -519,8 +614,7 @@ public static InetAddress forUriString(String hostAddr) { return addr; } - @NullableDecl - private static InetAddress forUriStringNoThrow(String hostAddr) { + private static @Nullable InetAddress forUriStringOrNull(String hostAddr, boolean parseScope) { checkNotNull(hostAddr); // Decide if this should be an IPv6 or IPv4 address. @@ -535,23 +629,33 @@ private static InetAddress forUriStringNoThrow(String hostAddr) { } // Parse the address, and make sure the length/version is correct. - byte[] addr = ipStringToBytes(ipString); + Scope scope = parseScope ? new Scope() : null; + byte[] addr = ipStringToBytes(ipString, scope); if (addr == null || addr.length != expectBytes) { return null; } - return bytesToInetAddress(addr); + return bytesToInetAddress(addr, (scope != null) ? scope.scope : null); } /** * Returns {@code true} if the supplied string is a valid URI IP string literal, {@code false} * otherwise. * + *

    This method accepts non-ASCII digits, for example {@code "192.168.0.1"} (those are fullwidth + * characters). That is consistent with {@link InetAddress}, but not with various RFCs. If you + * want to accept ASCII digits only, you can use something like {@code + * CharMatcher.ascii().matchesAllOf(ipString)}. + * + *

    Note that if this method returns {@code true}, a call to {@link #forUriString(String)} can + * still throw if the address has a scope ID that fails validation against the interfaces on the + * machine. + * * @param ipString {@code String} to evaluated as an IP URI host string literal * @return {@code true} if the argument is a valid IP URI host */ public static boolean isUriInetAddress(String ipString) { - return forUriStringNoThrow(ipString) != null; + return forUriStringOrNull(ipString, /* parseScope= */ false) != null; } /** @@ -645,7 +749,6 @@ public static Inet4Address get6to4IPv4Address(Inet6Address ip) { * * @since 5.0 */ - @Beta public static final class TeredoInfo { private final Inet4Address server; private final Inet4Address client; @@ -663,7 +766,7 @@ public static final class TeredoInfo { */ // TODO: why is this public? public TeredoInfo( - @NullableDecl Inet4Address server, @NullableDecl Inet4Address client, int port, int flags) { + @Nullable Inet4Address server, @Nullable Inet4Address client, int port, int flags) { checkArgument( (port >= 0) && (port <= 0xffff), "port '%s' is out of range (0 <= port <= 0xffff)", port); checkArgument( @@ -842,12 +945,16 @@ public static Inet4Address getEmbeddedIPv4ClientAddress(Inet6Address ip) { * obscure {@link Inet6Address} methods, but it would be unwise to depend on such a * poorly-documented feature.) * + *

    This method accepts non-ASCII digits. That is consistent with {@link InetAddress}, but not + * with various RFCs. If you want to accept ASCII digits only, you can use something like {@code + * CharMatcher.ascii().matchesAllOf(ipString)}. + * * @param ipString {@code String} to be examined for embedded IPv4-mapped IPv6 address format * @return {@code true} if the argument is a valid "mapped" address * @since 10.0 */ public static boolean isMappedIPv4Address(String ipString) { - byte[] bytes = ipStringToBytes(ipString); + byte[] bytes = ipStringToBytes(ipString, null); if (bytes != null && bytes.length == 16) { for (int i = 0; i < 10; i++) { if (bytes[i] != 0) { @@ -869,7 +976,7 @@ public static boolean isMappedIPv4Address(String ipString) { * *

    HACK: As long as applications continue to use IPv4 addresses for indexing into tables, * accounting, et cetera, it may be necessary to coerce IPv6 addresses into IPv4 addresses. - * This function does so by hashing 64 bits of the IPv6 address into {@code 224.0.0.0/3} (64 bits + * This method does so by hashing 64 bits of the IPv6 address into {@code 224.0.0.0/3} (64 bits * into 29 bits): * *

      @@ -879,7 +986,7 @@ public static boolean isMappedIPv4Address(String ipString) { * *

      A "coerced" IPv4 address is equivalent to itself. * - *

      NOTE: This function is failsafe for security purposes: ALL IPv6 addresses (except localhost + *

      NOTE: This method is failsafe for security purposes: ALL IPv6 addresses (except localhost * (::1)) are hashed to avoid the security risk associated with extracting an embedded IPv4 * address that might permit elevated privileges. * @@ -917,7 +1024,7 @@ public static Inet4Address getCoercedIPv4Address(InetAddress ip) { } // Many strategies for hashing are possible. This might suffice for now. - int coercedHash = Hashing.murmur3_32().hashLong(addressAsLong).asInt(); + int coercedHash = Hashing.murmur3_32_fixed().hashLong(addressAsLong).asInt(); // Squash into 224/4 Multicast and 240/4 Reserved space (i.e. 224/3). coercedHash |= 0xe0000000; @@ -987,6 +1094,7 @@ public static Inet4Address fromInteger(int address) { public static Inet4Address fromIPv4BigInteger(BigInteger address) { return (Inet4Address) fromBigInteger(address, false); } + /** * Returns the {@code Inet6Address} corresponding to a given {@code BigInteger}. * @@ -1001,7 +1109,7 @@ public static Inet6Address fromIPv6BigInteger(BigInteger address) { /** * Converts a BigInteger to either an IPv4 or IPv6 address. If the IP is IPv4, it must be - * constrainted to 32 bits, otherwise it is constrained to 128 bits. + * constrained to 32 bits, otherwise it is constrained to 128 bits. * * @param address the address represented as a big integer * @param isIpv6 whether the created address should be IPv4 or IPv6 @@ -1017,7 +1125,7 @@ private static InetAddress fromBigInteger(BigInteger address, boolean isIpv6) { byte[] addressBytes = address.toByteArray(); byte[] targetCopyArray = new byte[numBytes]; - int srcPos = Math.max(0, addressBytes.length - numBytes); + int srcPos = max(0, addressBytes.length - numBytes); int copyLength = addressBytes.length - srcPos; int destPos = numBytes - copyLength; @@ -1079,7 +1187,7 @@ public static InetAddress decrement(InetAddress address) { checkArgument(i >= 0, "Decrementing %s would wrap.", address); addr[i]--; - return bytesToInetAddress(addr); + return bytesToInetAddress(addr, null); } /** @@ -1102,7 +1210,7 @@ public static InetAddress increment(InetAddress address) { checkArgument(i >= 0, "Incrementing %s would wrap.", address); addr[i]++; - return bytesToInetAddress(addr); + return bytesToInetAddress(addr, null); } /** diff --git a/android/guava/src/com/google/common/net/InternetDomainName.java b/android/guava/src/com/google/common/net/InternetDomainName.java index 4cc505696820..5603a5368adc 100644 --- a/android/guava/src/com/google/common/net/InternetDomainName.java +++ b/android/guava/src/com/google/common/net/InternetDomainName.java @@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Ascii; import com.google.common.base.CharMatcher; @@ -26,11 +25,13 @@ import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.thirdparty.publicsuffix.PublicSuffixPatterns; import com.google.thirdparty.publicsuffix.PublicSuffixType; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An immutable well-formed internet domain name, such as {@code com} or {@code foo.co.uk}. Only @@ -71,8 +72,7 @@ * @author Catherine Berry * @since 5.0 */ -@Beta -@GwtCompatible(emulated = true) +@GwtCompatible @Immutable public final class InternetDomainName { @@ -81,11 +81,17 @@ public final class InternetDomainName { private static final Joiner DOT_JOINER = Joiner.on('.'); /** - * Value of {@link #publicSuffixIndex} or {@link #registrySuffixIndex} which indicates that no + * Value of {@link #publicSuffixIndex()} or {@link #registrySuffixIndex()} which indicates that no * relevant suffix was found. */ private static final int NO_SUFFIX_FOUND = -1; + /** + * Value of {@link #publicSuffixIndexCache} or {@link #registrySuffixIndexCache} which indicates + * that they were not initialized yet. + */ + private static final int SUFFIX_NOT_INITIALIZED = -2; + /** * Maximum parts (labels) in a domain name. This value arises from the 255-octet limit described * in RFC 2181 part 11 with the fact that the @@ -113,20 +119,26 @@ public final class InternetDomainName { private final ImmutableList parts; /** - * The index in the {@link #parts()} list at which the public suffix begins. For example, for the - * domain name {@code myblog.blogspot.co.uk}, the value would be 1 (the index of the {@code - * blogspot} part). The value is negative (specifically, {@link #NO_SUFFIX_FOUND}) if no public - * suffix was found. + * Cached value of #publicSuffixIndex(). Do not use directly. + * + *

      Since this field isn't {@code volatile}, if an instance of this class is shared across + * threads before it is initialized, then each thread is likely to compute their own copy of the + * value. */ - private final int publicSuffixIndex; + @SuppressWarnings("Immutable") + @LazyInit + private int publicSuffixIndexCache = SUFFIX_NOT_INITIALIZED; /** - * The index in the {@link #parts()} list at which the registry suffix begins. For example, for - * the domain name {@code myblog.blogspot.co.uk}, the value would be 2 (the index of the {@code - * co} part). The value is negative (specifically, {@link #NO_SUFFIX_FOUND}) if no registry suffix - * was found. + * Cached value of #registrySuffixIndex(). Do not use directly. + * + *

      Since this field isn't {@code volatile}, if an instance of this class is shared across + * threads before it is initialized, then each thread is likely to compute their own copy of the + * value. */ - private final int registrySuffixIndex; + @SuppressWarnings("Immutable") + @LazyInit + private int registrySuffixIndexCache = SUFFIX_NOT_INITIALIZED; /** Constructor used to implement {@link #from(String)}, and from subclasses. */ InternetDomainName(String name) { @@ -147,9 +159,46 @@ public final class InternetDomainName { this.parts = ImmutableList.copyOf(DOT_SPLITTER.split(name)); checkArgument(parts.size() <= MAX_PARTS, "Domain has too many parts: '%s'", name); checkArgument(validateSyntax(parts), "Not a valid domain name: '%s'", name); + } - this.publicSuffixIndex = findSuffixOfType(Optional.absent()); - this.registrySuffixIndex = findSuffixOfType(Optional.of(PublicSuffixType.REGISTRY)); + /** + * Internal constructor that skips validations when creating an instance from parts of an + * already-validated InternetDomainName, as in {@link ancestor}. + */ + private InternetDomainName(String name, ImmutableList parts) { + checkArgument(!parts.isEmpty(), "Cannot create an InternetDomainName with zero parts."); + this.name = name; + this.parts = parts; + } + + /** + * The index in the {@link #parts()} list at which the public suffix begins. For example, for the + * domain name {@code myblog.blogspot.co.uk}, the value would be 1 (the index of the {@code + * blogspot} part). The value is negative (specifically, {@link #NO_SUFFIX_FOUND}) if no public + * suffix was found. + */ + private int publicSuffixIndex() { + int publicSuffixIndexLocal = publicSuffixIndexCache; + if (publicSuffixIndexLocal == SUFFIX_NOT_INITIALIZED) { + publicSuffixIndexCache = + publicSuffixIndexLocal = findSuffixOfType(Optional.absent()); + } + return publicSuffixIndexLocal; + } + + /** + * The index in the {@link #parts()} list at which the registry suffix begins. For example, for + * the domain name {@code myblog.blogspot.co.uk}, the value would be 2 (the index of the {@code + * co} part). The value is negative (specifically, {@link #NO_SUFFIX_FOUND}) if no registry suffix + * was found. + */ + private int registrySuffixIndex() { + int registrySuffixIndexLocal = registrySuffixIndexCache; + if (registrySuffixIndexLocal == SUFFIX_NOT_INITIALIZED) { + registrySuffixIndexCache = + registrySuffixIndexLocal = findSuffixOfType(Optional.of(PublicSuffixType.REGISTRY)); + } + return registrySuffixIndexLocal; } /** @@ -162,11 +211,17 @@ public final class InternetDomainName { * Otherwise, it finds the first suffix of any type. */ private int findSuffixOfType(Optional desiredType) { - final int partsSize = parts.size(); + int partsSize = parts.size(); for (int i = 0; i < partsSize; i++) { String ancestorName = DOT_JOINER.join(parts.subList(i, partsSize)); + if (i > 0 + && matchesType( + desiredType, Optional.fromNullable(PublicSuffixPatterns.UNDER.get(ancestorName)))) { + return i - 1; + } + if (matchesType( desiredType, Optional.fromNullable(PublicSuffixPatterns.EXACT.get(ancestorName)))) { return i; @@ -178,10 +233,6 @@ private int findSuffixOfType(Optional desiredType) { if (PublicSuffixPatterns.EXCLUDED.containsKey(ancestorName)) { return i + 1; } - - if (matchesWildcardSuffixType(desiredType, ancestorName)) { - return i; - } } return NO_SUFFIX_FOUND; @@ -205,6 +256,7 @@ private int findSuffixOfType(Optional desiredType) { * {@link #isValid} * @since 10.0 (previously named {@code fromLenient}) */ + @CanIgnoreReturnValue // TODO(b/219820829): consider removing public static InternetDomainName from(String domain) { return new InternetDomainName(checkNotNull(domain)); } @@ -216,7 +268,7 @@ public static InternetDomainName from(String domain) { * @return Is the domain name syntactically valid? */ private static boolean validateSyntax(List parts) { - final int lastIndex = parts.size() - 1; + int lastIndex = parts.size() - 1; // Validate the last part specially, as it has different syntax rules. @@ -328,7 +380,7 @@ public ImmutableList parts() { * @since 6.0 */ public boolean isPublicSuffix() { - return publicSuffixIndex == 0; + return publicSuffixIndex() == 0; } /** @@ -344,7 +396,7 @@ public boolean isPublicSuffix() { * @since 6.0 */ public boolean hasPublicSuffix() { - return publicSuffixIndex != NO_SUFFIX_FOUND; + return publicSuffixIndex() != NO_SUFFIX_FOUND; } /** @@ -353,8 +405,8 @@ public boolean hasPublicSuffix() { * * @since 6.0 */ - public InternetDomainName publicSuffix() { - return hasPublicSuffix() ? ancestor(publicSuffixIndex) : null; + public @Nullable InternetDomainName publicSuffix() { + return hasPublicSuffix() ? ancestor(publicSuffixIndex()) : null; } /** @@ -370,7 +422,7 @@ public InternetDomainName publicSuffix() { * @since 6.0 */ public boolean isUnderPublicSuffix() { - return publicSuffixIndex > 0; + return publicSuffixIndex() > 0; } /** @@ -386,7 +438,7 @@ public boolean isUnderPublicSuffix() { * @since 6.0 */ public boolean isTopPrivateDomain() { - return publicSuffixIndex == 1; + return publicSuffixIndex() == 1; } /** @@ -410,7 +462,7 @@ public InternetDomainName topPrivateDomain() { return this; } checkState(isUnderPublicSuffix(), "Not under a public suffix: %s", name); - return ancestor(publicSuffixIndex - 1); + return ancestor(publicSuffixIndex() - 1); } /** @@ -437,7 +489,7 @@ public InternetDomainName topPrivateDomain() { * @since 23.3 */ public boolean isRegistrySuffix() { - return registrySuffixIndex == 0; + return registrySuffixIndex() == 0; } /** @@ -452,7 +504,7 @@ public boolean isRegistrySuffix() { * @since 23.3 */ public boolean hasRegistrySuffix() { - return registrySuffixIndex != NO_SUFFIX_FOUND; + return registrySuffixIndex() != NO_SUFFIX_FOUND; } /** @@ -461,8 +513,8 @@ public boolean hasRegistrySuffix() { * * @since 23.3 */ - public InternetDomainName registrySuffix() { - return hasRegistrySuffix() ? ancestor(registrySuffixIndex) : null; + public @Nullable InternetDomainName registrySuffix() { + return hasRegistrySuffix() ? ancestor(registrySuffixIndex()) : null; } /** @@ -474,7 +526,7 @@ public InternetDomainName registrySuffix() { * @since 23.3 */ public boolean isUnderRegistrySuffix() { - return registrySuffixIndex > 0; + return registrySuffixIndex() > 0; } /** @@ -489,7 +541,7 @@ public boolean isUnderRegistrySuffix() { * @since 23.3 */ public boolean isTopDomainUnderRegistrySuffix() { - return registrySuffixIndex == 1; + return registrySuffixIndex() == 1; } /** @@ -512,7 +564,7 @@ public InternetDomainName topDomainUnderRegistrySuffix() { return this; } checkState(isUnderRegistrySuffix(), "Not under a registry suffix: %s", name); - return ancestor(registrySuffixIndex - 1); + return ancestor(registrySuffixIndex() - 1); } /** Indicates whether this domain is composed of two or more parts. */ @@ -540,7 +592,17 @@ public InternetDomainName parent() { *

      TODO: Reasonable candidate for addition to public API. */ private InternetDomainName ancestor(int levels) { - return from(DOT_JOINER.join(parts.subList(levels, parts.size()))); + ImmutableList ancestorParts = parts.subList(levels, parts.size()); + + // levels equals the number of dots that are getting clipped away, then add the length of each + // clipped part to get the length of the leading substring that is being removed. + int substringFrom = levels; + for (int i = 0; i < levels; i++) { + substringFrom += parts.get(i).length(); + } + String ancestorName = name.substring(substringFrom); + + return new InternetDomainName(ancestorName, ancestorParts); } /** @@ -563,43 +625,31 @@ public InternetDomainName child(String leftParts) { * *

      The following two code snippets are equivalent: * - *

      {@code
      +   * {@snippet :
          * domainName = InternetDomainName.isValid(name)
          *     ? InternetDomainName.from(name)
          *     : DEFAULT_DOMAIN;
      -   * }
      + * } * - *
      {@code
      +   * {@snippet :
          * try {
          *   domainName = InternetDomainName.from(name);
          * } catch (IllegalArgumentException e) {
          *   domainName = DEFAULT_DOMAIN;
          * }
      -   * }
      + * } * * @since 8.0 (previously named {@code isValidLenient}) */ public static boolean isValid(String name) { try { - from(name); + InternetDomainName unused = from(name); return true; } catch (IllegalArgumentException e) { return false; } } - /** - * Does the domain name match one of the "wildcard" patterns (e.g. {@code "*.ar"})? If a {@code - * desiredType} is specified, the wildcard pattern must also match that type. - */ - private static boolean matchesWildcardSuffixType( - Optional desiredType, String domain) { - List pieces = DOT_SPLITTER.limit(2).splitToList(domain); - return pieces.size() == 2 - && matchesType( - desiredType, Optional.fromNullable(PublicSuffixPatterns.UNDER.get(pieces.get(1)))); - } - /** * If a {@code desiredType} is specified, returns true only if the {@code actualType} is * identical. Otherwise, returns true as long as {@code actualType} is present. @@ -621,7 +671,7 @@ public String toString() { * version of the same domain name would not be considered equal. */ @Override - public boolean equals(@NullableDecl Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } diff --git a/android/guava/src/com/google/common/net/MediaType.java b/android/guava/src/com/google/common/net/MediaType.java index a5b710f85315..07732d9aa66f 100644 --- a/android/guava/src/com/google/common/net/MediaType.java +++ b/android/guava/src/com/google/common/net/MediaType.java @@ -16,20 +16,18 @@ import static com.google.common.base.CharMatcher.ascii; import static com.google.common.base.CharMatcher.javaIsoControl; -import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.hash; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Ascii; import com.google.common.base.CharMatcher; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Joiner.MapJoiner; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMultiset; @@ -37,15 +35,16 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.concurrent.LazyInit; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; -import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Represents an Internet Media Type @@ -72,7 +71,6 @@ * @since 12.0 * @author Gregory Kick */ -@Beta @GwtCompatible @Immutable public final class MediaType { @@ -105,7 +103,7 @@ public final class MediaType { private static final String WILDCARD = "*"; - private static final Map KNOWN_TYPES = Maps.newHashMap(); + private static final Map knownTypes = new HashMap<>(); private static MediaType createConstant(String type, String subtype) { MediaType mediaType = @@ -120,8 +118,9 @@ private static MediaType createConstantUtf8(String type, String subtype) { return mediaType; } + @CanIgnoreReturnValue private static MediaType addKnownType(MediaType mediaType) { - KNOWN_TYPES.put(mediaType, mediaType); + knownTypes.put(mediaType, mediaType); return mediaType; } @@ -156,6 +155,15 @@ private static MediaType addKnownType(MediaType mediaType) { public static final MediaType CSV_UTF_8 = createConstantUtf8(TEXT_TYPE, "csv"); public static final MediaType HTML_UTF_8 = createConstantUtf8(TEXT_TYPE, "html"); public static final MediaType I_CALENDAR_UTF_8 = createConstantUtf8(TEXT_TYPE, "calendar"); + + /** + * As described in RFC 7763, this + * constant ({@code text/markdown}) is used for Markdown documents. + * + * @since 33.3.0 + */ + public static final MediaType MD_UTF_8 = createConstantUtf8(TEXT_TYPE, "markdown"); + public static final MediaType PLAIN_TEXT_UTF_8 = createConstantUtf8(TEXT_TYPE, "plain"); /** @@ -164,6 +172,7 @@ private static MediaType addKnownType(MediaType mediaType) { * may be necessary in certain situations for compatibility. */ public static final MediaType TEXT_JAVASCRIPT_UTF_8 = createConstantUtf8(TEXT_TYPE, "javascript"); + /** * Tab separated * values. @@ -244,6 +253,13 @@ private static MediaType addKnownType(MediaType mediaType) { public static final MediaType SVG_UTF_8 = createConstantUtf8(IMAGE_TYPE, "svg+xml"); public static final MediaType TIFF = createConstant(IMAGE_TYPE, "tiff"); + /** + * AVIF image format. + * + * @since 33.5.0 + */ + public static final MediaType AVIF = createConstant(IMAGE_TYPE, "avif"); + /** * WebP image format. * @@ -398,7 +414,9 @@ private static MediaType addKnownType(MediaType mediaType) { public static final MediaType DART_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "dart"); /** - * Apple Passbook. + * Apple + * Passbook. * * @since 19.0 */ @@ -449,6 +467,15 @@ private static MediaType addKnownType(MediaType mediaType) { */ public static final MediaType APPLICATION_BINARY = createConstant(APPLICATION_TYPE, "binary"); + /** + * As described in RFC 8949, this + * constant ({@code application/cbor}) is used for the Concise Binary Object Representation (CBOR) + * data format. + * + * @since 33.4.0 + */ + public static final MediaType CBOR = createConstant(APPLICATION_TYPE, "cbor"); + /** * Media type for the GeoJSON Format, a * geospatial data interchange format based on JSON. @@ -493,6 +520,13 @@ private static MediaType addKnownType(MediaType mediaType) { public static final MediaType JSON_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "json"); + /** + * For JWT objects using the compact Serialization. + * + * @since 32.0.0 + */ + public static final MediaType JWT = createConstant(APPLICATION_TYPE, "jwt"); + /** * The Manifest for a web application. * @@ -520,29 +554,44 @@ private static MediaType addKnownType(MediaType mediaType) { public static final MediaType MBOX = createConstant(APPLICATION_TYPE, "mbox"); /** - * Apple over-the-air mobile configuration profiles. + * Apple + * over-the-air mobile configuration profiles. * * @since 18.0 */ public static final MediaType APPLE_MOBILE_CONFIG = createConstant(APPLICATION_TYPE, "x-apple-aspen-config"); - /** Microsoft Excel spreadsheets. */ + /** + * Microsoft + * Excel spreadsheets. + */ public static final MediaType MICROSOFT_EXCEL = createConstant(APPLICATION_TYPE, "vnd.ms-excel"); /** - * Microsoft Outlook items. + * Microsoft + * Outlook items. * * @since 27.1 */ public static final MediaType MICROSOFT_OUTLOOK = createConstant(APPLICATION_TYPE, "vnd.ms-outlook"); - /** Microsoft Powerpoint presentations. */ + /** + * Microsoft + * Powerpoint presentations. + */ public static final MediaType MICROSOFT_POWERPOINT = createConstant(APPLICATION_TYPE, "vnd.ms-powerpoint"); - /** Microsoft Word documents. */ + /** + * Microsoft + * Word documents. + */ public static final MediaType MICROSOFT_WORD = createConstant(APPLICATION_TYPE, "msword"); /** @@ -763,11 +812,14 @@ private static MediaType addKnownType(MediaType mediaType) { private final String subtype; private final ImmutableListMultimap parameters; - @LazyInit private String toString; + @LazyInit private @Nullable String toString; @LazyInit private int hashCode; - @LazyInit private Optional parsedCharset; + // We need to differentiate between "not computed" and "computed to be absent." + @SuppressWarnings("NullableOptional") + @LazyInit + private @Nullable Optional parsedCharset; private MediaType(String type, String subtype, ImmutableListMultimap parameters) { this.type = type; @@ -791,14 +843,7 @@ public ImmutableListMultimap parameters() { } private Map> parametersAsMap() { - return Maps.transformValues( - parameters.asMap(), - new Function, ImmutableMultiset>() { - @Override - public ImmutableMultiset apply(Collection input) { - return ImmutableMultiset.copyOf(input); - } - }); + return Maps.transformValues(parameters.asMap(), ImmutableMultiset::copyOf); } /** @@ -857,23 +902,27 @@ public MediaType withParameters(String attribute, Iterable values) { checkNotNull(attribute); checkNotNull(values); String normalizedAttribute = normalizeToken(attribute); - ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + ImmutableListMultimap.Builder updatedParameters = + ImmutableListMultimap.builder(); for (Entry entry : parameters.entries()) { String key = entry.getKey(); if (!normalizedAttribute.equals(key)) { - builder.put(key, entry.getValue()); + updatedParameters.put(key, entry.getValue()); } } for (String value : values) { - builder.put(normalizedAttribute, normalizeParameterValue(normalizedAttribute, value)); + updatedParameters.put( + normalizedAttribute, normalizeParameterValue(normalizedAttribute, value)); } - MediaType mediaType = new MediaType(type, subtype, builder.build()); + MediaType mediaType = new MediaType(type, subtype, updatedParameters.build()); // if the attribute isn't charset, we can just inherit the current parsedCharset if (!normalizedAttribute.equals(CHARSET_ATTRIBUTE)) { mediaType.parsedCharset = this.parsedCharset; } // Return one of the constants if the media type is a known type. - return MoreObjects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType); + @SuppressWarnings("GetOrDefaultNotNull") // getOrDefault requires API Level 24 + MediaType result = firstNonNull(knownTypes.get(mediaType), mediaType); + return result; } /** @@ -895,7 +944,7 @@ public MediaType withParameter(String attribute, String value) { * one. * *

      If a charset must be specified that is not supported on this JVM (and thus is not - * representable as a {@link Charset} instance, use {@link #withParameter}. + * representable as a {@link Charset} instance), use {@link #withParameter}. */ public MediaType withCharset(Charset charset) { checkNotNull(charset); @@ -907,7 +956,7 @@ public MediaType withCharset(Charset charset) { /** Returns true if either the type or subtype is the wildcard. */ public boolean hasWildcard() { - return WILDCARD.equals(type) || WILDCARD.equals(subtype); + return type.equals(WILDCARD) || subtype.equals(WILDCARD); } /** @@ -923,7 +972,7 @@ public boolean hasWildcard() { * *

      For example: * - *

      {@code
      +   * {@snippet :
          * PLAIN_TEXT_UTF_8.is(PLAIN_TEXT_UTF_8) // true
          * PLAIN_TEXT_UTF_8.is(HTML_UTF_8) // false
          * PLAIN_TEXT_UTF_8.is(ANY_TYPE) // true
      @@ -932,7 +981,7 @@ public boolean hasWildcard() {
          * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_8)) // true
          * PLAIN_TEXT_UTF_8.withoutParameters().is(ANY_TEXT_TYPE.withCharset(UTF_8)) // false
          * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_16)) // false
      -   * }
      + * } * *

      Note that while it is possible to have the same parameter declared multiple times within a * media type this method does not consider the number of occurrences of a parameter. For example, @@ -965,7 +1014,7 @@ private static MediaType create( String normalizedType = normalizeToken(type); String normalizedSubtype = normalizeToken(subtype); checkArgument( - !WILDCARD.equals(normalizedType) || WILDCARD.equals(normalizedSubtype), + !normalizedType.equals(WILDCARD) || normalizedSubtype.equals(WILDCARD), "A wildcard type cannot be used with a non-wildcard subtype"); ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); for (Entry entry : parameters.entries()) { @@ -974,7 +1023,9 @@ private static MediaType create( } MediaType mediaType = new MediaType(normalizedType, normalizedSubtype, builder.build()); // Return one of the constants if the media type is a known type. - return MoreObjects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType); + @SuppressWarnings("GetOrDefaultNotNull") // getOrDefault requires API Level 24 + MediaType result = firstNonNull(knownTypes.get(mediaType), mediaType); + return result; } /** @@ -1040,7 +1091,7 @@ private static String normalizeToken(String token) { private static String normalizeParameterValue(String attribute, String value) { checkNotNull(value); // for GWT checkArgument(ascii().matchesAllOf(value), "parameter values must be ASCII: %s", value); - return CHARSET_ATTRIBUTE.equals(attribute) ? Ascii.toLowerCase(value) : value; + return attribute.equals(CHARSET_ATTRIBUTE) ? Ascii.toLowerCase(value) : value; } /** @@ -1048,26 +1099,25 @@ private static String normalizeParameterValue(String attribute, String value) { * * @throws IllegalArgumentException if the input is not parsable */ + @CanIgnoreReturnValue // TODO(b/219820829): consider removing public static MediaType parse(String input) { checkNotNull(input); Tokenizer tokenizer = new Tokenizer(input); try { String type = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('/'); + consumeSeparator(tokenizer, '/'); String subtype = tokenizer.consumeToken(TOKEN_MATCHER); ImmutableListMultimap.Builder parameters = ImmutableListMultimap.builder(); while (tokenizer.hasMore()) { - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); - tokenizer.consumeCharacter(';'); - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + consumeSeparator(tokenizer, ';'); String attribute = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('='); - final String value; - if ('"' == tokenizer.previewChar()) { + consumeSeparator(tokenizer, '='); + String value; + if (tokenizer.previewChar() == '"') { tokenizer.consumeCharacter('"'); StringBuilder valueBuilder = new StringBuilder(); - while ('"' != tokenizer.previewChar()) { - if ('\\' == tokenizer.previewChar()) { + while (tokenizer.previewChar() != '"') { + if (tokenizer.previewChar() == '\\') { tokenizer.consumeCharacter('\\'); valueBuilder.append(tokenizer.consumeCharacter(ascii())); } else { @@ -1087,6 +1137,12 @@ public static MediaType parse(String input) { } } + private static void consumeSeparator(Tokenizer tokenizer, char c) { + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + tokenizer.consumeCharacter(c); + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + } + private static final class Tokenizer { final String input; int position = 0; @@ -1095,6 +1151,7 @@ private static final class Tokenizer { this.input = input; } + @CanIgnoreReturnValue String consumeTokenIfPresent(CharMatcher matcher) { checkState(hasMore()); int startPosition = position; @@ -1117,6 +1174,7 @@ char consumeCharacter(CharMatcher matcher) { return c; } + @CanIgnoreReturnValue char consumeCharacter(char c) { checkState(hasMore()); checkState(previewChar() == c); @@ -1135,7 +1193,7 @@ boolean hasMore() { } @Override - public boolean equals(@NullableDecl Object obj) { + public boolean equals(@Nullable Object obj) { if (obj == this) { return true; } else if (obj instanceof MediaType) { @@ -1154,7 +1212,7 @@ public int hashCode() { // racy single-check idiom int h = hashCode; if (h == 0) { - h = Objects.hashCode(type, subtype, parametersAsMap()); + h = hash(type, subtype, parametersAsMap()); hashCode = h; } return h; @@ -1184,14 +1242,10 @@ private String computeToString() { Multimap quotedParameters = Multimaps.transformValues( parameters, - new Function() { - @Override - public String apply(String value) { - return (TOKEN_MATCHER.matchesAllOf(value) && !value.isEmpty()) + (String value) -> + (TOKEN_MATCHER.matchesAllOf(value) && !value.isEmpty()) ? value - : escapeAndQuote(value); - } - }); + : escapeAndQuote(value)); PARAMETER_JOINER.appendTo(builder, quotedParameters.entries()); } return builder.toString(); diff --git a/android/guava/src/com/google/common/net/ParametricNullness.java b/android/guava/src/com/google/common/net/ParametricNullness.java new file mode 100644 index 000000000000..d79abc1991ed --- /dev/null +++ b/android/guava/src/com/google/common/net/ParametricNullness.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

        + *
      • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
      • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
      + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
        + *
      • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
      • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
      + * + *

      Consumers of this annotation include: + * + *

        + *
      • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
      • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
      + * + *

      This annotation is a temporary hack. We will remove it after tools no longer need + * it. + */ +@GwtCompatible +@Retention(CLASS) +@Target({FIELD, METHOD, PARAMETER}) +@interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/net/PercentEscaper.java b/android/guava/src/com/google/common/net/PercentEscaper.java index 554d04d78d7a..259bc674f14d 100644 --- a/android/guava/src/com/google/common/net/PercentEscaper.java +++ b/android/guava/src/com/google/common/net/PercentEscaper.java @@ -15,10 +15,11 @@ package com.google.common.net; import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.Math.max; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.escape.UnicodeEscaper; +import org.jspecify.annotations.Nullable; /** * A {@code UnicodeEscaper} that escapes some set of Java characters using a UTF-8 based percent @@ -49,15 +50,14 @@ * @author David Beaumont * @since 15.0 */ -@Beta @GwtCompatible public final class PercentEscaper extends UnicodeEscaper { // In some escapers spaces are escaped to '+' - private static final char[] PLUS_SIGN = {'+'}; + private static final char[] plusSign = {'+'}; // Percent escapers output upper case hex digits (uri escapers require this). - private static final char[] UPPER_HEX_DIGITS = "0123456789ABCDEF".toCharArray(); + private static final char[] upperHexDigits = "0123456789ABCDEF".toCharArray(); /** If true we should convert space to the {@code +} character. */ private final boolean plusForSpace; @@ -74,10 +74,10 @@ public final class PercentEscaper extends UnicodeEscaper { * space character. * *

      Not that it is allowed, but not necessarily desirable to specify {@code %} as a safe - * character. This has the effect of creating an escaper which has no well defined inverse but it + * character. This has the effect of creating an escaper which has no well-defined inverse but it * can be useful when escaping additional characters. * - * @param safeChars a non null string specifying additional safe characters for this escaper (the + * @param safeChars a non-null string specifying additional safe characters for this escaper (the * ranges 0..9, a..z and A..Z are always safe and should not be specified here) * @param plusForSpace true if ASCII space should be escaped to {@code +} rather than {@code %20} * @throws IllegalArgumentException if any of the parameters were invalid @@ -111,7 +111,7 @@ private static boolean[] createSafeOctets(String safeChars) { int maxChar = -1; char[] safeCharArray = safeChars.toCharArray(); for (char c : safeCharArray) { - maxChar = Math.max(c, maxChar); + maxChar = max(c, maxChar); } boolean[] octets = new boolean[maxChar + 1]; for (char c : safeCharArray) { @@ -155,20 +155,20 @@ public String escape(String s) { /** Escapes the given Unicode code point in UTF-8. */ @Override - protected char[] escape(int cp) { + protected char @Nullable [] escape(int cp) { // We should never get negative values here but if we do it will throw an // IndexOutOfBoundsException, so at least it will get spotted. if (cp < safeOctets.length && safeOctets[cp]) { return null; } else if (cp == ' ' && plusForSpace) { - return PLUS_SIGN; + return plusSign; } else if (cp <= 0x7F) { // Single byte UTF-8 characters // Start with "%--" and fill in the blanks char[] dest = new char[3]; dest[0] = '%'; - dest[2] = UPPER_HEX_DIGITS[cp & 0xF]; - dest[1] = UPPER_HEX_DIGITS[cp >>> 4]; + dest[2] = upperHexDigits[cp & 0xF]; + dest[1] = upperHexDigits[cp >>> 4]; return dest; } else if (cp <= 0x7ff) { // Two byte UTF-8 characters [cp >= 0x80 && cp <= 0x7ff] @@ -176,13 +176,13 @@ protected char[] escape(int cp) { char[] dest = new char[6]; dest[0] = '%'; dest[3] = '%'; - dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; + dest[5] = upperHexDigits[cp & 0xF]; cp >>>= 4; - dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + dest[4] = upperHexDigits[0x8 | (cp & 0x3)]; cp >>>= 2; - dest[2] = UPPER_HEX_DIGITS[cp & 0xF]; + dest[2] = upperHexDigits[cp & 0xF]; cp >>>= 4; - dest[1] = UPPER_HEX_DIGITS[0xC | cp]; + dest[1] = upperHexDigits[0xC | cp]; return dest; } else if (cp <= 0xffff) { // Three byte UTF-8 characters [cp >= 0x800 && cp <= 0xffff] @@ -192,15 +192,15 @@ protected char[] escape(int cp) { dest[1] = 'E'; dest[3] = '%'; dest[6] = '%'; - dest[8] = UPPER_HEX_DIGITS[cp & 0xF]; + dest[8] = upperHexDigits[cp & 0xF]; cp >>>= 4; - dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + dest[7] = upperHexDigits[0x8 | (cp & 0x3)]; cp >>>= 2; - dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; + dest[5] = upperHexDigits[cp & 0xF]; cp >>>= 4; - dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + dest[4] = upperHexDigits[0x8 | (cp & 0x3)]; cp >>>= 2; - dest[2] = UPPER_HEX_DIGITS[cp]; + dest[2] = upperHexDigits[cp]; return dest; } else if (cp <= 0x10ffff) { char[] dest = new char[12]; @@ -211,19 +211,19 @@ protected char[] escape(int cp) { dest[3] = '%'; dest[6] = '%'; dest[9] = '%'; - dest[11] = UPPER_HEX_DIGITS[cp & 0xF]; + dest[11] = upperHexDigits[cp & 0xF]; cp >>>= 4; - dest[10] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + dest[10] = upperHexDigits[0x8 | (cp & 0x3)]; cp >>>= 2; - dest[8] = UPPER_HEX_DIGITS[cp & 0xF]; + dest[8] = upperHexDigits[cp & 0xF]; cp >>>= 4; - dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + dest[7] = upperHexDigits[0x8 | (cp & 0x3)]; cp >>>= 2; - dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; + dest[5] = upperHexDigits[cp & 0xF]; cp >>>= 4; - dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + dest[4] = upperHexDigits[0x8 | (cp & 0x3)]; cp >>>= 2; - dest[2] = UPPER_HEX_DIGITS[cp & 0x7]; + dest[2] = upperHexDigits[cp & 0x7]; return dest; } else { // If this ever happens it is due to bug in UnicodeEscaper, not bad input. diff --git a/android/guava/src/com/google/common/net/UrlEscapers.java b/android/guava/src/com/google/common/net/UrlEscapers.java index 28da2e337753..60fadbfea894 100644 --- a/android/guava/src/com/google/common/net/UrlEscapers.java +++ b/android/guava/src/com/google/common/net/UrlEscapers.java @@ -44,10 +44,12 @@ private UrlEscapers() {} /** * Returns an {@link Escaper} instance that escapes strings so they can be safely included in URL form parameter names and values. Escaping is performed - * with the UTF-8 character encoding. The caller is responsible for replacing any unpaired carriage return or line feed characters - * with a CR+LF pair on any non-file inputs before escaping them with this escaper. + * href="https://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set">URL + * form parameter names and values. Escaping is performed with the UTF-8 character encoding. + * The caller is responsible for replacing + * any unpaired carriage return or line feed characters with a CR+LF pair on any non-file + * inputs before escaping them with this escaper. * *

      When escaping a String, the following rules apply: * @@ -62,9 +64,9 @@ private UrlEscapers() {} *

    * *

    This escaper is suitable for escaping parameter names and values even when using the non-standard semicolon, rather than the ampersand, as - * a parameter delimiter. Nevertheless, we recommend using the ampersand unless you must - * interoperate with systems that require semicolons. + * href="https://www.w3.org/TR/html401/appendix/notes.html#h-B.2.2">using the non-standard + * semicolon, rather than the ampersand, as a parameter delimiter. Nevertheless, we recommend + * using the ampersand unless you must interoperate with systems that require semicolons. * *

    Note: Unlike other escapers, URL escapers produce uppercase hexadecimal sequences. @@ -79,14 +81,15 @@ public static Escaper urlFormParameterEscaper() { /** * Returns an {@link Escaper} instance that escapes strings so they can be safely included in URL path segments. The returned escaper escapes all non-ASCII - * characters, even though many of these are accepted in modern - * URLs. (If the escaper were to leave these characters - * unescaped, they would be escaped by the consumer at parse time, anyway.) Additionally, the - * escaper escapes the slash character ("/"). While slashes are acceptable in URL paths, they are - * considered by the specification to be separators between "path segments." This implies that, if - * you wish for your path to contain slashes, you must escape each segment separately and then - * join them. + * href="https://url.spec.whatwg.org/#syntax-url-path-segment">URL path segments. The returned + * escaper escapes all non-ASCII characters, even though many of these are accepted in modern + * URLs. (If the escaper were to leave these + * characters unescaped, they would be escaped by the consumer at parse time, anyway.) + * Additionally, the escaper escapes the slash character ("/"). While slashes are acceptable in + * URL paths, they are considered by the specification to be separators between "path segments." + * This implies that, if you wish for your path to contain slashes, you must escape each segment + * separately and then join them. * *

    When escaping a String, the following rules apply: * @@ -115,9 +118,8 @@ public static Escaper urlPathSegmentEscaper() { /** * Returns an {@link Escaper} instance that escapes strings so they can be safely included in a URL fragment. The returned escaper escapes all non-ASCII - * characters, even though many of these are accepted in modern - * URLs. + * href="https://url.spec.whatwg.org/#concept-url-fragment">URL fragment. The returned escaper + * escapes all non-ASCII characters. * *

    When escaping a String, the following rules apply: * diff --git a/android/guava/src/com/google/common/net/package-info.java b/android/guava/src/com/google/common/net/package-info.java index d9db26637974..562bb10e4f99 100644 --- a/android/guava/src/com/google/common/net/package-info.java +++ b/android/guava/src/com/google/common/net/package-info.java @@ -13,15 +13,16 @@ */ /** - * This package contains utility methods and classes for working with net addresses (numeric IP and - * domain names). + * Utility methods and classes for networking (such as IP addresses and domain names). * - *

    This package is a part of the open-source Guava + *

    This package is a part of the open-source Guava * library. * * @author Craig Berry */ -@ParametersAreNonnullByDefault +@CheckReturnValue +@NullMarked package com.google.common.net; -import javax.annotation.ParametersAreNonnullByDefault; +import com.google.errorprone.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/primitives/Booleans.java b/android/guava/src/com/google/common/primitives/Booleans.java index 522049bfe3eb..38c0cbbdee67 100644 --- a/android/guava/src/com/google/common/primitives/Booleans.java +++ b/android/guava/src/com/google/common/primitives/Booleans.java @@ -19,8 +19,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -29,7 +31,7 @@ import java.util.Comparator; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code boolean} primitives, that are not already found in @@ -42,7 +44,6 @@ * @since 1.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Booleans { private Booleans() {} @@ -75,12 +76,11 @@ public String toString() { /** * Returns a {@code Comparator} that sorts {@code true} before {@code false}. * - *

    This is particularly useful in Java 8+ in combination with {@code Comparators.comparing}, - * e.g. {@code Comparators.comparing(Foo::hasBar, trueFirst())}. + *

    This is particularly useful in Java 8+ in combination with {@code Comparator.comparing}, + * e.g. {@code Comparator.comparing(Foo::hasBar, trueFirst())}. * * @since 21.0 */ - @Beta public static Comparator trueFirst() { return BooleanComparator.TRUE_FIRST; } @@ -88,27 +88,25 @@ public static Comparator trueFirst() { /** * Returns a {@code Comparator} that sorts {@code false} before {@code true}. * - *

    This is particularly useful in Java 8+ in combination with {@code Comparators.comparing}, - * e.g. {@code Comparators.comparing(Foo::hasBar, falseFirst())}. + *

    This is particularly useful in Java 8+ in combination with {@code Comparator.comparing}, + * e.g. {@code Comparator.comparing(Foo::hasBar, falseFirst())}. * * @since 21.0 */ - @Beta public static Comparator falseFirst() { return BooleanComparator.FALSE_FIRST; } /** - * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Boolean) - * value).hashCode()}. - * - *

    Java 8 users: use {@link Boolean#hashCode(boolean)} instead. + * Returns a hash code for {@code value}; obsolete alternative to {@link + * Boolean#hashCode(boolean)}. * * @param value a primitive {@code boolean} value * @return a hash code for the value */ + @InlineMe(replacement = "Boolean.hashCode(value)") public static int hashCode(boolean value) { - return value ? 1231 : 1237; + return Boolean.hashCode(value); } /** @@ -116,7 +114,7 @@ public static int hashCode(boolean value) { * considered less than {@code true}). The sign of the value returned is the same as that of * {@code ((Boolean) a).compareTo(b)}. * - *

    Note for Java 7 and later: this method should be treated as deprecated; use the + *

    Note: this method is now unnecessary and should be treated as deprecated; use the * equivalent {@link Boolean#compare} method instead. * * @param a the first {@code boolean} to compare @@ -124,8 +122,9 @@ public static int hashCode(boolean value) { * @return a positive number if only {@code a} is {@code true}, a negative number if only {@code * b} is true, or zero if {@code a == b} */ + @InlineMe(replacement = "Boolean.compare(a, b)") public static int compare(boolean a, boolean b) { - return (a == b) ? 0 : (a ? 1 : -1); + return Boolean.compare(a, b); } /** @@ -231,13 +230,15 @@ private static int lastIndexOf(boolean[] array, boolean target, int start, int e * * @param arrays zero or more {@code boolean} arrays * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} */ public static boolean[] concat(boolean[]... arrays) { - int length = 0; + long length = 0; for (boolean[] array : arrays) { length += array.length; } - boolean[] result = new boolean[length]; + boolean[] result = new boolean[checkNoOverflow(length)]; int pos = 0; for (boolean[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); @@ -246,6 +247,14 @@ public static boolean[] concat(boolean[]... arrays) { return result; } + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + /** * Returns an array containing the same values as {@code array}, but guaranteed to be of a * specified minimum length. If {@code array} already has a length of at least {@code minLength}, @@ -311,9 +320,11 @@ private enum LexicographicalComparator implements Comparator { @Override public int compare(boolean[] left, boolean[] right) { + // do not static import Math.min due to https://bugs.openjdk.org/browse/JDK-8357219 + @SuppressWarnings("StaticImportPreferred") int minLength = Math.min(left.length, right.length); for (int i = 0; i < minLength; i++) { - int result = Booleans.compare(left[i], right[i]); + int result = Boolean.compare(left[i], right[i]); if (result != 0) { return result; } @@ -361,9 +372,10 @@ public static boolean[] toArray(Collection collection) { * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to * set a value to {@code null} will result in a {@link NullPointerException}. * - *

    The returned list maintains the values, but not the identities, of {@code Boolean} objects - * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for - * the returned list is unspecified. + *

    There are at most two distinct objects in this list, {@code (Boolean) true} and {@code + * (Boolean) false}. Java guarantees that those are always represented by the same objects. + * + *

    The returned list is serializable. * * @param backingArray the array to back the list * @return a list view of the array @@ -375,8 +387,7 @@ public static List asList(boolean... backingArray) { return new BooleanArrayAsList(backingArray); } - @GwtCompatible - private static class BooleanArrayAsList extends AbstractList + private static final class BooleanArrayAsList extends AbstractList implements RandomAccess, Serializable { final boolean[] array; final int start; @@ -409,14 +420,14 @@ public Boolean get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + public boolean contains(@Nullable Object target) { // Overridden to prevent a ton of boxing return (target instanceof Boolean) && Booleans.indexOf(array, (Boolean) target, start, end) != -1; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Boolean) { int i = Booleans.indexOf(array, (Boolean) target, start, end); @@ -428,7 +439,7 @@ public int indexOf(@CheckForNull Object target) { } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Boolean) { int i = Booleans.lastIndexOf(array, (Boolean) target, start, end); @@ -459,7 +470,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -483,7 +494,7 @@ public boolean equals(@CheckForNull Object object) { public int hashCode() { int result = 1; for (int i = start; i < end; i++) { - result = 31 * result + Booleans.hashCode(array[i]); + result = 31 * result + Boolean.hashCode(array[i]); } return result; } @@ -502,7 +513,7 @@ boolean[] toBooleanArray() { return Arrays.copyOfRange(array, start, end); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -510,7 +521,6 @@ boolean[] toBooleanArray() { * * @since 16.0 */ - @Beta public static int countTrue(boolean... values) { int count = 0; for (boolean value : values) { @@ -551,4 +561,54 @@ public static void reverse(boolean[] array, int fromIndex, int toIndex) { array[j] = tmp; } } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Booleans.asList(array), + * distance)}, but is somewhat faster. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(boolean[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Booleans.asList(array).subList(fromIndex, toIndex), distance)}, but is + * somewhat faster. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(boolean[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } } diff --git a/android/guava/src/com/google/common/primitives/Bytes.java b/android/guava/src/com/google/common/primitives/Bytes.java index 62997f34aac7..b5545e28817a 100644 --- a/android/guava/src/com/google/common/primitives/Bytes.java +++ b/android/guava/src/com/google/common/primitives/Bytes.java @@ -20,6 +20,10 @@ import static com.google.common.base.Preconditions.checkPositionIndexes; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.InlineMeValidationDisabled; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -27,7 +31,7 @@ import java.util.Collections; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code byte} primitives, that are not already found in @@ -44,19 +48,19 @@ // TODO(kevinb): how to prevent warning on UnsignedBytes when building GWT // javadoc? @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Bytes { private Bytes() {} /** - * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Byte) - * value).hashCode()}. - * - *

    Java 8 users: use {@link Byte#hashCode(byte)} instead. + * Returns a hash code for {@code value}; obsolete alternative to {@link Byte#hashCode(byte)}. * * @param value a primitive {@code byte} value * @return a hash code for the value */ + @InlineMe(replacement = "Byte.hashCode(value)") + @InlineMeValidationDisabled( + "The hash code of a byte is the int version of the byte itself, so it's simplest to return" + + " that.") public static int hashCode(byte value) { return value; } @@ -156,13 +160,15 @@ private static int lastIndexOf(byte[] array, byte target, int start, int end) { * * @param arrays zero or more {@code byte} arrays * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} */ public static byte[] concat(byte[]... arrays) { - int length = 0; + long length = 0; for (byte[] array : arrays) { length += array.length; } - byte[] result = new byte[length]; + byte[] result = new byte[checkNoOverflow(length)]; int pos = 0; for (byte[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); @@ -171,6 +177,14 @@ public static byte[] concat(byte[]... arrays) { return result; } + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + /** * Returns an array containing the same values as {@code array}, but guaranteed to be of a * specified minimum length. If {@code array} already has a length of at least {@code minLength}, @@ -227,6 +241,8 @@ public static byte[] toArray(Collection collection) { * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for * the returned list is unspecified. * + *

    The returned list is serializable. + * * @param backingArray the array to back the list * @return a list view of the array */ @@ -237,8 +253,7 @@ public static List asList(byte... backingArray) { return new ByteArrayAsList(backingArray); } - @GwtCompatible - private static class ByteArrayAsList extends AbstractList + private static final class ByteArrayAsList extends AbstractList implements RandomAccess, Serializable { final byte[] array; final int start; @@ -271,13 +286,13 @@ public Byte get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + public boolean contains(@Nullable Object target) { // Overridden to prevent a ton of boxing return (target instanceof Byte) && Bytes.indexOf(array, (Byte) target, start, end) != -1; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Byte) { int i = Bytes.indexOf(array, (Byte) target, start, end); @@ -289,7 +304,7 @@ public int indexOf(@CheckForNull Object target) { } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Byte) { int i = Bytes.lastIndexOf(array, (Byte) target, start, end); @@ -320,7 +335,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -344,7 +359,7 @@ public boolean equals(@CheckForNull Object object) { public int hashCode() { int result = 1; for (int i = start; i < end; i++) { - result = 31 * result + Bytes.hashCode(array[i]); + result = 31 * result + Byte.hashCode(array[i]); } return result; } @@ -363,7 +378,7 @@ byte[] toByteArray() { return Arrays.copyOfRange(array, start, end); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -396,4 +411,54 @@ public static void reverse(byte[] array, int fromIndex, int toIndex) { array[j] = tmp; } } + + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Bytes.asList(array), + * distance)}, but is somewhat faster. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(byte[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Bytes.asList(array).subList(fromIndex, toIndex), distance)}, but is somewhat + * faster. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(byte[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } } diff --git a/android/guava/src/com/google/common/primitives/Chars.java b/android/guava/src/com/google/common/primitives/Chars.java index 4a2e3a3449cb..4a078a6ba389 100644 --- a/android/guava/src/com/google/common/primitives/Chars.java +++ b/android/guava/src/com/google/common/primitives/Chars.java @@ -19,9 +19,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.InlineMeValidationDisabled; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -30,7 +32,7 @@ import java.util.Comparator; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code char} primitives, that are not already found in @@ -45,27 +47,29 @@ * @author Kevin Bourrillion * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Chars { private Chars() {} /** * The number of bytes required to represent a primitive {@code char} value. * - *

    Java 8 users: use {@link Character#BYTES} instead. + *

    Prefer {@link Character#BYTES} instead. */ + // We don't use Character.BYTES here because it's not available under J2KT. public static final int BYTES = Character.SIZE / Byte.SIZE; /** - * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Character) - * value).hashCode()}. - * - *

    Java 8 users: use {@link Character#hashCode(char)} instead. + * Returns a hash code for {@code value}; obsolete alternative to {@link + * Character#hashCode(char)}. * * @param value a primitive {@code char} value * @return a hash code for the value */ + @InlineMe(replacement = "Character.hashCode(value)") + @InlineMeValidationDisabled( + "The hash code of a char is the int version of the char itself, so it's simplest to return" + + " that.") public static int hashCode(char value) { return value; } @@ -106,7 +110,7 @@ public static char saturatedCast(long value) { * Compares the two specified {@code char} values. The sign of the value returned is the same as * that of {@code ((Character) a).compareTo(b)}. * - *

    Note for Java 7 and later: this method should be treated as deprecated; use the + *

    Note: this method is now unnecessary and should be treated as deprecated; use the * equivalent {@link Character#compare} method instead. * * @param a the first {@code char} to compare @@ -114,8 +118,9 @@ public static char saturatedCast(long value) { * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ + @InlineMe(replacement = "Character.compare(a, b)") public static int compare(char a, char b) { - return a - b; // safe due to restricted range + return Character.compare(a, b); } /** @@ -258,7 +263,6 @@ public static char max(char... array) { * @throws IllegalArgumentException if {@code min > max} * @since 21.0 */ - @Beta public static char constrainToRange(char value, char min, char max) { checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); return value < min ? min : value < max ? value : max; @@ -270,13 +274,15 @@ public static char constrainToRange(char value, char min, char max) { * * @param arrays zero or more {@code char} arrays * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} */ public static char[] concat(char[]... arrays) { - int length = 0; + long length = 0; for (char[] array : arrays) { length += array.length; } - char[] result = new char[length]; + char[] result = new char[checkNoOverflow(length)]; int pos = 0; for (char[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); @@ -285,6 +291,14 @@ public static char[] concat(char[]... arrays) { return result; } + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + /** * Returns a big-endian representation of {@code value} in a 2-element byte array; equivalent to * {@code ByteBuffer.allocate(2).putChar(value).array()}. For example, the input value {@code @@ -393,7 +407,7 @@ private enum LexicographicalComparator implements Comparator { public int compare(char[] left, char[] right) { int minLength = Math.min(left.length, right.length); for (int i = 0; i < minLength; i++) { - int result = Chars.compare(left[i], right[i]); + int result = Character.compare(left[i], right[i]); if (result != 0) { return result; } @@ -488,6 +502,56 @@ public static void reverse(char[] array, int fromIndex, int toIndex) { } } + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Chars.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(char[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Chars.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(char[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + /** * Returns a fixed-size list backed by the specified array, similar to {@link * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to @@ -497,6 +561,8 @@ public static void reverse(char[] array, int fromIndex, int toIndex) { * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for * the returned list is unspecified. * + *

    The returned list is serializable. + * * @param backingArray the array to back the list * @return a list view of the array */ @@ -507,8 +573,7 @@ public static List asList(char... backingArray) { return new CharArrayAsList(backingArray); } - @GwtCompatible - private static class CharArrayAsList extends AbstractList + private static final class CharArrayAsList extends AbstractList implements RandomAccess, Serializable { final char[] array; final int start; @@ -541,14 +606,14 @@ public Character get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + public boolean contains(@Nullable Object target) { // Overridden to prevent a ton of boxing return (target instanceof Character) && Chars.indexOf(array, (Character) target, start, end) != -1; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Character) { int i = Chars.indexOf(array, (Character) target, start, end); @@ -560,7 +625,7 @@ public int indexOf(@CheckForNull Object target) { } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Character) { int i = Chars.lastIndexOf(array, (Character) target, start, end); @@ -591,7 +656,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -615,7 +680,7 @@ public boolean equals(@CheckForNull Object object) { public int hashCode() { int result = 1; for (int i = start; i < end; i++) { - result = 31 * result + Chars.hashCode(array[i]); + result = 31 * result + Character.hashCode(array[i]); } return result; } @@ -634,6 +699,6 @@ char[] toCharArray() { return Arrays.copyOfRange(array, start, end); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/primitives/Doubles.java b/android/guava/src/com/google/common/primitives/Doubles.java index ce5df2e394db..ec1cbe019b7f 100644 --- a/android/guava/src/com/google/common/primitives/Doubles.java +++ b/android/guava/src/com/google/common/primitives/Doubles.java @@ -19,13 +19,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; import static com.google.common.base.Strings.lenientFormat; -import static java.lang.Double.NEGATIVE_INFINITY; -import static java.lang.Double.POSITIVE_INFINITY; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -34,7 +33,9 @@ import java.util.Comparator; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import java.util.Spliterator; +import java.util.Spliterators; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code double} primitives, that are not already found in @@ -46,34 +47,28 @@ * @author Kevin Bourrillion * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Doubles extends DoublesMethodsForWeb { private Doubles() {} /** * The number of bytes required to represent a primitive {@code double} value. * - *

    Java 8 users: use {@link Double#BYTES} instead. + *

    Prefer {@link Double#BYTES} instead. * * @since 10.0 */ - public static final int BYTES = Double.SIZE / Byte.SIZE; + public static final int BYTES = Double.BYTES; /** - * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Double) - * value).hashCode()}. - * - *

    Java 8 users: use {@link Double#hashCode(double)} instead. + * Returns a hash code for {@code value}; obsolete alternative to {@link Double#hashCode(double)}. * * @param value a primitive {@code double} value * @return a hash code for the value */ + @InlineMe(replacement = "Double.hashCode(value)") public static int hashCode(double value) { - return ((Double) value).hashCode(); - // TODO(kevinb): do it this way when we can (GWT problem): - // long bits = Double.doubleToLongBits(value); - // return (int) (bits ^ (bits >>> 32)); + return Double.hashCode(value); } /** @@ -90,6 +85,7 @@ public static int hashCode(double value) { * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ + @InlineMe(replacement = "Double.compare(a, b)") public static int compare(double a, double b) { return Double.compare(a, b); } @@ -98,12 +94,13 @@ public static int compare(double a, double b) { * Returns {@code true} if {@code value} represents a real number. This is equivalent to, but not * necessarily implemented as, {@code !(Double.isInfinite(value) || Double.isNaN(value))}. * - *

    Java 8 users: use {@link Double#isFinite(double)} instead. + *

    Prefer {@link Double#isFinite(double)} instead. * * @since 10.0 */ + @InlineMe(replacement = "Double.isFinite(value)") public static boolean isFinite(double value) { - return NEGATIVE_INFINITY < value && value < POSITIVE_INFINITY; + return Double.isFinite(value); } /** @@ -247,13 +244,14 @@ public static double max(double... array) { * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code * value} is greater than {@code max}, {@code max} is returned. * + *

    Java 21+ users: Use {@code Math.clamp} instead. + * * @param value the {@code double} value to constrain * @param min the lower bound (inclusive) of the range to constrain {@code value} to * @param max the upper bound (inclusive) of the range to constrain {@code value} to * @throws IllegalArgumentException if {@code min > max} * @since 21.0 */ - @Beta public static double constrainToRange(double value, double min, double max) { // avoid auto-boxing by not using Preconditions.checkArgument(); see Guava issue 3984 // Reject NaN by testing for the good case (min <= max) instead of the bad (min > max). @@ -271,13 +269,15 @@ public static double constrainToRange(double value, double min, double max) { * * @param arrays zero or more {@code double} arrays * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} */ public static double[] concat(double[]... arrays) { - int length = 0; + long length = 0; for (double[] array : arrays) { length += array.length; } - double[] result = new double[length]; + double[] result = new double[checkNoOverflow(length)]; int pos = 0; for (double[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); @@ -286,9 +286,17 @@ public static double[] concat(double[]... arrays) { return result; } + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + private static final class DoubleConverter extends Converter implements Serializable { - static final DoubleConverter INSTANCE = new DoubleConverter(); + static final Converter INSTANCE = new DoubleConverter(); @Override protected Double doForward(String value) { @@ -309,7 +317,7 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } /** @@ -318,7 +326,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return DoubleConverter.INSTANCE; } @@ -467,6 +474,56 @@ public static void reverse(double[] array, int fromIndex, int toIndex) { } } + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Bytes.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(double[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Bytes.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(double[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + /** * Returns an array containing each value of {@code collection}, converted to a {@code double} * value in the manner of {@link Number#doubleValue}. @@ -507,6 +564,8 @@ public static double[] toArray(Collection collection) { *

    The returned list may have unexpected behavior if it contains {@code NaN}, or if {@code NaN} * is used as a parameter to any of its methods. * + *

    The returned list is serializable. + * *

    Note: when possible, you should represent your data as an {@link * ImmutableDoubleArray} instead, which has an {@link ImmutableDoubleArray#asList asList} view. * @@ -520,8 +579,7 @@ public static List asList(double... backingArray) { return new DoubleArrayAsList(backingArray); } - @GwtCompatible - private static class DoubleArrayAsList extends AbstractList + private static final class DoubleArrayAsList extends AbstractList implements RandomAccess, Serializable { final double[] array; final int start; @@ -554,14 +612,24 @@ public Double get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + /* + * This is an override that is not directly visible to callers, so NewApi will catch calls to + * Collection.spliterator() where necessary. + */ + @IgnoreJRERequirement + public Spliterator.OfDouble spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(@Nullable Object target) { // Overridden to prevent a ton of boxing return (target instanceof Double) && Doubles.indexOf(array, (Double) target, start, end) != -1; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Double) { int i = Doubles.indexOf(array, (Double) target, start, end); @@ -573,7 +641,7 @@ public int indexOf(@CheckForNull Object target) { } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Double) { int i = Doubles.lastIndexOf(array, (Double) target, start, end); @@ -604,7 +672,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -628,7 +696,7 @@ public boolean equals(@CheckForNull Object object) { public int hashCode() { int result = 1; for (int i = start; i < end; i++) { - result = 31 * result + Doubles.hashCode(array[i]); + result = 31 * result + Double.hashCode(array[i]); } return result; } @@ -647,7 +715,7 @@ public String toString() { return Arrays.copyOfRange(array, start, end); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -701,10 +769,8 @@ public String toString() { * @throws NullPointerException if {@code string} is {@code null} * @since 14.0 */ - @Beta @GwtIncompatible // regular expressions - @CheckForNull - public static Double tryParse(String string) { + public static @Nullable Double tryParse(String string) { if (FLOATING_POINT_PATTERN.matcher(string).matches()) { // TODO(lowasser): could be potentially optimized, but only with // extensive testing diff --git a/android/guava/src/com/google/common/primitives/DoublesMethodsForWeb.java b/android/guava/src/com/google/common/primitives/DoublesMethodsForWeb.java index 949cbe0f5a6b..71233c62a38a 100644 --- a/android/guava/src/com/google/common/primitives/DoublesMethodsForWeb.java +++ b/android/guava/src/com/google/common/primitives/DoublesMethodsForWeb.java @@ -20,6 +20,5 @@ * Holder for web specializations of methods of {@code Doubles}. Intended to be empty for regular * version. */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible abstract class DoublesMethodsForWeb {} diff --git a/android/guava/src/com/google/common/primitives/ElementTypesAreNonnullByDefault.java b/android/guava/src/com/google/common/primitives/ElementTypesAreNonnullByDefault.java deleted file mode 100755 index 44f6869c7cda..000000000000 --- a/android/guava/src/com/google/common/primitives/ElementTypesAreNonnullByDefault.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.primitives; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import com.google.common.annotations.GwtCompatible; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; - -/** - * Marks all "top-level" types as non-null in a way that is recognized by Kotlin. Note that this - * unfortunately includes type-variable usages, so we also provide {@link ParametricNullness} to - * "undo" it as best we can. - */ -@GwtCompatible -@Retention(RUNTIME) -@Target(TYPE) -@TypeQualifierDefault({FIELD, METHOD, PARAMETER}) -@Nonnull -@interface ElementTypesAreNonnullByDefault {} diff --git a/android/guava/src/com/google/common/primitives/Floats.java b/android/guava/src/com/google/common/primitives/Floats.java index b038cb289636..5266e20d9ac6 100644 --- a/android/guava/src/com/google/common/primitives/Floats.java +++ b/android/guava/src/com/google/common/primitives/Floats.java @@ -19,13 +19,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; import static com.google.common.base.Strings.lenientFormat; -import static java.lang.Float.NEGATIVE_INFINITY; -import static java.lang.Float.POSITIVE_INFINITY; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -34,7 +33,7 @@ import java.util.Comparator; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code float} primitives, that are not already found in @@ -46,32 +45,28 @@ * @author Kevin Bourrillion * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Floats extends FloatsMethodsForWeb { private Floats() {} /** * The number of bytes required to represent a primitive {@code float} value. * - *

    Java 8 users: use {@link Float#BYTES} instead. + *

    Prefer {@link Float#BYTES} instead. * * @since 10.0 */ - public static final int BYTES = Float.SIZE / Byte.SIZE; + public static final int BYTES = Float.BYTES; /** - * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Float) - * value).hashCode()}. - * - *

    Java 8 users: use {@link Float#hashCode(float)} instead. + * Returns a hash code for {@code value}; obsolete alternative to {@link Float#hashCode(float)}. * * @param value a primitive {@code float} value * @return a hash code for the value */ + @InlineMe(replacement = "Float.hashCode(value)") public static int hashCode(float value) { - // TODO(kevinb): is there a better way, that's still gwt-safe? - return ((Float) value).hashCode(); + return Float.hashCode(value); } /** @@ -87,6 +82,7 @@ public static int hashCode(float value) { * @param b the second {@code float} to compare * @return the result of invoking {@link Float#compare(float, float)} */ + @InlineMe(replacement = "Float.compare(a, b)") public static int compare(float a, float b) { return Float.compare(a, b); } @@ -95,12 +91,13 @@ public static int compare(float a, float b) { * Returns {@code true} if {@code value} represents a real number. This is equivalent to, but not * necessarily implemented as, {@code !(Float.isInfinite(value) || Float.isNaN(value))}. * - *

    Java 8 users: use {@link Float#isFinite(float)} instead. + *

    Prefer {@link Float#isFinite(float)} instead. * * @since 10.0 */ + @InlineMe(replacement = "Float.isFinite(value)") public static boolean isFinite(float value) { - return NEGATIVE_INFINITY < value && value < POSITIVE_INFINITY; + return Float.isFinite(value); } /** @@ -244,13 +241,14 @@ public static float max(float... array) { * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code * value} is greater than {@code max}, {@code max} is returned. * + *

    Java 21+ users: Use {@code Math.clamp} instead. + * * @param value the {@code float} value to constrain * @param min the lower bound (inclusive) of the range to constrain {@code value} to * @param max the upper bound (inclusive) of the range to constrain {@code value} to * @throws IllegalArgumentException if {@code min > max} * @since 21.0 */ - @Beta public static float constrainToRange(float value, float min, float max) { // avoid auto-boxing by not using Preconditions.checkArgument(); see Guava issue 3984 // Reject NaN by testing for the good case (min <= max) instead of the bad (min > max). @@ -268,13 +266,15 @@ public static float constrainToRange(float value, float min, float max) { * * @param arrays zero or more {@code float} arrays * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} */ public static float[] concat(float[]... arrays) { - int length = 0; + long length = 0; for (float[] array : arrays) { length += array.length; } - float[] result = new float[length]; + float[] result = new float[checkNoOverflow(length)]; int pos = 0; for (float[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); @@ -283,9 +283,17 @@ public static float[] concat(float[]... arrays) { return result; } + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + private static final class FloatConverter extends Converter implements Serializable { - static final FloatConverter INSTANCE = new FloatConverter(); + static final Converter INSTANCE = new FloatConverter(); @Override protected Float doForward(String value) { @@ -306,7 +314,7 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } /** @@ -315,7 +323,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return FloatConverter.INSTANCE; } @@ -464,6 +471,56 @@ public static void reverse(float[] array, int fromIndex, int toIndex) { } } + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Floats.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(float[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Floats.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(float[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + /** * Returns an array containing each value of {@code collection}, converted to a {@code float} * value in the manner of {@link Number#floatValue}. @@ -504,6 +561,8 @@ public static float[] toArray(Collection collection) { *

    The returned list may have unexpected behavior if it contains {@code NaN}, or if {@code NaN} * is used as a parameter to any of its methods. * + *

    The returned list is serializable. + * * @param backingArray the array to back the list * @return a list view of the array */ @@ -514,8 +573,7 @@ public static List asList(float... backingArray) { return new FloatArrayAsList(backingArray); } - @GwtCompatible - private static class FloatArrayAsList extends AbstractList + private static final class FloatArrayAsList extends AbstractList implements RandomAccess, Serializable { final float[] array; final int start; @@ -548,13 +606,13 @@ public Float get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + public boolean contains(@Nullable Object target) { // Overridden to prevent a ton of boxing return (target instanceof Float) && Floats.indexOf(array, (Float) target, start, end) != -1; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Float) { int i = Floats.indexOf(array, (Float) target, start, end); @@ -566,7 +624,7 @@ public int indexOf(@CheckForNull Object target) { } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Float) { int i = Floats.lastIndexOf(array, (Float) target, start, end); @@ -597,7 +655,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -621,7 +679,7 @@ public boolean equals(@CheckForNull Object object) { public int hashCode() { int result = 1; for (int i = start; i < end; i++) { - result = 31 * result + Floats.hashCode(array[i]); + result = 31 * result + Float.hashCode(array[i]); } return result; } @@ -640,7 +698,7 @@ float[] toFloatArray() { return Arrays.copyOfRange(array, start, end); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -660,10 +718,8 @@ float[] toFloatArray() { * @throws NullPointerException if {@code string} is {@code null} * @since 14.0 */ - @Beta @GwtIncompatible // regular expressions - @CheckForNull - public static Float tryParse(String string) { + public static @Nullable Float tryParse(String string) { if (Doubles.FLOATING_POINT_PATTERN.matcher(string).matches()) { // TODO(lowasser): could be potentially optimized, but only with // extensive testing diff --git a/android/guava/src/com/google/common/primitives/FloatsMethodsForWeb.java b/android/guava/src/com/google/common/primitives/FloatsMethodsForWeb.java index 801e2f3ef110..b12ad692243f 100644 --- a/android/guava/src/com/google/common/primitives/FloatsMethodsForWeb.java +++ b/android/guava/src/com/google/common/primitives/FloatsMethodsForWeb.java @@ -20,6 +20,5 @@ * Holder for web specializations of methods of {@code Floats}. Intended to be empty for regular * version. */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible abstract class FloatsMethodsForWeb {} diff --git a/android/guava/src/com/google/common/primitives/Platform.java b/android/guava/src/com/google/common/primitives/IgnoreJRERequirement.java similarity index 51% rename from android/guava/src/com/google/common/primitives/Platform.java rename to android/guava/src/com/google/common/primitives/IgnoreJRERequirement.java index 83b248517993..a34ae0fdb9a4 100644 --- a/android/guava/src/com/google/common/primitives/Platform.java +++ b/android/guava/src/com/google/common/primitives/IgnoreJRERequirement.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Guava Authors + * Copyright 2019 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,13 +14,17 @@ package com.google.common.primitives; -import com.google.common.annotations.GwtCompatible; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; -/** Methods factored out so that they can be emulated differently in GWT. */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault -final class Platform { - static void checkGwtRpcEnabled() {} +import java.lang.annotation.Target; - private Platform() {} -} +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/primitives/ImmutableDoubleArray.java b/android/guava/src/com/google/common/primitives/ImmutableDoubleArray.java index 1627fab2baa8..c179ad6e6516 100644 --- a/android/guava/src/com/google/common/primitives/ImmutableDoubleArray.java +++ b/android/guava/src/com/google/common/primitives/ImmutableDoubleArray.java @@ -15,12 +15,11 @@ package com.google.common.primitives; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; import java.io.Serializable; import java.util.AbstractList; @@ -28,7 +27,11 @@ import java.util.Collection; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.DoubleConsumer; +import java.util.stream.DoubleStream; +import org.jspecify.annotations.Nullable; /** * An immutable array of {@code double} values, with an API resembling {@link List}. @@ -44,6 +47,7 @@ * hunt through classes like {@link Arrays} and {@link Doubles} for them. *

  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to * add overloads that accept start and end indexes. + *
  • Can be streamed without "breaking the chain": {@code foo.getBarDoubles().stream()...}. *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of * allocating garbage). * @@ -65,6 +69,8 @@ *
      *
    • Improved memory compactness and locality. *
    • Can be queried without allocating garbage. + *
    • Access to {@code DoubleStream} features (like {@link DoubleStream#sum}) using {@code + * stream()} instead of the awkward {@code stream().mapToDouble(v -> v)}. *
    * *

    Disadvantages compared to {@code ImmutableList}: @@ -77,10 +83,8 @@ * * @since 22.0 */ -@Beta @GwtCompatible @Immutable -@ElementTypesAreNonnullByDefault public final class ImmutableDoubleArray implements Serializable { private static final ImmutableDoubleArray EMPTY = new ImmutableDoubleArray(new double[0]); @@ -164,6 +168,18 @@ public static ImmutableDoubleArray copyOf(Iterable values) { return builder().addAll(values).build(); } + /** + * Returns an immutable array containing all the values from {@code stream}, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static ImmutableDoubleArray copyOf(DoubleStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + double[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableDoubleArray(array); + } + /** * Returns a new, empty builder for {@link ImmutableDoubleArray} instances, sized to hold up to * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. @@ -195,7 +211,6 @@ public static Builder builder() { * A builder for {@link ImmutableDoubleArray} instances; obtained using {@link * ImmutableDoubleArray#builder}. */ - @CanIgnoreReturnValue public static final class Builder { private double[] array; private int count = 0; // <= array.length @@ -208,6 +223,7 @@ public static final class Builder { * Appends {@code value} to the end of the values the built {@link ImmutableDoubleArray} will * contain. */ + @CanIgnoreReturnValue public Builder add(double value) { ensureRoomFor(1); array[count] = value; @@ -219,6 +235,7 @@ public Builder add(double value) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableDoubleArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(double[] values) { ensureRoomFor(values.length); System.arraycopy(values, 0, array, count, values.length); @@ -230,6 +247,7 @@ public Builder addAll(double[] values) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableDoubleArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(Iterable values) { if (values instanceof Collection) { return addAll((Collection) values); @@ -244,6 +262,7 @@ public Builder addAll(Iterable values) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableDoubleArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(Collection values) { ensureRoomFor(values.size()); for (Double value : values) { @@ -252,10 +271,29 @@ public Builder addAll(Collection values) { return this; } + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + @CanIgnoreReturnValue + public Builder addAll(DoubleStream stream) { + Spliterator.OfDouble spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((DoubleConsumer) this::add); + return this; + } + /** * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableDoubleArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(ImmutableDoubleArray values) { ensureRoomFor(values.length()); System.arraycopy(values.array, values.start, array, count, values.length()); @@ -294,7 +332,6 @@ private static int expandedCapacity(int oldCapacity, int minCapacity) { * no data is copied as part of this step, but this may occupy more memory than strictly * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. */ - @CheckReturnValue public ImmutableDoubleArray build() { return count == 0 ? EMPTY : new ImmutableDoubleArray(array, 0, count); } @@ -383,6 +420,30 @@ public boolean contains(double target) { return indexOf(target) >= 0; } + /** + * Invokes {@code consumer} for each value contained in this array, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // We rely on users not to call this without library desugaring. + public void forEach(DoubleConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** + * Returns a stream over the values in this array, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + // If users use this when they shouldn't, we hope that NewApi will catch subsequent stream calls + @IgnoreJRERequirement + public DoubleStream stream() { + return Arrays.stream(array, start, end); + } + /** Returns a new, mutable copy of this array's values, as a primitive {@code double[]}. */ public double[] toArray() { return Arrays.copyOfRange(array, start, end); @@ -402,6 +463,15 @@ public ImmutableDoubleArray subArray(int startIndex, int endIndex) { : new ImmutableDoubleArray(array, start + startIndex, start + endIndex); } + @IgnoreJRERequirement // used only from APIs that use streams + /* + * We declare this as package-private, rather than private, to avoid generating a synthetic + * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. + */ + Spliterator.OfDouble spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + /** * Returns an immutable view of this array's values as a {@code List}; note that {@code * double} values are boxed into {@link Double} instances on demand, which can be very expensive. @@ -418,14 +488,15 @@ public List asList() { return new AsList(this); } - static class AsList extends AbstractList implements RandomAccess, Serializable { + private static final class AsList extends AbstractList + implements RandomAccess, Serializable { private final ImmutableDoubleArray parent; private AsList(ImmutableDoubleArray parent) { this.parent = parent; } - // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, mutations + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations @Override public int size() { @@ -438,17 +509,17 @@ public Double get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + public boolean contains(@Nullable Object target) { return indexOf(target) >= 0; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { return target instanceof Double ? parent.indexOf((Double) target) : -1; } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { return target instanceof Double ? parent.lastIndexOf((Double) target) : -1; } @@ -457,8 +528,19 @@ public List subList(int fromIndex, int toIndex) { return parent.subArray(fromIndex, toIndex).asList(); } + // The default List spliterator is not efficiently splittable + @Override + /* + * This is an override that is not directly visible to callers, so NewApi will catch calls to + * Collection.spliterator() where necessary. + */ + @IgnoreJRERequirement + public Spliterator spliterator() { + return parent.spliterator(); + } + @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof AsList) { AsList that = (AsList) object; return this.parent.equals(that.parent); @@ -498,7 +580,7 @@ public String toString() { * values as this one, in the same order. Values are compared as if by {@link Double#equals}. */ @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -528,7 +610,7 @@ public int hashCode() { int hash = 1; for (int i = start; i < end; i++) { hash *= 31; - hash += Doubles.hashCode(array[i]); + hash += Double.hashCode(array[i]); } return hash; } diff --git a/android/guava/src/com/google/common/primitives/ImmutableIntArray.java b/android/guava/src/com/google/common/primitives/ImmutableIntArray.java index 8f972c49b4fc..d6cda61b4a93 100644 --- a/android/guava/src/com/google/common/primitives/ImmutableIntArray.java +++ b/android/guava/src/com/google/common/primitives/ImmutableIntArray.java @@ -15,12 +15,11 @@ package com.google.common.primitives; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; import java.io.Serializable; import java.util.AbstractList; @@ -28,7 +27,11 @@ import java.util.Collection; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.IntConsumer; +import java.util.stream.IntStream; +import org.jspecify.annotations.Nullable; /** * An immutable array of {@code int} values, with an API resembling {@link List}. @@ -39,11 +42,12 @@ *

  • All the many well-known advantages of immutability (read Effective Java, third * edition, Item 17). *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link - * #toString} behavior you expect + * #toString} behavior you expect. *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to * hunt through classes like {@link Arrays} and {@link Ints} for them. *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to * add overloads that accept start and end indexes. + *
  • Can be streamed without "breaking the chain": {@code foo.getBarInts().stream()...}. *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of * allocating garbage). * @@ -63,8 +67,10 @@ * }: * *
      - *
    • Improved memory compactness and locality - *
    • Can be queried without allocating garbage + *
    • Improved memory compactness and locality. + *
    • Can be queried without allocating garbage. + *
    • Access to {@code IntStream} features (like {@link IntStream#sum}) using {@code stream()} + * instead of the awkward {@code stream().mapToInt(v -> v)}. *
    * *

    Disadvantages compared to {@code ImmutableList}: @@ -77,10 +83,8 @@ * * @since 22.0 */ -@Beta @GwtCompatible @Immutable -@ElementTypesAreNonnullByDefault public final class ImmutableIntArray implements Serializable { private static final ImmutableIntArray EMPTY = new ImmutableIntArray(new int[0]); @@ -161,6 +165,18 @@ public static ImmutableIntArray copyOf(Iterable values) { return builder().addAll(values).build(); } + /** + * Returns an immutable array containing all the values from {@code stream}, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static ImmutableIntArray copyOf(IntStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + int[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableIntArray(array); + } + /** * Returns a new, empty builder for {@link ImmutableIntArray} instances, sized to hold up to * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. @@ -192,7 +208,6 @@ public static Builder builder() { * A builder for {@link ImmutableIntArray} instances; obtained using {@link * ImmutableIntArray#builder}. */ - @CanIgnoreReturnValue public static final class Builder { private int[] array; private int count = 0; // <= array.length @@ -205,6 +220,7 @@ public static final class Builder { * Appends {@code value} to the end of the values the built {@link ImmutableIntArray} will * contain. */ + @CanIgnoreReturnValue public Builder add(int value) { ensureRoomFor(1); array[count] = value; @@ -216,6 +232,7 @@ public Builder add(int value) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableIntArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(int[] values) { ensureRoomFor(values.length); System.arraycopy(values, 0, array, count, values.length); @@ -227,6 +244,7 @@ public Builder addAll(int[] values) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableIntArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(Iterable values) { if (values instanceof Collection) { return addAll((Collection) values); @@ -241,6 +259,7 @@ public Builder addAll(Iterable values) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableIntArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(Collection values) { ensureRoomFor(values.size()); for (Integer value : values) { @@ -249,10 +268,29 @@ public Builder addAll(Collection values) { return this; } + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + @CanIgnoreReturnValue + public Builder addAll(IntStream stream) { + Spliterator.OfInt spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((IntConsumer) this::add); + return this; + } + /** * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableIntArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(ImmutableIntArray values) { ensureRoomFor(values.length()); System.arraycopy(values.array, values.start, array, count, values.length()); @@ -291,7 +329,6 @@ private static int expandedCapacity(int oldCapacity, int minCapacity) { * no data is copied as part of this step, but this may occupy more memory than strictly * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. */ - @CheckReturnValue public ImmutableIntArray build() { return count == 0 ? EMPTY : new ImmutableIntArray(array, 0, count); } @@ -378,6 +415,30 @@ public boolean contains(int target) { return indexOf(target) >= 0; } + /** + * Invokes {@code consumer} for each value contained in this array, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // We rely on users not to call this without library desugaring. + public void forEach(IntConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** + * Returns a stream over the values in this array, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + // If users use this when they shouldn't, we hope that NewApi will catch subsequent stream calls + @IgnoreJRERequirement + public IntStream stream() { + return Arrays.stream(array, start, end); + } + /** Returns a new, mutable copy of this array's values, as a primitive {@code int[]}. */ public int[] toArray() { return Arrays.copyOfRange(array, start, end); @@ -397,6 +458,15 @@ public ImmutableIntArray subArray(int startIndex, int endIndex) { : new ImmutableIntArray(array, start + startIndex, start + endIndex); } + @IgnoreJRERequirement // used only from APIs that use streams + /* + * We declare this as package-private, rather than private, to avoid generating a synthetic + * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. + */ + Spliterator.OfInt spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + /** * Returns an immutable view of this array's values as a {@code List}; note that {@code * int} values are boxed into {@link Integer} instances on demand, which can be very expensive. @@ -413,14 +483,15 @@ public List asList() { return new AsList(this); } - static class AsList extends AbstractList implements RandomAccess, Serializable { + private static final class AsList extends AbstractList + implements RandomAccess, Serializable { private final ImmutableIntArray parent; private AsList(ImmutableIntArray parent) { this.parent = parent; } - // inherit: isEmpty, containsAll, toArray x2, {,list,spl}iterator, stream, forEach, mutations + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations @Override public int size() { @@ -433,17 +504,17 @@ public Integer get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + public boolean contains(@Nullable Object target) { return indexOf(target) >= 0; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { return target instanceof Integer ? parent.indexOf((Integer) target) : -1; } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { return target instanceof Integer ? parent.lastIndexOf((Integer) target) : -1; } @@ -452,8 +523,19 @@ public List subList(int fromIndex, int toIndex) { return parent.subArray(fromIndex, toIndex).asList(); } + // The default List spliterator is not efficiently splittable + @Override + /* + * This is an override that is not directly visible to callers, so NewApi will catch calls to + * Collection.spliterator() where necessary. + */ + @IgnoreJRERequirement + public Spliterator spliterator() { + return parent.spliterator(); + } + @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof AsList) { AsList that = (AsList) object; return this.parent.equals(that.parent); @@ -493,7 +575,7 @@ public String toString() { * values as this one, in the same order. */ @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -518,7 +600,7 @@ public int hashCode() { int hash = 1; for (int i = start; i < end; i++) { hash *= 31; - hash += Ints.hashCode(array[i]); + hash += Integer.hashCode(array[i]); } return hash; } diff --git a/android/guava/src/com/google/common/primitives/ImmutableLongArray.java b/android/guava/src/com/google/common/primitives/ImmutableLongArray.java index 4ebf5b406fb9..34a4f48be882 100644 --- a/android/guava/src/com/google/common/primitives/ImmutableLongArray.java +++ b/android/guava/src/com/google/common/primitives/ImmutableLongArray.java @@ -15,12 +15,11 @@ package com.google.common.primitives; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; import java.io.Serializable; import java.util.AbstractList; @@ -28,7 +27,11 @@ import java.util.Collection; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.LongConsumer; +import java.util.stream.LongStream; +import org.jspecify.annotations.Nullable; /** * An immutable array of {@code long} values, with an API resembling {@link List}. @@ -44,6 +47,7 @@ * hunt through classes like {@link Arrays} and {@link Longs} for them. *

  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to * add overloads that accept start and end indexes. + *
  • Can be streamed without "breaking the chain": {@code foo.getBarLongs().stream()...}. *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of * allocating garbage). * @@ -65,6 +69,8 @@ *
      *
    • Improved memory compactness and locality. *
    • Can be queried without allocating garbage. + *
    • Access to {@code LongStream} features (like {@link LongStream#sum}) using {@code stream()} + * instead of the awkward {@code stream().mapToLong(v -> v)}. *
    * *

    Disadvantages compared to {@code ImmutableList}: @@ -77,10 +83,8 @@ * * @since 22.0 */ -@Beta @GwtCompatible @Immutable -@ElementTypesAreNonnullByDefault public final class ImmutableLongArray implements Serializable { private static final ImmutableLongArray EMPTY = new ImmutableLongArray(new long[0]); @@ -163,6 +167,18 @@ public static ImmutableLongArray copyOf(Iterable values) { return builder().addAll(values).build(); } + /** + * Returns an immutable array containing all the values from {@code stream}, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + public static ImmutableLongArray copyOf(LongStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + long[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableLongArray(array); + } + /** * Returns a new, empty builder for {@link ImmutableLongArray} instances, sized to hold up to * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. @@ -194,7 +210,6 @@ public static Builder builder() { * A builder for {@link ImmutableLongArray} instances; obtained using {@link * ImmutableLongArray#builder}. */ - @CanIgnoreReturnValue public static final class Builder { private long[] array; private int count = 0; // <= array.length @@ -207,6 +222,7 @@ public static final class Builder { * Appends {@code value} to the end of the values the built {@link ImmutableLongArray} will * contain. */ + @CanIgnoreReturnValue public Builder add(long value) { ensureRoomFor(1); array[count] = value; @@ -218,6 +234,7 @@ public Builder add(long value) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableLongArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(long[] values) { ensureRoomFor(values.length); System.arraycopy(values, 0, array, count, values.length); @@ -229,6 +246,7 @@ public Builder addAll(long[] values) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableLongArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(Iterable values) { if (values instanceof Collection) { return addAll((Collection) values); @@ -243,6 +261,7 @@ public Builder addAll(Iterable values) { * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableLongArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(Collection values) { ensureRoomFor(values.size()); for (Long value : values) { @@ -251,10 +270,29 @@ public Builder addAll(Collection values) { return this; } + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using streams. + @CanIgnoreReturnValue + public Builder addAll(LongStream stream) { + Spliterator.OfLong spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((LongConsumer) this::add); + return this; + } + /** * Appends {@code values}, in order, to the end of the values the built {@link * ImmutableLongArray} will contain. */ + @CanIgnoreReturnValue public Builder addAll(ImmutableLongArray values) { ensureRoomFor(values.length()); System.arraycopy(values.array, values.start, array, count, values.length()); @@ -293,7 +331,6 @@ private static int expandedCapacity(int oldCapacity, int minCapacity) { * no data is copied as part of this step, but this may occupy more memory than strictly * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. */ - @CheckReturnValue public ImmutableLongArray build() { return count == 0 ? EMPTY : new ImmutableLongArray(array, 0, count); } @@ -380,6 +417,30 @@ public boolean contains(long target) { return indexOf(target) >= 0; } + /** + * Invokes {@code consumer} for each value contained in this array, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + @IgnoreJRERequirement // We rely on users not to call this without library desugaring. + public void forEach(LongConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** + * Returns a stream over the values in this array, in order. + * + * @since 33.4.0 (but since 22.0 in the JRE flavor) + */ + // If users use this when they shouldn't, we hope that NewApi will catch subsequent stream calls + @IgnoreJRERequirement + public LongStream stream() { + return Arrays.stream(array, start, end); + } + /** Returns a new, mutable copy of this array's values, as a primitive {@code long[]}. */ public long[] toArray() { return Arrays.copyOfRange(array, start, end); @@ -399,6 +460,15 @@ public ImmutableLongArray subArray(int startIndex, int endIndex) { : new ImmutableLongArray(array, start + startIndex, start + endIndex); } + @IgnoreJRERequirement // used only from APIs that use streams + /* + * We declare this as package-private, rather than private, to avoid generating a synthetic + * accessor method (under -target 8) that would lack the Android flavor's @IgnoreJRERequirement. + */ + Spliterator.OfLong spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + /** * Returns an immutable view of this array's values as a {@code List}; note that {@code * long} values are boxed into {@link Long} instances on demand, which can be very expensive. The @@ -415,14 +485,15 @@ public List asList() { return new AsList(this); } - static class AsList extends AbstractList implements RandomAccess, Serializable { + private static final class AsList extends AbstractList + implements RandomAccess, Serializable { private final ImmutableLongArray parent; private AsList(ImmutableLongArray parent) { this.parent = parent; } - // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, mutations + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations @Override public int size() { @@ -435,17 +506,17 @@ public Long get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + public boolean contains(@Nullable Object target) { return indexOf(target) >= 0; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { return target instanceof Long ? parent.indexOf((Long) target) : -1; } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { return target instanceof Long ? parent.lastIndexOf((Long) target) : -1; } @@ -454,8 +525,19 @@ public List subList(int fromIndex, int toIndex) { return parent.subArray(fromIndex, toIndex).asList(); } + // The default List spliterator is not efficiently splittable + @Override + /* + * This is an override that is not directly visible to callers, so NewApi will catch calls to + * Collection.spliterator() where necessary. + */ + @IgnoreJRERequirement + public Spliterator spliterator() { + return parent.spliterator(); + } + @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object instanceof AsList) { AsList that = (AsList) object; return this.parent.equals(that.parent); @@ -495,7 +577,7 @@ public String toString() { * values as this one, in the same order. */ @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -520,7 +602,7 @@ public int hashCode() { int hash = 1; for (int i = start; i < end; i++) { hash *= 31; - hash += Longs.hashCode(array[i]); + hash += Long.hashCode(array[i]); } return hash; } diff --git a/android/guava/src/com/google/common/primitives/Ints.java b/android/guava/src/com/google/common/primitives/Ints.java index e07bdd88d93e..f474a72d9ed5 100644 --- a/android/guava/src/com/google/common/primitives/Ints.java +++ b/android/guava/src/com/google/common/primitives/Ints.java @@ -19,10 +19,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.InlineMeValidationDisabled; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -31,7 +33,9 @@ import java.util.Comparator; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import java.util.Spliterator; +import java.util.Spliterators; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code int} primitives, that are not already found in either @@ -43,17 +47,16 @@ * @author Kevin Bourrillion * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Ints extends IntsMethodsForWeb { private Ints() {} /** * The number of bytes required to represent a primitive {@code int} value. * - *

    Java 8 users: use {@link Integer#BYTES} instead. + *

    Prefer {@link Integer#BYTES} instead. */ - public static final int BYTES = Integer.SIZE / Byte.SIZE; + public static final int BYTES = Integer.BYTES; /** * The largest power of two that can be represented as an {@code int}. @@ -63,14 +66,14 @@ private Ints() {} public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); /** - * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Integer) - * value).hashCode()}. - * - *

    Java 8 users: use {@link Integer#hashCode(int)} instead. + * Returns a hash code for {@code value}; obsolete alternative to {@link Integer#hashCode(int)}. * * @param value a primitive {@code int} value * @return a hash code for the value */ + @InlineMe(replacement = "Integer.hashCode(value)") + @InlineMeValidationDisabled( + "The hash code of a int is the int itself, so it's simplest to return that.") public static int hashCode(int value) { return value; } @@ -78,6 +81,10 @@ public static int hashCode(int value) { /** * Returns the {@code int} value that is equal to {@code value}, if possible. * + *

    Note: this method is now unnecessary and should be treated as deprecated. Use {@link + * Math#toIntExact(long)} instead, but be aware that that method throws {@link + * ArithmeticException} rather than {@link IllegalArgumentException}. + * * @param value any value in the range of the {@code int} type * @return the {@code int} value that equals {@code value} * @throws IllegalArgumentException if {@code value} is greater than {@link Integer#MAX_VALUE} or @@ -111,7 +118,7 @@ public static int saturatedCast(long value) { * Compares the two specified {@code int} values. The sign of the value returned is the same as * that of {@code ((Integer) a).compareTo(b)}. * - *

    Note for Java 7 and later: this method should be treated as deprecated; use the + *

    Note: this method is now unnecessary and should be treated as deprecated; use the * equivalent {@link Integer#compare} method instead. * * @param a the first {@code int} to compare @@ -119,8 +126,9 @@ public static int saturatedCast(long value) { * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ + @InlineMe(replacement = "Integer.compare(a, b)") public static int compare(int a, int b) { - return (a < b) ? -1 : ((a > b) ? 1 : 0); + return Integer.compare(a, b); } /** @@ -261,13 +269,17 @@ public static int max(int... array) { * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code * value} is greater than {@code max}, {@code max} is returned. * + *

    Java 21+ users: Use {@code Math.clamp} instead. Note that that method is capable of + * constraining a {@code long} input to an {@code int} range. + * * @param value the {@code int} value to constrain * @param min the lower bound (inclusive) of the range to constrain {@code value} to * @param max the upper bound (inclusive) of the range to constrain {@code value} to * @throws IllegalArgumentException if {@code min > max} * @since 21.0 */ - @Beta + // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. + @SuppressWarnings("StaticImportPreferred") public static int constrainToRange(int value, int min, int max) { checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); return Math.min(Math.max(value, min), max); @@ -279,13 +291,15 @@ public static int constrainToRange(int value, int min, int max) { * * @param arrays zero or more {@code int} arrays * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} */ public static int[] concat(int[]... arrays) { - int length = 0; + long length = 0; for (int[] array : arrays) { length += array.length; } - int[] result = new int[length]; + int[] result = new int[checkNoOverflow(length)]; int pos = 0; for (int[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); @@ -294,6 +308,14 @@ public static int[] concat(int[]... arrays) { return result; } + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + /** * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code @@ -337,7 +359,7 @@ public static int fromBytes(byte b1, byte b2, byte b3, byte b4) { private static final class IntConverter extends Converter implements Serializable { - static final IntConverter INSTANCE = new IntConverter(); + static final Converter INSTANCE = new IntConverter(); @Override protected Integer doForward(String value) { @@ -358,7 +380,7 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } /** @@ -372,7 +394,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return IntConverter.INSTANCE; } @@ -439,10 +460,12 @@ private enum LexicographicalComparator implements Comparator { INSTANCE; @Override + // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. + @SuppressWarnings("StaticImportPreferred") public int compare(int[] left, int[] right) { int minLength = Math.min(left.length, right.length); for (int i = 0; i < minLength; i++) { - int result = Ints.compare(left[i], right[i]); + int result = Integer.compare(left[i], right[i]); if (result != 0) { return result; } @@ -510,6 +533,82 @@ public static void reverse(int[] array, int fromIndex, int toIndex) { } } + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Ints.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(int[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Ints.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(int[] array, int distance, int fromIndex, int toIndex) { + // There are several well-known algorithms for rotating part of an array (or, equivalently, + // exchanging two blocks of memory). This classic text by Gries and Mills mentions several: + // https://ecommons.cornell.edu/bitstream/handle/1813/6292/81-452.pdf. + // (1) "Reversal", the one we have here. + // (2) "Dolphin". If we're rotating an array a of size n by a distance of d, then element a[0] + // ends up at a[d], which in turn ends up at a[2d], and so on until we get back to a[0]. + // (All indices taken mod n.) If d and n are mutually prime, all elements will have been + // moved at that point. Otherwise, we can rotate the cycle a[1], a[1 + d], a[1 + 2d], etc, + // then a[2] etc, and so on until we have rotated all elements. There are gcd(d, n) cycles + // in all. + // (3) "Successive". We can consider that we are exchanging a block of size d (a[0..d-1]) with a + // block of size n-d (a[d..n-1]), where in general these blocks have different sizes. If we + // imagine a line separating the first block from the second, we can proceed by exchanging + // the smaller of these blocks with the far end of the other one. That leaves us with a + // smaller version of the same problem. + // Say we are rotating abcdefgh by 5. We start with abcde|fgh. The smaller block is [fgh]: + // [abc]de|[fgh] -> [fgh]de|[abc]. Now [fgh] is in the right place, but we need to swap [de] + // with [abc]: fgh[de]|a[bc] -> fgh[bc]|a[de]. Now we need to swap [a] with [bc]: + // fgh[b]c|[a]de -> fgh[a]c|[b]de. Finally we need to swap [c] with [b]: + // fgha[c]|[b]de -> fgha[b]|[c]de. Because these two blocks are the same size, we are done. + // The Dolphin algorithm is attractive because it does the fewest array reads and writes: each + // array slot is read and written exactly once. However, it can have very poor memory locality: + // benchmarking shows it can take 7 times longer than the other two in some cases. The other two + // do n swaps, minus a delta (0 or 2 for Reversal, gcd(d, n) for Successive), so that's about + // twice as many reads and writes. But benchmarking shows that they usually perform better than + // Dolphin. Reversal is about as good as Successive on average, and it is much simpler, + // especially since we already have a `reverse` method. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + /** * Returns an array containing each value of {@code collection}, converted to a {@code int} value * in the manner of {@link Number#intValue}. @@ -547,6 +646,8 @@ public static int[] toArray(Collection collection) { * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for * the returned list is unspecified. * + *

    The returned list is serializable. + * *

    Note: when possible, you should represent your data as an {@link ImmutableIntArray} * instead, which has an {@link ImmutableIntArray#asList asList} view. * @@ -560,8 +661,7 @@ public static List asList(int... backingArray) { return new IntArrayAsList(backingArray); } - @GwtCompatible - private static class IntArrayAsList extends AbstractList + private static final class IntArrayAsList extends AbstractList implements RandomAccess, Serializable { final int[] array; final int start; @@ -594,13 +694,23 @@ public Integer get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + /* + * This is an override that is not directly visible to callers, so NewApi will catch calls to + * Collection.spliterator() where necessary. + */ + @IgnoreJRERequirement + public Spliterator.OfInt spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(@Nullable Object target) { // Overridden to prevent a ton of boxing return (target instanceof Integer) && Ints.indexOf(array, (Integer) target, start, end) != -1; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Integer) { int i = Ints.indexOf(array, (Integer) target, start, end); @@ -612,7 +722,7 @@ public int indexOf(@CheckForNull Object target) { } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Integer) { int i = Ints.lastIndexOf(array, (Integer) target, start, end); @@ -643,7 +753,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -667,7 +777,7 @@ public boolean equals(@CheckForNull Object object) { public int hashCode() { int result = 1; for (int i = start; i < end; i++) { - result = 31 * result + Ints.hashCode(array[i]); + result = 31 * result + Integer.hashCode(array[i]); } return result; } @@ -686,7 +796,7 @@ int[] toIntArray() { return Arrays.copyOfRange(array, start, end); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } /** @@ -697,8 +807,8 @@ int[] toIntArray() { * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, * and returns {@code null} if non-ASCII digits are present in the string. * - *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even under JDK 7, despite - * the change to {@link Integer#parseInt(String)} for that version. + *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link + * Integer#parseInt(String)} accepts them. * * @param string the string representation of an integer value * @return the integer value represented by {@code string}, or {@code null} if {@code string} has @@ -706,9 +816,7 @@ int[] toIntArray() { * @throws NullPointerException if {@code string} is {@code null} * @since 11.0 */ - @Beta - @CheckForNull - public static Integer tryParse(String string) { + public static @Nullable Integer tryParse(String string) { return tryParse(string, 10); } @@ -720,8 +828,8 @@ public static Integer tryParse(String string) { * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, * and returns {@code null} if non-ASCII digits are present in the string. * - *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even under JDK 7, despite - * the change to {@link Integer#parseInt(String, int)} for that version. + *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link + * Integer#parseInt(String)} accepts them. * * @param string the string representation of an integer value * @param radix the radix to use when parsing @@ -732,9 +840,7 @@ public static Integer tryParse(String string) { * @throws NullPointerException if {@code string} is {@code null} * @since 19.0 */ - @Beta - @CheckForNull - public static Integer tryParse(String string, int radix) { + public static @Nullable Integer tryParse(String string, int radix) { Long result = Longs.tryParse(string, radix); if (result == null || result.longValue() != result.intValue()) { return null; diff --git a/android/guava/src/com/google/common/primitives/IntsMethodsForWeb.java b/android/guava/src/com/google/common/primitives/IntsMethodsForWeb.java index c59c6b05862d..2027b586f533 100644 --- a/android/guava/src/com/google/common/primitives/IntsMethodsForWeb.java +++ b/android/guava/src/com/google/common/primitives/IntsMethodsForWeb.java @@ -20,6 +20,5 @@ * Holder for web specializations of methods of {@code Ints}. Intended to be empty for regular * version. */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible abstract class IntsMethodsForWeb {} diff --git a/android/guava/src/com/google/common/primitives/Longs.java b/android/guava/src/com/google/common/primitives/Longs.java index 6f60656bcb1a..9cc9972decf5 100644 --- a/android/guava/src/com/google/common/primitives/Longs.java +++ b/android/guava/src/com/google/common/primitives/Longs.java @@ -19,9 +19,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; +import com.google.errorprone.annotations.InlineMe; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -30,7 +32,9 @@ import java.util.Comparator; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import java.util.Spliterator; +import java.util.Spliterators; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code long} primitives, that are not already found in @@ -43,16 +47,15 @@ * @since 1.0 */ @GwtCompatible -@ElementTypesAreNonnullByDefault public final class Longs { private Longs() {} /** * The number of bytes required to represent a primitive {@code long} value. * - *

    Java 8 users: use {@link Long#BYTES} instead. + *

    Prefer {@link Long#BYTES} instead. */ - public static final int BYTES = Long.SIZE / Byte.SIZE; + public static final int BYTES = Long.BYTES; /** * The largest power of two that can be represented as a {@code long}. @@ -62,27 +65,21 @@ private Longs() {} public static final long MAX_POWER_OF_TWO = 1L << (Long.SIZE - 2); /** - * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Long) - * value).hashCode()}. - * - *

    This method always return the value specified by {@link Long#hashCode()} in java, which - * might be different from {@code ((Long) value).hashCode()} in GWT because {@link - * Long#hashCode()} in GWT does not obey the JRE contract. - * - *

    Java 8 users: use {@link Long#hashCode(long)} instead. + * Returns a hash code for {@code value}; obsolete alternative to {@link Long#hashCode(long)}. * * @param value a primitive {@code long} value * @return a hash code for the value */ + @InlineMe(replacement = "Long.hashCode(value)") public static int hashCode(long value) { - return (int) (value ^ (value >>> 32)); + return Long.hashCode(value); } /** * Compares the two specified {@code long} values. The sign of the value returned is the same as * that of {@code ((Long) a).compareTo(b)}. * - *

    Note for Java 7 and later: this method should be treated as deprecated; use the + *

    Note: this method is now unnecessary and should be treated as deprecated; use the * equivalent {@link Long#compare} method instead. * * @param a the first {@code long} to compare @@ -90,8 +87,9 @@ public static int hashCode(long value) { * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ + @InlineMe(replacement = "Long.compare(a, b)") public static int compare(long a, long b) { - return (a < b) ? -1 : ((a > b) ? 1 : 0); + return Long.compare(a, b); } /** @@ -228,13 +226,15 @@ public static long max(long... array) { * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code * value} is greater than {@code max}, {@code max} is returned. * + *

    Java 21+ users: Use {@code Math.clamp} instead. Note that that method is capable of + * constraining a {@code long} input to an {@code int} range. + * * @param value the {@code long} value to constrain * @param min the lower bound (inclusive) of the range to constrain {@code value} to * @param max the upper bound (inclusive) of the range to constrain {@code value} to * @throws IllegalArgumentException if {@code min > max} * @since 21.0 */ - @Beta public static long constrainToRange(long value, long min, long max) { checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); return Math.min(Math.max(value, min), max); @@ -246,13 +246,15 @@ public static long constrainToRange(long value, long min, long max) { * * @param arrays zero or more {@code long} arrays * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} */ public static long[] concat(long[]... arrays) { - int length = 0; + long length = 0; for (long[] array : arrays) { length += array.length; } - long[] result = new long[length]; + long[] result = new long[checkNoOverflow(length)]; int pos = 0; for (long[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); @@ -261,6 +263,14 @@ public static long[] concat(long[]... arrays) { return result; } + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + /** * Returns a big-endian representation of {@code value} in an 8-element byte array; equivalent to * {@code ByteBuffer.allocate(8).putLong(value).array()}. For example, the input value {@code @@ -352,8 +362,8 @@ static int digit(char c) { * an exception if parsing fails. Additionally, this method only accepts ASCII digits, and returns * {@code null} if non-ASCII digits are present in the string. * - *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even under JDK 7, despite - * the change to {@link Long#parseLong(String)} for that version. + *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link + * Integer#parseInt(String)} accepts them. * * @param string the string representation of a long value * @return the long value represented by {@code string}, or {@code null} if {@code string} has a @@ -361,9 +371,7 @@ static int digit(char c) { * @throws NullPointerException if {@code string} is {@code null} * @since 14.0 */ - @Beta - @CheckForNull - public static Long tryParse(String string) { + public static @Nullable Long tryParse(String string) { return tryParse(string, 10); } @@ -375,10 +383,10 @@ public static Long tryParse(String string) { * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, * and returns {@code null} if non-ASCII digits are present in the string. * - *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even under JDK 7, despite - * the change to {@link Long#parseLong(String, int)} for that version. + *

    Note that strings prefixed with ASCII {@code '+'} are rejected, even though {@link + * Integer#parseInt(String)} accepts them. * - * @param string the string representation of an long value + * @param string the string representation of a long value * @param radix the radix to use when parsing * @return the long value represented by {@code string} using {@code radix}, or {@code null} if * {@code string} has a length of zero or cannot be parsed as a long value @@ -387,9 +395,7 @@ public static Long tryParse(String string) { * @throws NullPointerException if {@code string} is {@code null} * @since 19.0 */ - @Beta - @CheckForNull - public static Long tryParse(String string, int radix) { + public static @Nullable Long tryParse(String string, int radix) { if (checkNotNull(string).isEmpty()) { return null; } @@ -432,7 +438,7 @@ public static Long tryParse(String string, int radix) { } private static final class LongConverter extends Converter implements Serializable { - static final LongConverter INSTANCE = new LongConverter(); + static final Converter INSTANCE = new LongConverter(); @Override protected Long doForward(String value) { @@ -453,7 +459,7 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } /** @@ -467,7 +473,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return LongConverter.INSTANCE; } @@ -538,7 +543,7 @@ private enum LexicographicalComparator implements Comparator { public int compare(long[] left, long[] right) { int minLength = Math.min(left.length, right.length); for (int i = 0; i < minLength; i++) { - int result = Longs.compare(left[i], right[i]); + int result = Long.compare(left[i], right[i]); if (result != 0) { return result; } @@ -606,6 +611,56 @@ public static void reverse(long[] array, int fromIndex, int toIndex) { } } + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Longs.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(long[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Longs.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(long[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + /** * Returns an array containing each value of {@code collection}, converted to a {@code long} value * in the manner of {@link Number#longValue}. @@ -643,6 +698,8 @@ public static long[] toArray(Collection collection) { * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for * the returned list is unspecified. * + *

    The returned list is serializable. + * *

    Note: when possible, you should represent your data as an {@link ImmutableLongArray} * instead, which has an {@link ImmutableLongArray#asList asList} view. * @@ -656,8 +713,7 @@ public static List asList(long... backingArray) { return new LongArrayAsList(backingArray); } - @GwtCompatible - private static class LongArrayAsList extends AbstractList + private static final class LongArrayAsList extends AbstractList implements RandomAccess, Serializable { final long[] array; final int start; @@ -690,13 +746,23 @@ public Long get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + /* + * This is an override that is not directly visible to callers, so NewApi will catch calls to + * Collection.spliterator() where necessary. + */ + @IgnoreJRERequirement + public Spliterator.OfLong spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(@Nullable Object target) { // Overridden to prevent a ton of boxing return (target instanceof Long) && Longs.indexOf(array, (Long) target, start, end) != -1; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Long) { int i = Longs.indexOf(array, (Long) target, start, end); @@ -708,7 +774,7 @@ public int indexOf(@CheckForNull Object target) { } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Long) { int i = Longs.lastIndexOf(array, (Long) target, start, end); @@ -739,7 +805,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -763,7 +829,7 @@ public boolean equals(@CheckForNull Object object) { public int hashCode() { int result = 1; for (int i = start; i < end; i++) { - result = 31 * result + Longs.hashCode(array[i]); + result = 31 * result + Long.hashCode(array[i]); } return result; } @@ -782,6 +848,6 @@ long[] toLongArray() { return Arrays.copyOfRange(array, start, end); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/primitives/ParametricNullness.java b/android/guava/src/com/google/common/primitives/ParametricNullness.java old mode 100755 new mode 100644 index 17d606c2c9b1..598e5e68bcf6 --- a/android/guava/src/com/google/common/primitives/ParametricNullness.java +++ b/android/guava/src/com/google/common/primitives/ParametricNullness.java @@ -19,25 +19,54 @@ import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static javax.annotation.meta.When.UNKNOWN; +import static java.lang.annotation.RetentionPolicy.CLASS; import com.google.common.annotations.GwtCompatible; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierNickname; /** - * Marks a "top-level" type-variable usage as (a) a Kotlin platform type when the type argument is - * non-nullable and (b) nullable when the type argument is nullable. This is the closest we can get - * to "non-nullable when non-nullable; nullable when nullable" (like the Android {@code - * NullFromTypeParam}). We use this to "undo" {@link ElementTypesAreNonnullByDefault}. + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *

      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. */ @GwtCompatible -@Retention(RUNTIME) +@Retention(CLASS) @Target({FIELD, METHOD, PARAMETER}) -@TypeQualifierNickname -@Nonnull(when = UNKNOWN) @interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/primitives/ParseRequest.java b/android/guava/src/com/google/common/primitives/ParseRequest.java index a102d69b06e1..97b0f1b57abc 100644 --- a/android/guava/src/com/google/common/primitives/ParseRequest.java +++ b/android/guava/src/com/google/common/primitives/ParseRequest.java @@ -18,7 +18,6 @@ /** A string to be parsed as a number and the radix to interpret it in. */ @GwtCompatible -@ElementTypesAreNonnullByDefault final class ParseRequest { final String rawValue; final int radix; diff --git a/android/guava/src/com/google/common/primitives/Primitives.java b/android/guava/src/com/google/common/primitives/Primitives.java index 7ceed036555a..9e2f71093b06 100644 --- a/android/guava/src/com/google/common/primitives/Primitives.java +++ b/android/guava/src/com/google/common/primitives/Primitives.java @@ -16,7 +16,7 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.GwtCompatible; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -29,15 +29,18 @@ * @author Kevin Bourrillion * @since 1.0 */ -@GwtIncompatible -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Primitives { private Primitives() {} /** A map from primitive types to their corresponding wrapper types. */ + // It's a constant, and we can't use ImmutableMap here without creating a circular dependency. + @SuppressWarnings("ConstantCaseForConstants") private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; /** A map from wrapper types to their corresponding primitive types. */ + // It's a constant, and we can't use ImmutableMap here without creating a circular dependency. + @SuppressWarnings("ConstantCaseForConstants") private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPE; // Sad that we can't use a BiMap. :( diff --git a/android/guava/src/com/google/common/primitives/Shorts.java b/android/guava/src/com/google/common/primitives/Shorts.java index 09e0f7cfc312..1e95a081b701 100644 --- a/android/guava/src/com/google/common/primitives/Shorts.java +++ b/android/guava/src/com/google/common/primitives/Shorts.java @@ -19,10 +19,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Converter; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.InlineMeValidationDisabled; import java.io.Serializable; import java.util.AbstractList; import java.util.Arrays; @@ -31,7 +33,7 @@ import java.util.Comparator; import java.util.List; import java.util.RandomAccess; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to {@code short} primitives, that are not already found in @@ -43,17 +45,16 @@ * @author Kevin Bourrillion * @since 1.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class Shorts extends ShortsMethodsForWeb { private Shorts() {} /** * The number of bytes required to represent a primitive {@code short} value. * - *

    Java 8 users: use {@link Short#BYTES} instead. + *

    Prefer {@link Short#BYTES} instead. */ - public static final int BYTES = Short.SIZE / Byte.SIZE; + public static final int BYTES = Short.BYTES; /** * The largest power of two that can be represented as a {@code short}. @@ -63,14 +64,15 @@ private Shorts() {} public static final short MAX_POWER_OF_TWO = 1 << (Short.SIZE - 2); /** - * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Short) - * value).hashCode()}. - * - *

    Java 8 users: use {@link Short#hashCode(short)} instead. + * Returns a hash code for {@code value}; obsolete alternative to {@link Short#hashCode(short)}. * * @param value a primitive {@code short} value * @return a hash code for the value */ + @InlineMe(replacement = "Short.hashCode(value)") + @InlineMeValidationDisabled( + "The hash code of a short is the int version of the short itself, so it's simplest to return" + + " that.") public static int hashCode(short value) { return value; } @@ -110,7 +112,7 @@ public static short saturatedCast(long value) { * Compares the two specified {@code short} values. The sign of the value returned is the same as * that of {@code ((Short) a).compareTo(b)}. * - *

    Note for Java 7 and later: this method should be treated as deprecated; use the + *

    Note: this method is now unnecessary and should be treated as deprecated; use the * equivalent {@link Short#compare} method instead. * * @param a the first {@code short} to compare @@ -118,8 +120,9 @@ public static short saturatedCast(long value) { * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ + @InlineMe(replacement = "Short.compare(a, b)") public static int compare(short a, short b) { - return a - b; // safe due to restricted range + return Short.compare(a, b); } /** @@ -266,7 +269,6 @@ public static short max(short... array) { * @throws IllegalArgumentException if {@code min > max} * @since 21.0 */ - @Beta public static short constrainToRange(short value, short min, short max) { checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); return value < min ? min : value < max ? value : max; @@ -279,13 +281,15 @@ public static short constrainToRange(short value, short min, short max) { * * @param arrays zero or more {@code short} arrays * @return a single array containing all the values from the source arrays, in order + * @throws IllegalArgumentException if the total number of elements in {@code arrays} does not fit + * in an {@code int} */ public static short[] concat(short[]... arrays) { - int length = 0; + long length = 0; for (short[] array : arrays) { length += array.length; } - short[] result = new short[length]; + short[] result = new short[checkNoOverflow(length)]; int pos = 0; for (short[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); @@ -294,6 +298,14 @@ public static short[] concat(short[]... arrays) { return result; } + private static int checkNoOverflow(long result) { + checkArgument( + result == (int) result, + "the total number of elements (%s) in the arrays must fit in an int", + result); + return (int) result; + } + /** * Returns a big-endian representation of {@code value} in a 2-element byte array; equivalent to * {@code ByteBuffer.allocate(2).putShort(value).array()}. For example, the input value {@code @@ -337,7 +349,7 @@ public static short fromBytes(byte b1, byte b2) { private static final class ShortConverter extends Converter implements Serializable { - static final ShortConverter INSTANCE = new ShortConverter(); + static final Converter INSTANCE = new ShortConverter(); @Override protected Short doForward(String value) { @@ -358,7 +370,7 @@ private Object readResolve() { return INSTANCE; } - private static final long serialVersionUID = 1; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1; } /** @@ -372,7 +384,6 @@ private Object readResolve() { * * @since 16.0 */ - @Beta public static Converter stringConverter() { return ShortConverter.INSTANCE; } @@ -444,7 +455,7 @@ private enum LexicographicalComparator implements Comparator { public int compare(short[] left, short[] right) { int minLength = Math.min(left.length, right.length); for (int i = 0; i < minLength; i++) { - int result = Shorts.compare(left[i], right[i]); + int result = Short.compare(left[i], right[i]); if (result != 0) { return result; } @@ -512,6 +523,56 @@ public static void reverse(short[] array, int fromIndex, int toIndex) { } } + /** + * Performs a right rotation of {@code array} of "distance" places, so that the first element is + * moved to index "distance", and the element at index {@code i} ends up at index {@code (distance + * + i) mod array.length}. This is equivalent to {@code Collections.rotate(Shorts.asList(array), + * distance)}, but is considerably faster and avoids allocation and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @since 32.0.0 + */ + public static void rotate(short[] array, int distance) { + rotate(array, distance, 0, array.length); + } + + /** + * Performs a right rotation of {@code array} between {@code fromIndex} inclusive and {@code + * toIndex} exclusive. This is equivalent to {@code + * Collections.rotate(Shorts.asList(array).subList(fromIndex, toIndex), distance)}, but is + * considerably faster and avoids allocations and garbage collection. + * + *

    The provided "distance" may be negative, which will rotate left. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 32.0.0 + */ + public static void rotate(short[] array, int distance, int fromIndex, int toIndex) { + // See Ints.rotate for more details about possible algorithms here. + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + if (array.length <= 1) { + return; + } + + int length = toIndex - fromIndex; + // Obtain m = (-distance mod length), a non-negative value less than "length". This is how many + // places left to rotate. + int m = -distance % length; + m = (m < 0) ? m + length : m; + // The current index of what will become the first element of the rotated section. + int newFirstIndex = m + fromIndex; + if (newFirstIndex == fromIndex) { + return; + } + + reverse(array, fromIndex, newFirstIndex); + reverse(array, newFirstIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + /** * Returns an array containing each value of {@code collection}, converted to a {@code short} * value in the manner of {@link Number#shortValue}. @@ -549,6 +610,8 @@ public static short[] toArray(Collection collection) { * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for * the returned list is unspecified. * + *

    The returned list is serializable. + * * @param backingArray the array to back the list * @return a list view of the array */ @@ -559,8 +622,7 @@ public static List asList(short... backingArray) { return new ShortArrayAsList(backingArray); } - @GwtCompatible - private static class ShortArrayAsList extends AbstractList + private static final class ShortArrayAsList extends AbstractList implements RandomAccess, Serializable { final short[] array; final int start; @@ -593,13 +655,13 @@ public Short get(int index) { } @Override - public boolean contains(@CheckForNull Object target) { + public boolean contains(@Nullable Object target) { // Overridden to prevent a ton of boxing return (target instanceof Short) && Shorts.indexOf(array, (Short) target, start, end) != -1; } @Override - public int indexOf(@CheckForNull Object target) { + public int indexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Short) { int i = Shorts.indexOf(array, (Short) target, start, end); @@ -611,7 +673,7 @@ public int indexOf(@CheckForNull Object target) { } @Override - public int lastIndexOf(@CheckForNull Object target) { + public int lastIndexOf(@Nullable Object target) { // Overridden to prevent a ton of boxing if (target instanceof Short) { int i = Shorts.lastIndexOf(array, (Short) target, start, end); @@ -642,7 +704,7 @@ public List subList(int fromIndex, int toIndex) { } @Override - public boolean equals(@CheckForNull Object object) { + public boolean equals(@Nullable Object object) { if (object == this) { return true; } @@ -666,7 +728,7 @@ public boolean equals(@CheckForNull Object object) { public int hashCode() { int result = 1; for (int i = start; i < end; i++) { - result = 31 * result + Shorts.hashCode(array[i]); + result = 31 * result + Short.hashCode(array[i]); } return result; } @@ -685,6 +747,6 @@ short[] toShortArray() { return Arrays.copyOfRange(array, start, end); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } } diff --git a/android/guava/src/com/google/common/primitives/ShortsMethodsForWeb.java b/android/guava/src/com/google/common/primitives/ShortsMethodsForWeb.java index bb0ff103ce2f..3774e5e7b338 100644 --- a/android/guava/src/com/google/common/primitives/ShortsMethodsForWeb.java +++ b/android/guava/src/com/google/common/primitives/ShortsMethodsForWeb.java @@ -20,6 +20,5 @@ * Holder for web specializations of methods of {@code Shorts}. Intended to be empty for regular * version. */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible abstract class ShortsMethodsForWeb {} diff --git a/android/guava/src/com/google/common/primitives/SignedBytes.java b/android/guava/src/com/google/common/primitives/SignedBytes.java index 5fabaab6bd93..0204de6dcb59 100644 --- a/android/guava/src/com/google/common/primitives/SignedBytes.java +++ b/android/guava/src/com/google/common/primitives/SignedBytes.java @@ -36,7 +36,6 @@ // TODO(kevinb): how to prevent warning on UnsignedBytes when building GWT // javadoc? @GwtCompatible -@ElementTypesAreNonnullByDefault public final class SignedBytes { private SignedBytes() {} @@ -82,17 +81,15 @@ public static byte saturatedCast(long value) { * Compares the two specified {@code byte} values. The sign of the value returned is the same as * that of {@code ((Byte) a).compareTo(b)}. * - *

    Note: this method behaves identically to the JDK 7 method {@link Byte#compare}. + *

    Note: this method behaves identically to {@link Byte#compare}. * * @param a the first {@code byte} to compare * @param b the second {@code byte} to compare * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ - // TODO(kevinb): if Ints.compare etc. are ever removed, *maybe* remove this - // one too, which would leave compare methods only on the Unsigned* classes. public static int compare(byte a, byte b) { - return a - b; // safe due to restricted range + return Byte.compare(a, b); } /** @@ -181,7 +178,7 @@ private enum LexicographicalComparator implements Comparator { public int compare(byte[] left, byte[] right) { int minLength = Math.min(left.length, right.length); for (int i = 0; i < minLength; i++) { - int result = SignedBytes.compare(left[i], right[i]); + int result = Byte.compare(left[i], right[i]); if (result != 0) { return result; } diff --git a/android/guava/src/com/google/common/primitives/UnsignedBytes.java b/android/guava/src/com/google/common/primitives/UnsignedBytes.java index bf9a3066a17c..6440cdbb718f 100644 --- a/android/guava/src/com/google/common/primitives/UnsignedBytes.java +++ b/android/guava/src/com/google/common/primitives/UnsignedBytes.java @@ -17,15 +17,22 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Byte.toUnsignedInt; +import static java.security.AccessController.doPrivileged; import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; +import java.lang.reflect.Field; import java.nio.ByteOrder; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Comparator; +import java.util.Objects; import sun.misc.Unsafe; /** @@ -43,8 +50,8 @@ * @author Louis Wasserman * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible -@ElementTypesAreNonnullByDefault public final class UnsignedBytes { private UnsignedBytes() {} @@ -68,12 +75,13 @@ private UnsignedBytes() {} * Returns the value of the given byte as an integer, when treated as unsigned. That is, returns * {@code value + 256} if {@code value} is negative; {@code value} itself otherwise. * - *

    Java 8 users: use {@link Byte#toUnsignedInt(byte)} instead. + *

    Prefer {@link Byte#toUnsignedInt(byte)} instead. * * @since 6.0 */ + @InlineMe(replacement = "Byte.toUnsignedInt(value)") public static int toInt(byte value) { - return value & UNSIGNED_MASK; + return Byte.toUnsignedInt(value); } /** @@ -99,7 +107,7 @@ public static byte checkedCast(long value) { * {@code value} cast to {@code byte} otherwise */ public static byte saturatedCast(long value) { - if (value > toInt(MAX_VALUE)) { + if (value > toUnsignedInt(MAX_VALUE)) { return MAX_VALUE; // -1 } if (value < 0) { @@ -119,7 +127,7 @@ public static byte saturatedCast(long value) { * greater than {@code b}; or zero if they are equal */ public static int compare(byte a, byte b) { - return toInt(a) - toInt(b); + return toUnsignedInt(a) - toUnsignedInt(b); } /** @@ -132,9 +140,9 @@ public static int compare(byte a, byte b) { */ public static byte min(byte... array) { checkArgument(array.length > 0); - int min = toInt(array[0]); + int min = toUnsignedInt(array[0]); for (int i = 1; i < array.length; i++) { - int next = toInt(array[i]); + int next = toUnsignedInt(array[i]); if (next < min) { min = next; } @@ -152,9 +160,9 @@ public static byte min(byte... array) { */ public static byte max(byte... array) { checkArgument(array.length > 0); - int max = toInt(array[0]); + int max = toUnsignedInt(array[0]); for (int i = 1; i < array.length; i++) { - int next = toInt(array[i]); + int next = toUnsignedInt(array[i]); if (next > max) { max = next; } @@ -167,7 +175,6 @@ public static byte max(byte... array) { * * @since 13.0 */ - @Beta public static String toString(byte x) { return toString(x, 10); } @@ -182,14 +189,13 @@ public static String toString(byte x) { * and {@link Character#MAX_RADIX}. * @since 13.0 */ - @Beta public static String toString(byte x, int radix) { checkArgument( radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", radix); // Benchmarks indicate this is probably not worth optimizing. - return Integer.toString(toInt(x), radix); + return Integer.toString(toUnsignedInt(x), radix); } /** @@ -201,7 +207,6 @@ public static String toString(byte x, int radix) { * Byte#parseByte(String)}) * @since 13.0 */ - @Beta @CanIgnoreReturnValue public static byte parseUnsignedByte(String string) { return parseUnsignedByte(string, 10); @@ -219,7 +224,6 @@ public static byte parseUnsignedByte(String string) { * Byte#parseByte(String)}) * @since 13.0 */ - @Beta @CanIgnoreReturnValue public static byte parseUnsignedByte(String string, int radix) { int parse = Integer.parseInt(checkNotNull(string), radix); @@ -248,7 +252,7 @@ public static String join(String separator, byte... array) { // For pre-sizing a builder, just get the right order of magnitude StringBuilder builder = new StringBuilder(array.length * (3 + separator.length())); - builder.append(toInt(array[0])); + builder.append(toUnsignedInt(array[0])); for (int i = 1; i < array.length; i++) { builder.append(separator).append(toString(array[i])); } @@ -267,6 +271,9 @@ public static String join(String separator, byte... array) { * support only identity equality), but it is consistent with {@link * java.util.Arrays#equals(byte[], byte[])}. * + *

    Java 9+ users: Use {@link Arrays#compareUnsigned(byte[], byte[]) + * Arrays::compareUnsigned}. + * * @since 2.0 */ public static Comparator lexicographicalComparator() { @@ -286,12 +293,13 @@ static Comparator lexicographicalComparatorJavaImpl() { * available. */ @VisibleForTesting - static class LexicographicalComparatorHolder { + static final class LexicographicalComparatorHolder { static final String UNSAFE_COMPARATOR_NAME = LexicographicalComparatorHolder.class.getName() + "$UnsafeComparator"; static final Comparator BEST_COMPARATOR = getBestComparator(); + @SuppressWarnings("SunApi") // b/345822163 @VisibleForTesting enum UnsafeComparator implements Comparator { INSTANCE; @@ -322,7 +330,7 @@ enum UnsafeComparator implements Comparator { static { // fall back to the safer pure java implementation unless we're in // a 64-bit JVM with an 8-byte aligned field offset. - if (!("64".equals(System.getProperty("sun.arch.data.model")) + if (!(Objects.equals(System.getProperty("sun.arch.data.model"), "64") && (BYTE_ARRAY_BASE_OFFSET % 8) == 0 // sanity check - this should never fail && theUnsafe.arrayIndexScale(byte[].class) == 1)) { @@ -336,36 +344,34 @@ enum UnsafeComparator implements Comparator { * * @return a sun.misc.Unsafe */ - private static sun.misc.Unsafe getUnsafe() { + private static Unsafe getUnsafe() { try { - return sun.misc.Unsafe.getUnsafe(); + return Unsafe.getUnsafe(); } catch (SecurityException e) { // that's okay; try reflection instead } try { - return java.security.AccessController.doPrivileged( - new java.security.PrivilegedExceptionAction() { - @Override - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) { - return k.cast(x); + return doPrivileged( + (PrivilegedExceptionAction) + () -> { + Class k = Unsafe.class; + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } } - } - throw new NoSuchFieldError("the Unsafe"); - } - }); - } catch (java.security.PrivilegedActionException e) { + throw new NoSuchFieldError("the Unsafe"); + }); + } catch (PrivilegedActionException e) { throw new RuntimeException("Could not initialize intrinsics", e.getCause()); } } @Override public int compare(byte[] left, byte[] right) { - final int stride = 8; + int stride = 8; int minLength = Math.min(left.length, right.length); int strideLimit = minLength & ~(stride - 1); int i; @@ -379,7 +385,7 @@ public int compare(byte[] left, byte[] right) { long rw = theUnsafe.getLong(right, BYTE_ARRAY_BASE_OFFSET + (long) i); if (lw != rw) { if (BIG_ENDIAN) { - return UnsignedLongs.compare(lw, rw); + return Long.compareUnsigned(lw, rw); } /* @@ -450,6 +456,8 @@ static Comparator getBestComparator() { return lexicographicalComparatorJavaImpl(); } } + + private LexicographicalComparatorHolder() {} } private static byte flip(byte b) { diff --git a/android/guava/src/com/google/common/primitives/UnsignedInteger.java b/android/guava/src/com/google/common/primitives/UnsignedInteger.java index 0b30cef3f4c6..8400477e11ad 100644 --- a/android/guava/src/com/google/common/primitives/UnsignedInteger.java +++ b/android/guava/src/com/google/common/primitives/UnsignedInteger.java @@ -22,8 +22,9 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.math.BigInteger; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * A wrapper class for unsigned {@code int} values, supporting arithmetic operations. @@ -38,8 +39,7 @@ * @author Louis Wasserman * @since 11.0 */ -@GwtCompatible(emulated = true) -@ElementTypesAreNonnullByDefault +@GwtCompatible public final class UnsignedInteger extends Number implements Comparable { public static final UnsignedInteger ZERO = fromIntBits(0); public static final UnsignedInteger ONE = fromIntBits(1); @@ -143,6 +143,7 @@ public UnsignedInteger minus(UnsignedInteger val) { * * @since 14.0 */ + @J2ktIncompatible @GwtIncompatible // Does not truncate correctly public UnsignedInteger times(UnsignedInteger val) { // TODO(lowasser): make this GWT-compatible @@ -197,7 +198,7 @@ public float floatValue() { } /** - * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening + * Returns the value of this {@code UnsignedInteger} as a {@code double}, analogous to a widening * primitive conversion from {@code int} to {@code double}, and correctly rounded. */ @Override @@ -227,7 +228,7 @@ public int hashCode() { } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof UnsignedInteger) { UnsignedInteger other = (UnsignedInteger) obj; return value == other.value; diff --git a/android/guava/src/com/google/common/primitives/UnsignedInts.java b/android/guava/src/com/google/common/primitives/UnsignedInts.java index ec6474e20f8a..d3fd623baa7a 100644 --- a/android/guava/src/com/google/common/primitives/UnsignedInts.java +++ b/android/guava/src/com/google/common/primitives/UnsignedInts.java @@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Arrays; @@ -45,9 +44,7 @@ * @author Louis Wasserman * @since 11.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault public final class UnsignedInts { static final long INT_MASK = 0xffffffffL; @@ -61,13 +58,15 @@ static int flip(int value) { * Compares the two specified {@code int} values, treating them as unsigned values between {@code * 0} and {@code 2^32 - 1} inclusive. * - *

    Java 8 users: use {@link Integer#compareUnsigned(int, int)} instead. + *

    Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Integer#compareUnsigned(int, int)} method instead. * * @param a the first unsigned {@code int} to compare * @param b the second unsigned {@code int} to compare * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ + @SuppressWarnings("InlineMeInliner") // Integer.compare unavailable under GWT+J2CL public static int compare(int a, int b) { return Ints.compare(flip(a), flip(b)); } @@ -75,7 +74,7 @@ public static int compare(int a, int b) { /** * Returns the value of the given {@code int} as a {@code long}, when treated as unsigned. * - *

    Java 8 users: use {@link Integer#toUnsignedLong(int)} instead. + *

    Java 8+ users: use {@link Integer#toUnsignedLong(int)} instead. */ public static long toLong(int value) { return value & INT_MASK; @@ -187,6 +186,9 @@ public static String join(String separator, int... array) { * *

    The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays * support only identity equality), but it is consistent with {@link Arrays#equals(int[], int[])}. + * + *

    Java 9+ users: Use {@link Arrays#compareUnsigned(int[], int[]) + * Arrays::compareUnsigned}. */ public static Comparator lexicographicalComparator() { return LexicographicalComparator.INSTANCE; @@ -196,6 +198,8 @@ enum LexicographicalComparator implements Comparator { INSTANCE; @Override + // A call to bare "min" or "max" would resolve to our varargs method, not to any static import. + @SuppressWarnings("StaticImportPreferred") public int compare(int[] left, int[] right) { int minLength = Math.min(left.length, right.length); for (int i = 0; i < minLength; i++) { @@ -273,7 +277,7 @@ public static void sortDescending(int[] array, int fromIndex, int toIndex) { * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 32-bit * quantities. * - *

    Java 8 users: use {@link Integer#divideUnsigned(int, int)} instead. + *

    Java 8+ users: use {@link Integer#divideUnsigned(int, int)} instead. * * @param dividend the dividend (numerator) * @param divisor the divisor (denominator) @@ -287,7 +291,7 @@ public static int divide(int dividend, int divisor) { * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 32-bit * quantities. * - *

    Java 8 users: use {@link Integer#remainderUnsigned(int, int)} instead. + *

    Java 8+ users: use {@link Integer#remainderUnsigned(int, int)} instead. * * @param dividend the dividend (numerator) * @param divisor the divisor (denominator) @@ -329,7 +333,7 @@ public static int decode(String stringValue) { /** * Returns the unsigned {@code int} value represented by the given decimal string. * - *

    Java 8 users: use {@link Integer#parseUnsignedInt(String)} instead. + *

    Java 8+ users: use {@link Integer#parseUnsignedInt(String)} instead. * * @throws NumberFormatException if the string does not contain a valid unsigned {@code int} value * @throws NullPointerException if {@code s} is null (in contrast to {@link @@ -343,7 +347,7 @@ public static int parseUnsignedInt(String s) { /** * Returns the unsigned {@code int} value represented by a string with the given radix. * - *

    Java 8 users: use {@link Integer#parseUnsignedInt(String, int)} instead. + *

    Java 8+ users: use {@link Integer#parseUnsignedInt(String, int)} instead. * * @param string the string containing the unsigned integer representation to be parsed. * @param radix the radix to use while parsing {@code s}; must be between {@link @@ -367,7 +371,7 @@ public static int parseUnsignedInt(String string, int radix) { /** * Returns a string representation of x, where x is treated as unsigned. * - *

    Java 8 users: use {@link Integer#toUnsignedString(int)} instead. + *

    Java 8+ users: use {@link Integer#toUnsignedString(int)} instead. */ public static String toString(int x) { return toString(x, 10); @@ -377,7 +381,7 @@ public static String toString(int x) { * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as * unsigned. * - *

    Java 8 users: use {@link Integer#toUnsignedString(int, int)} instead. + *

    Java 8+ users: use {@link Integer#toUnsignedString(int, int)} instead. * * @param x the value to convert to a string. * @param radix the radix to use while working with {@code x} diff --git a/android/guava/src/com/google/common/primitives/UnsignedLong.java b/android/guava/src/com/google/common/primitives/UnsignedLong.java index d803634f4946..f729381bde52 100644 --- a/android/guava/src/com/google/common/primitives/UnsignedLong.java +++ b/android/guava/src/com/google/common/primitives/UnsignedLong.java @@ -19,9 +19,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import java.io.Serializable; import java.math.BigInteger; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * A wrapper class for unsigned {@code long} values, supporting arithmetic operations. @@ -37,9 +36,8 @@ * @author Colin Evans * @since 11.0 */ -@GwtCompatible(serializable = true) -@ElementTypesAreNonnullByDefault -public final class UnsignedLong extends Number implements Comparable, Serializable { +@GwtCompatible +public final class UnsignedLong extends Number implements Comparable { private static final long UNSIGNED_MASK = 0x7fffffffffffffffL; @@ -238,11 +236,11 @@ public int compareTo(UnsignedLong o) { @Override public int hashCode() { - return Longs.hashCode(value); + return Long.hashCode(value); } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof UnsignedLong) { UnsignedLong other = (UnsignedLong) obj; return value == other.value; diff --git a/android/guava/src/com/google/common/primitives/UnsignedLongs.java b/android/guava/src/com/google/common/primitives/UnsignedLongs.java index 31c51cc3464d..e8bb2566abdb 100644 --- a/android/guava/src/com/google/common/primitives/UnsignedLongs.java +++ b/android/guava/src/com/google/common/primitives/UnsignedLongs.java @@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkPositionIndexes; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.math.BigInteger; @@ -48,9 +47,7 @@ * @author Colin Evans * @since 10.0 */ -@Beta @GwtCompatible -@ElementTypesAreNonnullByDefault public final class UnsignedLongs { private UnsignedLongs() {} @@ -69,13 +66,15 @@ private static long flip(long a) { * Compares the two specified {@code long} values, treating them as unsigned values between {@code * 0} and {@code 2^64 - 1} inclusive. * - *

    Java 8 users: use {@link Long#compareUnsigned(long, long)} instead. + *

    Note: this method is now unnecessary and should be treated as deprecated; use the + * equivalent {@link Long#compareUnsigned(long, long)} method instead. * * @param a the first unsigned {@code long} to compare * @param b the second unsigned {@code long} to compare * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ + @SuppressWarnings("InlineMeInliner") // Integer.compare unavailable under GWT+J2CL public static int compare(long a, long b) { return Longs.compare(flip(a), flip(b)); } @@ -153,6 +152,9 @@ public static String join(String separator, long... array) { *

    The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays * support only identity equality), but it is consistent with {@link Arrays#equals(long[], * long[])}. + * + *

    Java 9+ users: Use {@link Arrays#compareUnsigned(long[], long[]) + * Arrays::compareUnsigned}. */ public static Comparator lexicographicalComparator() { return LexicographicalComparator.INSTANCE; @@ -239,7 +241,7 @@ public static void sortDescending(long[] array, int fromIndex, int toIndex) { * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit * quantities. * - *

    Java 8 users: use {@link Long#divideUnsigned(long, long)} instead. + *

    Java 8+ users: use {@link Long#divideUnsigned(long, long)} instead. * * @param dividend the dividend (numerator) * @param divisor the divisor (denominator) @@ -274,7 +276,7 @@ public static long divide(long dividend, long divisor) { * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 64-bit * quantities. * - *

    Java 8 users: use {@link Long#remainderUnsigned(long, long)} instead. + *

    Java 8+ users: use {@link Long#remainderUnsigned(long, long)} instead. * * @param dividend the dividend (numerator) * @param divisor the divisor (denominator) @@ -309,7 +311,7 @@ public static long remainder(long dividend, long divisor) { /** * Returns the unsigned {@code long} value represented by the given decimal string. * - *

    Java 8 users: use {@link Long#parseUnsignedLong(String)} instead. + *

    Java 8+ users: use {@link Long#parseUnsignedLong(String)} instead. * * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} * value @@ -324,7 +326,7 @@ public static long parseUnsignedLong(String string) { /** * Returns the unsigned {@code long} value represented by a string with the given radix. * - *

    Java 8 users: use {@link Long#parseUnsignedLong(String, int)} instead. + *

    Java 8+ users: use {@link Long#parseUnsignedLong(String, int)} instead. * * @param string the string containing the unsigned {@code long} representation to be parsed. * @param radix the radix to use while parsing {@code string} @@ -403,7 +405,7 @@ private ParseOverflowDetection() {} static final int[] maxSafeDigits = new int[Character.MAX_RADIX + 1]; static { - BigInteger overflow = new BigInteger("10000000000000000", 16); + BigInteger overflow = BigInteger.ONE.shiftLeft(64); for (int i = Character.MIN_RADIX; i <= Character.MAX_RADIX; i++) { maxValueDivs[i] = divide(MAX_VALUE, i); maxValueMods[i] = (int) remainder(MAX_VALUE, i); @@ -426,7 +428,7 @@ static boolean overflowInParse(long current, int digit, int radix) { return true; } // current == maxValueDivs[radix] - return (digit > maxValueMods[radix]); + return digit > maxValueMods[radix]; } // current < 0: high bit is set @@ -437,7 +439,7 @@ static boolean overflowInParse(long current, int digit, int radix) { /** * Returns a string representation of x, where x is treated as unsigned. * - *

    Java 8 users: use {@link Long#toUnsignedString(long)} instead. + *

    Java 8+ users: use {@link Long#toUnsignedString(long)} instead. */ public static String toString(long x) { return toString(x, 10); @@ -447,7 +449,7 @@ public static String toString(long x) { * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as * unsigned. * - *

    Java 8 users: use {@link Long#toUnsignedString(long, int)} instead. + *

    Java 8+ users: use {@link Long#toUnsignedString(long, int)} instead. * * @param x the value to convert to a string. * @param radix the radix to use while working with {@code x} diff --git a/android/guava/src/com/google/common/primitives/package-info.java b/android/guava/src/com/google/common/primitives/package-info.java index 9504fa79be1a..1262afce6d72 100644 --- a/android/guava/src/com/google/common/primitives/package-info.java +++ b/android/guava/src/com/google/common/primitives/package-info.java @@ -13,10 +13,10 @@ */ /** - * Static utilities for working with the eight primitive types and {@code void}, and value types for - * treating them as unsigned. + * Static utilities for the eight primitive types and {@code void}, and value types for treating + * them as unsigned or storing them in immutable arrays. * - *

    This package is a part of the open-source Guava + *

    This package is a part of the open-source Guava * library. * *

    See the Guava User Guide article on Contents * - *

    General static utilities

    + *

    Value types

    * *
      - *
    • {@link com.google.common.primitives.Primitives} + *
    • {@link ImmutableDoubleArray} + *
    • {@link ImmutableIntArray} + *
    • {@link ImmutableLongArray} + *
    • {@link UnsignedInteger} + *
    • {@link UnsignedLong} *
    * *

    Per-type static utilities

    * *
      - *
    • {@link com.google.common.primitives.Booleans} - *
    • {@link com.google.common.primitives.Bytes} + *
    • {@link Booleans} + *
    • {@link Bytes} *
        - *
      • {@link com.google.common.primitives.SignedBytes} - *
      • {@link com.google.common.primitives.UnsignedBytes} + *
      • {@link SignedBytes} + *
      • {@link UnsignedBytes} *
      - *
    • {@link com.google.common.primitives.Chars} - *
    • {@link com.google.common.primitives.Doubles} - *
    • {@link com.google.common.primitives.Floats} - *
    • {@link com.google.common.primitives.Ints} + *
    • {@link Chars} + *
    • {@link Doubles} + *
    • {@link Floats} + *
    • {@link Ints} *
        - *
      • {@link com.google.common.primitives.UnsignedInts} + *
      • {@link UnsignedInts} *
      - *
    • {@link com.google.common.primitives.Longs} + *
    • {@link Longs} *
        - *
      • {@link com.google.common.primitives.UnsignedLongs} + *
      • {@link UnsignedLongs} *
      - *
    • {@link com.google.common.primitives.Shorts} + *
    • {@link Shorts} *
    * - *

    Value types

    + *

    General static utilities

    * *
      - *
    • {@link com.google.common.primitives.UnsignedInteger} - *
    • {@link com.google.common.primitives.UnsignedLong} + *
    • {@link Primitives} *
    */ -@ParametersAreNonnullByDefault @CheckReturnValue +@NullMarked package com.google.common.primitives; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/reflect/AbstractInvocationHandler.java b/android/guava/src/com/google/common/reflect/AbstractInvocationHandler.java index 4cb481ab8a08..622596817bdd 100644 --- a/android/guava/src/com/google/common/reflect/AbstractInvocationHandler.java +++ b/android/guava/src/com/google/common/reflect/AbstractInvocationHandler.java @@ -14,13 +14,11 @@ package com.google.common.reflect; -import com.google.common.annotations.Beta; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Abstract implementation of {@link InvocationHandler} that handles {@link Object#equals}, {@link @@ -39,10 +37,9 @@ * @author Ben Yu * @since 12.0 */ -@Beta -// TODO(cpovirk): after adding @Nullable below -@ElementTypesAreNonnullByDefault public abstract class AbstractInvocationHandler implements InvocationHandler { + /** Constructor for use by subclasses. */ + public AbstractInvocationHandler() {} private static final Object[] NO_ARGS = {}; @@ -62,9 +59,8 @@ public abstract class AbstractInvocationHandler implements InvocationHandler { * */ @Override - @CheckForNull - public final Object invoke(Object proxy, Method method, @CheckForNull @Nullable Object[] args) - throws Throwable { + public final @Nullable Object invoke( + Object proxy, Method method, @Nullable Object @Nullable [] args) throws Throwable { if (args == null) { args = NO_ARGS; } @@ -98,9 +94,8 @@ public final Object invoke(Object proxy, Method method, @CheckForNull @Nullable *

    Unlike {@link #invoke}, {@code args} will never be null. When the method has no parameter, * an empty array is passed in. */ - @CheckForNull - protected abstract Object handleInvocation( - Object proxy, Method method, /* TODO(cpovirk): @Nullable */ Object[] args) throws Throwable; + protected abstract @Nullable Object handleInvocation( + Object proxy, Method method, @Nullable Object[] args) throws Throwable; /** * By default delegates to {@link Object#equals} so instances are only equal if they are @@ -114,7 +109,7 @@ protected abstract Object handleInvocation( *

    Subclasses can override this method to provide custom equality. */ @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { return super.equals(obj); } diff --git a/android/guava/src/com/google/common/reflect/ClassPath.java b/android/guava/src/com/google/common/reflect/ClassPath.java index de693da5f218..4e471e07702c 100644 --- a/android/guava/src/com/google/common/reflect/ClassPath.java +++ b/android/guava/src/com/google/common/reflect/ClassPath.java @@ -20,16 +20,13 @@ import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR; import static java.util.logging.Level.WARNING; -import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.CharMatcher; -import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; import com.google.common.io.ByteSource; import com.google.common.io.CharSource; import com.google.common.io.Resources; @@ -51,7 +48,7 @@ import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Logger; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Scans the source of a {@link ClassLoader} and finds all loadable classes and resources. @@ -92,8 +89,6 @@ * @author Ben Yu * @since 14.0 */ -@Beta -@ElementTypesAreNonnullByDefault public final class ClassPath { private static final Logger logger = Logger.getLogger(ClassPath.class.getName()); @@ -167,13 +162,7 @@ public ImmutableSet getAllClasses() { public ImmutableSet getTopLevelClasses() { return FluentIterable.from(resources) .filter(ClassInfo.class) - .filter( - new Predicate() { - @Override - public boolean apply(ClassInfo info) { - return info.isTopLevel(); - } - }) + .filter(ClassInfo::isTopLevel) .toSet(); } @@ -211,7 +200,6 @@ public ImmutableSet getTopLevelClassesRecursive(String packageName) { * * @since 14.0 */ - @Beta public static class ResourceInfo { private final File file; private final String resourceName; @@ -287,7 +275,7 @@ public int hashCode() { } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof ResourceInfo) { ResourceInfo that = (ResourceInfo) obj; return resourceName.equals(that.resourceName) && loader == that.loader; @@ -307,7 +295,6 @@ public String toString() { * * @since 14.0 */ - @Beta public static final class ClassInfo extends ResourceInfo { private final String className; @@ -561,7 +548,7 @@ private void scanDirectory( } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof LocationInfo) { LocationInfo that = (LocationInfo) obj; return home.equals(that.home) && classloader.equals(that.classloader); @@ -588,8 +575,7 @@ public String toString() { * an empty set will be returned. */ @VisibleForTesting - static ImmutableSet getClassPathFromManifest( - File jarFile, @CheckForNull Manifest manifest) { + static ImmutableSet getClassPathFromManifest(File jarFile, @Nullable Manifest manifest) { if (manifest == null) { return ImmutableSet.of(); } @@ -616,7 +602,7 @@ static ImmutableSet getClassPathFromManifest( @VisibleForTesting static ImmutableMap getClassPathEntries(ClassLoader classloader) { - LinkedHashMap entries = Maps.newLinkedHashMap(); + LinkedHashMap entries = new LinkedHashMap<>(); // Search parent first, since it's the order ClassLoader#loadClass() uses. ClassLoader parent = classloader.getParent(); if (parent != null) { diff --git a/android/guava/src/com/google/common/reflect/Element.java b/android/guava/src/com/google/common/reflect/Element.java deleted file mode 100644 index 34f9e8057164..000000000000 --- a/android/guava/src/com/google/common/reflect/Element.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2012 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package com.google.common.reflect; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import javax.annotation.CheckForNull; - -/** - * Represents either a {@link Field}, a {@link Method} or a {@link Constructor}. Provides - * convenience methods such as {@link #isPublic} and {@link #isPackagePrivate}. - * - * @author Ben Yu - */ -@ElementTypesAreNonnullByDefault -class Element extends AccessibleObject implements Member { - - private final AccessibleObject accessibleObject; - private final Member member; - - Element(M member) { - checkNotNull(member); - this.accessibleObject = member; - this.member = member; - } - - public TypeToken getOwnerType() { - return TypeToken.of(getDeclaringClass()); - } - - @Override - public final boolean isAnnotationPresent(Class annotationClass) { - return accessibleObject.isAnnotationPresent(annotationClass); - } - - @Override - @CheckForNull - public final A getAnnotation(Class annotationClass) { - return accessibleObject.getAnnotation(annotationClass); - } - - @Override - public final Annotation[] getAnnotations() { - return accessibleObject.getAnnotations(); - } - - @Override - public final Annotation[] getDeclaredAnnotations() { - return accessibleObject.getDeclaredAnnotations(); - } - - @Override - public final void setAccessible(boolean flag) throws SecurityException { - accessibleObject.setAccessible(flag); - } - - @Override - public final boolean isAccessible() { - return accessibleObject.isAccessible(); - } - - @Override - public Class getDeclaringClass() { - return member.getDeclaringClass(); - } - - @Override - public final String getName() { - return member.getName(); - } - - @Override - public final int getModifiers() { - return member.getModifiers(); - } - - @Override - public final boolean isSynthetic() { - return member.isSynthetic(); - } - - /** Returns true if the element is public. */ - public final boolean isPublic() { - return Modifier.isPublic(getModifiers()); - } - - /** Returns true if the element is protected. */ - public final boolean isProtected() { - return Modifier.isProtected(getModifiers()); - } - - /** Returns true if the element is package-private. */ - public final boolean isPackagePrivate() { - return !isPrivate() && !isPublic() && !isProtected(); - } - - /** Returns true if the element is private. */ - public final boolean isPrivate() { - return Modifier.isPrivate(getModifiers()); - } - - /** Returns true if the element is static. */ - public final boolean isStatic() { - return Modifier.isStatic(getModifiers()); - } - - /** - * Returns {@code true} if this method is final, per {@code Modifier.isFinal(getModifiers())}. - * - *

    Note that a method may still be effectively "final", or non-overridable when it has no - * {@code final} keyword. For example, it could be private, or it could be declared by a final - * class. To tell whether a method is overridable, use {@link Invokable#isOverridable}. - */ - public final boolean isFinal() { - return Modifier.isFinal(getModifiers()); - } - - /** Returns true if the method is abstract. */ - public final boolean isAbstract() { - return Modifier.isAbstract(getModifiers()); - } - - /** Returns true if the element is native. */ - public final boolean isNative() { - return Modifier.isNative(getModifiers()); - } - - /** Returns true if the method is synchronized. */ - public final boolean isSynchronized() { - return Modifier.isSynchronized(getModifiers()); - } - - /** Returns true if the field is volatile. */ - final boolean isVolatile() { - return Modifier.isVolatile(getModifiers()); - } - - /** Returns true if the field is transient. */ - final boolean isTransient() { - return Modifier.isTransient(getModifiers()); - } - - @Override - public boolean equals(@CheckForNull Object obj) { - if (obj instanceof Element) { - Element that = (Element) obj; - return getOwnerType().equals(that.getOwnerType()) && member.equals(that.member); - } - return false; - } - - @Override - public int hashCode() { - return member.hashCode(); - } - - @Override - public String toString() { - return member.toString(); - } -} diff --git a/android/guava/src/com/google/common/reflect/ElementTypesAreNonnullByDefault.java b/android/guava/src/com/google/common/reflect/ElementTypesAreNonnullByDefault.java deleted file mode 100755 index 0e8ef3cb7bcb..000000000000 --- a/android/guava/src/com/google/common/reflect/ElementTypesAreNonnullByDefault.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.reflect; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import com.google.common.annotations.GwtCompatible; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; - -/** - * Marks all "top-level" types as non-null in a way that is recognized by Kotlin. Note that this - * unfortunately includes type-variable usages, so we also provide {@link ParametricNullness} to - * "undo" it as best we can. - */ -@GwtCompatible -@Retention(RUNTIME) -@Target(TYPE) -@TypeQualifierDefault({FIELD, METHOD, PARAMETER}) -@Nonnull -@interface ElementTypesAreNonnullByDefault {} diff --git a/android/guava/src/com/google/common/reflect/IgnoreJRERequirement.java b/android/guava/src/com/google/common/reflect/IgnoreJRERequirement.java new file mode 100644 index 000000000000..d73dc4fd479f --- /dev/null +++ b/android/guava/src/com/google/common/reflect/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) +@interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/reflect/ImmutableTypeToInstanceMap.java b/android/guava/src/com/google/common/reflect/ImmutableTypeToInstanceMap.java index 7fad5ded3bdf..63047bc392d4 100644 --- a/android/guava/src/com/google/common/reflect/ImmutableTypeToInstanceMap.java +++ b/android/guava/src/com/google/common/reflect/ImmutableTypeToInstanceMap.java @@ -14,12 +14,12 @@ package com.google.common.reflect; -import com.google.common.annotations.Beta; import com.google.common.collect.ForwardingMap; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import java.util.Map; +import org.jspecify.annotations.Nullable; /** * A type-to-instance map backed by an {@link ImmutableMap}. See also {@link @@ -28,37 +28,35 @@ * @author Ben Yu * @since 13.0 */ -@Beta public final class ImmutableTypeToInstanceMap extends ForwardingMap, B> implements TypeToInstanceMap { /** Returns an empty type to instance map. */ public static ImmutableTypeToInstanceMap of() { - return new ImmutableTypeToInstanceMap(ImmutableMap., B>of()); + return new ImmutableTypeToInstanceMap<>(ImmutableMap., B>of()); } /** Returns a new builder. */ public static Builder builder() { - return new Builder(); + return new Builder<>(); } /** * A builder for creating immutable type-to-instance maps. Example: * - *

    {@code
    +   * {@snippet :
        * static final ImmutableTypeToInstanceMap> HANDLERS =
        *     ImmutableTypeToInstanceMap.>builder()
        *         .put(new TypeToken>() {}, new FooHandler())
        *         .put(new TypeToken>() {}, new SubBarHandler())
        *         .build();
    -   * }
    + * } * *

    After invoking {@link #build()} it is still possible to add more entries and build again. * Thus each map generated by this builder will be a superset of any map generated before it. * * @since 13.0 */ - @Beta public static final class Builder { private final ImmutableMap.Builder, B> mapBuilder = ImmutableMap.builder(); @@ -91,7 +89,7 @@ public Builder put(TypeToken key, T value) { * @throws IllegalArgumentException if duplicate keys were added */ public ImmutableTypeToInstanceMap build() { - return new ImmutableTypeToInstanceMap(mapBuilder.build()); + return new ImmutableTypeToInstanceMap<>(mapBuilder.buildOrThrow()); } } @@ -102,12 +100,12 @@ private ImmutableTypeToInstanceMap(ImmutableMap, B> deleg } @Override - public T getInstance(TypeToken type) { + public @Nullable T getInstance(TypeToken type) { return trustedGet(type.rejectTypeVariables()); } @Override - public T getInstance(Class type) { + public @Nullable T getInstance(Class type) { return trustedGet(TypeToken.of(type)); } @@ -121,7 +119,7 @@ public T getInstance(Class type) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public T putInstance(TypeToken type, T value) { + public @Nullable T putInstance(TypeToken type, T value) { throw new UnsupportedOperationException(); } @@ -135,7 +133,7 @@ public T putInstance(TypeToken type, T value) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public T putInstance(Class type, T value) { + public @Nullable T putInstance(Class type, T value) { throw new UnsupportedOperationException(); } @@ -149,7 +147,7 @@ public T putInstance(Class type, T value) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public B put(TypeToken key, B value) { + public @Nullable B put(TypeToken key, B value) { throw new UnsupportedOperationException(); } @@ -172,7 +170,7 @@ protected Map, B> delegate() { } @SuppressWarnings("unchecked") // value could not get in if not a T - private T trustedGet(TypeToken type) { + private @Nullable T trustedGet(TypeToken type) { return (T) delegate.get(type); } } diff --git a/android/guava/src/com/google/common/reflect/Invokable.java b/android/guava/src/com/google/common/reflect/Invokable.java index a7379f78af52..f4efa102e02f 100644 --- a/android/guava/src/com/google/common/reflect/Invokable.java +++ b/android/guava/src/com/google/common/reflect/Invokable.java @@ -16,13 +16,12 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.annotation.Annotation; import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; -import java.lang.reflect.GenericDeclaration; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -30,8 +29,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Arrays; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Wrapper around either a {@link Method} or a {@link Constructor}. Convenience API is provided to @@ -42,25 +40,34 @@ * will resolve the type parameters of the method or constructor in the context of the owner type, * which may be a subtype of the declaring class. For example: * - *

    {@code
    + * {@snippet :
      * Method getMethod = List.class.getMethod("get", int.class);
      * Invokable, ?> invokable = new TypeToken>() {}.method(getMethod);
      * assertEquals(TypeToken.of(String.class), invokable.getReturnType()); // Not Object.class!
      * assertEquals(new TypeToken>() {}, invokable.getOwnerType());
    - * }
    + * } + * + *

    Note: earlier versions of this class inherited from {@link + * java.lang.reflect.AccessibleObject AccessibleObject} and {@link + * java.lang.reflect.GenericDeclaration GenericDeclaration}. Since version 31.0 that is no longer + * the case. However, most methods from those types are present with the same signature in this + * class. * * @param the type that owns this method or constructor. * @param the return type of (or supertype thereof) the method or the declaring type of the * constructor. * @author Ben Yu - * @since 14.0 + * @since 14.0 (no longer implements {@link AccessibleObject} or {@code GenericDeclaration} since + * 31.0) */ -@Beta -@ElementTypesAreNonnullByDefault -public abstract class Invokable extends Element implements GenericDeclaration { +public abstract class Invokable implements AnnotatedElement, Member { + private final AccessibleObject accessibleObject; + private final Member member; Invokable(M member) { - super(member); + checkNotNull(member); + this.accessibleObject = member; + this.member = member; } /** Returns {@link Invokable} of {@code method}. */ @@ -73,6 +80,151 @@ public static Invokable from(Constructor constructor) { return new ConstructorInvokable(constructor); } + @Override + public final boolean isAnnotationPresent(Class annotationClass) { + return accessibleObject.isAnnotationPresent(annotationClass); + } + + @Override + public final @Nullable A getAnnotation(Class annotationClass) { + return accessibleObject.getAnnotation(annotationClass); + } + + @Override + public final Annotation[] getAnnotations() { + return accessibleObject.getAnnotations(); + } + + @Override + public final Annotation[] getDeclaredAnnotations() { + return accessibleObject.getDeclaredAnnotations(); + } + + // We ought to be able to implement GenericDeclaration instead its parent AnnotatedElement. + // That would give us this method declaration. But for some reason, implementing + // GenericDeclaration leads to weird errors in Android tests: + // IncompatibleClassChangeError: interface not implemented + /** See {@link java.lang.reflect.GenericDeclaration#getTypeParameters()}. */ + public abstract TypeVariable[] getTypeParameters(); + + /** See {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)}. */ + public final void setAccessible(boolean flag) { + accessibleObject.setAccessible(flag); + } + + /** See {@link java.lang.reflect.AccessibleObject#trySetAccessible()}. */ + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception + public final boolean trySetAccessible() { + // We can't call accessibleObject.trySetAccessible since that was added in Java 9 and this code + // should work on Java 8. So we emulate it this way. + try { + accessibleObject.setAccessible(true); + return true; + } catch (Exception e) { // sneaky checked exception + return false; + } + } + + /** See {@link java.lang.reflect.AccessibleObject#isAccessible()}. */ + public final boolean isAccessible() { + return accessibleObject.isAccessible(); + } + + @Override + public final String getName() { + return member.getName(); + } + + @Override + public final int getModifiers() { + return member.getModifiers(); + } + + @Override + public final boolean isSynthetic() { + return member.isSynthetic(); + } + + /** Returns true if the element is public. */ + public final boolean isPublic() { + return Modifier.isPublic(getModifiers()); + } + + /** Returns true if the element is protected. */ + public final boolean isProtected() { + return Modifier.isProtected(getModifiers()); + } + + /** Returns true if the element is package-private. */ + public final boolean isPackagePrivate() { + return !isPrivate() && !isPublic() && !isProtected(); + } + + /** Returns true if the element is private. */ + public final boolean isPrivate() { + return Modifier.isPrivate(getModifiers()); + } + + /** Returns true if the element is static. */ + public final boolean isStatic() { + return Modifier.isStatic(getModifiers()); + } + + /** + * Returns {@code true} if this method is final, per {@code Modifier.isFinal(getModifiers())}. + * + *

    Note that a method may still be effectively "final", or non-overridable when it has no + * {@code final} keyword. For example, it could be private, or it could be declared by a final + * class. To tell whether a method is overridable, use {@link Invokable#isOverridable}. + */ + public final boolean isFinal() { + return Modifier.isFinal(getModifiers()); + } + + /** Returns true if the method is abstract. */ + public final boolean isAbstract() { + return Modifier.isAbstract(getModifiers()); + } + + /** Returns true if the element is native. */ + public final boolean isNative() { + return Modifier.isNative(getModifiers()); + } + + /** Returns true if the method is synchronized. */ + public final boolean isSynchronized() { + return Modifier.isSynchronized(getModifiers()); + } + + /** Returns true if the field is volatile. */ + final boolean isVolatile() { + return Modifier.isVolatile(getModifiers()); + } + + /** Returns true if the field is transient. */ + final boolean isTransient() { + return Modifier.isTransient(getModifiers()); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof Invokable) { + Invokable that = (Invokable) obj; + return getOwnerType().equals(that.getOwnerType()) && member.equals(that.member); + } + return false; + } + + @Override + public int hashCode() { + return member.hashCode(); + } + + @Override + public String toString() { + return member.toString(); + } + /** * Returns {@code true} if this is an overridable method. Constructors, private, static or final * methods, or methods declared by final classes are not overridable. @@ -98,8 +250,7 @@ public static Invokable from(Constructor constructor) { // All subclasses are owned by us and we'll make sure to get the R type right, including nullness. @SuppressWarnings({"unchecked", "nullness"}) @CanIgnoreReturnValue - @CheckForNull - public final R invoke(@CheckForNull T receiver, @Nullable Object... args) + public final @Nullable R invoke(@Nullable T receiver, @Nullable Object... args) throws InvocationTargetException, IllegalAccessException { return (R) invokeInternal(receiver, checkNotNull(args)); } @@ -116,12 +267,17 @@ public final TypeToken getReturnType() { * of a non-static inner class, unlike {@link Constructor#getParameterTypes}, the hidden {@code * this} parameter of the enclosing class is excluded from the returned parameters. */ + @IgnoreJRERequirement public final ImmutableList getParameters() { Type[] parameterTypes = getGenericParameterTypes(); Annotation[][] annotations = getParameterAnnotations(); + @Nullable Object[] annotatedTypes = + new Object[parameterTypes.length]; ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < parameterTypes.length; i++) { - builder.add(new Parameter(this, i, TypeToken.of(parameterTypes[i]), annotations[i])); + builder.add( + new Parameter( + this, i, TypeToken.of(parameterTypes[i]), annotations[i], annotatedTypes[i])); } return builder.build(); } @@ -142,10 +298,10 @@ public final ImmutableList> getExceptionTypes() { /** * Explicitly specifies the return type of this {@code Invokable}. For example: * - *

    {@code
    +   * {@snippet :
        * Method factoryMethod = Person.class.getMethod("create");
        * Invokable factory = Invokable.of(getNameMethod).returning(Person.class);
    -   * }
    + * } */ public final Invokable returning(Class returnType) { return returning(TypeToken.of(returnType)); @@ -165,19 +321,17 @@ public final Invokable returning(TypeToken returnType) @SuppressWarnings("unchecked") // The declaring class is T's raw class, or one of its supertypes. @Override public final Class getDeclaringClass() { - return (Class) super.getDeclaringClass(); + return (Class) member.getDeclaringClass(); } /** Returns the type of {@code T}. */ // Overridden in TypeToken#method() and TypeToken#constructor() @SuppressWarnings("unchecked") // The declaring class is T. - @Override public TypeToken getOwnerType() { return (TypeToken) TypeToken.of(getDeclaringClass()); } - @CheckForNull - abstract Object invokeInternal(@CheckForNull Object receiver, @Nullable Object[] args) + abstract @Nullable Object invokeInternal(@Nullable Object receiver, @Nullable Object[] args) throws InvocationTargetException, IllegalAccessException; abstract Type[] getGenericParameterTypes(); @@ -199,8 +353,7 @@ static class MethodInvokable extends Invokable { } @Override - @CheckForNull - final Object invokeInternal(@CheckForNull Object receiver, @Nullable Object[] args) + final @Nullable Object invokeInternal(@Nullable Object receiver, @Nullable Object[] args) throws InvocationTargetException, IllegalAccessException { return method.invoke(receiver, args); } @@ -254,7 +407,7 @@ static class ConstructorInvokable extends Invokable { } @Override - final Object invokeInternal(@CheckForNull Object receiver, @Nullable Object[] args) + final Object invokeInternal(@Nullable Object receiver, @Nullable Object[] args) throws InvocationTargetException, IllegalAccessException { try { return constructor.newInstance(args); @@ -355,4 +508,15 @@ private boolean mayNeedHiddenThis() { } } } + + private static final boolean ANNOTATED_TYPE_EXISTS = initAnnotatedTypeExists(); + + private static boolean initAnnotatedTypeExists() { + try { + Class.forName("java.lang.reflect.AnnotatedType"); + } catch (ClassNotFoundException e) { + return false; + } + return true; + } } diff --git a/android/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java b/android/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java index dccd2983b32c..7d355cc6677c 100644 --- a/android/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java +++ b/android/guava/src/com/google/common/reflect/MutableTypeToInstanceMap.java @@ -16,19 +16,18 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; -import com.google.common.base.Function; import com.google.common.collect.ForwardingMap; import com.google.common.collect.ForwardingMapEntry; import com.google.common.collect.ForwardingSet; import com.google.common.collect.Iterators; -import com.google.common.collect.Maps; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A mutable type-to-instance map. See also {@link ImmutableTypeToInstanceMap}. @@ -36,36 +35,35 @@ * @author Ben Yu * @since 13.0 */ -@Beta -public final class MutableTypeToInstanceMap extends ForwardingMap, B> - implements TypeToInstanceMap { +public final class MutableTypeToInstanceMap + extends ForwardingMap, B> implements TypeToInstanceMap { + /** Creates a new map. */ + public MutableTypeToInstanceMap() {} - private final Map, B> backingMap = Maps.newHashMap(); + private final Map, B> backingMap = new HashMap<>(); @Override - @NullableDecl - public T getInstance(Class type) { + public @Nullable T getInstance(Class type) { return trustedGet(TypeToken.of(type)); } @Override - @NullableDecl - public T getInstance(TypeToken type) { + public @Nullable T getInstance(TypeToken type) { return trustedGet(type.rejectTypeVariables()); } @Override @CanIgnoreReturnValue - @NullableDecl - public T putInstance(Class type, @NullableDecl T value) { + public @Nullable T putInstance( + Class<@NonNull T> type, @ParametricNullness T value) { return trustedPut(TypeToken.of(type), value); } @Override @CanIgnoreReturnValue - @NullableDecl - public T putInstance(TypeToken type, @NullableDecl T value) { - return trustedPut(type.rejectTypeVariables(), value); + public @Nullable T putInstance( + TypeToken<@NonNull T> type, @ParametricNullness T value) { + return this.trustedPut(type.rejectTypeVariables(), value); } /** @@ -78,7 +76,7 @@ public T putInstance(TypeToken type, @NullableDecl T value) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public B put(TypeToken key, B value) { + public @Nullable B put(TypeToken key, @ParametricNullness B value) { throw new UnsupportedOperationException("Please use putInstance() instead."); } @@ -91,37 +89,38 @@ public B put(TypeToken key, B value) { @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") - public void putAll(Map, ? extends B> map) { + public void putAll(Map, ? extends B> map) { throw new UnsupportedOperationException("Please use putInstance() instead."); } @Override - public Set, B>> entrySet() { + public Set, B>> entrySet() { return UnmodifiableEntry.transformEntries(super.entrySet()); } @Override - protected Map, B> delegate() { + protected Map, B> delegate() { return backingMap; } @SuppressWarnings("unchecked") // value could not get in if not a T - @NullableDecl - private T trustedPut(TypeToken type, @NullableDecl T value) { + private @Nullable T trustedPut( + TypeToken<@NonNull T> type, @ParametricNullness T value) { return (T) backingMap.put(type, value); } @SuppressWarnings("unchecked") // value could not get in if not a T - @NullableDecl - private T trustedGet(TypeToken type) { + private @Nullable T trustedGet(TypeToken type) { return (T) backingMap.get(type); } - private static final class UnmodifiableEntry extends ForwardingMapEntry { + private static final class UnmodifiableEntry + extends ForwardingMapEntry { private final Entry delegate; - static Set> transformEntries(final Set> entries) { + static Set> transformEntries( + Set> entries) { return new ForwardingSet>() { @Override protected Set> delegate() { @@ -135,28 +134,31 @@ public Iterator> iterator() { @Override public Object[] toArray() { - return standardToArray(); + /* + * standardToArray returns `@Nullable Object[]` rather than `Object[]` but only because it + * can be used with collections that may contain null. This collection is a collection of + * non-null Entry objects (Entry objects that might contain null values but are not + * themselves null), so we can treat it as a plain `Object[]`. + */ + @SuppressWarnings("nullness") + Object[] result = standardToArray(); + return result; } @Override - public T[] toArray(T[] array) { + @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations + public T[] toArray(T[] array) { return standardToArray(array); } }; } - private static Iterator> transformEntries(Iterator> entries) { - return Iterators.transform( - entries, - new Function, Entry>() { - @Override - public Entry apply(Entry entry) { - return new UnmodifiableEntry<>(entry); - } - }); + private static Iterator> transformEntries( + Iterator> entries) { + return Iterators.transform(entries, UnmodifiableEntry::new); } - private UnmodifiableEntry(java.util.Map.Entry delegate) { + private UnmodifiableEntry(Entry delegate) { this.delegate = checkNotNull(delegate); } @@ -166,7 +168,8 @@ protected Entry delegate() { } @Override - public V setValue(V value) { + @ParametricNullness + public V setValue(@ParametricNullness V value) { throw new UnsupportedOperationException(); } } diff --git a/android/guava/src/com/google/common/reflect/Parameter.java b/android/guava/src/com/google/common/reflect/Parameter.java index 85027b7199f4..8f4bca276225 100644 --- a/android/guava/src/com/google/common/reflect/Parameter.java +++ b/android/guava/src/com/google/common/reflect/Parameter.java @@ -16,12 +16,11 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Represents a method or constructor parameter. @@ -29,8 +28,6 @@ * @author Ben Yu * @since 14.0 */ -@Beta -@ElementTypesAreNonnullByDefault public final class Parameter implements AnnotatedElement { private final Invokable declaration; @@ -38,12 +35,26 @@ public final class Parameter implements AnnotatedElement { private final TypeToken type; private final ImmutableList annotations; + /** + * An {@code AnnotatedType} instance, or {@code null} under Android VMs (possible only when using + * the Android flavor of Guava). The field is declared with a type of {@code Object} to avoid + * compatibility problems on Android VMs. The corresponding accessor method, however, can have the + * more specific return type as long as users are careful to guard calls to it with version checks + * or reflection: Android VMs ignore the types of elements that aren't used. + */ + private final @Nullable Object annotatedType; + Parameter( - Invokable declaration, int position, TypeToken type, Annotation[] annotations) { + Invokable declaration, + int position, + TypeToken type, + Annotation[] annotations, + @Nullable Object annotatedType) { this.declaration = declaration; this.position = position; this.type = type; this.annotations = ImmutableList.copyOf(annotations); + this.annotatedType = annotatedType; } /** Returns the type of the parameter. */ @@ -62,8 +73,7 @@ public boolean isAnnotationPresent(Class annotationType) { } @Override - @CheckForNull - public A getAnnotation(Class annotationType) { + public @Nullable A getAnnotation(Class annotationType) { checkNotNull(annotationType); for (Annotation annotation : annotations) { if (annotationType.isInstance(annotation)) { @@ -78,35 +88,45 @@ public Annotation[] getAnnotations() { return getDeclaredAnnotations(); } - /** @since 18.0 */ - // @Override on JDK8 + /** + * @since 18.0 + */ + @Override public A[] getAnnotationsByType(Class annotationType) { return getDeclaredAnnotationsByType(annotationType); } - /** @since 18.0 */ - // @Override on JDK8 + /** + * @since 18.0 + */ @Override public Annotation[] getDeclaredAnnotations() { return annotations.toArray(new Annotation[0]); } - /** @since 18.0 */ - // @Override on JDK8 - @CheckForNull - public A getDeclaredAnnotation(Class annotationType) { + /** + * @since 18.0 + */ + @Override + public @Nullable A getDeclaredAnnotation(Class annotationType) { checkNotNull(annotationType); return FluentIterable.from(annotations).filter(annotationType).first().orNull(); } - /** @since 18.0 */ - // @Override on JDK8 + /** + * @since 18.0 + */ + @Override public A[] getDeclaredAnnotationsByType(Class annotationType) { - return FluentIterable.from(annotations).filter(annotationType).toArray(annotationType); + @Nullable A[] result = + FluentIterable.from(annotations).filter(annotationType).toArray(annotationType); + @SuppressWarnings("nullness") // safe because the input list contains no nulls + A[] cast = (A[]) result; + return cast; } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof Parameter) { Parameter that = (Parameter) obj; return position == that.position && declaration.equals(that.declaration); diff --git a/android/guava/src/com/google/common/reflect/ParametricNullness.java b/android/guava/src/com/google/common/reflect/ParametricNullness.java old mode 100755 new mode 100644 index 588aa5f59798..4c9afbe59684 --- a/android/guava/src/com/google/common/reflect/ParametricNullness.java +++ b/android/guava/src/com/google/common/reflect/ParametricNullness.java @@ -19,25 +19,54 @@ import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static javax.annotation.meta.When.UNKNOWN; +import static java.lang.annotation.RetentionPolicy.CLASS; import com.google.common.annotations.GwtCompatible; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierNickname; /** - * Marks a "top-level" type-variable usage as (a) a Kotlin platform type when the type argument is - * non-nullable and (b) nullable when the type argument is nullable. This is the closest we can get - * to "non-nullable when non-nullable; nullable when nullable" (like the Android {@code - * NullFromTypeParam}). We use this to "undo" {@link ElementTypesAreNonnullByDefault}. + * Annotates a "top-level" type-variable usage that takes its nullness from the type argument + * supplied by the user of the class. For example, {@code Multiset.Entry.getElement()} returns + * {@code @ParametricNullness E}, which means: + * + *
      + *
    • {@code getElement} on a {@code Multiset.Entry<@NonNull String>} returns {@code @NonNull + * String}. + *
    • {@code getElement} on a {@code Multiset.Entry<@Nullable String>} returns {@code @Nullable + * String}. + *
    + * + * This is the same behavior as type-variable usages have to Kotlin and to the Checker Framework. + * Contrast the method above to: + * + *
      + *
    • methods whose return type is a type variable but which can never return {@code null}, + * typically because the type forbids nullable type arguments: For example, {@code + * ImmutableList.get} returns {@code E}, but that value is never {@code null}. (Accordingly, + * {@code ImmutableList} is declared to forbid {@code ImmutableList<@Nullable String>}.) + *
    • methods whose return type is a type variable but which can return {@code null} regardless + * of the type argument supplied by the user of the class: For example, {@code + * ImmutableMap.get} returns {@code @Nullable E} because the method can return {@code null} + * even on an {@code ImmutableMap}. + *
    + * + *

    Consumers of this annotation include: + * + *

      + *
    • NullAway, which treats it + * identically to {@code Nullable} as of version 0.9.9. + *
    • J2ObjC, maybe: It might no longer be + * necessary there, since we have stopped using the {@code @ParametersAreNonnullByDefault} + * annotations that {@code ParametricNullness} was counteracting. + *
    + * + *

    This annotation is a temporary hack. We will remove it after tools no longer need + * it. */ @GwtCompatible -@Retention(RUNTIME) +@Retention(CLASS) @Target({FIELD, METHOD, PARAMETER}) -@TypeQualifierNickname -@Nonnull(when = UNKNOWN) @interface ParametricNullness {} diff --git a/android/guava/src/com/google/common/reflect/Reflection.java b/android/guava/src/com/google/common/reflect/Reflection.java index fa35f7f2de6a..3cc06250c344 100644 --- a/android/guava/src/com/google/common/reflect/Reflection.java +++ b/android/guava/src/com/google/common/reflect/Reflection.java @@ -17,7 +17,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; @@ -26,8 +25,6 @@ * * @since 12.0 */ -@Beta -@ElementTypesAreNonnullByDefault public final class Reflection { /** @@ -56,7 +53,7 @@ public static String getPackageName(String classFullName) { * *

    WARNING: Normally it's a smell if a class needs to be explicitly initialized, because static * state hurts system maintainability and testability. In cases when you have no choice while - * inter-operating with a legacy framework, this method helps to keep the code less ugly. + * interoperating with a legacy framework, this method helps to keep the code less ugly. * * @throws ExceptionInInitializerError if an exception is thrown during initialization of a class */ diff --git a/android/guava/src/com/google/common/reflect/TypeCapture.java b/android/guava/src/com/google/common/reflect/TypeCapture.java index 2be7b4fddad1..effb382b2826 100644 --- a/android/guava/src/com/google/common/reflect/TypeCapture.java +++ b/android/guava/src/com/google/common/reflect/TypeCapture.java @@ -24,7 +24,6 @@ * * @author Ben Yu */ -@ElementTypesAreNonnullByDefault abstract class TypeCapture { /** Returns the captured type. */ diff --git a/android/guava/src/com/google/common/reflect/TypeParameter.java b/android/guava/src/com/google/common/reflect/TypeParameter.java index 9c64abb72794..52da4e0b0a87 100644 --- a/android/guava/src/com/google/common/reflect/TypeParameter.java +++ b/android/guava/src/com/google/common/reflect/TypeParameter.java @@ -16,26 +16,23 @@ import static com.google.common.base.Preconditions.checkArgument; -import com.google.common.annotations.Beta; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * Captures a free type variable that can be used in {@link TypeToken#where}. For example: * - *

    {@code
    + * {@snippet :
      * static  TypeToken> listOf(Class elementType) {
      *   return new TypeToken>() {}
      *       .where(new TypeParameter() {}, elementType);
      * }
    - * }
    + * } * * @author Ben Yu * @since 12.0 */ -@Beta -@ElementTypesAreNonnullByDefault /* * A nullable bound would let users create a TypeParameter instance for a parameter with a nullable * bound. However, it would also let them create `new TypeParameter<@Nullable T>() {}`, which @@ -62,7 +59,7 @@ public final int hashCode() { } @Override - public final boolean equals(@CheckForNull Object o) { + public final boolean equals(@Nullable Object o) { if (o instanceof TypeParameter) { TypeParameter that = (TypeParameter) o; return typeVariable.equals(that.typeVariable); diff --git a/android/guava/src/com/google/common/reflect/TypeResolver.java b/android/guava/src/com/google/common/reflect/TypeResolver.java index 1409efdd007d..9e184a47855d 100644 --- a/android/guava/src/com/google/common/reflect/TypeResolver.java +++ b/android/guava/src/com/google/common/reflect/TypeResolver.java @@ -19,23 +19,23 @@ import static com.google.common.base.Preconditions.checkState; import static java.util.Arrays.asList; -import com.google.common.annotations.Beta; import com.google.common.base.Joiner; -import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * An object of this class encapsulates type mappings from type variables. Mappings are established @@ -43,7 +43,7 @@ * *

    Note that usually type mappings are already implied by the static type hierarchy (for example, * the {@code E} type variable declared by class {@code List} naturally maps to {@code String} in - * the context of {@code class MyStringList implements List}. In such case, prefer to use + * the context of {@code class MyStringList implements List}). In such case, prefer to use * {@link TypeToken#resolveType} since it's simpler and more type safe. This class should only be * used when the type mapping isn't implied by the static type hierarchy, but provided through other * means such as an annotation or external configuration file. @@ -51,8 +51,6 @@ * @author Ben Yu * @since 15.0 */ -@Beta -@ElementTypesAreNonnullByDefault public final class TypeResolver { private final TypeTable typeTable; @@ -112,7 +110,7 @@ static TypeResolver invariantly(Type contextType) { * corresponding mappings exist in the current {@code TypeResolver} instance. */ public TypeResolver where(Type formal, Type actual) { - Map mappings = Maps.newHashMap(); + Map mappings = new HashMap<>(); populateTypeMappings(mappings, checkNotNull(formal), checkNotNull(actual)); return where(mappings); } @@ -123,7 +121,7 @@ TypeResolver where(Map mappings) { } private static void populateTypeMappings( - final Map mappings, final Type from, final Type to) { + Map mappings, Type from, Type to) { if (from.equals(to)) { return; } @@ -228,6 +226,7 @@ public Type resolveType(Type type) { } } + @CanIgnoreReturnValue Type[] resolveTypesInPlace(Type[] types) { for (int i = 0; i < types.length; i++) { types[i] = resolveType(types[i]); @@ -296,11 +295,11 @@ final TypeTable where(Map mappings) { checkArgument(!variable.equalsType(type), "Type variable %s bound to itself", variable); builder.put(variable, type); } - return new TypeTable(builder.build()); + return new TypeTable(builder.buildOrThrow()); } - final Type resolve(final TypeVariable var) { - final TypeTable unguarded = this; + final Type resolve(TypeVariable var) { + TypeTable unguarded = this; TypeTable guarded = new TypeTable() { @Override @@ -332,17 +331,28 @@ Type resolveInternal(TypeVariable var, TypeTable forDependants) { Type[] resolvedBounds = new TypeResolver(forDependants).resolveTypes(bounds); /* * We'd like to simply create our own TypeVariable with the newly resolved bounds. There's - * just one problem: Starting with JDK 7u51, the JDK TypeVariable's equals() method doesn't - * recognize instances of our TypeVariable implementation. This is a problem because users - * compare TypeVariables from the JDK against TypeVariables returned by TypeResolver. To - * work with all JDK versions, TypeResolver must return the appropriate TypeVariable + * just one problem: We want to interoperate properly with the platform's built-in + * implementation of TypeVariable, but the behavior of the built-in implementation differs + * across platforms: + * + * - Under the JDK, the built-in TypeVariable's equals() method doesn't recognize instances + * of our TypeVariable implementation. + * + * - Under Android, it does. + * + * We want users to see the same behavior when they compare a built-in TypeVariable against + * ours as they do when they perform the same comparison in reverse. To provide that + * behavior on all platforms, TypeResolver must return the appropriate TypeVariable * implementation in each of the three possible cases: * - * 1. Prior to JDK 7u51, the JDK TypeVariable implementation interoperates with ours. - * Therefore, we can always create our own TypeVariable. + * 1. Under Android, the built-in TypeVariable implementation interoperates with ours. + * Therefore, we can always create our own TypeVariable. (One downside of our TypeVariable + * in some situations is that it does not support the AnnotatedType API. However, those + * situations don't arise under Android because Android does not provide the AnnotatedType + * API at all.) * - * 2. Starting with JDK 7u51, the JDK TypeVariable implementations does not interoperate - * with ours. Therefore, we have to be careful about whether we create our own TypeVariable: + * 2. Under the JDK, the built-in TypeVariable implementation does not interoperate with + * ours. Therefore, we have to be careful about whether we create our own TypeVariable: * * 2a. If the resolved types are identical to the original types, then we can return the * original, identical JDK TypeVariable. By doing so, we sidestep the problem entirely. @@ -357,6 +367,14 @@ Type resolveInternal(TypeVariable var, TypeTable forDependants) { * new TypeVariable _will_ be equal to is an equivalent TypeVariable that was also created * by us. And that equality is guaranteed to hold because it doesn't involve the JDK * TypeVariable implementation at all. + * + * NOTE: b/147144588 - Custom TypeVariables created by Guava do not preserve + * annotations. This is intentional. The semantics of annotation handling during + * type resolution are unclear and have changed across Java versions. Until there's + * a clear specification for what annotations should mean on resolved TypeVariables + * with modified bounds, annotation methods will throw + * UnsupportedOperationException. Frameworks requiring annotation preservation + * should use the original TypeVariable when bounds haven't changed. */ if (Types.NativeTypeVariableEquals.NATIVE_TYPE_VARIABLE_ONLY && Arrays.equals(bounds, resolvedBounds)) { @@ -372,7 +390,7 @@ Type resolveInternal(TypeVariable var, TypeTable forDependants) { private static final class TypeMappingIntrospector extends TypeVisitor { - private final Map mappings = Maps.newHashMap(); + private final Map mappings = new HashMap<>(); /** * Returns type mappings using type parameters and type arguments found in the generic @@ -414,7 +432,7 @@ void visitWildcardType(WildcardType t) { visit(t.getUpperBounds()); } - private void map(final TypeVariableKey var, final Type arg) { + private void map(TypeVariableKey var, Type arg) { if (mappings.containsKey(var)) { // Mapping already established // This is possible when following both superClass -> enclosingClass @@ -428,7 +446,7 @@ private void map(final TypeVariableKey var, final Type arg) { if (var.equalsType(t)) { // cycle detected, remove the entire cycle from the mapping so that // each type variable resolves deterministically to itself. - // Otherwise, a F -> T cycle will end up resolving both F and T + // Otherwise, an F -> T cycle will end up resolving both F and T // nondeterministically to either F or T. for (Type x = arg; x != null; x = mappings.remove(TypeVariableKey.forLookup(x))) {} return; @@ -504,12 +522,12 @@ TypeVariable captureAsTypeVariable(Type[] upperBounds) { return Types.newArtificialTypeVariable(WildcardCapturer.class, name, upperBounds); } - private WildcardCapturer forTypeVariable(final TypeVariable typeParam) { + private WildcardCapturer forTypeVariable(TypeVariable typeParam) { return new WildcardCapturer(id) { @Override TypeVariable captureAsTypeVariable(Type[] upperBounds) { Set combined = new LinkedHashSet<>(asList(upperBounds)); - // Since this is an artifically generated type variable, we don't bother checking + // Since this is an artificially generated type variable, we don't bother checking // subtyping between declared type bound and actual type bound. So it's possible that we // may generate something like . // Checking subtype between declared and actual type bounds @@ -528,8 +546,7 @@ private WildcardCapturer notForTypeVariable() { return new WildcardCapturer(id); } - @CheckForNull - private Type captureNullable(@CheckForNull Type type) { + private @Nullable Type captureNullable(@Nullable Type type) { if (type == null) { return null; } @@ -559,11 +576,11 @@ static final class TypeVariableKey { @Override public int hashCode() { - return Objects.hashCode(var.getGenericDeclaration(), var.getName()); + return Objects.hash(var.getGenericDeclaration(), var.getName()); } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof TypeVariableKey) { TypeVariableKey that = (TypeVariableKey) obj; return equalsTypeVariable(that.var); @@ -578,8 +595,7 @@ public String toString() { } /** Wraps {@code t} in a {@code TypeVariableKey} if it's a type variable. */ - @CheckForNull - static TypeVariableKey forLookup(Type t) { + static @Nullable TypeVariableKey forLookup(Type t) { if (t instanceof TypeVariable) { return new TypeVariableKey((TypeVariable) t); } else { diff --git a/android/guava/src/com/google/common/reflect/TypeToInstanceMap.java b/android/guava/src/com/google/common/reflect/TypeToInstanceMap.java index 80396bc7893f..b1ffbadb52ec 100644 --- a/android/guava/src/com/google/common/reflect/TypeToInstanceMap.java +++ b/android/guava/src/com/google/common/reflect/TypeToInstanceMap.java @@ -14,11 +14,11 @@ package com.google.common.reflect; -import com.google.common.annotations.Beta; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotMock; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; /** * A map, each entry of which maps a {@link TypeToken} to an instance of that type. In addition to @@ -39,9 +39,9 @@ * @author Ben Yu * @since 13.0 */ -@Beta @DoNotMock("Use ImmutableTypeToInstanceMap or MutableTypeToInstanceMap") -public interface TypeToInstanceMap extends Map, B> { +public interface TypeToInstanceMap + extends Map, B> { /** * Returns the value the specified class is mapped to, or {@code null} if no entry for this class @@ -51,16 +51,14 @@ public interface TypeToInstanceMap extends Map, B> { *

    {@code getInstance(Foo.class)} is equivalent to {@code * getInstance(TypeToken.of(Foo.class))}. */ - @NullableDecl - T getInstance(Class type); + @Nullable T getInstance(Class type); /** * Returns the value the specified type is mapped to, or {@code null} if no entry for this type is * present. This will only return a value that was bound to this specific type, not a value that * may have been bound to a subtype. */ - @NullableDecl - T getInstance(TypeToken type); + @Nullable T getInstance(TypeToken type); /** * Maps the specified class to the specified value. Does not associate this value with any @@ -73,8 +71,7 @@ public interface TypeToInstanceMap extends Map, B> { * null} if there was no previous entry. */ @CanIgnoreReturnValue - @NullableDecl - T putInstance(Class type, @NullableDecl T value); + @Nullable T putInstance(Class<@NonNull T> type, @ParametricNullness T value); /** * Maps the specified type to the specified value. Does not associate this value with any @@ -84,6 +81,5 @@ public interface TypeToInstanceMap extends Map, B> { * if there was no previous entry. */ @CanIgnoreReturnValue - @NullableDecl - T putInstance(TypeToken type, @NullableDecl T value); + @Nullable T putInstance(TypeToken<@NonNull T> type, @ParametricNullness T value); } diff --git a/android/guava/src/com/google/common/reflect/TypeToken.java b/android/guava/src/com/google/common/reflect/TypeToken.java index f107b70818e8..866fa925fe36 100644 --- a/android/guava/src/com/google/common/reflect/TypeToken.java +++ b/android/guava/src/com/google/common/reflect/TypeToken.java @@ -17,9 +17,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static java.lang.Math.max; import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Predicate; @@ -28,10 +28,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.primitives.Primitives; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.GenericArrayType; @@ -44,10 +44,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.CheckForNull; +import org.jspecify.annotations.Nullable; /** * A {@link Type} with generics. @@ -62,13 +63,13 @@ *

  • Wrap a {@code Type} obtained via reflection. For example: {@code * TypeToken.of(method.getGenericReturnType())}. *
  • Capture a generic type with a (usually anonymous) subclass. For example: - *
    {@code
    + *       {@snippet :
      * new TypeToken>() {}
    - * }
    + * } *

    Note that it's critical that the actual type argument is carried by a subclass. The * following code is wrong because it only captures the {@code } type variable of the * {@code listType()} method signature; while {@code } is lost in erasure: - *

    {@code
    + *       {@snippet :
      * class Util {
      *   static  TypeToken> listType() {
      *     return new TypeToken>() {};
    @@ -76,20 +77,20 @@
      * }
      *
      * TypeToken> stringListType = Util.listType();
    - * }
    + * } *
  • Capture a generic type with a (usually anonymous) subclass and resolve it against a context * class that knows what the type parameters are. For example: - *
    {@code
    + *       {@snippet :
      * abstract class IKnowMyType {
      *   TypeToken type = new TypeToken(getClass()) {};
      * }
      * new IKnowMyType() {}.type => String
    - * }
    + * } * * *

    {@code TypeToken} is serializable when no type variable is contained in the type. * - *

    Note to Guice users: {@code} TypeToken is similar to Guice's {@code TypeLiteral} class except + *

    Note to Guice users: {@code TypeToken} is similar to Guice's {@code TypeLiteral} class except * that it is serializable and offers numerous additional utility methods. * * @author Bob Lee @@ -97,18 +98,16 @@ * @author Ben Yu * @since 12.0 */ -@Beta @SuppressWarnings("serial") // SimpleTypeToken is the serialized form. -@ElementTypesAreNonnullByDefault public abstract class TypeToken extends TypeCapture implements Serializable { private final Type runtimeType; /** Resolver for resolving parameter and field types with {@link #runtimeType} as context. */ - @CheckForNull private transient TypeResolver invariantTypeResolver; + @LazyInit private transient @Nullable TypeResolver invariantTypeResolver; /** Resolver for resolving covariant types with {@link #runtimeType} as context. */ - @CheckForNull private transient TypeResolver covariantTypeResolver; + @LazyInit private transient @Nullable TypeResolver covariantTypeResolver; /** * Constructs a new type token of {@code T}. @@ -118,9 +117,9 @@ public abstract class TypeToken extends TypeCapture implements Serializabl * *

    For example: * - *

    {@code
    +   * {@snippet :
        * TypeToken> t = new TypeToken>() {};
    -   * }
    + * } */ protected TypeToken() { this.runtimeType = capture(); @@ -143,7 +142,7 @@ protected TypeToken() { * *

    For example: * - *

    {@code
    +   * {@snippet :
        * abstract class IKnowMyType {
        *   TypeToken getMyType() {
        *     return new TypeToken(getClass()) {};
    @@ -151,7 +150,7 @@ protected TypeToken() {
        * }
        *
        * new IKnowMyType() {}.getMyType() => String
    -   * }
    + * } */ protected TypeToken(Class declaringClass) { Type captured = super.capture(); @@ -168,7 +167,7 @@ private TypeToken(Type type) { /** Returns an instance of type token that wraps {@code type}. */ public static TypeToken of(Class type) { - return new SimpleTypeToken(type); + return new SimpleTypeToken<>(type); } /** Returns an instance of type token that wraps {@code type}. */ @@ -192,11 +191,19 @@ public static TypeToken of(Type type) { * */ public final Class getRawType() { - // For wildcard or type variable, the first bound determines the runtime type. - Class rawType = getRawTypes().iterator().next(); - @SuppressWarnings("unchecked") // raw type is |T| - Class result = (Class) rawType; - return result; + if (runtimeType instanceof Class) { + @SuppressWarnings("unchecked") // raw type is T + Class result = (Class) runtimeType; + return result; + } else if (runtimeType instanceof ParameterizedType) { + @SuppressWarnings("unchecked") // raw type is |T| + Class result = (Class) ((ParameterizedType) runtimeType).getRawType(); + return result; + } else { + // For a wildcard or type variable, the first bound determines the runtime type. + // This case also covers GenericArrayType. + return getRawTypes().iterator().next(); + } } /** Returns the represented type. */ @@ -209,14 +216,14 @@ public final Type getType() { * substituted by {@code typeArg}. For example, it can be used to construct {@code Map} for * any {@code K} and {@code V} type: * - *
    {@code
    +   * {@snippet :
        * static  TypeToken> mapOf(
        *     TypeToken keyType, TypeToken valueType) {
        *   return new TypeToken>() {}
        *       .where(new TypeParameter() {}, keyType)
        *       .where(new TypeParameter() {}, valueType);
        * }
    -   * }
    + * } * * @param The parameter type * @param typeParam the parameter type variable @@ -240,7 +247,7 @@ public final TypeToken where(TypeParameter typeParam, TypeToken typ ImmutableMap.of( new TypeResolver.TypeVariableKey(typeParam.typeVariable), typeArg.runtimeType)); // If there's any type error, we'd report now rather than later. - return new SimpleTypeToken(resolver.resolveType(runtimeType)); + return new SimpleTypeToken<>(resolver.resolveType(runtimeType)); } /** @@ -248,14 +255,14 @@ public final TypeToken where(TypeParameter typeParam, TypeToken typ * substituted by {@code typeArg}. For example, it can be used to construct {@code Map} for * any {@code K} and {@code V} type: * - *
    {@code
    +   * {@snippet :
        * static  TypeToken> mapOf(
        *     Class keyType, Class valueType) {
        *   return new TypeToken>() {}
        *       .where(new TypeParameter() {}, keyType)
        *       .where(new TypeParameter() {}, valueType);
        * }
    -   * }
    + * } * * @param The parameter type * @param typeParam the parameter type variable @@ -272,11 +279,11 @@ public final TypeToken where(TypeParameter typeParam, Class typeArg /** * Resolves the given {@code type} against the type context represented by this type. For example: * - *
    {@code
    +   * {@snippet :
        * new TypeToken>() {}.resolveType(
        *     List.class.getMethod("get", int.class).getGenericReturnType())
        * => String.class
    -   * }
    + * } */ public final TypeToken resolveType(Type type) { checkNotNull(type); @@ -305,8 +312,7 @@ private TypeToken resolveSupertype(Type type) { * if the bound is a class or extends from a class. This means that the returned type could be a * type variable too. */ - @CheckForNull - final TypeToken getGenericSuperclass() { + final @Nullable TypeToken getGenericSuperclass() { if (runtimeType instanceof TypeVariable) { // First bound is always the super class, if one exists. return boundAsSuperclass(((TypeVariable) runtimeType).getBounds()[0]); @@ -324,8 +330,7 @@ final TypeToken getGenericSuperclass() { return superToken; } - @CheckForNull - private TypeToken boundAsSuperclass(Type bound) { + private @Nullable TypeToken boundAsSuperclass(Type bound) { TypeToken token = of(bound); if (token.getRawType().isInterface()) { return null; @@ -577,8 +582,7 @@ public final TypeToken unwrap() { * Returns the array component type if this type represents an array ({@code int[]}, {@code T[]}, * {@code []>} etc.), or else {@code null} is returned. */ - @CheckForNull - public final TypeToken getComponentType() { + public final @Nullable TypeToken getComponentType() { Type componentType = Types.getComponentType(runtimeType); if (componentType == null) { return null; @@ -672,7 +676,7 @@ public String toString() { */ public class TypeSet extends ForwardingSet> implements Serializable { - @CheckForNull private transient ImmutableSet> types; + private transient @Nullable ImmutableSet> types; TypeSet() {} @@ -718,7 +722,7 @@ public Set> rawTypes() { private final class InterfaceSet extends TypeSet { private final transient TypeSet allTypes; - @CheckForNull private transient ImmutableSet> interfaces; + private transient @Nullable ImmutableSet> interfaces; InterfaceSet(TypeSet allTypes) { this.allTypes = allTypes; @@ -746,15 +750,7 @@ public Set> rawTypes() { @SuppressWarnings({"unchecked", "rawtypes"}) ImmutableList> collectedTypes = (ImmutableList) TypeCollector.FOR_RAW_TYPE.collectTypes(getRawTypes()); - return FluentIterable.from(collectedTypes) - .filter( - new Predicate>() { - @Override - public boolean apply(Class type) { - return type.isInterface(); - } - }) - .toSet(); + return FluentIterable.from(collectedTypes).filter(Class::isInterface).toSet(); } @Override @@ -771,7 +767,7 @@ private Object readResolve() { private final class ClassSet extends TypeSet { - @CheckForNull private transient ImmutableSet> classes; + private transient @Nullable ImmutableSet> classes; @Override protected Set> delegate() { @@ -836,7 +832,7 @@ public boolean apply(TypeToken type) { * Returns true if {@code o} is another {@code TypeToken} that represents the same {@link Type}. */ @Override - public boolean equals(@CheckForNull Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof TypeToken) { TypeToken that = (TypeToken) o; return runtimeType.equals(that.runtimeType); @@ -1073,7 +1069,7 @@ private static Bounds any(Type[] bounds) { return new Bounds(bounds, true); } - private static class Bounds { + private static final class Bounds { private final Type[] bounds; private final boolean target; @@ -1103,7 +1099,7 @@ boolean isSupertypeOf(Type subtype) { } private ImmutableSet> getRawTypes() { - final ImmutableSet.Builder> builder = ImmutableSet.builder(); + ImmutableSet.Builder> builder = ImmutableSet.builder(); new TypeVisitor() { @Override void visitTypeVariable(TypeVariable t) { @@ -1150,8 +1146,7 @@ private boolean isOwnedBySubtypeOf(Type supertype) { * Returns the owner type of a {@link ParameterizedType} or enclosing class of a {@link Class}, or * null otherwise. */ - @CheckForNull - private Type getOwnerTypeIfPresent() { + private @Nullable Type getOwnerTypeIfPresent() { if (runtimeType instanceof ParameterizedType) { return ((ParameterizedType) runtimeType).getOwnerType(); } else if (runtimeType instanceof Class) { @@ -1323,7 +1318,7 @@ private static final class SimpleTypeToken extends TypeToken { } /** - * Collects parent types from a sub type. + * Collects parent types from a subtype. * * @param The type "kind". Either a TypeToken, or Class. */ @@ -1342,8 +1337,7 @@ Iterable> getInterfaces(TypeToken type) { } @Override - @CheckForNull - TypeToken getSuperclass(TypeToken type) { + @Nullable TypeToken getSuperclass(TypeToken type) { return type.getGenericSuperclass(); } }; @@ -1361,8 +1355,7 @@ Iterable> getInterfaces(Class type) { } @Override - @CheckForNull - Class getSuperclass(Class type) { + @Nullable Class getSuperclass(Class type) { return type.getSuperclass(); } }; @@ -1394,7 +1387,7 @@ final ImmutableList collectTypes(K type) { ImmutableList collectTypes(Iterable types) { // type -> order number. 1 for Object, 2 for anything directly below, so on so forth. - Map map = Maps.newHashMap(); + Map map = new HashMap<>(); for (K type : types) { collectTypes(type, map); } @@ -1412,11 +1405,11 @@ private int collectTypes(K type, Map map) { // Interfaces should be listed before Object. int aboveMe = getRawType(type).isInterface() ? 1 : 0; for (K interfaceType : getInterfaces(type)) { - aboveMe = Math.max(aboveMe, collectTypes(interfaceType, map)); + aboveMe = max(aboveMe, collectTypes(interfaceType, map)); } K superclass = getSuperclass(type); if (superclass != null) { - aboveMe = Math.max(aboveMe, collectTypes(superclass, map)); + aboveMe = max(aboveMe, collectTypes(superclass, map)); } /* * TODO(benyu): should we include Object for interface? Also, CharSequence[] and Object[] for @@ -1428,7 +1421,7 @@ private int collectTypes(K type, Map map) { } private static ImmutableList sortKeysByValue( - final Map map, final Comparator valueComparator) { + Map map, Comparator valueComparator) { Ordering keyOrdering = new Ordering() { @Override @@ -1445,8 +1438,7 @@ public int compare(K left, K right) { abstract Iterable getInterfaces(K type); - @CheckForNull - abstract K getSuperclass(K type); + abstract @Nullable K getSuperclass(K type); private static class ForwardingTypeCollector extends TypeCollector { @@ -1467,8 +1459,7 @@ Iterable getInterfaces(K type) { } @Override - @CheckForNull - K getSuperclass(K type) { + @Nullable K getSuperclass(K type) { return delegate.getSuperclass(type); } } diff --git a/android/guava/src/com/google/common/reflect/TypeVisitor.java b/android/guava/src/com/google/common/reflect/TypeVisitor.java index 416397bc77d6..e42ced4c2d0a 100644 --- a/android/guava/src/com/google/common/reflect/TypeVisitor.java +++ b/android/guava/src/com/google/common/reflect/TypeVisitor.java @@ -14,14 +14,14 @@ package com.google.common.reflect; -import com.google.common.collect.Sets; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; +import java.util.HashSet; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Based on what a {@link Type} is, dispatch it to the corresponding {@code visit*} method. By @@ -29,7 +29,7 @@ * recursion by calling {@link #visit} for any {@code Type} while visitation is in progress. For * example, this can be used to reject wildcards or type variables contained in a type as in: * - *
    {@code
    + * {@snippet :
      * new TypeVisitor() {
      *   protected void visitParameterizedType(ParameterizedType t) {
      *     visit(t.getOwnerType());
    @@ -45,7 +45,7 @@
      *     throw new IllegalArgumentException("Cannot contain wildcard type.");
      *   }
      * }.visit(type);
    - * }
    + * } * *

    One {@code Type} is visited at most once. The second time the same type is visited, it's * ignored by {@link #visit}. This avoids infinite recursion caused by recursive type bounds. @@ -54,10 +54,9 @@ * * @author Ben Yu */ -@ElementTypesAreNonnullByDefault abstract class TypeVisitor { - private final Set visited = Sets.newHashSet(); + private final Set visited = new HashSet<>(); /** * Visits the given types. Null types are ignored. This allows subclasses to call {@code diff --git a/android/guava/src/com/google/common/reflect/Types.java b/android/guava/src/com/google/common/reflect/Types.java index 0dc327d3d1b6..a194e372740a 100644 --- a/android/guava/src/com/google/common/reflect/Types.java +++ b/android/guava/src/com/google/common/reflect/Types.java @@ -20,13 +20,12 @@ import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.base.Joiner; -import com.google.common.base.Objects; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import com.google.errorprone.annotations.Keep; import java.io.Serializable; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; @@ -44,27 +43,18 @@ import java.util.Arrays; import java.util.Collection; import java.util.Map.Entry; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.CheckForNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * Utilities for working with {@link Type}. * * @author Ben Yu */ -@ElementTypesAreNonnullByDefault final class Types { /** Class#toString without the "class " and "interface " prefixes */ - private static final Function TYPE_NAME = - new Function() { - @Override - public String apply(Type from) { - return JavaVersion.CURRENT.typeName(from); - } - }; - private static final Joiner COMMA_JOINER = Joiner.on(", ").useForNull("null"); /** Returns the array type of {@code componentType}. */ @@ -89,7 +79,7 @@ static Type newArrayType(Type componentType) { * {@code ownerType}. */ static ParameterizedType newParameterizedTypeWithOwner( - @CheckForNull Type ownerType, Class rawType, Type... arguments) { + @Nullable Type ownerType, Class rawType, Type... arguments) { if (ownerType == null) { return newParameterizedType(rawType, arguments); } @@ -109,15 +99,13 @@ static ParameterizedType newParameterizedType(Class rawType, Type... argument private enum ClassOwnership { OWNED_BY_ENCLOSING_CLASS { @Override - @CheckForNull - Class getOwnerType(Class rawType) { + @Nullable Class getOwnerType(Class rawType) { return rawType.getEnclosingClass(); } }, LOCAL_CLASS_HAS_NO_OWNER { @Override - @CheckForNull - Class getOwnerType(Class rawType) { + @Nullable Class getOwnerType(Class rawType) { if (rawType.isLocalClass()) { return null; } else { @@ -126,8 +114,7 @@ Class getOwnerType(Class rawType) { } }; - @CheckForNull - abstract Class getOwnerType(Class rawType); + abstract @Nullable Class getOwnerType(Class rawType); static final ClassOwnership JVM_BEHAVIOR = detectJvmBehavior(); @@ -169,7 +156,7 @@ static WildcardType supertypeOf(Type lowerBound) { } /** - * Returns human readable string representation of {@code type}. + * Returns a human-readable string representation of {@code type}. * *

    The format is subject to change. */ @@ -177,10 +164,9 @@ static String toString(Type type) { return (type instanceof Class) ? ((Class) type).getName() : type.toString(); } - @CheckForNull - static Type getComponentType(Type type) { + static @Nullable Type getComponentType(Type type) { checkNotNull(type); - final AtomicReference<@Nullable Type> result = new AtomicReference<>(); + AtomicReference<@Nullable Type> result = new AtomicReference<>(); new TypeVisitor() { @Override void visitTypeVariable(TypeVariable t) { @@ -209,8 +195,7 @@ void visitClass(Class t) { * Returns {@code ? extends X} if any of {@code bounds} is a subtype of {@code X[]}; or null * otherwise. */ - @CheckForNull - private static Type subtypeOfComponentType(Type[] bounds) { + private static @Nullable Type subtypeOfComponentType(Type[] bounds) { for (Type bound : bounds) { Type componentType = getComponentType(bound); if (componentType != null) { @@ -252,10 +237,10 @@ public int hashCode() { } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof GenericArrayType) { GenericArrayType that = (GenericArrayType) obj; - return Objects.equal(getGenericComponentType(), that.getGenericComponentType()); + return Objects.equals(getGenericComponentType(), that.getGenericComponentType()); } return false; } @@ -265,11 +250,11 @@ public boolean equals(@CheckForNull Object obj) { private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable { - @CheckForNull private final Type ownerType; + private final @Nullable Type ownerType; private final ImmutableList argumentsList; private final Class rawType; - ParameterizedTypeImpl(@CheckForNull Type ownerType, Class rawType, Type[] typeArguments) { + ParameterizedTypeImpl(@Nullable Type ownerType, Class rawType, Type[] typeArguments) { checkNotNull(rawType); checkArgument(typeArguments.length == rawType.getTypeParameters().length); disallowPrimitiveType(typeArguments, "type parameter"); @@ -289,8 +274,7 @@ public Type getRawType() { } @Override - @CheckForNull - public Type getOwnerType() { + public @Nullable Type getOwnerType() { return ownerType; } @@ -303,7 +287,7 @@ public String toString() { return builder .append(rawType.getName()) .append('<') - .append(COMMA_JOINER.join(transform(argumentsList, TYPE_NAME))) + .append(COMMA_JOINER.join(transform(argumentsList, JavaVersion.CURRENT::typeName))) .append('>') .toString(); } @@ -316,13 +300,13 @@ public int hashCode() { } @Override - public boolean equals(@CheckForNull Object other) { + public boolean equals(@Nullable Object other) { if (!(other instanceof ParameterizedType)) { return false; } ParameterizedType that = (ParameterizedType) other; return getRawType().equals(that.getRawType()) - && Objects.equal(getOwnerType(), that.getOwnerType()) + && Objects.equals(getOwnerType(), that.getOwnerType()) && Arrays.equals(getActualTypeArguments(), that.getActualTypeArguments()); } @@ -331,8 +315,7 @@ public boolean equals(@CheckForNull Object other) { private static TypeVariable newTypeVariableImpl( D genericDeclaration, String name, Type[] bounds) { - TypeVariableImpl typeVariableImpl = - new TypeVariableImpl(genericDeclaration, name, bounds); + TypeVariableImpl typeVariableImpl = new TypeVariableImpl<>(genericDeclaration, name, bounds); @SuppressWarnings("unchecked") TypeVariable typeVariable = Reflection.newProxy( @@ -341,31 +324,33 @@ private static TypeVariable newTypeVariableImp } /** - * Invocation handler to work around a compatibility problem between Java 7 and Java 8. + * Invocation handler to work around a compatibility problem between Android and Java. * *

    Java 8 introduced a new method {@code getAnnotatedBounds()} in the {@link TypeVariable} - * interface, whose return type {@code AnnotatedType[]} is also new in Java 8. That means that we - * cannot implement that interface in source code in a way that will compile on both Java 7 and - * Java 8. If we include the {@code getAnnotatedBounds()} method then its return type means it - * won't compile on Java 7, while if we don't include the method then the compiler will complain - * that an abstract method is unimplemented. So instead we use a dynamic proxy to get an - * implementation. If the method being called on the {@code TypeVariable} instance has the same - * name as one of the public methods of {@link TypeVariableImpl}, the proxy calls the same method - * on its instance of {@code TypeVariableImpl}. Otherwise it throws {@link - * UnsupportedOperationException}; this should only apply to {@code getAnnotatedBounds()}. This - * does mean that users on Java 8 who obtain an instance of {@code TypeVariable} from {@link - * TypeResolver#resolveType} will not be able to call {@code getAnnotatedBounds()} on it, but that - * should hopefully be rare. + * interface, whose return type {@code AnnotatedType[]} is also new in Java 8. As of 2025, Android + * has not added {@link AnnotatedType}. That means that we cannot implement that interface in + * source code in a way that will compile on both Java and Android. If we include the {@code + * getAnnotatedBounds()} method, then its return type means it won't compile on Android, while if + * we don't include the method, then the compiler will complain that an abstract method is + * unimplemented. So instead we use a dynamic proxy to get an implementation. If the method being + * called on the {@code TypeVariable} instance has the same name as one of the public methods of + * {@link TypeVariableImpl}, the proxy calls the same method on its instance of {@code + * TypeVariableImpl}. Otherwise it throws {@link UnsupportedOperationException}; this should only + * apply to {@code getAnnotatedBounds()}. This does mean that users on Java who obtain an instance + * of {@code TypeVariable} from {@link TypeResolver#resolveType} will not be able to call {@code + * getAnnotatedBounds()} on it, but that should hopefully be rare. * - *

    TODO(b/147144588): We are currently also missing the methods inherited from {@link + *

    TODO: b/147144588 - We are currently also missing the methods inherited from {@link * AnnotatedElement}, which {@code TypeVariable} began to extend only in Java 8. Those methods - * refer only to types present in Java 7, so we could implement them in {@code TypeVariableImpl} - * today. (We could probably then make {@code TypeVariableImpl} implement {@code AnnotatedElement} - * so that we get partial compile-time checking.) + * refer only to types present under Android, so we could implement them in {@code + * TypeVariableImpl} today. (We could probably then make {@code TypeVariableImpl} implement {@code + * AnnotatedElement} so that we get partial compile-time checking.) * - *

    This workaround should be removed at a distant future time when we no longer support Java - * versions earlier than 8. + *

    This workaround should be removed at a distant future time when Android supports {@code + * AnnotatedType}. */ + @SuppressWarnings("removal") // b/318391980 private static final class TypeVariableInvocationHandler implements InvocationHandler { private static final ImmutableMap typeVariableMethods; @@ -382,7 +367,7 @@ private static final class TypeVariableInvocationHandler implements InvocationHa builder.put(method.getName(), method); } } - typeVariableMethods = builder.build(); + typeVariableMethods = builder.buildKeepingLast(); } private final TypeVariableImpl typeVariableImpl; @@ -392,12 +377,23 @@ private static final class TypeVariableInvocationHandler implements InvocationHa } @Override - @CheckForNull - public Object invoke(Object proxy, Method method, @CheckForNull @Nullable Object[] args) + public @Nullable Object invoke(Object proxy, Method method, @Nullable Object @Nullable [] args) throws Throwable { String methodName = method.getName(); Method typeVariableMethod = typeVariableMethods.get(methodName); if (typeVariableMethod == null) { + if (methodName.equals("getAnnotatedBounds") + || methodName.equals("isAnnotationPresent") + // Each of these prefixes is shared by a family of methods: + || methodName.startsWith("getAnnotation") + || methodName.startsWith("getDeclaredAnnotation")) { + throw new UnsupportedOperationException( + "Annotation methods are not supported on synthetic TypeVariables created during type" + + " resolution. The semantics of annotations on resolved types with modified" + + " bounds are undefined. Use the original TypeVariable for annotation access." + + " See b/147144588."); + } + // If any other method appears or if we forgot one, include it in the exception message: throw new UnsupportedOperationException(methodName); } else { try { @@ -422,18 +418,22 @@ private static final class TypeVariableImpl { this.bounds = ImmutableList.copyOf(bounds); } + @Keep public Type[] getBounds() { return toArray(bounds); } + @Keep public D getGenericDeclaration() { return genericDeclaration; } + @Keep public String getName() { return name; } + @Keep public String getTypeName() { return name; } @@ -449,7 +449,7 @@ public int hashCode() { } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (NativeTypeVariableEquals.NATIVE_TYPE_VARIABLE_ONLY) { // equal only to our TypeVariable implementation with identical bounds if (obj != null @@ -498,7 +498,7 @@ public Type[] getUpperBounds() { } @Override - public boolean equals(@CheckForNull Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof WildcardType) { WildcardType that = (WildcardType) obj; return lowerBounds.equals(Arrays.asList(that.getLowerBounds())) @@ -552,7 +552,10 @@ static Class getArrayClass(Class componentType) { return Array.newInstance(componentType, 0).getClass(); } - // TODO(benyu): Once behavior is the same for all Java versions we support, delete this. + /* + * TODO(benyu): Once behavior is the same for all Java (and Android) versions we support, delete + * this. It is possible that one or both of JAVA6 and JAVA7 have become unnecessary already. + */ enum JavaVersion { JAVA6 { @Override @@ -598,23 +601,18 @@ Type usedInGenericType(Type type) { return JAVA7.usedInGenericType(type); } + /* + * We use this only when getTypeName is available. + * + * Well, really, we use this when we think we're running under Java 8, as determined by some + * logic in the static initializer, which does not check for getTypeName specifically. We + * should really validate that it works as desired for all Android versions that we support. + */ + @IgnoreJRERequirement + @SuppressWarnings("NewApi") @Override String typeName(Type type) { - try { - Method getTypeName = Type.class.getMethod("getTypeName"); - return (String) getTypeName.invoke(type); - } catch (NoSuchMethodException e) { - throw new AssertionError("Type.getTypeName should be available in Java 8"); - /* - * Do not merge the 2 catch blocks below. javac would infer a type of - * ReflectiveOperationException, which Animal Sniffer would reject. (Old versions of - * Android don't *seem* to mind, but there might be edge cases of which we're unaware.) - */ - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } + return type.getTypeName(); } }, JAVA9 { @@ -628,9 +626,11 @@ Type usedInGenericType(Type type) { return JAVA8.usedInGenericType(type); } + @IgnoreJRERequirement + @SuppressWarnings("NewApi") // see JAVA8.typeName @Override String typeName(Type type) { - return JAVA8.typeName(type); + return type.getTypeName(); } @Override @@ -642,6 +642,7 @@ boolean jdkTypeDuplicatesOwnerName() { static final JavaVersion CURRENT; static { + // Under Android, TypeVariable does not implement AnnotatedElement even under recent versions. if (AnnotatedElement.class.isAssignableFrom(TypeVariable.class)) { if (new TypeCapture>() {}.capture() .toString() @@ -679,19 +680,21 @@ boolean jdkTypeDuplicatesOwnerName() { } /** - * Per issue 1635, - * In JDK 1.7.0_51-b13, {@link TypeVariableImpl#equals(Object)} is changed to no longer be equal - * to custom TypeVariable implementations. As a result, we need to make sure our TypeVariable - * implementation respects symmetry. Moreover, we don't want to reconstruct a native type variable - * {@code } using our implementation unless some of its bounds have changed in resolution. This - * avoids creating unequal TypeVariable implementation unnecessarily. When the bounds do change, - * however, it's fine for the synthetic TypeVariable to be unequal to any native TypeVariable - * anyway. + * Per issue 1635, In JDK 1.7.0_51-b13, + * {@link TypeVariableImpl#equals(Object)} is changed to no longer be equal to custom TypeVariable + * implementations. As a result, we need to make sure our TypeVariable implementation respects + * symmetry. Moreover, we don't want to reconstruct a native type variable {@code } using our + * implementation unless some of its bounds have changed in resolution. This avoids creating + * unequal TypeVariable implementation unnecessarily. When the bounds do change, however, it's + * fine for the synthetic TypeVariable to be unequal to any native TypeVariable anyway. */ + @SuppressWarnings("UnusedTypeParameter") // It's used reflectively. static final class NativeTypeVariableEquals { static final boolean NATIVE_TYPE_VARIABLE_ONLY = !NativeTypeVariableEquals.class.getTypeParameters()[0].equals( newArtificialTypeVariable(NativeTypeVariableEquals.class, "X")); + + private NativeTypeVariableEquals() {} } private Types() {} diff --git a/android/guava/src/com/google/common/reflect/package-info.java b/android/guava/src/com/google/common/reflect/package-info.java index 6b6047169c13..7a3f435e530b 100644 --- a/android/guava/src/com/google/common/reflect/package-info.java +++ b/android/guava/src/com/google/common/reflect/package-info.java @@ -13,12 +13,12 @@ */ /** - * This package contains utilities to work with Java reflection. It is a part of the open-source Guava library. + * Utilities for reflection. This package is a part of the open-source Guava library. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package com.google.common.reflect; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractCatchingFuture.java b/android/guava/src/com/google/common/util/concurrent/AbstractCatchingFuture.java index 740a0d535ee6..1f799c24cbcc 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractCatchingFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractCatchingFuture.java @@ -17,48 +17,55 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Futures.getDone; import static com.google.common.util.concurrent.MoreExecutors.rejectionPropagatingExecutor; +import static com.google.common.util.concurrent.NullnessCasts.uncheckedCastNullableTToT; import static com.google.common.util.concurrent.Platform.isInstanceOfThrowableClass; +import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import com.google.common.util.concurrent.internal.InternalFutureFailureAccess; import com.google.common.util.concurrent.internal.InternalFutures; import com.google.errorprone.annotations.ForOverride; +import com.google.errorprone.annotations.concurrent.LazyInit; +import com.google.j2objc.annotations.RetainedLocalRef; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** Implementations of {@code Futures.catching*}. */ @GwtCompatible -abstract class AbstractCatchingFuture +// Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || +@SuppressWarnings("ShortCircuitBoolean") +abstract class AbstractCatchingFuture< + V extends @Nullable Object, X extends Throwable, F, T extends @Nullable Object> extends FluentFuture.TrustedFuture implements Runnable { - static ListenableFuture create( + static ListenableFuture create( ListenableFuture input, Class exceptionType, Function fallback, Executor executor) { - CatchingFuture future = new CatchingFuture<>(input, exceptionType, fallback); - input.addListener(future, rejectionPropagatingExecutor(executor, future)); - return future; + CatchingFuture output = new CatchingFuture<>(input, exceptionType, fallback); + input.addListener(output, rejectionPropagatingExecutor(executor, output)); + return output; } - static ListenableFuture create( + static ListenableFuture createAsync( ListenableFuture input, Class exceptionType, AsyncFunction fallback, Executor executor) { - AsyncCatchingFuture future = new AsyncCatchingFuture<>(input, exceptionType, fallback); - input.addListener(future, rejectionPropagatingExecutor(executor, future)); - return future; + AsyncCatchingFuture output = new AsyncCatchingFuture<>(input, exceptionType, fallback); + input.addListener(output, rejectionPropagatingExecutor(executor, output)); + return output; } /* * In certain circumstances, this field might theoretically not be visible to an afterDone() call * triggered by cancel(). For details, see the comments on the fields of TimeoutFuture. */ - @NullableDecl ListenableFuture inputFuture; - @NullableDecl Class exceptionType; - @NullableDecl F fallback; + @LazyInit @Nullable ListenableFuture inputFuture; + @LazyInit @Nullable Class exceptionType; + @LazyInit @Nullable F fallback; AbstractCatchingFuture( ListenableFuture inputFuture, Class exceptionType, F fallback) { @@ -68,11 +75,12 @@ static ListenableFuture create( } @Override + @SuppressWarnings("nullness") // TODO(b/147136275): Remove once our checker understands & and |. public final void run() { - ListenableFuture localInputFuture = inputFuture; - Class localExceptionType = exceptionType; - F localFallback = fallback; - if (localInputFuture == null | localExceptionType == null | localFallback == null + @RetainedLocalRef ListenableFuture localInputFuture = inputFuture; + @RetainedLocalRef Class localExceptionType = exceptionType; + @RetainedLocalRef F localFallback = fallback; + if ((localInputFuture == null | localExceptionType == null | localFallback == null) // This check, unlike all the others, is a volatile read || isCancelled()) { return; @@ -102,12 +110,16 @@ public final void run() { + e.getClass() + " without a cause"); } - } catch (Throwable e) { // this includes cancellation exception - throwable = e; + } catch (Throwable t) { // this includes CancellationException and sneaky checked exception + throwable = t; } if (throwable == null) { - set(sourceResult); + /* + * The cast is safe: There was no exception, so the assignment from getDone must have + * succeeded. + */ + set(uncheckedCastNullableTToT(sourceResult)); return; } @@ -123,6 +135,7 @@ public final void run() { try { fallbackResult = doFallback(localFallback, castThrowable); } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); setException(t); return; } finally { @@ -133,11 +146,29 @@ public final void run() { setResult(fallbackResult); } + /** Template method for subtypes to actually run the fallback. */ + @ForOverride + @ParametricNullness + abstract T doFallback(F fallback, X throwable) throws Exception; + + /** Template method for subtypes to actually set the result. */ + @ForOverride + abstract void setResult(@ParametricNullness T result); + + @Override + protected final void afterDone() { + @RetainedLocalRef ListenableFuture localInputFuture = inputFuture; + maybePropagateCancellationTo(localInputFuture); + this.inputFuture = null; + this.exceptionType = null; + this.fallback = null; + } + @Override - protected String pendingToString() { - ListenableFuture localInputFuture = inputFuture; - Class localExceptionType = exceptionType; - F localFallback = fallback; + protected @Nullable String pendingToString() { + @RetainedLocalRef ListenableFuture localInputFuture = inputFuture; + @RetainedLocalRef Class localExceptionType = exceptionType; + @RetainedLocalRef F localFallback = fallback; String superString = super.pendingToString(); String resultString = ""; if (localInputFuture != null) { @@ -156,28 +187,11 @@ protected String pendingToString() { return null; } - /** Template method for subtypes to actually run the fallback. */ - @ForOverride - @NullableDecl - abstract T doFallback(F fallback, X throwable) throws Exception; - - /** Template method for subtypes to actually set the result. */ - @ForOverride - abstract void setResult(@NullableDecl T result); - - @Override - protected final void afterDone() { - maybePropagateCancellationTo(inputFuture); - this.inputFuture = null; - this.exceptionType = null; - this.fallback = null; - } - /** * An {@link AbstractCatchingFuture} that delegates to an {@link AsyncFunction} and {@link * #setFuture(ListenableFuture)}. */ - private static final class AsyncCatchingFuture + private static final class AsyncCatchingFuture extends AbstractCatchingFuture< V, X, AsyncFunction, ListenableFuture> { AsyncCatchingFuture( @@ -190,13 +204,13 @@ private static final class AsyncCatchingFuture @Override ListenableFuture doFallback( AsyncFunction fallback, X cause) throws Exception { - ListenableFuture replacement = fallback.apply(cause); + ListenableFuture output = fallback.apply(cause); checkNotNull( - replacement, + output, "AsyncFunction.apply returned null instead of a Future. " + "Did you mean to return immediateFuture(null)? %s", fallback); - return replacement; + return output; } @Override @@ -209,7 +223,7 @@ void setResult(ListenableFuture result) { * An {@link AbstractCatchingFuture} that delegates to a {@link Function} and {@link * #set(Object)}. */ - private static final class CatchingFuture + private static final class CatchingFuture extends AbstractCatchingFuture, V> { CatchingFuture( ListenableFuture input, @@ -219,13 +233,13 @@ private static final class CatchingFuture } @Override - @NullableDecl + @ParametricNullness V doFallback(Function fallback, X cause) throws Exception { return fallback.apply(cause); } @Override - void setResult(@NullableDecl V result) { + void setResult(@ParametricNullness V result) { set(result); } } diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractExecutionThreadService.java b/android/guava/src/com/google/common/util/concurrent/AbstractExecutionThreadService.java index 2bd392ca6df7..ead4e620dab8 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractExecutionThreadService.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractExecutionThreadService.java @@ -14,15 +14,16 @@ package com.google.common.util.concurrent; -import com.google.common.annotations.Beta; +import static com.google.common.util.concurrent.MoreExecutors.newThread; +import static com.google.common.util.concurrent.MoreExecutors.renamingDecorator; +import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; + import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Supplier; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Base class for services that can implement {@link #startUp}, {@link #run} and {@link #shutDown} @@ -33,58 +34,42 @@ * @since 1.0 */ @GwtIncompatible +@J2ktIncompatible public abstract class AbstractExecutionThreadService implements Service { - private static final Logger logger = - Logger.getLogger(AbstractExecutionThreadService.class.getName()); - /* use AbstractService for state management */ private final Service delegate = new AbstractService() { @Override protected final void doStart() { - Executor executor = - MoreExecutors.renamingDecorator( - executor(), - new Supplier() { - @Override - public String get() { - return serviceName(); - } - }); + Executor executor = renamingDecorator(executor(), () -> serviceName()); executor.execute( - new Runnable() { - @Override - public void run() { - try { - startUp(); - notifyStarted(); - // If stopAsync() is called while starting we may be in the STOPPING state in - // which case we should skip right down to shutdown. - if (isRunning()) { + () -> { + try { + startUp(); + notifyStarted(); + // If stopAsync() is called while starting we may be in the STOPPING state in + // which case we should skip right down to shutdown. + if (isRunning()) { + try { + AbstractExecutionThreadService.this.run(); + } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); try { - AbstractExecutionThreadService.this.run(); - } catch (Throwable t) { - try { - shutDown(); - } catch (Exception ignored) { - // TODO(lukes): if guava ever moves to java7, this would be a good - // candidate for a suppressed exception, or maybe we could generalize - // Closer.Suppressor - logger.log( - Level.WARNING, - "Error while attempting to shut down the service after failure.", - ignored); - } - notifyFailed(t); - return; + shutDown(); + } catch (Exception ignored) { + restoreInterruptIfIsInterruptedException(ignored); + t.addSuppressed(ignored); } + notifyFailed(t); + return; } - - shutDown(); - notifyStopped(); - } catch (Throwable t) { - notifyFailed(t); } + + shutDown(); + notifyStopped(); + } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); + notifyFailed(t); } }); } @@ -146,7 +131,6 @@ protected void shutDown() throws Exception {} * implementing {@code stopping}. Note, however, that {@code stopping} does not run at exactly the * same times as {@code triggerShutdown}. */ - @Beta protected void triggerShutdown() {} /** @@ -160,12 +144,7 @@ protected void triggerShutdown() {} * to the string returned by {@link #serviceName} */ protected Executor executor() { - return new Executor() { - @Override - public void execute(Runnable command) { - MoreExecutors.newThread(serviceName(), command).start(); - } - }; + return command -> newThread(serviceName(), command).start(); } @Override @@ -183,19 +162,25 @@ public final State state() { return delegate.state(); } - /** @since 13.0 */ + /** + * @since 13.0 + */ @Override public final void addListener(Listener listener, Executor executor) { delegate.addListener(listener, executor); } - /** @since 14.0 */ + /** + * @since 14.0 + */ @Override public final Throwable failureCause() { return delegate.failureCause(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service startAsync() { @@ -203,7 +188,9 @@ public final Service startAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service stopAsync() { @@ -211,25 +198,33 @@ public final Service stopAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitRunning() { delegate.awaitRunning(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { delegate.awaitRunning(timeout, unit); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitTerminated() { delegate.awaitTerminated(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { delegate.awaitTerminated(timeout, unit); diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractFuture.java b/android/guava/src/com/google/common/util/concurrent/AbstractFuture.java index ab415cd00422..1e04a155f7a8 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractFuture.java @@ -15,12 +15,15 @@ package com.google.common.util.concurrent; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.common.util.concurrent.NullnessCasts.uncheckedNull; +import static com.google.common.util.concurrent.Platform.interruptCurrentThread; +import static com.google.common.util.concurrent.Platform.rethrowIfErrorOtherThanStackOverflow; import static java.lang.Integer.toHexString; import static java.lang.System.identityHashCode; -import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.logging.Level.SEVERE; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Strings; import com.google.common.util.concurrent.internal.InternalFutureFailureAccess; @@ -28,10 +31,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.ForOverride; import com.google.j2objc.annotations.ReflectionSupport; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.Locale; +import com.google.j2objc.annotations.RetainedLocalRef; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -39,11 +39,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import java.util.concurrent.locks.LockSupport; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * An abstract implementation of {@link ListenableFuture}, intended for advanced users only. More @@ -64,49 +60,45 @@ * @author Luke Sandberg * @since 1.0 */ -// we use non-short circuiting comparisons intentionally -@SuppressWarnings({"ShortCircuitBoolean", "ShouldNotSubclass"}) -@GwtCompatible(emulated = true) +// Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || +@SuppressWarnings("ShortCircuitBoolean") +@GwtCompatible +/* + * TODO(cpovirk): Do we still need @ReflectionSupport on *this* class now that the fields live in + * the superclass? Note that Listener (which we also reflect on) still lives here. + */ @ReflectionSupport(value = ReflectionSupport.Level.FULL) -public abstract class AbstractFuture extends InternalFutureFailureAccess - implements ListenableFuture { - // NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || - - private static final boolean GENERATE_CANCELLATION_CAUSES; - - static { - // System.getProperty may throw if the security policy does not permit access. - boolean generateCancellationCauses; - try { - generateCancellationCauses = - Boolean.parseBoolean( - System.getProperty("guava.concurrent.generate_cancellation_cause", "false")); - } catch (SecurityException e) { - generateCancellationCauses = false; - } - GENERATE_CANCELLATION_CAUSES = generateCancellationCauses; - } +public abstract class AbstractFuture extends AbstractFutureState { + /* + * All static initialization should be performed in AbstractFutureState: AbstractFutureState's + * initialization may trigger logging, which may assume that AbstractFuture is initialized. + * + * TODO(cpovirk): Write a test that asserts that AbstractFuture has no clinit? + */ /** * Tag interface marking trusted subclasses. This enables some optimizations. The implementation * of this interface must also be an AbstractFuture and must not override or expose for overriding * any of the public methods of ListenableFuture. */ - interface Trusted extends ListenableFuture {} + interface Trusted extends ListenableFuture {} /** * A less abstract subclass of AbstractFuture. This can be used to optimize setFuture by ensuring * that {@link #get} calls exactly the implementation of {@link AbstractFuture#get}. */ - abstract static class TrustedFuture extends AbstractFuture implements Trusted { + abstract static class TrustedFuture extends AbstractFuture + implements Trusted { @CanIgnoreReturnValue @Override + @ParametricNullness public final V get() throws InterruptedException, ExecutionException { return super.get(); } @CanIgnoreReturnValue @Override + @ParametricNullness public final V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return super.get(timeout, unit); @@ -134,152 +126,27 @@ public final boolean cancel(boolean mayInterruptIfRunning) { } } - // Logger to log exceptions caught when running listeners. - private static final Logger log = Logger.getLogger(AbstractFuture.class.getName()); - - // A heuristic for timed gets. If the remaining timeout is less than this, spin instead of - // blocking. This value is what AbstractQueuedSynchronizer uses. - private static final long SPIN_THRESHOLD_NANOS = 1000L; - - private static final AtomicHelper ATOMIC_HELPER; - - static { - AtomicHelper helper; - Throwable thrownUnsafeFailure = null; - Throwable thrownAtomicReferenceFieldUpdaterFailure = null; - - try { - helper = new UnsafeAtomicHelper(); - } catch (Throwable unsafeFailure) { - thrownUnsafeFailure = unsafeFailure; - // catch absolutely everything and fall through to our 'SafeAtomicHelper' - // The access control checks that ARFU does means the caller class has to be AbstractFuture - // instead of SafeAtomicHelper, so we annoyingly define these here - try { - helper = - new SafeAtomicHelper( - newUpdater(Waiter.class, Thread.class, "thread"), - newUpdater(Waiter.class, Waiter.class, "next"), - newUpdater(AbstractFuture.class, Waiter.class, "waiters"), - newUpdater(AbstractFuture.class, Listener.class, "listeners"), - newUpdater(AbstractFuture.class, Object.class, "value")); - } catch (Throwable atomicReferenceFieldUpdaterFailure) { - // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause - // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. - // For these users fallback to a suboptimal implementation, based on synchronized. This will - // be a definite performance hit to those users. - thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure; - helper = new SynchronizedHelper(); - } - } - ATOMIC_HELPER = helper; - - // Prevent rare disastrous classloading in first call to LockSupport.park. - // See: https://bugs.openjdk.java.net/browse/JDK-8074773 - @SuppressWarnings("unused") - Class ensureLoaded = LockSupport.class; - - // Log after all static init is finished; if an installed logger uses any Futures methods, it - // shouldn't break in cases where reflection is missing/broken. - if (thrownAtomicReferenceFieldUpdaterFailure != null) { - log.log(Level.SEVERE, "UnsafeAtomicHelper is broken!", thrownUnsafeFailure); - log.log( - Level.SEVERE, "SafeAtomicHelper is broken!", thrownAtomicReferenceFieldUpdaterFailure); - } - } - - /** Waiter links form a Treiber stack, in the {@link #waiters} field. */ - private static final class Waiter { - static final Waiter TOMBSTONE = new Waiter(false /* ignored param */); - - @NullableDecl volatile Thread thread; - @NullableDecl volatile Waiter next; - - /** - * Constructor for the TOMBSTONE, avoids use of ATOMIC_HELPER in case this class is loaded - * before the ATOMIC_HELPER. Apparently this is possible on some android platforms. - */ - Waiter(boolean unused) {} - - Waiter() { - // avoid volatile write, write is made visible by subsequent CAS on waiters field - ATOMIC_HELPER.putThread(this, Thread.currentThread()); - } - - // non-volatile write to the next field. Should be made visible by subsequent CAS on waiters - // field. - void setNext(Waiter next) { - ATOMIC_HELPER.putNext(this, next); - } - - void unpark() { - // This is racy with removeWaiter. The consequence of the race is that we may spuriously call - // unpark even though the thread has already removed itself from the list. But even if we did - // use a CAS, that race would still exist (it would just be ever so slightly smaller). - Thread w = thread; - if (w != null) { - thread = null; - LockSupport.unpark(w); - } - } - } - - /** - * Marks the given node as 'deleted' (null waiter) and then scans the list to unlink all deleted - * nodes. This is an O(n) operation in the common case (and O(n^2) in the worst), but we are saved - * by two things. - * - *

      - *
    • This is only called when a waiting thread times out or is interrupted. Both of which - * should be rare. - *
    • The waiters list should be very short. - *
    - */ - private void removeWaiter(Waiter node) { - node.thread = null; // mark as 'deleted' - restart: - while (true) { - Waiter pred = null; - Waiter curr = waiters; - if (curr == Waiter.TOMBSTONE) { - return; // give up if someone is calling complete - } - Waiter succ; - while (curr != null) { - succ = curr.next; - if (curr.thread != null) { // we aren't unlinking this node, update pred. - pred = curr; - } else if (pred != null) { // We are unlinking this node and it has a predecessor. - pred.next = succ; - if (pred.thread == null) { // We raced with another node that unlinked pred. Restart. - continue restart; - } - } else if (!ATOMIC_HELPER.casWaiters(this, curr, succ)) { // We are unlinking head - continue restart; // We raced with an add or complete - } - curr = succ; - } - break; - } - } - - /** Listeners also form a stack through the {@link #listeners} field. */ - private static final class Listener { - static final Listener TOMBSTONE = new Listener(null, null); - final Runnable task; - final Executor executor; + /** Listeners form a Treiber stack through the {@link #listeners} field. */ + static final class Listener { + static final Listener TOMBSTONE = new Listener(); + // null only for TOMBSTONE + final @Nullable Runnable task; + // null only for TOMBSTONE + final @Nullable Executor executor; // writes to next are made visible by subsequent CAS's on the listeners field - @NullableDecl Listener next; + @Nullable Listener next; Listener(Runnable task, Executor executor) { this.task = task; this.executor = executor; } - } - /** A special value to represent {@code null}. */ - private static final Object NULL = new Object(); + Listener() { + this.task = null; + this.executor = null; + } + } /** A special value to represent failure, when {@link #setException} is called successfully. */ private static final class Failure { @@ -287,7 +154,7 @@ private static final class Failure { new Failure( new Throwable("Failure occurred while trying to finish a future.") { @Override - public synchronized Throwable fillInStackTrace() { + public Throwable fillInStackTrace() { return this; // no stack trace } }); @@ -301,8 +168,8 @@ public synchronized Throwable fillInStackTrace() { /** A special value to represent cancellation and the 'wasInterrupted' bit. */ private static final class Cancellation { // constants to use when GENERATE_CANCELLATION_CAUSES = false - static final Cancellation CAUSELESS_INTERRUPTED; - static final Cancellation CAUSELESS_CANCELLED; + static final @Nullable Cancellation CAUSELESS_INTERRUPTED; + static final @Nullable Cancellation CAUSELESS_CANCELLED; static { if (GENERATE_CANCELLATION_CAUSES) { @@ -315,89 +182,46 @@ private static final class Cancellation { } final boolean wasInterrupted; - @NullableDecl final Throwable cause; + final @Nullable Throwable cause; - Cancellation(boolean wasInterrupted, @NullableDecl Throwable cause) { + Cancellation(boolean wasInterrupted, @Nullable Throwable cause) { this.wasInterrupted = wasInterrupted; this.cause = cause; } } /** A special value that encodes the 'setFuture' state. */ - private static final class SetFuture implements Runnable { + private static final class DelegatingToFuture implements Runnable { final AbstractFuture owner; final ListenableFuture future; - SetFuture(AbstractFuture owner, ListenableFuture future) { + DelegatingToFuture(AbstractFuture owner, ListenableFuture future) { this.owner = owner; this.future = future; } @Override public void run() { - if (owner.value != this) { + if (owner.value() != this) { // nothing to do, we must have been cancelled, don't bother inspecting the future. return; } Object valueToSet = getFutureValue(future); - if (ATOMIC_HELPER.casValue(owner, this, valueToSet)) { - complete(owner); + if (casValue(owner, this, valueToSet)) { + complete( + owner, + /* + * Interruption doesn't propagate through a DelegatingToFuture chain (see + * getFutureValue), so don't invoke interruptTask. + */ + false); } } } - // TODO(lukes): investigate using the @Contended annotation on these fields when jdk8 is - // available. - /** - * This field encodes the current state of the future. - * - *

    The valid values are: - * - *

      - *
    • {@code null} initial state, nothing has happened. - *
    • {@link Cancellation} terminal state, {@code cancel} was called. - *
    • {@link Failure} terminal state, {@code setException} was called. - *
    • {@link SetFuture} intermediate state, {@code setFuture} was called. - *
    • {@link #NULL} terminal state, {@code set(null)} was called. - *
    • Any other non-null value, terminal state, {@code set} was called with a non-null - * argument. - *
    - */ - @NullableDecl private volatile Object value; - - /** All listeners. */ - @NullableDecl private volatile Listener listeners; - - /** All waiting threads. */ - @NullableDecl private volatile Waiter waiters; - /** Constructor for use by subclasses. */ protected AbstractFuture() {} - // Gets and Timed Gets - // - // * Be responsive to interruption - // * Don't create Waiter nodes if you aren't going to park, this helps reduce contention on the - // waiters field. - // * Future completion is defined by when #value becomes non-null/non SetFuture - // * Future completion can be observed if the waiters field contains a TOMBSTONE - - // Timed Get - // There are a few design constraints to consider - // * We want to be responsive to small timeouts, unpark() has non trivial latency overheads (I - // have observed 12 micros on 64 bit linux systems to wake up a parked thread). So if the - // timeout is small we shouldn't park(). This needs to be traded off with the cpu overhead of - // spinning, so we use SPIN_THRESHOLD_NANOS which is what AbstractQueuedSynchronizer uses for - // similar purposes. - // * We want to behave reasonably for timeouts of 0 - // * We are more responsive to completion than timeouts. This is because parkNanos depends on - // system scheduling and as such we could either miss our deadline, or unpark() could be delayed - // so that it looks like we timed out even though we didn't. For comparison FutureTask respects - // completion preferably and AQS is non-deterministic (depends on where in the queue the waiter - // is). If we wanted to be strict about it, we could store the unpark() time in the Waiter node - // and we could use that to make a decision about whether or not we timed out prior to being - // unparked. - /** * {@inheritDoc} * @@ -408,105 +232,10 @@ protected AbstractFuture() {} */ @CanIgnoreReturnValue @Override + @ParametricNullness public V get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException, ExecutionException { - // NOTE: if timeout < 0, remainingNanos will be < 0 and we will fall into the while(true) loop - // at the bottom and throw a timeoutexception. - final long timeoutNanos = unit.toNanos(timeout); // we rely on the implicit null check on unit. - long remainingNanos = timeoutNanos; - if (Thread.interrupted()) { - throw new InterruptedException(); - } - Object localValue = value; - if (localValue != null & !(localValue instanceof SetFuture)) { - return getDoneValue(localValue); - } - // we delay calling nanoTime until we know we will need to either park or spin - final long endNanos = remainingNanos > 0 ? System.nanoTime() + remainingNanos : 0; - long_wait_loop: - if (remainingNanos >= SPIN_THRESHOLD_NANOS) { - Waiter oldHead = waiters; - if (oldHead != Waiter.TOMBSTONE) { - Waiter node = new Waiter(); - do { - node.setNext(oldHead); - if (ATOMIC_HELPER.casWaiters(this, oldHead, node)) { - while (true) { - OverflowAvoidingLockSupport.parkNanos(this, remainingNanos); - // Check interruption first, if we woke up due to interruption we need to honor that. - if (Thread.interrupted()) { - removeWaiter(node); - throw new InterruptedException(); - } - - // Otherwise re-read and check doneness. If we loop then it must have been a spurious - // wakeup - localValue = value; - if (localValue != null & !(localValue instanceof SetFuture)) { - return getDoneValue(localValue); - } - - // timed out? - remainingNanos = endNanos - System.nanoTime(); - if (remainingNanos < SPIN_THRESHOLD_NANOS) { - // Remove the waiter, one way or another we are done parking this thread. - removeWaiter(node); - break long_wait_loop; // jump down to the busy wait loop - } - } - } - oldHead = waiters; // re-read and loop. - } while (oldHead != Waiter.TOMBSTONE); - } - // re-read value, if we get here then we must have observed a TOMBSTONE while trying to add a - // waiter. - return getDoneValue(value); - } - // If we get here then we have remainingNanos < SPIN_THRESHOLD_NANOS and there is no node on the - // waiters list - while (remainingNanos > 0) { - localValue = value; - if (localValue != null & !(localValue instanceof SetFuture)) { - return getDoneValue(localValue); - } - if (Thread.interrupted()) { - throw new InterruptedException(); - } - remainingNanos = endNanos - System.nanoTime(); - } - - String futureToString = toString(); - final String unitString = unit.toString().toLowerCase(Locale.ROOT); - String message = "Waited " + timeout + " " + unit.toString().toLowerCase(Locale.ROOT); - // Only report scheduling delay if larger than our spin threshold - otherwise it's just noise - if (remainingNanos + SPIN_THRESHOLD_NANOS < 0) { - // We over-waited for our timeout. - message += " (plus "; - long overWaitNanos = -remainingNanos; - long overWaitUnits = unit.convert(overWaitNanos, TimeUnit.NANOSECONDS); - long overWaitLeftoverNanos = overWaitNanos - unit.toNanos(overWaitUnits); - boolean shouldShowExtraNanos = - overWaitUnits == 0 || overWaitLeftoverNanos > SPIN_THRESHOLD_NANOS; - if (overWaitUnits > 0) { - message += overWaitUnits + " " + unitString; - if (shouldShowExtraNanos) { - message += ","; - } - message += " "; - } - if (shouldShowExtraNanos) { - message += overWaitLeftoverNanos + " nanoseconds "; - } - - message += "delay)"; - } - // It's confusing to see a completed future in a timeout message; if isDone() returns false, - // then we know it must have given a pending toString value earlier. If not, then the future - // completed after the timeout expired, and the message might be success. - if (isDone()) { - throw new TimeoutException(message + " but future completed as timeout expired"); - } - throw new TimeoutException(message + " for " + futureToString); + return Platform.get(this, timeout, unit); } /** @@ -519,54 +248,54 @@ public V get(long timeout, TimeUnit unit) */ @CanIgnoreReturnValue @Override + @ParametricNullness public V get() throws InterruptedException, ExecutionException { - if (Thread.interrupted()) { - throw new InterruptedException(); - } - Object localValue = value; - if (localValue != null & !(localValue instanceof SetFuture)) { - return getDoneValue(localValue); - } - Waiter oldHead = waiters; - if (oldHead != Waiter.TOMBSTONE) { - Waiter node = new Waiter(); - do { - node.setNext(oldHead); - if (ATOMIC_HELPER.casWaiters(this, oldHead, node)) { - // we are on the stack, now wait for completion. - while (true) { - LockSupport.park(this); - // Check interruption first, if we woke up due to interruption we need to honor that. - if (Thread.interrupted()) { - removeWaiter(node); - throw new InterruptedException(); - } - // Otherwise re-read and check doneness. If we loop then it must have been a spurious - // wakeup - localValue = value; - if (localValue != null & !(localValue instanceof SetFuture)) { - return getDoneValue(localValue); - } - } - } - oldHead = waiters; // re-read and loop. - } while (oldHead != Waiter.TOMBSTONE); + return Platform.get(this); + } + + /** + * Returns the result of this future or throws in case of failure, just like {@link #get()} except + * that this method also throws if this future is not done. + * + *

    This method computes its result based on the internal state of {@link AbstractFuture}, so it + * does not necessarily return the same result as {@link #get()} if {@link #get()} has been + * overridden. Thus, it should be called only on instances of {@link Trusted} or from within + * {@link #get()} itself. + */ + @ParametricNullness + @SuppressWarnings("nullness") // TODO(b/147136275): Remove once our checker understands & and |. + /* + * TODO: b/112550045 - Use this from Futures.getDone when applicable? Note the small difference in + * failure message between the two at present. + */ + final V getFromAlreadyDoneTrustedFuture() throws ExecutionException { + @RetainedLocalRef Object localValue = value(); + if (localValue == null | localValue instanceof DelegatingToFuture) { + throw new IllegalStateException("Cannot get() on a pending future."); } - // re-read value, if we get here then we must have observed a TOMBSTONE while trying to add a - // waiter. - return getDoneValue(value); + return getDoneValue(localValue); } - /** Unboxes {@code obj}. Assumes that obj is not {@code null} or a {@link SetFuture}. */ - private V getDoneValue(Object obj) throws ExecutionException { + /** Unboxes {@code obj}. Assumes that obj is not {@code null} or a {@link DelegatingToFuture}. */ + @ParametricNullness + @SuppressWarnings("TypeParameterUnusedInFormals") // sorry not sorry + static V getDoneValue(Object obj) throws ExecutionException { // While this seems like it might be too branch-y, simple benchmarking proves it to be // unmeasurable (comparing done AbstractFutures with immediateFuture) if (obj instanceof Cancellation) { - throw cancellationExceptionWithCause("Task was cancelled.", ((Cancellation) obj).cause); + Cancellation cancellation = (Cancellation) obj; + Throwable cause = cancellation.cause; + throw cancellationExceptionWithCause("Task was cancelled.", cause); } else if (obj instanceof Failure) { - throw new ExecutionException(((Failure) obj).exception); + Failure failure = (Failure) obj; + Throwable exception = failure.exception; + throw new ExecutionException(exception); } else if (obj == NULL) { - return null; + /* + * It's safe to return null because we would only have stored it in the first place if it were + * a valid value for V. + */ + return uncheckedNull(); } else { @SuppressWarnings("unchecked") // this is the only other option V asV = (V) obj; @@ -574,15 +303,23 @@ private V getDoneValue(Object obj) throws ExecutionException { } } + /** Returns whether {@code obj} is not an instance of {@code DelegatingToFuture}. */ + // This method lets us: + // - avoid exposing DelegatingToFuture to the whole package + // - avoid fighting with the relative operator precedence of `instanceof` and `!` + static boolean notInstanceOfDelegatingToFuture(@Nullable Object obj) { + return !(obj instanceof DelegatingToFuture); + } + @Override public boolean isDone() { - final Object localValue = value; - return localValue != null & !(localValue instanceof SetFuture); + @RetainedLocalRef Object localValue = value(); + return localValue != null & notInstanceOfDelegatingToFuture(localValue); } @Override public boolean isCancelled() { - final Object localValue = value; + @RetainedLocalRef Object localValue = value(); return localValue instanceof Cancellation; } @@ -605,59 +342,66 @@ public boolean isCancelled() { @CanIgnoreReturnValue @Override public boolean cancel(boolean mayInterruptIfRunning) { - Object localValue = value; + @RetainedLocalRef Object localValue = value(); boolean rValue = false; - if (localValue == null | localValue instanceof SetFuture) { + if (localValue == null | localValue instanceof DelegatingToFuture) { // Try to delay allocating the exception. At this point we may still lose the CAS, but it is // certainly less likely. Object valueToSet = GENERATE_CANCELLATION_CAUSES ? new Cancellation( mayInterruptIfRunning, new CancellationException("Future.cancel() was called.")) - : (mayInterruptIfRunning - ? Cancellation.CAUSELESS_INTERRUPTED - : Cancellation.CAUSELESS_CANCELLED); + /* + * requireNonNull is safe because we've initialized these if + * !GENERATE_CANCELLATION_CAUSES. + * + * TODO(cpovirk): Maybe it would be cleaner to define a CancellationSupplier interface + * with two implementations, one that contains causeless Cancellation instances and + * the other of which creates new Cancellation instances each time it's called? Yet + * another alternative is to fill in a non-null value for each of the fields no matter + * what and to just not use it if !GENERATE_CANCELLATION_CAUSES. + */ + : requireNonNull( + mayInterruptIfRunning + ? Cancellation.CAUSELESS_INTERRUPTED + : Cancellation.CAUSELESS_CANCELLED); AbstractFuture abstractFuture = this; while (true) { - if (ATOMIC_HELPER.casValue(abstractFuture, localValue, valueToSet)) { + if (casValue(abstractFuture, localValue, valueToSet)) { rValue = true; - // We call interruptTask before calling complete(), which is consistent with - // FutureTask - if (mayInterruptIfRunning) { - abstractFuture.interruptTask(); - } - complete(abstractFuture); - if (localValue instanceof SetFuture) { + complete(abstractFuture, mayInterruptIfRunning); + if (localValue instanceof DelegatingToFuture) { // propagate cancellation to the future set in setfuture, this is racy, and we don't // care if we are successful or not. - ListenableFuture futureToPropagateTo = ((SetFuture) localValue).future; + ListenableFuture futureToPropagateTo = ((DelegatingToFuture) localValue).future; if (futureToPropagateTo instanceof Trusted) { - // If the future is a TrustedFuture then we specifically avoid calling cancel() + // If the future is a Trusted instance then we specifically avoid calling cancel() // this has 2 benefits // 1. for long chains of futures strung together with setFuture we consume less stack // 2. we avoid allocating Cancellation objects at every level of the cancellation // chain - // We can only do this for TrustedFuture, because TrustedFuture.cancel is final and - // does nothing but delegate to this method. + // We can only do this for Trusted, because Trusted implementations of cancel do + // nothing but delegate to this method and do not permit user overrides. AbstractFuture trusted = (AbstractFuture) futureToPropagateTo; - localValue = trusted.value; - if (localValue == null | localValue instanceof SetFuture) { + localValue = trusted.value(); + if (localValue == null | localValue instanceof DelegatingToFuture) { abstractFuture = trusted; continue; // loop back up and try to complete the new future } } else { - // not a TrustedFuture, call cancel directly. + // not a Trusted instance, call cancel directly. futureToPropagateTo.cancel(mayInterruptIfRunning); } } break; } // obj changed, reread - localValue = abstractFuture.value; - if (!(localValue instanceof SetFuture)) { + localValue = abstractFuture.value(); + if (notInstanceOfDelegatingToFuture(localValue)) { // obj cannot be null at this point, because value can only change from null to non-null. // So if value changed (and it did since we lost the CAS), then it cannot be null and - // since it isn't a SetFuture, then the future must be done and we should exit the loop + // since it isn't a DelegatingToFuture, then the future must be done and we should exit + // the loop break; } } @@ -671,7 +415,7 @@ mayInterruptIfRunning, new CancellationException("Future.cancel() was called.")) * *

    The default implementation does nothing. * - *

    This method is likely to be deprecated. Prefer to override {@link #afterDone}, consulting + *

    This method is likely to be deprecated. Prefer to override {@link #afterDone}, checking * {@link #wasInterrupted} to decide whether to interrupt your task. * * @since 10.0 @@ -685,7 +429,7 @@ protected void interruptTask() {} * @since 14.0 */ protected final boolean wasInterrupted() { - final Object localValue = value; + @RetainedLocalRef Object localValue = value(); return (localValue instanceof Cancellation) && ((Cancellation) localValue).wasInterrupted; } @@ -708,15 +452,15 @@ public void addListener(Runnable listener, Executor executor) { // get into the loop we know that we weren't done when we entered and therefore we aren't under // an obligation to execute 'immediately'. if (!isDone()) { - Listener oldHead = listeners; + Listener oldHead = listeners(); if (oldHead != Listener.TOMBSTONE) { Listener newNode = new Listener(listener, executor); do { newNode.next = oldHead; - if (ATOMIC_HELPER.casListeners(this, oldHead, newNode)) { + if (casListeners(oldHead, newNode)) { return; } - oldHead = listeners; // re-read + oldHead = listeners(); // re-read } while (oldHead != Listener.TOMBSTONE); } } @@ -741,10 +485,10 @@ public void addListener(Runnable listener, Executor executor) { * @return true if the attempt was accepted, completing the {@code Future} */ @CanIgnoreReturnValue - protected boolean set(@NullableDecl V value) { + protected boolean set(@ParametricNullness V value) { Object valueToSet = value == null ? NULL : value; - if (ATOMIC_HELPER.casValue(this, null, valueToSet)) { - complete(this); + if (casValue(this, null, valueToSet)) { + complete(this, /* callInterruptTask= */ false); return true; } return false; @@ -768,8 +512,8 @@ protected boolean set(@NullableDecl V value) { @CanIgnoreReturnValue protected boolean setException(Throwable throwable) { Object valueToSet = new Failure(checkNotNull(throwable)); - if (ATOMIC_HELPER.casValue(this, null, valueToSet)) { - complete(this); + if (casValue(this, null, valueToSet)) { + complete(this, /* callInterruptTask= */ false); return true; } return false; @@ -805,40 +549,49 @@ protected boolean setException(Throwable throwable) { * @since 19.0 */ @CanIgnoreReturnValue + @SuppressWarnings("Interruption") // We are propagating an interrupt from a caller. protected boolean setFuture(ListenableFuture future) { checkNotNull(future); - Object localValue = value; + @RetainedLocalRef Object localValue = value(); if (localValue == null) { if (future.isDone()) { Object value = getFutureValue(future); - if (ATOMIC_HELPER.casValue(this, null, value)) { - complete(this); + if (casValue(this, null, value)) { + complete( + this, + /* + * Interruption doesn't propagate through a DelegatingToFuture chain (see + * getFutureValue), so don't invoke interruptTask. + */ + false); return true; } return false; } - SetFuture valueToSet = new SetFuture(this, future); - if (ATOMIC_HELPER.casValue(this, null, valueToSet)) { + DelegatingToFuture valueToSet = new DelegatingToFuture<>(this, future); + if (casValue(this, null, valueToSet)) { // the listener is responsible for calling completeWithFuture, directExecutor is appropriate // since all we are doing is unpacking a completed future which should be fast. try { future.addListener(valueToSet, DirectExecutor.INSTANCE); } catch (Throwable t) { - // addListener has thrown an exception! SetFuture.run can't throw any exceptions so this - // must have been caused by addListener itself. The most likely explanation is a + // Any Exception is either a RuntimeException or sneaky checked exception. + // + // addListener has thrown an exception! DelegatingToFuture.run can't throw any exceptions + // so this must have been caused by addListener itself. The most likely explanation is a // misconfigured mock. Try to switch to Failure. Failure failure; try { failure = new Failure(t); - } catch (Throwable oomMostLikely) { + } catch (Exception | Error oomMostLikely) { // sneaky checked exception failure = Failure.FALLBACK_INSTANCE; } // Note: The only way this CAS could fail is if cancel() has raced with us. That is ok. - boolean unused = ATOMIC_HELPER.casValue(this, valueToSet, failure); + boolean unused = casValue(this, valueToSet, failure); } return true; } - localValue = value; // we lost the cas, fall through and maybe cancel + localValue = value(); // we lost the cas, fall through and maybe cancel } // The future has already been set to something. If it is cancellation we should cancel the // incoming future. @@ -857,11 +610,11 @@ protected boolean setFuture(ListenableFuture future) { */ private static Object getFutureValue(ListenableFuture future) { if (future instanceof Trusted) { - // Break encapsulation for TrustedFuture instances since we know that subclasses cannot - // override .get() (since it is final) and therefore this is equivalent to calling .get() - // and unpacking the exceptions like we do below (just much faster because it is a single - // field read instead of a read, several branches and possibly creating exceptions). - Object v = ((AbstractFuture) future).value; + // Break encapsulation for Trusted instances since we know that subclasses cannot override + // .get() and therefore this is equivalent to calling .get() and unpacking the exceptions like + // we do below (just much faster because it is a single field read instead of a read, several + // branches and possibly creating exceptions). + Object v = ((AbstractFuture) future).value(); if (v instanceof Cancellation) { // If the other future was interrupted, clear the interrupted bit while preserving the cause // this will make it consistent with how non-trustedfutures work which cannot propagate the @@ -874,7 +627,8 @@ private static Object getFutureValue(ListenableFuture future) { : Cancellation.CAUSELESS_CANCELLED; } } - return v; + // requireNonNull is safe as long as we call this method only on completed futures. + return requireNonNull(v); } if (future instanceof InternalFutureFailureAccess) { Throwable throwable = @@ -886,7 +640,11 @@ private static Object getFutureValue(ListenableFuture future) { boolean wasCancelled = future.isCancelled(); // Don't allocate a CancellationException if it's not necessary if (!GENERATE_CANCELLATION_CAUSES & wasCancelled) { - return Cancellation.CAUSELESS_CANCELLED; + /* + * requireNonNull is safe because we've initialized CAUSELESS_CANCELLED if + * !GENERATE_CANCELLATION_CAUSES. + */ + return requireNonNull(Cancellation.CAUSELESS_CANCELLED); } // Otherwise calculate the value by calling .get() try { @@ -920,7 +678,7 @@ private static Object getFutureValue(ListenableFuture future) { cancellation)); } return new Cancellation(false, cancellation); - } catch (Throwable t) { + } catch (Exception | Error t) { // sneaky checked exception return new Failure(t); } } @@ -929,7 +687,9 @@ private static Object getFutureValue(ListenableFuture future) { * An inlined private copy of {@link Uninterruptibles#getUninterruptibly} used to break an * internal dependency on other /util/concurrent classes. */ - private static V getUninterruptibly(Future future) throws ExecutionException { + @ParametricNullness + private static V getUninterruptibly(Future future) + throws ExecutionException { boolean interrupted = false; try { while (true) { @@ -941,17 +701,32 @@ private static V getUninterruptibly(Future future) throws ExecutionExcept } } finally { if (interrupted) { - Thread.currentThread().interrupt(); + interruptCurrentThread(); } } } /** Unblocks all threads and runs all listeners. */ - private static void complete(AbstractFuture future) { - Listener next = null; + private static void complete(AbstractFuture param, boolean callInterruptTask) { + // Declare a "true" local variable so that the Checker Framework will infer nullness. + @RetainedLocalRef AbstractFuture future = param; + + @RetainedLocalRef Listener next = null; outer: while (true) { future.releaseWaiters(); + /* + * We call interruptTask() immediately before afterDone() so that migrating between the two + * can be a no-op. + */ + if (callInterruptTask) { + future.interruptTask(); + /* + * Interruption doesn't propagate through a DelegatingToFuture chain (see getFutureValue), + * so don't invoke interruptTask on any subsequent futures. + */ + callInterruptTask = false; + } // We call this before the listeners in order to avoid needing to manage a separate stack data // structure for them. Also, some implementations rely on this running prior to listeners // so that the cleanup work is visible to listeners. @@ -962,26 +737,34 @@ private static void complete(AbstractFuture future) { next = future.clearListeners(next); future = null; while (next != null) { - Listener curr = next; + @RetainedLocalRef Listener curr = next; next = next.next; - Runnable task = curr.task; - if (task instanceof SetFuture) { - SetFuture setFuture = (SetFuture) task; + /* + * requireNonNull is safe because the listener stack never contains TOMBSTONE until after + * clearListeners. + */ + Runnable task = requireNonNull(curr.task); + if (task instanceof DelegatingToFuture) { + DelegatingToFuture setFuture = (DelegatingToFuture) task; // We unwind setFuture specifically to avoid StackOverflowErrors in the case of long - // chains of SetFutures + // chains of DelegatingToFutures // Handling this special case is important because there is no way to pass an executor to // setFuture, so a user couldn't break the chain by doing this themselves. It is also // potentially common if someone writes a recursive Futures.transformAsync transformer. future = setFuture.owner; - if (future.value == setFuture) { + if (future.value() == setFuture) { Object valueToSet = getFutureValue(setFuture.future); - if (ATOMIC_HELPER.casValue(future, setFuture, valueToSet)) { + if (casValue(future, setFuture, valueToSet)) { continue outer; } } - // other wise the future we were trying to set is already done. + // otherwise the future we were trying to set is already done. } else { - executeListener(task, curr.executor); + /* + * requireNonNull is safe because the listener stack never contains TOMBSTONE until after + * clearListeners. + */ + executeListener(task, requireNonNull(curr.executor)); } } break; @@ -999,7 +782,6 @@ private static void complete(AbstractFuture future) { * * @since 20.0 */ - @Beta @ForOverride protected void afterDone() {} @@ -1025,12 +807,16 @@ protected void afterDone() {} * @since 27.0 */ @Override - @NullableDecl - protected final Throwable tryInternalFastPathGetFailure() { + /* + * We should annotate the superclass, InternalFutureFailureAccess, to say that its copy of this + * method returns @Nullable, too. However, we're not sure if we want to make any changes to that + * class, since it's in a separate artifact that we planned to release only a single version of. + */ + protected final @Nullable Throwable tryInternalFastPathGetFailure() { if (this instanceof Trusted) { - Object obj = value; - if (obj instanceof Failure) { - return ((Failure) obj).exception; + @RetainedLocalRef Object localValue = value(); + if (localValue instanceof Failure) { + return ((Failure) localValue).exception; } } return null; @@ -1040,38 +826,25 @@ protected final Throwable tryInternalFastPathGetFailure() { * If this future has been cancelled (and possibly interrupted), cancels (and possibly interrupts) * the given future (if available). */ - final void maybePropagateCancellationTo(@NullableDecl Future related) { + @SuppressWarnings("nullness") // TODO(b/147136275): Remove once our checker understands & and |. + final void maybePropagateCancellationTo(@Nullable Future related) { if (related != null & isCancelled()) { related.cancel(wasInterrupted()); } } - /** Releases all threads in the {@link #waiters} list, and clears the list. */ - private void releaseWaiters() { - Waiter head; - do { - head = waiters; - } while (!ATOMIC_HELPER.casWaiters(this, head, Waiter.TOMBSTONE)); - for (Waiter currentWaiter = head; currentWaiter != null; currentWaiter = currentWaiter.next) { - currentWaiter.unpark(); - } - } - /** * Clears the {@link #listeners} list and prepends its contents to {@code onto}, least recently * added first. */ - private Listener clearListeners(Listener onto) { + private @Nullable Listener clearListeners(@Nullable Listener onto) { // We need to - // 1. atomically swap the listeners with TOMBSTONE, this is because addListener uses that to + // 1. atomically swap the listeners with TOMBSTONE, this is because addListener uses that // to synchronize with us // 2. reverse the linked list, because despite our rather clear contract, people depend on us // executing listeners in the order they were added // 3. push all the items onto 'onto' and return the new head of the stack - Listener head; - do { - head = listeners; - } while (!ATOMIC_HELPER.casListeners(this, head, Listener.TOMBSTONE)); + Listener head = gasListeners(Listener.TOMBSTONE); Listener reversedList = onto; while (head != null) { Listener tmp = head; @@ -1098,7 +871,7 @@ public String toString() { } else if (isDone()) { addDoneString(builder); } else { - addPendingString(builder); // delegates to addDoneString if future completes mid-way + addPendingString(builder); // delegates to addDoneString if future completes midway } return builder.append("]").toString(); } @@ -1109,17 +882,15 @@ public String toString() { * @return null if an explanation cannot be provided (e.g. because the future is done). * @since 23.0 */ - @NullableDecl - protected String pendingToString() { + protected @Nullable String pendingToString() { // TODO(diamondm) consider moving this into addPendingString so it's always in the output if (this instanceof ScheduledFuture) { - return "remaining delay=[" - + ((ScheduledFuture) this).getDelay(TimeUnit.MILLISECONDS) - + " ms]"; + return "remaining delay=[" + ((ScheduledFuture) this).getDelay(MILLISECONDS) + " ms]"; } return null; } + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception private void addPendingString(StringBuilder builder) { // Capture current builder length so it can be truncated if this future ends up completing while // the toString is being calculated @@ -1127,16 +898,23 @@ private void addPendingString(StringBuilder builder) { builder.append("PENDING"); - Object localValue = value; - if (localValue instanceof SetFuture) { + @RetainedLocalRef Object localValue = value(); + if (localValue instanceof DelegatingToFuture) { builder.append(", setFuture=["); - appendUserObject(builder, ((SetFuture) localValue).future); + appendUserObject(builder, ((DelegatingToFuture) localValue).future); builder.append("]"); } else { String pendingDescription; try { pendingDescription = Strings.emptyToNull(pendingToString()); - } catch (RuntimeException | StackOverflowError e) { + } catch (Throwable e) { + /* + * We want to catch (Exception | StackOverflowError), but we can't under environments where + * StackOverflowError doesn't exist. + */ + rethrowIfErrorOtherThanStackOverflow(e); + // The Throwable is either a RuntimeException, an Error, or sneaky checked exception. + // // Don't call getMessage or toString() on the exception, in case the exception thrown by the // subclass is implemented with bugs similar to the subclass. pendingDescription = "Exception thrown from implementation: " + e.getClass(); @@ -1155,6 +933,7 @@ private void addPendingString(StringBuilder builder) { } } + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception private void addDoneString(StringBuilder builder) { try { V value = getUninterruptibly(this); @@ -1165,7 +944,7 @@ private void addDoneString(StringBuilder builder) { builder.append("FAILURE, cause=[").append(e.getCause()).append("]"); } catch (CancellationException e) { builder.append("CANCELLED"); // shouldn't be reachable - } catch (RuntimeException e) { + } catch (Exception e) { // sneaky checked exception builder.append("UNKNOWN, cause=[").append(e.getClass()).append(" thrown from get()]"); } } @@ -1175,7 +954,7 @@ private void addDoneString(StringBuilder builder) { * implementation. Using a reconstruction of the default Object.toString() prevents OOMs and stack * overflows, and helps avoid sensitive data inadvertently ending up in exception messages. */ - private void appendResultObject(StringBuilder builder, Object o) { + private void appendResultObject(StringBuilder builder, @Nullable Object o) { if (o == null) { builder.append("null"); } else if (o == this) { @@ -1189,9 +968,10 @@ private void appendResultObject(StringBuilder builder, Object o) { } /** Helper for printing user supplied objects into our toString method. */ - private void appendUserObject(StringBuilder builder, Object o) { + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception + private void appendUserObject(StringBuilder builder, @Nullable Object o) { // This is some basic recursion detection for when people create cycles via set/setFuture or - // when deep chains of futures exist resulting in a StackOverflowException. We could detect + // when deep chains of futures exist resulting in a StackOverflowError. We could detect // arbitrary cycles using a thread local but this should be a good enough solution (it is also // what jdk collections do in these cases) try { @@ -1200,7 +980,14 @@ private void appendUserObject(StringBuilder builder, Object o) { } else { builder.append(o); } - } catch (RuntimeException | StackOverflowError e) { + } catch (Throwable e) { + /* + * We want to catch (Exception | StackOverflowError), but we can't under environments where + * StackOverflowError doesn't exist. + */ + rethrowIfErrorOtherThanStackOverflow(e); + // The Throwable is either a RuntimeException, an Error, or sneaky checked exception. + // // Don't call getMessage or toString() on the exception, in case the exception thrown by the // user object is implemented with bugs similar to the user object. builder.append("Exception thrown from implementation: ").append(e.getClass()); @@ -1211,222 +998,26 @@ private void appendUserObject(StringBuilder builder, Object o) { * Submits the given runnable to the given {@link Executor} catching and logging all {@linkplain * RuntimeException runtime exceptions} thrown by the executor. */ + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception private static void executeListener(Runnable runnable, Executor executor) { try { executor.execute(runnable); - } catch (RuntimeException e) { + } catch (Exception e) { // sneaky checked exception // Log it and keep going -- bad runnable and/or executor. Don't punish the other runnables if - // we're given a bad one. We only catch RuntimeException because we want Errors to propagate - // up. - log.log( - Level.SEVERE, - "RuntimeException while executing runnable " + runnable + " with executor " + executor, - e); - } - } - - private abstract static class AtomicHelper { - /** Non volatile write of the thread to the {@link Waiter#thread} field. */ - abstract void putThread(Waiter waiter, Thread newValue); - - /** Non volatile write of the waiter to the {@link Waiter#next} field. */ - abstract void putNext(Waiter waiter, Waiter newValue); - - /** Performs a CAS operation on the {@link #waiters} field. */ - abstract boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update); - - /** Performs a CAS operation on the {@link #listeners} field. */ - abstract boolean casListeners(AbstractFuture future, Listener expect, Listener update); - - /** Performs a CAS operation on the {@link #value} field. */ - abstract boolean casValue(AbstractFuture future, Object expect, Object update); - } - - /** - * {@link AtomicHelper} based on {@link sun.misc.Unsafe}. - * - *

    Static initialization of this class will fail if the {@link sun.misc.Unsafe} object cannot - * be accessed. - */ - @SuppressWarnings("sunapi") - private static final class UnsafeAtomicHelper extends AtomicHelper { - static final sun.misc.Unsafe UNSAFE; - static final long LISTENERS_OFFSET; - static final long WAITERS_OFFSET; - static final long VALUE_OFFSET; - static final long WAITER_THREAD_OFFSET; - static final long WAITER_NEXT_OFFSET; - - static { - sun.misc.Unsafe unsafe = null; - try { - unsafe = sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) { - try { - unsafe = - AccessController.doPrivileged( - new PrivilegedExceptionAction() { - @Override - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) { - return k.cast(x); - } - } - throw new NoSuchFieldError("the Unsafe"); - } - }); - } catch (PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", e.getCause()); - } - } - try { - Class abstractFuture = AbstractFuture.class; - WAITERS_OFFSET = unsafe.objectFieldOffset(abstractFuture.getDeclaredField("waiters")); - LISTENERS_OFFSET = unsafe.objectFieldOffset(abstractFuture.getDeclaredField("listeners")); - VALUE_OFFSET = unsafe.objectFieldOffset(abstractFuture.getDeclaredField("value")); - WAITER_THREAD_OFFSET = unsafe.objectFieldOffset(Waiter.class.getDeclaredField("thread")); - WAITER_NEXT_OFFSET = unsafe.objectFieldOffset(Waiter.class.getDeclaredField("next")); - UNSAFE = unsafe; - } catch (Exception e) { - throwIfUnchecked(e); - throw new RuntimeException(e); - } - } - - @Override - void putThread(Waiter waiter, Thread newValue) { - UNSAFE.putObject(waiter, WAITER_THREAD_OFFSET, newValue); - } - - @Override - void putNext(Waiter waiter, Waiter newValue) { - UNSAFE.putObject(waiter, WAITER_NEXT_OFFSET, newValue); - } - - /** Performs a CAS operation on the {@link #waiters} field. */ - @Override - boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update) { - return UNSAFE.compareAndSwapObject(future, WAITERS_OFFSET, expect, update); - } - - /** Performs a CAS operation on the {@link #listeners} field. */ - @Override - boolean casListeners(AbstractFuture future, Listener expect, Listener update) { - return UNSAFE.compareAndSwapObject(future, LISTENERS_OFFSET, expect, update); - } - - /** Performs a CAS operation on the {@link #value} field. */ - @Override - boolean casValue(AbstractFuture future, Object expect, Object update) { - return UNSAFE.compareAndSwapObject(future, VALUE_OFFSET, expect, update); - } - } - - /** {@link AtomicHelper} based on {@link AtomicReferenceFieldUpdater}. */ - @SuppressWarnings("rawtypes") - private static final class SafeAtomicHelper extends AtomicHelper { - final AtomicReferenceFieldUpdater waiterThreadUpdater; - final AtomicReferenceFieldUpdater waiterNextUpdater; - final AtomicReferenceFieldUpdater waitersUpdater; - final AtomicReferenceFieldUpdater listenersUpdater; - final AtomicReferenceFieldUpdater valueUpdater; - - SafeAtomicHelper( - AtomicReferenceFieldUpdater waiterThreadUpdater, - AtomicReferenceFieldUpdater waiterNextUpdater, - AtomicReferenceFieldUpdater waitersUpdater, - AtomicReferenceFieldUpdater listenersUpdater, - AtomicReferenceFieldUpdater valueUpdater) { - this.waiterThreadUpdater = waiterThreadUpdater; - this.waiterNextUpdater = waiterNextUpdater; - this.waitersUpdater = waitersUpdater; - this.listenersUpdater = listenersUpdater; - this.valueUpdater = valueUpdater; - } - - @Override - void putThread(Waiter waiter, Thread newValue) { - waiterThreadUpdater.lazySet(waiter, newValue); - } - - @Override - void putNext(Waiter waiter, Waiter newValue) { - waiterNextUpdater.lazySet(waiter, newValue); - } - - @Override - boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update) { - return waitersUpdater.compareAndSet(future, expect, update); - } - - @Override - boolean casListeners(AbstractFuture future, Listener expect, Listener update) { - return listenersUpdater.compareAndSet(future, expect, update); - } - - @Override - boolean casValue(AbstractFuture future, Object expect, Object update) { - return valueUpdater.compareAndSet(future, expect, update); - } - } - - /** - * {@link AtomicHelper} based on {@code synchronized} and volatile writes. - * - *

    This is an implementation of last resort for when certain basic VM features are broken (like - * AtomicReferenceFieldUpdater). - */ - private static final class SynchronizedHelper extends AtomicHelper { - @Override - void putThread(Waiter waiter, Thread newValue) { - waiter.thread = newValue; - } - - @Override - void putNext(Waiter waiter, Waiter newValue) { - waiter.next = newValue; - } - - @Override - boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update) { - synchronized (future) { - if (future.waiters == expect) { - future.waiters = update; - return true; - } - return false; - } - } - - @Override - boolean casListeners(AbstractFuture future, Listener expect, Listener update) { - synchronized (future) { - if (future.listeners == expect) { - future.listeners = update; - return true; - } - return false; - } - } - - @Override - boolean casValue(AbstractFuture future, Object expect, Object update) { - synchronized (future) { - if (future.value == expect) { - future.value = update; - return true; - } - return false; - } + // we're given a bad one. We only catch Exception because we want Errors to propagate up. + log.get() + .log( + SEVERE, + "RuntimeException while executing runnable " + + runnable + + " with executor " + + executor, + e); } } private static CancellationException cancellationExceptionWithCause( - @NullableDecl String message, @NullableDecl Throwable cause) { + String message, @Nullable Throwable cause) { CancellationException exception = new CancellationException(message); exception.initCause(cause); return exception; diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractFutureState.java b/android/guava/src/com/google/common/util/concurrent/AbstractFutureState.java new file mode 100644 index 000000000000..5468bec97eb2 --- /dev/null +++ b/android/guava/src/com/google/common/util/concurrent/AbstractFutureState.java @@ -0,0 +1,831 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.util.concurrent.AbstractFuture.getDoneValue; +import static com.google.common.util.concurrent.AbstractFuture.notInstanceOfDelegatingToFuture; +import static java.lang.Boolean.parseBoolean; +import static java.security.AccessController.doPrivileged; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; +import static java.util.logging.Level.SEVERE; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.util.concurrent.AbstractFuture.Listener; +import com.google.common.util.concurrent.internal.InternalFutureFailureAccess; +import com.google.j2objc.annotations.ReflectionSupport; +import com.google.j2objc.annotations.RetainedLocalRef; +import java.lang.reflect.Field; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Locale; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.locks.LockSupport; +import org.jspecify.annotations.Nullable; +import sun.misc.Unsafe; + +/** Supertype of {@link AbstractFuture} that contains platform-specific functionality. */ +// Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || +@SuppressWarnings("ShortCircuitBoolean") +@GwtCompatible +@ReflectionSupport(value = ReflectionSupport.Level.FULL) +abstract class AbstractFutureState extends InternalFutureFailureAccess + implements ListenableFuture { + /** + * Performs a {@linkplain java.lang.invoke.VarHandle#compareAndSet compare-and-set} operation on + * {@link #listenersField}. + */ + final boolean casListeners(@Nullable Listener expect, Listener update) { + return ATOMIC_HELPER.casListeners(this, expect, update); + } + + /** + * Performs a {@linkplain java.lang.invoke.VarHandle#getAndSet get-and-set} operation on {@link + * #listenersField}. + */ + final @Nullable Listener gasListeners(Listener update) { + return ATOMIC_HELPER.gasListeners(this, update); + } + + /** + * Performs a {@linkplain java.lang.invoke.VarHandle#compareAndSet compare-and-set} operation on + * {@link #valueField} of {@code future}. + */ + static boolean casValue(AbstractFutureState future, @Nullable Object expect, Object update) { + return ATOMIC_HELPER.casValue(future, expect, update); + } + + /** Returns the value of the future, using a volatile read. */ + final @Nullable Object value() { + return valueField; + } + + /** Returns the head of the listener stack, using a volatile read. */ + final @Nullable Listener listeners() { + return listenersField; + } + + /** Releases all threads in the {@link #waitersField} list, and clears the list. */ + final void releaseWaiters() { + Waiter head = gasWaiters(Waiter.TOMBSTONE); + for (Waiter currentWaiter = head; currentWaiter != null; currentWaiter = currentWaiter.next) { + currentWaiter.unpark(); + } + } + + // Gets and Timed Gets + // + // * Be responsive to interruption + // * Don't create Waiter nodes if you aren't going to park, this helps reduce contention on + // waitersField. + // * Future completion is defined by when #valueField becomes non-null/non DelegatingToFuture + // * Future completion can be observed if the waitersField field contains a TOMBSTONE + + // Timed Get + // There are a few design constraints to consider + // * We want to be responsive to small timeouts, unpark() has non trivial latency overheads (I + // have observed 12 micros on 64-bit linux systems to wake up a parked thread). So if the + // timeout is small we shouldn't park(). This needs to be traded off with the cpu overhead of + // spinning, so we use SPIN_THRESHOLD_NANOS which is what AbstractQueuedSynchronizer uses for + // similar purposes. + // * We want to behave reasonably for timeouts of 0 + // * We are more responsive to completion than timeouts. This is because parkNanos depends on + // system scheduling and as such we could either miss our deadline, or unpark() could be delayed + // so that it looks like we timed out even though we didn't. For comparison FutureTask respects + // completion preferably and AQS is non-deterministic (depends on where in the queue the waiter + // is). If we wanted to be strict about it, we could store the unpark() time in the Waiter node + // and we could use that to make a decision about whether or not we timed out prior to being + // unparked. + + @SuppressWarnings({ + "LabelledBreakTarget", // TODO(b/345814817): Maybe fix? + "nullness", // TODO(b/147136275): Remove once our checker understands & and |. + }) + @ParametricNullness + final V blockingGet(long timeout, TimeUnit unit) + throws InterruptedException, TimeoutException, ExecutionException { + // NOTE: if timeout < 0, remainingNanos will be < 0 and we will fall into the while(true) loop + // at the bottom and throw a timeoutexception. + long timeoutNanos = unit.toNanos(timeout); // we rely on the implicit null check on unit. + long remainingNanos = timeoutNanos; + if (Thread.interrupted()) { + throw new InterruptedException(); + } + @RetainedLocalRef Object localValue = valueField; + if (localValue != null & notInstanceOfDelegatingToFuture(localValue)) { + return getDoneValue(localValue); + } + // we delay calling nanoTime until we know we will need to either park or spin + long endNanos = remainingNanos > 0 ? System.nanoTime() + remainingNanos : 0; + long_wait_loop: + if (remainingNanos >= SPIN_THRESHOLD_NANOS) { + Waiter oldHead = waitersField; + if (oldHead != Waiter.TOMBSTONE) { + Waiter node = new Waiter(); + do { + node.setNext(oldHead); + if (casWaiters(oldHead, node)) { + while (true) { + OverflowAvoidingLockSupport.parkNanos(this, remainingNanos); + // Check interruption first, if we woke up due to interruption we need to honor that. + if (Thread.interrupted()) { + removeWaiter(node); + throw new InterruptedException(); + } + + // Otherwise re-read and check doneness. If we loop then it must have been a spurious + // wakeup + localValue = valueField; + if (localValue != null & notInstanceOfDelegatingToFuture(localValue)) { + return getDoneValue(localValue); + } + + // timed out? + remainingNanos = endNanos - System.nanoTime(); + if (remainingNanos < SPIN_THRESHOLD_NANOS) { + // Remove the waiter, one way or another we are done parking this thread. + removeWaiter(node); + break long_wait_loop; // jump down to the busy wait loop + } + } + } + oldHead = waitersField; // re-read and loop. + } while (oldHead != Waiter.TOMBSTONE); + } + // re-read valueField, if we get here then we must have observed a TOMBSTONE while trying to + // add a waiter. + // requireNonNull is safe because valueField is always set before TOMBSTONE. + return getDoneValue(requireNonNull(valueField)); + } + // If we get here then we have remainingNanos < SPIN_THRESHOLD_NANOS and there is no node on the + // waiters list + while (remainingNanos > 0) { + localValue = valueField; + if (localValue != null & notInstanceOfDelegatingToFuture(localValue)) { + return getDoneValue(localValue); + } + if (Thread.interrupted()) { + throw new InterruptedException(); + } + remainingNanos = endNanos - System.nanoTime(); + } + + String futureToString = toString(); + String unitString = unit.toString().toLowerCase(Locale.ROOT); + String message = "Waited " + timeout + " " + unit.toString().toLowerCase(Locale.ROOT); + // Only report scheduling delay if larger than our spin threshold - otherwise it's just noise + if (remainingNanos + SPIN_THRESHOLD_NANOS < 0) { + // We over-waited for our timeout. + message += " (plus "; + long overWaitNanos = -remainingNanos; + long overWaitUnits = unit.convert(overWaitNanos, NANOSECONDS); + long overWaitLeftoverNanos = overWaitNanos - unit.toNanos(overWaitUnits); + boolean shouldShowExtraNanos = + overWaitUnits == 0 || overWaitLeftoverNanos > SPIN_THRESHOLD_NANOS; + if (overWaitUnits > 0) { + message += overWaitUnits + " " + unitString; + if (shouldShowExtraNanos) { + message += ","; + } + message += " "; + } + if (shouldShowExtraNanos) { + message += overWaitLeftoverNanos + " nanoseconds "; + } + + message += "delay)"; + } + // It's confusing to see a completed future in a timeout message; if isDone() returns false, + // then we know it must have given a pending toString value earlier. If not, then the future + // completed after the timeout expired, and the message might be success. + if (isDone()) { + throw new TimeoutException(message + " but future completed as timeout expired"); + } + throw new TimeoutException(message + " for " + futureToString); + } + + @ParametricNullness + @SuppressWarnings("nullness") // TODO(b/147136275): Remove once our checker understands & and |. + final V blockingGet() throws InterruptedException, ExecutionException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + @RetainedLocalRef Object localValue = valueField; + if (localValue != null & notInstanceOfDelegatingToFuture(localValue)) { + return getDoneValue(localValue); + } + Waiter oldHead = waitersField; + if (oldHead != Waiter.TOMBSTONE) { + Waiter node = new Waiter(); + do { + node.setNext(oldHead); + if (casWaiters(oldHead, node)) { + // we are on the stack, now wait for completion. + while (true) { + LockSupport.park(this); + // Check interruption first, if we woke up due to interruption we need to honor that. + if (Thread.interrupted()) { + removeWaiter(node); + throw new InterruptedException(); + } + // Otherwise re-read and check doneness. If we loop then it must have been a spurious + // wakeup + localValue = valueField; + if (localValue != null & notInstanceOfDelegatingToFuture(localValue)) { + return getDoneValue(localValue); + } + } + } + oldHead = waitersField; // re-read and loop. + } while (oldHead != Waiter.TOMBSTONE); + } + // re-read valueField, if we get here then we must have observed a TOMBSTONE while trying to add + // a waiter. + // requireNonNull is safe because valueField is always set before TOMBSTONE. + return getDoneValue(requireNonNull(valueField)); + } + + /** Constructor for use by {@link AbstractFuture}. */ + AbstractFutureState() {} + + /* + * We put various static objects here rather than in AbstractFuture so that they're initialized in + * time for AbstractFutureState to potentially use them during class initialization. + * (AbstractFutureState class initialization can log, and that logging could in theory call into + * AbstractFuture, which wouldn't yet have had the chance to perform any class initialization of + * its own.) + */ + + /** A special value to represent {@code null}. */ + static final Object NULL = new Object(); + + /* + * Despite declaring this field in AbstractFutureState, we still use the logger for + * AbstractFuture: Users may have tests or log configuration that expects that to be the logger + * used for exceptions from listeners, as it's been in the past. + */ + static final LazyLogger log = new LazyLogger(AbstractFuture.class); + + static final boolean GENERATE_CANCELLATION_CAUSES; + + static { + // System.getProperty may throw if the security policy does not permit access. + boolean generateCancellationCauses; + try { + generateCancellationCauses = + parseBoolean(System.getProperty("guava.concurrent.generate_cancellation_cause", "false")); + } catch (SecurityException e) { + generateCancellationCauses = false; + } + GENERATE_CANCELLATION_CAUSES = generateCancellationCauses; + } + + /** Waiter links form a Treiber stack in {@link #waitersField}. */ + static final class Waiter { + static final Waiter TOMBSTONE = new Waiter(false /* ignored param */); + + volatile @Nullable Thread thread; + volatile @Nullable Waiter next; + + /** + * Constructor for the TOMBSTONE, avoids use of ATOMIC_HELPER in case this class is loaded + * before the ATOMIC_HELPER. Apparently this is possible on some android platforms. + */ + Waiter(boolean unused) {} + + Waiter() { + // avoid volatile write, write is made visible by subsequent CAS on waitersField field + putThread(this, Thread.currentThread()); + } + + // non-volatile write to the next field. Should be made visible by a subsequent CAS on + // waitersField. + void setNext(@Nullable Waiter next) { + putNext(this, next); + } + + void unpark() { + // This is racy with removeWaiter. The consequence of the race is that we may spuriously call + // unpark even though the thread has already removed itself from the list. But even if we did + // use a CAS, that race would still exist (it would just be ever so slightly smaller). + Thread w = thread; + if (w != null) { + thread = null; + LockSupport.unpark(w); + } + } + } + + /* + * Now that we've initialized everything else, we can run the initialization code for + * ATOMIC_HELPER. That initialization code may log after we assign to ATOMIC_HELPER. + */ + + private static final AtomicHelper ATOMIC_HELPER; + + static { + AtomicHelper helper; + Throwable thrownUnsafeFailure = null; + Throwable thrownAtomicReferenceFieldUpdaterFailure = null; + + if (mightBeAndroid()) { + try { + helper = new UnsafeAtomicHelper(); + } catch (Exception | Error unsafeFailure) { // sneaky checked exception + thrownUnsafeFailure = unsafeFailure; + // Catch absolutely everything and fall through to AtomicReferenceFieldUpdaterAtomicHelper. + try { + helper = new AtomicReferenceFieldUpdaterAtomicHelper(); + } catch (Exception // sneaky checked exception + | Error atomicReferenceFieldUpdaterFailure) { + // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause + // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. + // For these users fallback to a suboptimal implementation, based on synchronized. This + // will be a definite performance hit to those users. + thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure; + helper = new SynchronizedHelper(); + } + } + } else { + /* + * We avoid Unsafe, since newer JVMs produce warnings or even errors for attempts to use it. + * + * In guava-jre, we avoid Unsafe by using VarHandle instead. But if we have references to + * VarHandle in guava-android, even if they're unused under Android, we cause errors under + * AGP: https://github.com/google/guava/issues/7769. + * + * My impression is that an AtomicReferenceFieldUpdater in a static field is similarly fast to + * Unsafe on modern JVMs (if perhaps not quite as fast as VarHandle?). However, I'm not sure + * exactly what we've benchmarked, and we certainly haven't benchmarked as far back as JDK 8. + * (We also haven't benchmarked under Android. We continue to use UnsafeAtomicHelper there so + * that we don't change the performance there, for better or for worse.) Fortunately, JVM + * users will typically use guava-jre, not guava-android, and guava-jre uses the VarHandle + * implementation when possible. + */ + try { + helper = new AtomicReferenceFieldUpdaterAtomicHelper(); + } catch (NoClassDefFoundError fromAggregateFutureStateFallbackAtomicHelperTest) { + /* + * AtomicReferenceFieldUpdaterAtomicHelper should always work on the JVM. (I mean, it + * "should" always work on Android, too, but we know of a Samsung bug there :)) However, in + * AggregateFutureStateFallbackAtomicHelperTest, we test what happens to AggregateFuture in + * the case of the Samsung bug, and we do that by breaking AtomicReferenceFieldUpdater. + * Breaking AtomicReferenceFieldUpdater not only forces AggregateFutureState to fall back to + * another implementation but also forces AbstractFutureState to be able to do the + * same—hence the try-catch here. + * + * (Really, we're fortunate that breaking AtomicReferenceFieldUpdater doesn't break _even + * more_ things.) + */ + helper = new SynchronizedHelper(); + } + } + ATOMIC_HELPER = helper; + + // Prevent rare disastrous classloading in first call to LockSupport.park. + // See: https://bugs.openjdk.org/browse/JDK-8074773 + @SuppressWarnings("unused") + Class ensureLoaded = LockSupport.class; + + // Log after all static init is finished; if an installed logger uses any Futures methods, it + // shouldn't break in cases where reflection is missing/broken. + if (thrownAtomicReferenceFieldUpdaterFailure != null) { + log.get().log(SEVERE, "UnsafeAtomicHelper is broken!", thrownUnsafeFailure); + log.get() + .log( + SEVERE, + "AtomicReferenceFieldUpdaterAtomicHelper is broken!", + thrownAtomicReferenceFieldUpdaterFailure); + } + } + + // TODO(lukes): Investigate using a @Contended annotation on these fields once one is available. + + /* + * The following fields are package-private, even though we intend never to use them outside this + * file. If they were instead private, then we wouldn't be able to access them reflectively from + * within VarHandleAtomicHelper and AtomicReferenceFieldUpdaterAtomicHelper. + * + * Package-private "shouldn't" be necessary: The *AtomicHelper classes and AbstractFutureState + * "should" be nestmates, so a call to MethodHandles.lookup or + * AtomicReferenceFieldUpdater.newUpdater inside *AtomicHelper "should" have access to + * AbstractFutureState's private fields. However, our open-source build uses `-source 8 -target + * 8`, so the class files from that build can't express nestmates. Thus, when those class files + * are used from Java 9 or higher (i.e., high enough to trigger the VarHandle code path), such a + * lookup would fail with an IllegalAccessException. That may then trigger use of Unsafe (possibly + * with a warning under recent JVMs), or it may fall back even further to + * AtomicReferenceFieldUpdaterAtomicHelper, which would fail with a similar problem to + * VarHandleAtomicHelperMaker, forcing us all the way to SynchronizedHelper. + * + * Additionally, it seems that nestmates do not help with runtime reflection under *Android*, even + * when we use a newer -source and -target. That doesn't normally matter for AbstractFutureState, + * since Android should normally succed in using UnsafeAtomicHelper and thus never even try the + * problematic AtomicReferenceFieldUpdaterAtomicHelper code path. However, the same problem *does* + * matter with AggregateFutureState, which does not have an Unsafe-based helper. + * + * This same problem is one of the reasons for us to likewise use package-private for the fields + * in Waiter. + */ + + /** + * This field encodes the current state of the future. + * + *

    The valid values are: + * + *

      + *
    • {@code null} initial state, nothing has happened. + *
    • {@link Cancellation} terminal state, {@code cancel} was called. + *
    • {@link Failure} terminal state, {@code setException} was called. + *
    • {@link DelegatingToFuture} intermediate state, {@code setFuture} was called. + *
    • {@link #NULL} terminal state, {@code set(null)} was called. + *
    • Any other non-null value, terminal state, {@code set} was called with a non-null + * argument. + *
    + */ + volatile @Nullable Object valueField; + + /** All listeners. */ + volatile @Nullable Listener listenersField; + + /** All waiting threads. */ + volatile @Nullable Waiter waitersField; + + /** Non-volatile write of the thread to the {@link Waiter#thread} field. */ + private static void putThread(Waiter waiter, Thread newValue) { + ATOMIC_HELPER.putThread(waiter, newValue); + } + + /** Non-volatile write of the waiter to the {@link Waiter#next} field. */ + private static void putNext(Waiter waiter, @Nullable Waiter newValue) { + ATOMIC_HELPER.putNext(waiter, newValue); + } + + /** + * Performs a {@linkplain java.lang.invoke.VarHandle#compareAndSet compare-and-set} operation + * {@link #waitersField}. + */ + private boolean casWaiters(@Nullable Waiter expect, @Nullable Waiter update) { + return ATOMIC_HELPER.casWaiters(this, expect, update); + } + + /** + * Performs a {@linkplain java.lang.invoke.VarHandle#getAndSet get-and-set} operation on {@link + * #waitersField}. + */ + private final @Nullable Waiter gasWaiters(Waiter update) { + return ATOMIC_HELPER.gasWaiters(this, update); + } + + /** + * Marks the given node as 'deleted' (null waiter) and then scans the list to unlink all deleted + * nodes. This is an O(n) operation in the common case (and O(n^2) in the worst), but we are saved + * by two things. + * + *
      + *
    • This is only called when a waiting thread times out or is interrupted. Both of which + * should be rare. + *
    • The waiters list should be very short. + *
    + */ + private void removeWaiter(Waiter node) { + node.thread = null; // mark as 'deleted' + restart: + while (true) { + Waiter pred = null; + Waiter curr = waitersField; + if (curr == Waiter.TOMBSTONE) { + return; // give up if someone is calling complete + } + Waiter succ; + while (curr != null) { + succ = curr.next; + if (curr.thread != null) { // we aren't unlinking this node, update pred. + pred = curr; + } else if (pred != null) { // We are unlinking this node and it has a predecessor. + pred.next = succ; + if (pred.thread == null) { // We raced with another node that unlinked pred. Restart. + continue restart; + } + } else if (!casWaiters(curr, succ)) { // We are unlinking head + continue restart; // We raced with an add or complete + } + curr = succ; + } + break; + } + } + + // A heuristic for timed gets. If the remaining timeout is less than this, spin instead of + // blocking. This value is what AbstractQueuedSynchronizer uses. + private static final long SPIN_THRESHOLD_NANOS = 1000L; + + @VisibleForTesting + static String atomicHelperTypeForTest() { + return ATOMIC_HELPER.atomicHelperTypeForTest(); + } + + private abstract static class AtomicHelper { + /** Non-volatile write of the thread to the {@link Waiter#thread} field. */ + abstract void putThread(Waiter waiter, Thread newValue); + + /** Non-volatile write of the waiter to the {@link Waiter#next} field. */ + abstract void putNext(Waiter waiter, @Nullable Waiter newValue); + + /** Performs a CAS operation on {@link AbstractFutureState#waitersField}. */ + abstract boolean casWaiters( + AbstractFutureState future, @Nullable Waiter expect, @Nullable Waiter update); + + /** Performs a CAS operation on {@link AbstractFutureState#listenersField}. */ + abstract boolean casListeners( + AbstractFutureState future, @Nullable Listener expect, Listener update); + + /** Performs a GAS operation on {@link AbstractFutureState#waitersField}. */ + abstract @Nullable Waiter gasWaiters(AbstractFutureState future, Waiter update); + + /** Performs a GAS operation on {@link AbstractFutureState#listenersField}. */ + abstract @Nullable Listener gasListeners(AbstractFutureState future, Listener update); + + /** Performs a CAS operation on {@link AbstractFutureState#valueField}. */ + abstract boolean casValue( + AbstractFutureState future, @Nullable Object expect, Object update); + + abstract String atomicHelperTypeForTest(); + } + + /** + * {@link AtomicHelper} based on {@link sun.misc.Unsafe}. + * + *

    Static initialization of this class will fail if the {@link sun.misc.Unsafe} object cannot + * be accessed. + */ + @SuppressWarnings("SunApi") // b/345822163 + private static final class UnsafeAtomicHelper extends AtomicHelper { + static final Unsafe UNSAFE; + static final long LISTENERS_OFFSET; + static final long WAITERS_OFFSET; + static final long VALUE_OFFSET; + static final long WAITER_THREAD_OFFSET; + static final long WAITER_NEXT_OFFSET; + + static { + Unsafe unsafe = null; + try { + unsafe = Unsafe.getUnsafe(); + } catch (SecurityException tryReflectionInstead) { + try { + unsafe = + doPrivileged( + (PrivilegedExceptionAction) + () -> { + Class k = Unsafe.class; + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } + } + throw new NoSuchFieldError("the Unsafe"); + }); + } catch (PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", e.getCause()); + } + } + try { + Class abstractFutureState = AbstractFutureState.class; + WAITERS_OFFSET = + unsafe.objectFieldOffset(abstractFutureState.getDeclaredField("waitersField")); + LISTENERS_OFFSET = + unsafe.objectFieldOffset(abstractFutureState.getDeclaredField("listenersField")); + VALUE_OFFSET = unsafe.objectFieldOffset(abstractFutureState.getDeclaredField("valueField")); + WAITER_THREAD_OFFSET = unsafe.objectFieldOffset(Waiter.class.getDeclaredField("thread")); + WAITER_NEXT_OFFSET = unsafe.objectFieldOffset(Waiter.class.getDeclaredField("next")); + UNSAFE = unsafe; + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + @Override + void putThread(Waiter waiter, Thread newValue) { + UNSAFE.putObject(waiter, WAITER_THREAD_OFFSET, newValue); + } + + @Override + void putNext(Waiter waiter, @Nullable Waiter newValue) { + UNSAFE.putObject(waiter, WAITER_NEXT_OFFSET, newValue); + } + + @Override + boolean casWaiters( + AbstractFutureState future, @Nullable Waiter expect, @Nullable Waiter update) { + return UNSAFE.compareAndSwapObject(future, WAITERS_OFFSET, expect, update); + } + + @Override + boolean casListeners( + AbstractFutureState future, @Nullable Listener expect, Listener update) { + return UNSAFE.compareAndSwapObject(future, LISTENERS_OFFSET, expect, update); + } + + @Override + @Nullable Listener gasListeners(AbstractFutureState future, Listener update) { + while (true) { + Listener listener = future.listenersField; + if (update == listener) { + return listener; + } + if (casListeners(future, listener, update)) { + return listener; + } + } + } + + @Override + @Nullable Waiter gasWaiters(AbstractFutureState future, Waiter update) { + while (true) { + Waiter waiter = future.waitersField; + if (update == waiter) { + return waiter; + } + if (casWaiters(future, waiter, update)) { + return waiter; + } + } + } + + @Override + boolean casValue(AbstractFutureState future, @Nullable Object expect, Object update) { + return UNSAFE.compareAndSwapObject(future, VALUE_OFFSET, expect, update); + } + + @Override + String atomicHelperTypeForTest() { + return "UnsafeAtomicHelper"; + } + } + + /** {@link AtomicHelper} based on {@link AtomicReferenceFieldUpdater}. */ + private static final class AtomicReferenceFieldUpdaterAtomicHelper extends AtomicHelper { + private static final AtomicReferenceFieldUpdater waiterThreadUpdater = + AtomicReferenceFieldUpdater.newUpdater( + Waiter.class, Thread.class, "thread"); + private static final AtomicReferenceFieldUpdater waiterNextUpdater = + AtomicReferenceFieldUpdater.newUpdater( + Waiter.class, Waiter.class, "next"); + private static final AtomicReferenceFieldUpdater< + ? super AbstractFutureState, @Nullable Waiter> + waitersUpdater = newUpdater(AbstractFutureState.class, Waiter.class, "waitersField"); + private static final AtomicReferenceFieldUpdater< + ? super AbstractFutureState, @Nullable Listener> + listenersUpdater = newUpdater(AbstractFutureState.class, Listener.class, "listenersField"); + private static final AtomicReferenceFieldUpdater< + ? super AbstractFutureState, @Nullable Object> + valueUpdater = newUpdater(AbstractFutureState.class, Object.class, "valueField"); + + @Override + void putThread(Waiter waiter, Thread newValue) { + waiterThreadUpdater.lazySet(waiter, newValue); + } + + @Override + void putNext(Waiter waiter, @Nullable Waiter newValue) { + waiterNextUpdater.lazySet(waiter, newValue); + } + + @Override + boolean casWaiters( + AbstractFutureState future, @Nullable Waiter expect, @Nullable Waiter update) { + return waitersUpdater.compareAndSet(future, expect, update); + } + + @Override + boolean casListeners( + AbstractFutureState future, @Nullable Listener expect, Listener update) { + return listenersUpdater.compareAndSet(future, expect, update); + } + + @Override + @Nullable Listener gasListeners(AbstractFutureState future, Listener update) { + return listenersUpdater.getAndSet(future, update); + } + + @Override + @Nullable Waiter gasWaiters(AbstractFutureState future, Waiter update) { + return waitersUpdater.getAndSet(future, update); + } + + @Override + boolean casValue(AbstractFutureState future, @Nullable Object expect, Object update) { + return valueUpdater.compareAndSet(future, expect, update); + } + + @Override + String atomicHelperTypeForTest() { + return "AtomicReferenceFieldUpdaterAtomicHelper"; + } + } + + /** + * {@link AtomicHelper} based on {@code synchronized} and volatile writes. + * + *

    This is an implementation of last resort for when certain basic VM features are broken (like + * AtomicReferenceFieldUpdater). + */ + private static final class SynchronizedHelper extends AtomicHelper { + @Override + void putThread(Waiter waiter, Thread newValue) { + waiter.thread = newValue; + } + + @Override + void putNext(Waiter waiter, @Nullable Waiter newValue) { + waiter.next = newValue; + } + + @Override + boolean casWaiters( + AbstractFutureState future, @Nullable Waiter expect, @Nullable Waiter update) { + synchronized (future) { + if (future.waitersField == expect) { + future.waitersField = update; + return true; + } + return false; + } + } + + @Override + boolean casListeners( + AbstractFutureState future, @Nullable Listener expect, Listener update) { + synchronized (future) { + if (future.listenersField == expect) { + future.listenersField = update; + return true; + } + return false; + } + } + + @Override + @Nullable Listener gasListeners(AbstractFutureState future, Listener update) { + synchronized (future) { + Listener old = future.listenersField; + if (old != update) { + future.listenersField = update; + } + return old; + } + } + + @Override + @Nullable Waiter gasWaiters(AbstractFutureState future, Waiter update) { + synchronized (future) { + Waiter old = future.waitersField; + if (old != update) { + future.waitersField = update; + } + return old; + } + } + + @Override + boolean casValue(AbstractFutureState future, @Nullable Object expect, Object update) { + synchronized (future) { + if (future.valueField == expect) { + future.valueField = update; + return true; + } + return false; + } + } + + @Override + String atomicHelperTypeForTest() { + return "SynchronizedHelper"; + } + } + + private static boolean mightBeAndroid() { + String runtime = System.getProperty("java.runtime.name", ""); + // I have no reason to believe that `null` is possible here, but let's make sure we don't crash: + return runtime == null || runtime.contains("Android"); + } +} diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractIdleService.java b/android/guava/src/com/google/common/util/concurrent/AbstractIdleService.java index 7416a9b655b2..8f53cae13bb7 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractIdleService.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractIdleService.java @@ -14,7 +14,12 @@ package com.google.common.util.concurrent; +import static com.google.common.util.concurrent.MoreExecutors.newThread; +import static com.google.common.util.concurrent.MoreExecutors.renamingDecorator; +import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; + import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Supplier; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.j2objc.annotations.WeakOuter; @@ -25,12 +30,13 @@ /** * Base class for services that do not need a thread while "running" but may need one during startup * and shutdown. Subclasses can implement {@link #startUp} and {@link #shutDown} methods, each which - * run in a executor which by default uses a separate thread for each method. + * run in an executor which by default uses a separate thread for each method. * * @author Chris Nokleberg * @since 1.0 */ @GwtIncompatible +@J2ktIncompatible public abstract class AbstractIdleService implements Service { /* Thread names will look like {@code "MyService STARTING"}. */ @@ -51,34 +57,30 @@ public String get() { private final class DelegateService extends AbstractService { @Override protected final void doStart() { - MoreExecutors.renamingDecorator(executor(), threadNameSupplier) + renamingDecorator(executor(), threadNameSupplier) .execute( - new Runnable() { - @Override - public void run() { - try { - startUp(); - notifyStarted(); - } catch (Throwable t) { - notifyFailed(t); - } + () -> { + try { + startUp(); + notifyStarted(); + } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); + notifyFailed(t); } }); } @Override protected final void doStop() { - MoreExecutors.renamingDecorator(executor(), threadNameSupplier) + renamingDecorator(executor(), threadNameSupplier) .execute( - new Runnable() { - @Override - public void run() { - try { - shutDown(); - notifyStopped(); - } catch (Throwable t) { - notifyFailed(t); - } + () -> { + try { + shutDown(); + notifyStopped(); + } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); + notifyFailed(t); } }); } @@ -106,12 +108,7 @@ protected AbstractIdleService() {} * stopped, and should return promptly. */ protected Executor executor() { - return new Executor() { - @Override - public void execute(Runnable command) { - MoreExecutors.newThread(threadNameSupplier.get(), command).start(); - } - }; + return command -> newThread(threadNameSupplier.get(), command).start(); } @Override @@ -129,19 +126,25 @@ public final State state() { return delegate.state(); } - /** @since 13.0 */ + /** + * @since 13.0 + */ @Override public final void addListener(Listener listener, Executor executor) { delegate.addListener(listener, executor); } - /** @since 14.0 */ + /** + * @since 14.0 + */ @Override public final Throwable failureCause() { return delegate.failureCause(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service startAsync() { @@ -149,7 +152,9 @@ public final Service startAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service stopAsync() { @@ -157,25 +162,33 @@ public final Service stopAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitRunning() { delegate.awaitRunning(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { delegate.awaitRunning(timeout, unit); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitTerminated() { delegate.awaitTerminated(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { delegate.awaitTerminated(timeout, unit); diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractListeningExecutorService.java b/android/guava/src/com/google/common/util/concurrent/AbstractListeningExecutorService.java index 22157bab2371..ac2dd03848d6 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractListeningExecutorService.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractListeningExecutorService.java @@ -14,13 +14,13 @@ package com.google.common.util.concurrent; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.CheckReturnValue; import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.Callable; import java.util.concurrent.RunnableFuture; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Abstract {@link ListeningExecutorService} implementation that creates {@link ListenableFuture} @@ -33,36 +33,48 @@ * @author Chris Povirk * @since 14.0 */ -@Beta -@CanIgnoreReturnValue +@CheckReturnValue @GwtIncompatible public abstract class AbstractListeningExecutorService extends AbstractExecutorService implements ListeningExecutorService { + /** Constructor for use by subclasses. */ + public AbstractListeningExecutorService() {} - /** @since 19.0 (present with return type {@code ListenableFutureTask} since 14.0) */ + /** + * @since 19.0 (present with return type {@code ListenableFutureTask} since 14.0) + */ + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override - protected final RunnableFuture newTaskFor(Runnable runnable, T value) { + protected final RunnableFuture newTaskFor( + Runnable runnable, @ParametricNullness T value) { return TrustedListenableFutureTask.create(runnable, value); } - /** @since 19.0 (present with return type {@code ListenableFutureTask} since 14.0) */ + /** + * @since 19.0 (present with return type {@code ListenableFutureTask} since 14.0) + */ + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override - protected final RunnableFuture newTaskFor(Callable callable) { + protected final RunnableFuture newTaskFor(Callable callable) { return TrustedListenableFutureTask.create(callable); } + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override public ListenableFuture submit(Runnable task) { return (ListenableFuture) super.submit(task); } + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override - public ListenableFuture submit(Runnable task, @NullableDecl T result) { + public ListenableFuture submit( + Runnable task, @ParametricNullness T result) { return (ListenableFuture) super.submit(task, result); } + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override - public ListenableFuture submit(Callable task) { + public ListenableFuture submit(Callable task) { return (ListenableFuture) super.submit(task); } } diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractScheduledService.java b/android/guava/src/com/google/common/util/concurrent/AbstractScheduledService.java index d70155ecddcb..0586c9d65a5c 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractScheduledService.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractScheduledService.java @@ -16,25 +16,31 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Futures.immediateCancelledFuture; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import com.google.common.annotations.GwtIncompatible; -import com.google.common.base.Supplier; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.concurrent.GuardedBy; import com.google.j2objc.annotations.WeakOuter; +import java.time.Duration; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Base class for services that can implement {@link #startUp} and {@link #shutDown} but while in @@ -59,7 +65,7 @@ *

    Here is a sketch of a service which crawls a website and uses the scheduling capabilities to * rate limit itself. * - *

    {@code
    + * {@snippet :
      * class CrawlingService extends AbstractScheduledService {
      *   private Set visited;
      *   private Queue toCrawl;
    @@ -84,7 +90,7 @@
      *     return Scheduler.newFixedRateSchedule(0, 1, TimeUnit.SECONDS);
      *   }
      * }
    - * }
    + * } * *

    This class uses the life cycle methods to read in a list of starting URIs and save the set of * outstanding URIs when shutting down. Also, it takes advantage of the scheduling functionality to @@ -94,8 +100,9 @@ * @since 11.0 */ @GwtIncompatible +@J2ktIncompatible public abstract class AbstractScheduledService implements Service { - private static final Logger logger = Logger.getLogger(AbstractScheduledService.class.getName()); + private static final LazyLogger logger = new LazyLogger(AbstractScheduledService.class); /** * A scheduler defines the policy for how the {@link AbstractScheduledService} should run its @@ -110,6 +117,21 @@ public abstract class AbstractScheduledService implements Service { * @since 11.0 */ public abstract static class Scheduler { + /** + * Returns a {@link Scheduler} that schedules the task using the {@link + * ScheduledExecutorService#scheduleWithFixedDelay} method. + * + * @param initialDelay the time to delay first execution + * @param delay the delay between the termination of one execution and the commencement of the + * next + * @since 33.4.0 (but since 28.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using Duration + public static Scheduler newFixedDelaySchedule(Duration initialDelay, Duration delay) { + return newFixedDelaySchedule( + toNanosSaturated(initialDelay), toNanosSaturated(delay), NANOSECONDS); + } + /** * Returns a {@link Scheduler} that schedules the task using the {@link * ScheduledExecutorService#scheduleWithFixedDelay} method. @@ -120,19 +142,33 @@ public abstract static class Scheduler { * @param unit the time unit of the initialDelay and delay parameters */ @SuppressWarnings("GoodTime") // should accept a java.time.Duration - public static Scheduler newFixedDelaySchedule( - final long initialDelay, final long delay, final TimeUnit unit) { + public static Scheduler newFixedDelaySchedule(long initialDelay, long delay, TimeUnit unit) { checkNotNull(unit); checkArgument(delay > 0, "delay must be > 0, found %s", delay); return new Scheduler() { @Override - public Future schedule( + public Cancellable schedule( AbstractService service, ScheduledExecutorService executor, Runnable task) { - return executor.scheduleWithFixedDelay(task, initialDelay, delay, unit); + return new FutureAsCancellable( + executor.scheduleWithFixedDelay(task, initialDelay, delay, unit)); } }; } + /** + * Returns a {@link Scheduler} that schedules the task using the {@link + * ScheduledExecutorService#scheduleAtFixedRate} method. + * + * @param initialDelay the time to delay first execution + * @param period the period between successive executions of the task + * @since 33.4.0 (but since 28.0 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using Duration + public static Scheduler newFixedRateSchedule(Duration initialDelay, Duration period) { + return newFixedRateSchedule( + toNanosSaturated(initialDelay), toNanosSaturated(period), NANOSECONDS); + } + /** * Returns a {@link Scheduler} that schedules the task using the {@link * ScheduledExecutorService#scheduleAtFixedRate} method. @@ -142,21 +178,21 @@ public Future schedule( * @param unit the time unit of the initialDelay and period parameters */ @SuppressWarnings("GoodTime") // should accept a java.time.Duration - public static Scheduler newFixedRateSchedule( - final long initialDelay, final long period, final TimeUnit unit) { + public static Scheduler newFixedRateSchedule(long initialDelay, long period, TimeUnit unit) { checkNotNull(unit); checkArgument(period > 0, "period must be > 0, found %s", period); return new Scheduler() { @Override - public Future schedule( + public Cancellable schedule( AbstractService service, ScheduledExecutorService executor, Runnable task) { - return executor.scheduleAtFixedRate(task, initialDelay, period, unit); + return new FutureAsCancellable( + executor.scheduleAtFixedRate(task, initialDelay, period, unit)); } }; } /** Schedules the task to run on the provided executor on behalf of the service. */ - abstract Future schedule( + abstract Cancellable schedule( AbstractService service, ScheduledExecutorService executor, Runnable runnable); private Scheduler() {} @@ -170,8 +206,8 @@ private final class ServiceDelegate extends AbstractService { // A handle to the running task so that we can stop it when a shutdown has been requested. // These two fields are volatile because their values will be accessed from multiple threads. - @NullableDecl private volatile Future runningTask; - @NullableDecl private volatile ScheduledExecutorService executorService; + private volatile @Nullable Cancellable runningTask; + private volatile @Nullable ScheduledExecutorService executorService; // This lock protects the task so we can ensure that none of the template methods (startUp, // shutDown or runOneIteration) run concurrently with one another. @@ -180,27 +216,36 @@ private final class ServiceDelegate extends AbstractService { private final ReentrantLock lock = new ReentrantLock(); @WeakOuter - class Task implements Runnable { + final class Task implements Runnable { @Override public void run() { lock.lock(); try { - if (runningTask.isCancelled()) { + /* + * requireNonNull is safe because Task isn't run (or at least it doesn't succeed in taking + * the lock) until after it's scheduled and the runningTask field is set. + */ + if (requireNonNull(runningTask).isCancelled()) { // task may have been cancelled while blocked on the lock. return; } AbstractScheduledService.this.runOneIteration(); } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); try { shutDown(); } catch (Exception ignored) { - logger.log( - Level.WARNING, - "Error while attempting to shut down the service after failure.", - ignored); + restoreInterruptIfIsInterruptedException(ignored); + logger + .get() + .log( + Level.WARNING, + "Error while attempting to shut down the service after failure.", + ignored); } notifyFailed(t); - runningTask.cancel(false); // prevent future invocations. + // requireNonNull is safe now, just as it was above. + requireNonNull(runningTask).cancel(false); // prevent future invocations. } finally { lock.unlock(); } @@ -212,61 +257,58 @@ public void run() { @Override protected final void doStart() { executorService = - MoreExecutors.renamingDecorator( - executor(), - new Supplier() { - @Override - public String get() { - return serviceName() + " " + state(); - } - }); + MoreExecutors.renamingDecorator(executor(), () -> serviceName() + " " + state()); executorService.execute( - new Runnable() { - @Override - public void run() { - lock.lock(); - try { - startUp(); - runningTask = scheduler().schedule(delegate, executorService, task); - notifyStarted(); - } catch (Throwable t) { - notifyFailed(t); - if (runningTask != null) { - // prevent the task from running if possible - runningTask.cancel(false); - } - } finally { - lock.unlock(); + () -> { + lock.lock(); + try { + startUp(); + /* + * requireNonNull is safe because executorService is never cleared after the + * assignment above. + */ + requireNonNull(executorService); + runningTask = scheduler().schedule(delegate, executorService, task); + notifyStarted(); + } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); + notifyFailed(t); + if (runningTask != null) { + // prevent the task from running if possible + runningTask.cancel(false); } + } finally { + lock.unlock(); } }); } @Override protected final void doStop() { + // Both requireNonNull calls are safe because doStop can run only after a successful doStart. + requireNonNull(runningTask); + requireNonNull(executorService); runningTask.cancel(false); executorService.execute( - new Runnable() { - @Override - public void run() { + () -> { + try { + lock.lock(); try { - lock.lock(); - try { - if (state() != State.STOPPING) { - // This means that the state has changed since we were scheduled. This implies - // that an execution of runOneIteration has thrown an exception and we have - // transitioned to a failed state, also this means that shutDown has already - // been called, so we do not want to call it again. - return; - } - shutDown(); - } finally { - lock.unlock(); + if (state() != State.STOPPING) { + // This means that the state has changed since we were scheduled. This implies + // that an execution of runOneIteration has thrown an exception and we have + // transitioned to a failed state, also this means that shutDown has already + // been called, so we do not want to call it again. + return; } - notifyStopped(); - } catch (Throwable t) { - notifyFailed(t); + shutDown(); + } finally { + lock.unlock(); } + notifyStopped(); + } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); + notifyFailed(t); } }); } @@ -324,15 +366,15 @@ protected void shutDown() throws Exception {} */ protected ScheduledExecutorService executor() { @WeakOuter - class ThreadFactoryImpl implements ThreadFactory { + final class ThreadFactoryImpl implements ThreadFactory { @Override public Thread newThread(Runnable runnable) { return MoreExecutors.newThread(serviceName(), runnable); } } - final ScheduledExecutorService executor = + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl()); - // Add a listener to shutdown the executor after the service is stopped. This ensures that the + // Add a listener to shut down the executor after the service is stopped. This ensures that the // JVM shutdown will not be prevented from exiting after this service has stopped or failed. // Technically this listener is added after start() was called so it is a little gross, but it // is called within doStart() so we know that the service cannot terminate or fail concurrently @@ -378,19 +420,25 @@ public final State state() { return delegate.state(); } - /** @since 13.0 */ + /** + * @since 13.0 + */ @Override public final void addListener(Listener listener, Executor executor) { delegate.addListener(listener, executor); } - /** @since 14.0 */ + /** + * @since 14.0 + */ @Override public final Throwable failureCause() { return delegate.failureCause(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service startAsync() { @@ -398,7 +446,9 @@ public final Service startAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @CanIgnoreReturnValue @Override public final Service stopAsync() { @@ -406,30 +456,63 @@ public final Service stopAsync() { return this; } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitRunning() { delegate.awaitRunning(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { delegate.awaitRunning(timeout, unit); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitTerminated() { delegate.awaitTerminated(); } - /** @since 15.0 */ + /** + * @since 15.0 + */ @Override public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { delegate.awaitTerminated(timeout, unit); } + interface Cancellable { + void cancel(boolean mayInterruptIfRunning); + + boolean isCancelled(); + } + + private static final class FutureAsCancellable implements Cancellable { + private final Future delegate; + + FutureAsCancellable(Future delegate) { + this.delegate = delegate; + } + + @Override + @SuppressWarnings("Interruption") // We are propagating an interrupt from a caller. + public void cancel(boolean mayInterruptIfRunning) { + delegate.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return delegate.isCancelled(); + } + } + /** * A {@link Scheduler} that provides a convenient way for the {@link AbstractScheduledService} to * use a dynamically changing schedule. After every execution of the task, assuming it hasn't been @@ -439,9 +522,11 @@ public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutExc * @since 11.0 */ public abstract static class CustomScheduler extends Scheduler { + /** Constructor for use by subclasses. */ + public CustomScheduler() {} /** A callable class that can reschedule itself using a {@link CustomScheduler}. */ - private class ReschedulableCallable extends ForwardingFuture implements Callable { + private final class ReschedulableCallable implements Callable<@Nullable Void> { /** The underlying task. */ private final Runnable wrappedRunnable; @@ -453,6 +538,27 @@ private class ReschedulableCallable extends ForwardingFuture implements Ca * The service that is managing this callable. This is used so that failure can be reported * properly. */ + /* + * This reference is part of a reference cycle, which is typically something we want to avoid + * under j2objc -- but it is not detected by our j2objc cycle test. The cycle: + * + * - CustomScheduler.service contains an instance of ServiceDelegate. (It needs it so that it + * can call notifyFailed.) + * + * - ServiceDelegate.runningTask contains an instance of ReschedulableCallable (at least in + * the case that the service is using CustomScheduler). (It needs it so that it can cancel + * the task and detect whether it has been cancelled.) + * + * - ReschedulableCallable has a reference back to its enclosing CustomScheduler. (It needs it + * so that it can call getNextSchedule). + * + * Maybe there is a way to avoid this cycle. But we think the cycle is safe enough to ignore: + * Each task is retained for only as long as it is running -- so it's retained only as long as + * it would already be retained by the underlying executor. + * + * If the cycle test starts reporting this cycle in the future, we should add an entry to + * cycle_suppress_list.txt. + */ private final AbstractService service; /** @@ -464,8 +570,7 @@ private class ReschedulableCallable extends ForwardingFuture implements Ca /** The future that represents the next execution of this task. */ @GuardedBy("lock") - @NullableDecl - private Future currentFuture; + private @Nullable SupplantableFuture cancellationDelegate; ReschedulableCallable( AbstractService service, ScheduledExecutorService executor, Runnable runnable) { @@ -475,33 +580,39 @@ private class ReschedulableCallable extends ForwardingFuture implements Ca } @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { wrappedRunnable.run(); reschedule(); return null; } - /** Atomically reschedules this task and assigns the new future to {@link #currentFuture}. */ - public void reschedule() { + /** + * Atomically reschedules this task and assigns the new future to {@link + * #cancellationDelegate}. + */ + @CanIgnoreReturnValue + Cancellable reschedule() { // invoke the callback outside the lock, prevents some shenanigans. Schedule schedule; try { schedule = CustomScheduler.this.getNextSchedule(); } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); service.notifyFailed(t); - return; + return new FutureAsCancellable(immediateCancelledFuture()); } // We reschedule ourselves with a lock held for two reasons. 1. we want to make sure that // cancel calls cancel on the correct future. 2. we want to make sure that the assignment // to currentFuture doesn't race with itself so that currentFuture is assigned in the // correct order. Throwable scheduleFailure = null; + Cancellable toReturn; lock.lock(); try { - if (currentFuture == null || !currentFuture.isCancelled()) { - currentFuture = executor.schedule(this, schedule.delay, schedule.unit); - } + toReturn = initializeOrUpdateCancellationDelegate(schedule); } catch (Throwable e) { + // Any Exception is either a RuntimeException or sneaky checked exception. + // // If an exception is thrown by the subclass then we need to make sure that the service // notices and transitions to the FAILED state. We do it by calling notifyFailed directly // because the service does not monitor the state of the future so if the exception is not @@ -511,6 +622,7 @@ public void reschedule() { // the AbstractService could monitor the future directly. Rescheduling is still hard... // but it would help with some of these lock ordering issues. scheduleFailure = e; + toReturn = new FutureAsCancellable(immediateCancelledFuture()); } finally { lock.unlock(); } @@ -518,16 +630,64 @@ public void reschedule() { if (scheduleFailure != null) { service.notifyFailed(scheduleFailure); } + return toReturn; + } + + @GuardedBy("lock") + /* + * The GuardedBy checker warns us that we're not holding cancellationDelegate.lock. But in + * fact we are holding it because it is the same as this.lock, which we know we are holding, + * thanks to @GuardedBy above. (cancellationDelegate.lock is initialized to this.lock in the + * call to `new SupplantableFuture` below.) + */ + @SuppressWarnings("GuardedBy") + private Cancellable initializeOrUpdateCancellationDelegate(Schedule schedule) { + if (cancellationDelegate == null) { + return cancellationDelegate = new SupplantableFuture(lock, submitToExecutor(schedule)); + } + if (!cancellationDelegate.currentFuture.isCancelled()) { + cancellationDelegate.currentFuture = submitToExecutor(schedule); + } + return cancellationDelegate; + } + + private ScheduledFuture<@Nullable Void> submitToExecutor(Schedule schedule) { + return executor.schedule(this, schedule.delay, schedule.unit); + } + } + + /** + * Contains the most recently submitted {@code Future}, which may be cancelled or updated, + * always under a lock. + */ + private static final class SupplantableFuture implements Cancellable { + private final ReentrantLock lock; + + @GuardedBy("lock") + private Future<@Nullable Void> currentFuture; + + SupplantableFuture(ReentrantLock lock, Future<@Nullable Void> currentFuture) { + this.lock = lock; + this.currentFuture = currentFuture; } - // N.B. Only protect cancel and isCancelled because those are the only methods that are - // invoked by the AbstractScheduledService. @Override - public boolean cancel(boolean mayInterruptIfRunning) { - // Ensure that a task cannot be rescheduled while a cancel is ongoing. + @SuppressWarnings("Interruption") // We are propagating an interrupt from a caller. + public void cancel(boolean mayInterruptIfRunning) { + /* + * Lock to ensure that a task cannot be rescheduled while a cancel is ongoing. + * + * In theory, cancel() could execute arbitrary listeners -- bad to do while holding a lock. + * However, we don't expose currentFuture to users, so they can't attach listeners. And the + * Future might not even be a ListenableFuture, just a plain Future. That said, similar + * problems can exist with methods like FutureTask.done(), not to mention slow calls to + * Thread.interrupt() (as discussed in InterruptibleTask). At the end of the day, it's + * unlikely that cancel() will be slow, so we can probably get away with calling it while + * holding a lock. Still, it would be nice to avoid somehow. + */ lock.lock(); try { - return currentFuture.cancel(mayInterruptIfRunning); + currentFuture.cancel(mayInterruptIfRunning); } finally { lock.unlock(); } @@ -542,20 +702,12 @@ public boolean isCancelled() { lock.unlock(); } } - - @Override - protected Future delegate() { - throw new UnsupportedOperationException( - "Only cancel and isCancelled is supported by this future"); - } } @Override - final Future schedule( + final Cancellable schedule( AbstractService service, ScheduledExecutorService executor, Runnable runnable) { - ReschedulableCallable task = new ReschedulableCallable(service, executor, runnable); - task.reschedule(); - return task; + return new ReschedulableCallable(service, executor, runnable).reschedule(); } /** @@ -577,6 +729,15 @@ public Schedule(long delay, TimeUnit unit) { this.delay = delay; this.unit = checkNotNull(unit); } + + /** + * @param delay the time from now to delay execution + * @since 33.4.0 (but since 31.1 in the JRE flavor) + */ + @IgnoreJRERequirement // Users will use this only if they're already using Duration + public Schedule(Duration delay) { + this(toNanosSaturated(delay), NANOSECONDS); + } } /** diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractService.java b/android/guava/src/com/google/common/util/concurrent/AbstractService.java index 733bf3b77813..7ba004bb88b1 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractService.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractService.java @@ -17,17 +17,19 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; import static com.google.common.util.concurrent.Service.State.FAILED; import static com.google.common.util.concurrent.Service.State.NEW; import static com.google.common.util.concurrent.Service.State.RUNNING; import static com.google.common.util.concurrent.Service.State.STARTING; import static com.google.common.util.concurrent.Service.State.STOPPING; import static com.google.common.util.concurrent.Service.State.TERMINATED; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.util.concurrent.Monitor.Guard; -import com.google.common.util.concurrent.Service.State; // javadoc needs this +import com.google.common.util.concurrent.Service.State; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.ForOverride; import com.google.errorprone.annotations.concurrent.GuardedBy; @@ -35,7 +37,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Base class for implementing services that can handle {@link #doStart} and {@link #doStop} @@ -48,6 +50,7 @@ * @since 1.0 */ @GwtIncompatible +@J2ktIncompatible public abstract class AbstractService implements Service { private static final ListenerCallQueue.Event STARTING_EVENT = new ListenerCallQueue.Event() { @@ -87,7 +90,7 @@ public String toString() { private static final ListenerCallQueue.Event TERMINATED_FROM_STOPPING_EVENT = terminatedEvent(STOPPING); - private static ListenerCallQueue.Event terminatedEvent(final State from) { + private static ListenerCallQueue.Event terminatedEvent(State from) { return new ListenerCallQueue.Event() { @Override public void call(Listener listener) { @@ -101,7 +104,7 @@ public String toString() { }; } - private static ListenerCallQueue.Event stoppingEvent(final State from) { + private static ListenerCallQueue.Event stoppingEvent(State from) { return new ListenerCallQueue.Event() { @Override public void call(Listener listener) { @@ -169,7 +172,7 @@ private final class IsStoppedGuard extends Guard { @Override public boolean isSatisfied() { - return state().isTerminal(); + return state().compareTo(TERMINATED) >= 0; } } @@ -234,7 +237,6 @@ protected AbstractService() {} * * @since 27.0 */ - @Beta @ForOverride protected void doCancelStart() {} @@ -247,6 +249,7 @@ public final Service startAsync() { enqueueStartingEvent(); doStart(); } catch (Throwable startupFailure) { + restoreInterruptIfIsInterruptedException(startupFailure); notifyFailed(startupFailure); } finally { monitor.leave(); @@ -286,6 +289,7 @@ public final Service stopAsync() { throw new AssertionError("isStoppable is incorrectly implemented, saw: " + previous); } } catch (Throwable shutdownFailure) { + restoreInterruptIfIsInterruptedException(shutdownFailure); notifyFailed(shutdownFailure); } finally { monitor.leave(); @@ -314,7 +318,7 @@ public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutExcept monitor.leave(); } } else { - // It is possible due to races the we are currently in the expected state even though we + // It is possible due to races that we are currently in the expected state even though we // timed out. e.g. if we weren't event able to grab the lock within the timeout we would never // even check the guard. I don't think we care too much about this use case but it could lead // to a confusing error message. @@ -341,7 +345,7 @@ public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutExc monitor.leave(); } } else { - // It is possible due to races the we are currently in the expected state even though we + // It is possible due to races that we are currently in the expected state even though we // timed out. e.g. if we weren't event able to grab the lock within the timeout we would never // even check the guard. I don't think we care too much about this use case but it could lead // to a confusing error message. @@ -475,13 +479,17 @@ public final State state() { return snapshot.externalState(); } - /** @since 14.0 */ + /** + * @since 14.0 + */ @Override public final Throwable failureCause() { return snapshot.failureCause(); } - /** @since 13.0 */ + /** + * @since 13.0 + */ @Override public final void addListener(Listener listener, Executor executor) { listeners.addListener(listener, executor); @@ -510,7 +518,7 @@ private void enqueueRunningEvent() { listeners.enqueue(RUNNING_EVENT); } - private void enqueueStoppingEvent(final State from) { + private void enqueueStoppingEvent(State from) { if (from == State.STARTING) { listeners.enqueue(STOPPING_FROM_STARTING_EVENT); } else if (from == State.RUNNING) { @@ -520,7 +528,7 @@ private void enqueueStoppingEvent(final State from) { } } - private void enqueueTerminatedEvent(final State from) { + private void enqueueTerminatedEvent(State from) { switch (from) { case NEW: listeners.enqueue(TERMINATED_FROM_NEW_EVENT); @@ -540,7 +548,7 @@ private void enqueueTerminatedEvent(final State from) { } } - private void enqueueFailedEvent(final State from, final Throwable cause) { + private void enqueueFailedEvent(State from, Throwable cause) { // can't memoize this one due to the exception listeners.enqueue( new ListenerCallQueue.Event() { @@ -575,20 +583,20 @@ private static final class StateSnapshot { * The exception that caused this service to fail. This will be {@code null} unless the service * has failed. */ - @NullableDecl final Throwable failure; + final @Nullable Throwable failure; StateSnapshot(State internalState) { this(internalState, false, null); } StateSnapshot( - State internalState, boolean shutdownWhenStartupFinishes, @NullableDecl Throwable failure) { + State internalState, boolean shutdownWhenStartupFinishes, @Nullable Throwable failure) { checkArgument( !shutdownWhenStartupFinishes || internalState == STARTING, "shutdownWhenStartupFinishes can only be set if state is STARTING. Got %s instead.", internalState); checkArgument( - !(failure != null ^ internalState == FAILED), + (failure != null) == (internalState == FAILED), "A failure cause should be set if and only if the state is failed. Got %s and %s " + "instead.", internalState, @@ -598,7 +606,9 @@ private static final class StateSnapshot { this.failure = failure; } - /** @see Service#state() */ + /** + * @see Service#state() + */ State externalState() { if (shutdownWhenStartupFinishes && state == STARTING) { return STOPPING; @@ -607,13 +617,16 @@ State externalState() { } } - /** @see Service#failureCause() */ + /** + * @see Service#failureCause() + */ Throwable failureCause() { checkState( state == FAILED, "failureCause() is only valid if the service has failed, service is %s", state); - return failure; + // requireNonNull is safe because the constructor requires a non-null cause with state=FAILED. + return requireNonNull(failure); } } } diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractTransformFuture.java b/android/guava/src/com/google/common/util/concurrent/AbstractTransformFuture.java index 4b908dc2cc2e..a455f7d24140 100644 --- a/android/guava/src/com/google/common/util/concurrent/AbstractTransformFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/AbstractTransformFuture.java @@ -17,32 +17,36 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Futures.getDone; import static com.google.common.util.concurrent.MoreExecutors.rejectionPropagatingExecutor; +import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import com.google.errorprone.annotations.ForOverride; +import com.google.errorprone.annotations.concurrent.LazyInit; +import com.google.j2objc.annotations.RetainedLocalRef; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** Implementations of {@code Futures.transform*}. */ @GwtCompatible -abstract class AbstractTransformFuture extends FluentFuture.TrustedFuture - implements Runnable { - static ListenableFuture create( +// Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || +@SuppressWarnings("ShortCircuitBoolean") +abstract class AbstractTransformFuture< + I extends @Nullable Object, O extends @Nullable Object, F, T extends @Nullable Object> + extends FluentFuture.TrustedFuture implements Runnable { + static ListenableFuture createAsync( ListenableFuture input, AsyncFunction function, Executor executor) { - checkNotNull(executor); AsyncTransformFuture output = new AsyncTransformFuture<>(input, function); input.addListener(output, rejectionPropagatingExecutor(executor, output)); return output; } - static ListenableFuture create( + static ListenableFuture create( ListenableFuture input, Function function, Executor executor) { - checkNotNull(function); TransformFuture output = new TransformFuture<>(input, function); input.addListener(output, rejectionPropagatingExecutor(executor, output)); return output; @@ -52,8 +56,8 @@ static ListenableFuture create( * In certain circumstances, this field might theoretically not be visible to an afterDone() call * triggered by cancel(). For details, see the comments on the fields of TimeoutFuture. */ - @NullableDecl ListenableFuture inputFuture; - @NullableDecl F function; + @LazyInit @Nullable ListenableFuture inputFuture; + @LazyInit @Nullable F function; AbstractTransformFuture(ListenableFuture inputFuture, F function) { this.inputFuture = checkNotNull(inputFuture); @@ -61,9 +65,13 @@ static ListenableFuture create( } @Override + @SuppressWarnings({ + "CatchingUnchecked", // sneaky checked exception + "nullness", // TODO(b/147136275): Remove once our checker understands & and |. + }) public final void run() { - ListenableFuture localInputFuture = inputFuture; - F localFunction = function; + @RetainedLocalRef ListenableFuture localInputFuture = inputFuture; + @RetainedLocalRef F localFunction = function; if (isCancelled() | localInputFuture == null | localFunction == null) { return; } @@ -99,7 +107,7 @@ public final void run() { // Set the cause of the exception as this future's exception. setException(e.getCause()); return; - } catch (RuntimeException e) { + } catch (Exception e) { // sneaky checked exception // Bug in inputFuture.get(). Propagate to the output Future so that its consumers don't hang. setException(e); return; @@ -117,6 +125,7 @@ public final void run() { try { transformResult = doTransform(localFunction, sourceResult); } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); // This exception is irrelevant in this thread, but useful for the client. setException(t); return; @@ -165,24 +174,25 @@ public final void run() { /** Template method for subtypes to actually run the transform. */ @ForOverride - @NullableDecl - abstract T doTransform(F function, @NullableDecl I result) throws Exception; + @ParametricNullness + abstract T doTransform(F function, @ParametricNullness I result) throws Exception; /** Template method for subtypes to actually set the result. */ @ForOverride - abstract void setResult(@NullableDecl T result); + abstract void setResult(@ParametricNullness T result); @Override protected final void afterDone() { - maybePropagateCancellationTo(inputFuture); + @RetainedLocalRef ListenableFuture localInputFuture = inputFuture; + maybePropagateCancellationTo(localInputFuture); this.inputFuture = null; this.function = null; } @Override - protected String pendingToString() { - ListenableFuture localInputFuture = inputFuture; - F localFunction = function; + protected @Nullable String pendingToString() { + @RetainedLocalRef ListenableFuture localInputFuture = inputFuture; + @RetainedLocalRef F localFunction = function; String superString = super.pendingToString(); String resultString = ""; if (localInputFuture != null) { @@ -200,7 +210,8 @@ protected String pendingToString() { * An {@link AbstractTransformFuture} that delegates to an {@link AsyncFunction} and {@link * #setFuture(ListenableFuture)}. */ - private static final class AsyncTransformFuture + private static final class AsyncTransformFuture< + I extends @Nullable Object, O extends @Nullable Object> extends AbstractTransformFuture< I, O, AsyncFunction, ListenableFuture> { AsyncTransformFuture( @@ -210,14 +221,15 @@ private static final class AsyncTransformFuture @Override ListenableFuture doTransform( - AsyncFunction function, @NullableDecl I input) throws Exception { - ListenableFuture outputFuture = function.apply(input); + AsyncFunction function, @ParametricNullness I input) + throws Exception { + ListenableFuture output = function.apply(input); checkNotNull( - outputFuture, + output, "AsyncFunction.apply returned null instead of a Future. " + "Did you mean to return immediateFuture(null)? %s", function); - return outputFuture; + return output; } @Override @@ -230,7 +242,7 @@ void setResult(ListenableFuture result) { * An {@link AbstractTransformFuture} that delegates to a {@link Function} and {@link * #set(Object)}. */ - private static final class TransformFuture + private static final class TransformFuture extends AbstractTransformFuture, O> { TransformFuture( ListenableFuture inputFuture, Function function) { @@ -238,13 +250,13 @@ private static final class TransformFuture } @Override - @NullableDecl - O doTransform(Function function, @NullableDecl I input) { + @ParametricNullness + O doTransform(Function function, @ParametricNullness I input) { return function.apply(input); } @Override - void setResult(@NullableDecl O result) { + void setResult(@ParametricNullness O result) { set(result); } } diff --git a/android/guava/src/com/google/common/util/concurrent/AggregateFuture.java b/android/guava/src/com/google/common/util/concurrent/AggregateFuture.java index 244f9fd5463d..9647758bdc66 100644 --- a/android/guava/src/com/google/common/util/concurrent/AggregateFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/AggregateFuture.java @@ -18,19 +18,21 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.util.concurrent.AggregateFuture.ReleaseResourcesReason.ALL_INPUT_FUTURES_PROCESSED; import static com.google.common.util.concurrent.AggregateFuture.ReleaseResourcesReason.OUTPUT_FUTURE_DONE; -import static com.google.common.util.concurrent.Futures.getDone; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static java.util.Objects.requireNonNull; import static java.util.logging.Level.SEVERE; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableCollection; import com.google.errorprone.annotations.ForOverride; import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper; +import com.google.errorprone.annotations.concurrent.LazyInit; +import com.google.j2objc.annotations.RetainedLocalRef; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A future whose value is derived from a collection of input futures. @@ -39,8 +41,12 @@ * @param the type of the output (i.e. this) future */ @GwtCompatible -abstract class AggregateFuture extends AggregateFutureState { - private static final Logger logger = Logger.getLogger(AggregateFuture.class.getName()); +@SuppressWarnings( + // Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + "ShortCircuitBoolean") +abstract class AggregateFuture + extends AggregateFutureState { + private static final LazyLogger logger = new LazyLogger(AggregateFuture.class); /** * The input futures. After {@link #init}, this field is read only by {@link #afterDone()} (to @@ -52,7 +58,8 @@ abstract class AggregateFuture extends AggregateFutureState> futures; + @LazyInit + private @Nullable ImmutableCollection> futures; private final boolean allMustSucceed; private final boolean collectsValues; @@ -68,10 +75,11 @@ abstract class AggregateFuture extends AggregateFutureState> localFutures = futures; + @RetainedLocalRef ImmutableCollection> localFutures = futures; releaseResources(OUTPUT_FUTURE_DONE); // nulls out `futures` if (isCancelled() & localFutures != null) { @@ -87,8 +95,8 @@ protected final void afterDone() { } @Override - protected final String pendingToString() { - ImmutableCollection> localFutures = futures; + protected final @Nullable String pendingToString() { + @RetainedLocalRef ImmutableCollection> localFutures = futures; if (localFutures != null) { return "futures=" + localFutures; } @@ -103,6 +111,13 @@ protected final String pendingToString() { * we're guaranteed to have properly initialized the subclass. */ final void init() { + /* + * requireNonNull is safe because this is called from the constructor after `futures` is set but + * before releaseResources could be called (because we have not yet set up any of the listeners + * that could call it, nor exposed this Future for users to call cancel() on). + */ + requireNonNull(futures); + // Corner case: List is empty. if (futures.isEmpty()) { handleAllCompleted(); @@ -123,32 +138,14 @@ final void init() { // This is not actually a problem, since the foreach only needs this.futures to be non-null // at the beginning of the loop. int i = 0; - for (final ListenableFuture future : futures) { - final int index = i++; - future.addListener( - new Runnable() { - @Override - public void run() { - try { - if (future.isCancelled()) { - // Clear futures prior to cancelling children. This sets our own state but lets - // the input futures keep running, as some of them may be used elsewhere. - futures = null; - cancel(false); - } else { - collectValueFromNonCancelledFuture(index, future); - } - } finally { - /* - * "null" means: There is no need to access `futures` again during - * `processCompleted` because we're reading each value during a call to - * handleOneInputDone. - */ - decrementCountAndMaybeComplete(null); - } - } - }, - directExecutor()); + for (ListenableFuture future : futures) { + int index = i++; + if (future.isDone()) { + processAllMustSucceedDoneFuture(index, future); + } else { + future.addListener( + () -> processAllMustSucceedDoneFuture(index, future), directExecutor()); + } } } else { /* @@ -157,28 +154,49 @@ public void run() { * Future.get() when we don't need to (specifically, for whenAllComplete().call*()), and it * lets all futures share the same listener. * - * We store `localFutures` inside the listener because `this.futures` might be nulled out by - * the time the listener runs for the final future -- at which point we need to check all - * inputs for exceptions *if* we're collecting values. If we're not, then the listener doesn't - * need access to the futures again, so we can just pass `null`. + * We store `localFuturesOrNull` inside the listener because `this.futures` might be nulled + * out by the time the listener runs for the final future -- at which point we need to check + * all inputs for exceptions *if* we're collecting values. If we're not, then the listener + * doesn't need access to the futures again, so we can just pass `null`. * * TODO(b/112550045): Allocating a single, cheaper listener is (I think) only an optimization. * If we make some other optimizations, this one will no longer be necessary. The optimization * could actually hurt in some cases, as it forces us to keep all inputs in memory until the * final input completes. */ - final ImmutableCollection> localFutures = - collectsValues ? futures : null; - Runnable listener = - new Runnable() { - @Override - public void run() { - decrementCountAndMaybeComplete(localFutures); - } - }; - for (ListenableFuture future : futures) { - future.addListener(listener, directExecutor()); + @RetainedLocalRef + ImmutableCollection> localFutures = futures; + ImmutableCollection> localFuturesOrNull = + collectsValues ? localFutures : null; + Runnable listener = () -> decrementCountAndMaybeComplete(localFuturesOrNull); + for (ListenableFuture future : localFutures) { + if (future.isDone()) { + decrementCountAndMaybeComplete(localFuturesOrNull); + } else { + future.addListener(listener, directExecutor()); + } + } + } + } + + private void processAllMustSucceedDoneFuture( + int index, ListenableFuture future) { + try { + if (future.isCancelled()) { + // Clear futures prior to cancelling children. This sets our own state but lets + // the input futures keep running, as some of them may be used elsewhere. + futures = null; + cancel(false); + } else { + collectValueFromNonCancelledFuture(index, future); } + } finally { + /* + * "null" means: There is no need to access `futures` again during + * `processCompleted` because we're reading each value during a call to + * handleOneInputDone. + */ + decrementCountAndMaybeComplete(null); } } @@ -227,15 +245,31 @@ private static void log(Throwable throwable) { (throwable instanceof Error) ? "Input Future failed with Error" : "Got more than one input Future failure. Logging failures after the first"; - logger.log(SEVERE, message, throwable); + logger.get().log(SEVERE, message, throwable); } @Override final void addInitialException(Set seen) { checkNotNull(seen); if (!isCancelled()) { - // TODO(cpovirk): Think about whether we could/should use Verify to check this. - boolean unused = addCausalChain(seen, tryInternalFastPathGetFailure()); + /* + * requireNonNull is safe because: + * + * - This is a TrustedFuture, so tryInternalFastPathGetFailure will in fact return the failure + * cause if this Future has failed. + * + * - And this future *has* failed: This method is called only from handleException (through + * getOrInitSeenExceptions). handleException tried to call setException and failed, so + * either this Future was cancelled (which we ruled out with the isCancelled check above), + * or it had already failed. (It couldn't have completed *successfully* or even had + * setFuture called on it: Neither of those can happen until we've finished processing all + * the completed inputs. And we're still processing at least one input, the one that + * triggered handleException.) + * + * TODO(cpovirk): Think about whether we could/should use Verify to check the return value of + * addCausalChain. + */ + boolean unused = addCausalChain(seen, requireNonNull(tryInternalFastPathGetFailure())); } } @@ -246,18 +280,18 @@ final void addInitialException(Set seen) { private void collectValueFromNonCancelledFuture(int index, Future future) { try { // We get the result, even if collectOneValue is a no-op, so that we can fail fast. - collectOneValue(index, getDone(future)); + // We use getUninterruptibly over getDone as a micro-optimization, we know the future is done. + collectOneValue(index, getUninterruptibly(future)); } catch (ExecutionException e) { handleException(e.getCause()); - } catch (Throwable t) { + } catch (Throwable t) { // sneaky checked exception handleException(t); } } private void decrementCountAndMaybeComplete( - @NullableDecl - ImmutableCollection> - futuresIfNeedToCollectAtCompletion) { + @Nullable ImmutableCollection> + futuresIfNeedToCollectAtCompletion) { int newRemaining = decrementRemainingAndGet(); checkState(newRemaining >= 0, "Less than 0 remaining futures"); if (newRemaining == 0) { @@ -266,9 +300,8 @@ private void decrementCountAndMaybeComplete( } private void processCompleted( - @NullableDecl - ImmutableCollection> - futuresIfNeedToCollectAtCompletion) { + @Nullable ImmutableCollection> + futuresIfNeedToCollectAtCompletion) { if (futuresIfNeedToCollectAtCompletion != null) { int i = 0; for (Future future : futuresIfNeedToCollectAtCompletion) { @@ -322,12 +355,15 @@ enum ReleaseResourcesReason { * If {@code allMustSucceed} is true, called as each future completes; otherwise, if {@code * collectsValues} is true, called for each future when all futures complete. */ - abstract void collectOneValue(int index, @NullableDecl InputT returnValue); + abstract void collectOneValue(int index, @ParametricNullness InputT returnValue); abstract void handleAllCompleted(); /** Adds the chain to the seen set, and returns whether all the chain was new to us. */ - private static boolean addCausalChain(Set seen, Throwable t) { + private static boolean addCausalChain(Set seen, Throwable param) { + // Declare a "true" local variable so that the Checker Framework will infer nullness. + Throwable t = param; + for (; t != null; t = t.getCause()) { boolean firstTimeSeen = seen.add(t); if (!firstTimeSeen) { @@ -335,7 +371,7 @@ private static boolean addCausalChain(Set seen, Throwable t) { * We've seen this, so we've seen its causes, too. No need to re-add them. (There's one case * where this isn't true, but we ignore it: If we record an exception, then someone calls * initCause() on it, and then we examine it again, we'll conclude that we've seen the whole - * chain before when it fact we haven't. But this should be rare.) + * chain before when in fact we haven't. But this should be rare.) */ return false; } diff --git a/android/guava/src/com/google/common/util/concurrent/AggregateFutureState.java b/android/guava/src/com/google/common/util/concurrent/AggregateFutureState.java index 2ba541bbcf56..62b9f85574d0 100644 --- a/android/guava/src/com/google/common/util/concurrent/AggregateFutureState.java +++ b/android/guava/src/com/google/common/util/concurrent/AggregateFutureState.java @@ -15,16 +15,18 @@ package com.google.common.util.concurrent; import static com.google.common.collect.Sets.newConcurrentHashSet; +import static java.util.Objects.requireNonNull; import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater; import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; import com.google.j2objc.annotations.ReflectionSupport; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.logging.Level; -import java.util.logging.Logger; +import org.jspecify.annotations.Nullable; /** * A helper which does some thread-safe operations for aggregate futures, which must be implemented @@ -35,28 +37,31 @@ *

  • Decrements a counter atomically * */ -@GwtCompatible(emulated = true) +@GwtCompatible @ReflectionSupport(value = ReflectionSupport.Level.FULL) -abstract class AggregateFutureState extends AbstractFuture.TrustedFuture { +abstract class AggregateFutureState + extends AbstractFuture.TrustedFuture { + /* + * The following fields are package-private, even though we intend never to use them outside this + * file. For discussion, see AbstractFutureState. + */ + // Lazily initialized the first time we see an exception; not released until all the input futures // have completed and we have processed them all. - private volatile Set seenExceptions = null; + volatile @Nullable Set seenExceptionsField = null; - private volatile int remaining; + volatile int remainingField; private static final AtomicHelper ATOMIC_HELPER; - private static final Logger log = Logger.getLogger(AggregateFutureState.class.getName()); + private static final LazyLogger log = new LazyLogger(AggregateFutureState.class); static { AtomicHelper helper; Throwable thrownReflectionFailure = null; try { - helper = - new SafeAtomicHelper( - newUpdater(AggregateFutureState.class, Set.class, "seenExceptions"), - newUpdater(AggregateFutureState.class, "remaining")); - } catch (Throwable reflectionFailure) { + helper = new SafeAtomicHelper(); + } catch (Throwable reflectionFailure) { // sneaky checked exception // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. // For these users fallback to a suboptimal implementation, based on synchronized. This will @@ -68,32 +73,34 @@ abstract class AggregateFutureState extends AbstractFuture.TrustedFutur // Log after all static init is finished; if an installed logger uses any Futures methods, it // shouldn't break in cases where reflection is missing/broken. if (thrownReflectionFailure != null) { - log.log(Level.SEVERE, "SafeAtomicHelper is broken!", thrownReflectionFailure); + log.get().log(Level.SEVERE, "SafeAtomicHelper is broken!", thrownReflectionFailure); } } AggregateFutureState(int remainingFutures) { - this.remaining = remainingFutures; + this.remainingField = remainingFutures; } final Set getOrInitSeenExceptions() { /* - * The initialization of seenExceptions has to be more complicated than we'd like. The simple - * approach would be for each caller CAS it from null to a Set populated with its exception. But - * there's another race: If the first thread fails with an exception and a second thread - * immediately fails with the same exception: + * The initialization of seenExceptionsField has to be more complicated than we'd like. The + * simple approach would be for each caller CAS it from null to a Set populated with its + * exception. But there's another race: If the first thread fails with an exception and a second + * thread immediately fails with the same exception: * * Thread1: calls setException(), which returns true, context switch before it can CAS - * seenExceptions to its exception + * seenExceptionsField to its exception * - * Thread2: calls setException(), which returns false, CASes seenExceptions to its exception, - * and wrongly believes that its exception is new (leading it to logging it when it shouldn't) + * Thread2: calls setException(), which returns false, CASes seenExceptionsField to its + * exception, and wrongly believes that its exception is new (leading it to logging it when it + * shouldn't) * - * Our solution is for threads to CAS seenExceptions from null to a Set populated with _the - * initial exception_, no matter which thread does the work. This ensures that seenExceptions - * always contains not just the current thread's exception but also the initial thread's. + * Our solution is for threads to CAS seenExceptionsField from null to a Set populated with _the + * initial exception_, no matter which thread does the work. This ensures that + * seenExceptionsField always contains not just the current thread's exception but also the + * initial thread's. */ - Set seenExceptionsLocal = seenExceptions; + Set seenExceptionsLocal = seenExceptionsField; if (seenExceptionsLocal == null) { // TODO(cpovirk): Should we use a simpler (presumably cheaper) data structure? /* @@ -124,8 +131,11 @@ final Set getOrInitSeenExceptions() { * other callers have added to it. * * This read is guaranteed to get us the right value because we only set this once (here). + * + * requireNonNull is safe because either our compareAndSet succeeded or it failed because + * another thread did it for us. */ - seenExceptionsLocal = seenExceptions; + seenExceptionsLocal = requireNonNull(seenExceptionsField); } return seenExceptionsLocal; } @@ -138,38 +148,37 @@ final int decrementRemainingAndGet() { } final void clearSeenExceptions() { - seenExceptions = null; + seenExceptionsField = null; + } + + @VisibleForTesting + static String atomicHelperTypeForTest() { + return ATOMIC_HELPER.atomicHelperTypeForTest(); } private abstract static class AtomicHelper { - /** Atomic compare-and-set of the {@link AggregateFutureState#seenExceptions} field. */ + /** Performs an atomic compare-and-set of {@link AggregateFutureState#seenExceptionsField}. */ abstract void compareAndSetSeenExceptions( - AggregateFutureState state, Set expect, Set update); + AggregateFutureState state, @Nullable Set expect, Set update); - /** Atomic decrement-and-get of the {@link AggregateFutureState#remaining} field. */ + /** Performs an atomic decrement-and-get of {@link AggregateFutureState#remainingField}. */ abstract int decrementAndGetRemainingCount(AggregateFutureState state); + + abstract String atomicHelperTypeForTest(); } private static final class SafeAtomicHelper extends AtomicHelper { - final AtomicReferenceFieldUpdater, Set> - seenExceptionsUpdater; - - final AtomicIntegerFieldUpdater> remainingCountUpdater; - - @SuppressWarnings({"rawtypes", "unchecked"}) // Unavoidable with reflection API - SafeAtomicHelper( - AtomicReferenceFieldUpdater seenExceptionsUpdater, - AtomicIntegerFieldUpdater remainingCountUpdater) { - this.seenExceptionsUpdater = - (AtomicReferenceFieldUpdater, Set>) - seenExceptionsUpdater; - this.remainingCountUpdater = - (AtomicIntegerFieldUpdater>) remainingCountUpdater; - } + private static final AtomicReferenceFieldUpdater< + ? super AggregateFutureState, ? super @Nullable Set> + seenExceptionsUpdater = + newUpdater(AggregateFutureState.class, Set.class, "seenExceptionsField"); + + private static final AtomicIntegerFieldUpdater> + remainingCountUpdater = newUpdater(AggregateFutureState.class, "remainingField"); @Override void compareAndSetSeenExceptions( - AggregateFutureState state, Set expect, Set update) { + AggregateFutureState state, @Nullable Set expect, Set update) { seenExceptionsUpdater.compareAndSet(state, expect, update); } @@ -177,15 +186,20 @@ void compareAndSetSeenExceptions( int decrementAndGetRemainingCount(AggregateFutureState state) { return remainingCountUpdater.decrementAndGet(state); } + + @Override + String atomicHelperTypeForTest() { + return "SafeAtomicHelper"; + } } private static final class SynchronizedAtomicHelper extends AtomicHelper { @Override void compareAndSetSeenExceptions( - AggregateFutureState state, Set expect, Set update) { + AggregateFutureState state, @Nullable Set expect, Set update) { synchronized (state) { - if (state.seenExceptions == expect) { - state.seenExceptions = update; + if (state.seenExceptionsField == expect) { + state.seenExceptionsField = update; } } } @@ -193,8 +207,13 @@ void compareAndSetSeenExceptions( @Override int decrementAndGetRemainingCount(AggregateFutureState state) { synchronized (state) { - return --state.remaining; + return --state.remainingField; } } + + @Override + String atomicHelperTypeForTest() { + return "SynchronizedAtomicHelper"; + } } } diff --git a/android/guava/src/com/google/common/util/concurrent/AsyncCallable.java b/android/guava/src/com/google/common/util/concurrent/AsyncCallable.java index 99807de386c9..3f2405a22098 100644 --- a/android/guava/src/com/google/common/util/concurrent/AsyncCallable.java +++ b/android/guava/src/com/google/common/util/concurrent/AsyncCallable.java @@ -14,9 +14,9 @@ package com.google.common.util.concurrent; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import java.util.concurrent.Future; +import org.jspecify.annotations.Nullable; /** * Computes a value, possibly asynchronously. For an example usage and more information, see {@link @@ -27,9 +27,8 @@ * * @since 20.0 */ -@Beta @GwtCompatible -public interface AsyncCallable { +public interface AsyncCallable { /** * Computes a result {@code Future}. The output {@code Future} need not be {@linkplain * Future#isDone done}, making {@code AsyncCallable} suitable for asynchronous derivations. diff --git a/android/guava/src/com/google/common/util/concurrent/AsyncFunction.java b/android/guava/src/com/google/common/util/concurrent/AsyncFunction.java index 67c3cc289b54..c79e1ffcf093 100644 --- a/android/guava/src/com/google/common/util/concurrent/AsyncFunction.java +++ b/android/guava/src/com/google/common/util/concurrent/AsyncFunction.java @@ -16,7 +16,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.concurrent.Future; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Transforms a value, possibly asynchronously. For an example usage and more information, see @@ -26,7 +26,7 @@ * @since 11.0 */ @GwtCompatible -public interface AsyncFunction { +public interface AsyncFunction { /** * Returns an output {@code Future} to use in place of the given {@code input}. The output {@code * Future} need not be {@linkplain Future#isDone done}, making {@code AsyncFunction} suitable for @@ -34,5 +34,5 @@ public interface AsyncFunction { * *

    Throwing an exception from this method is equivalent to returning a failing {@code Future}. */ - ListenableFuture apply(@NullableDecl I input) throws Exception; + ListenableFuture apply(@ParametricNullness I input) throws Exception; } diff --git a/android/guava/src/com/google/common/util/concurrent/AtomicDouble.java b/android/guava/src/com/google/common/util/concurrent/AtomicDouble.java index 563381bf1008..745ca7733fec 100644 --- a/android/guava/src/com/google/common/util/concurrent/AtomicDouble.java +++ b/android/guava/src/com/google/common/util/concurrent/AtomicDouble.java @@ -18,6 +18,9 @@ import static java.lang.Double.longBitsToDouble; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.concurrent.atomic.AtomicLong; /** @@ -32,13 +35,13 @@ * Double#doubleToRawLongBits}, which differs from both the primitive double {@code ==} operator and * from {@link Double#equals}, as if implemented by: * - *

    {@code
    + * {@snippet :
      * static boolean bitEquals(double x, double y) {
      *   long xBits = Double.doubleToRawLongBits(x);
      *   long yBits = Double.doubleToRawLongBits(y);
      *   return xBits == yBits;
      * }
    - * }
    + * } * *

    It is possible to write a more scalable updater, at the cost of giving up strict atomicity. * See for example {@code + * {@snippet : * static boolean bitEquals(double x, double y) { * long xBits = Double.doubleToRawLongBits(x); * long yBits = Double.doubleToRawLongBits(y); * return xBits == yBits; * } - * } + * } * * @author Doug Lea * @author Martin Buchholz * @since 11.0 */ @GwtIncompatible -public class AtomicDoubleArray implements java.io.Serializable { +@J2ktIncompatible +public class AtomicDoubleArray implements Serializable { private static final long serialVersionUID = 0L; // Making this non-final is the lesser evil according to Effective @@ -68,7 +74,7 @@ public AtomicDoubleArray(int length) { * @throws NullPointerException if array is null */ public AtomicDoubleArray(double[] array) { - final int len = array.length; + int len = array.length; long[] longArray = new long[len]; for (int i = 0; i < len; i++) { longArray[i] = doubleToRawLongBits(array[i]); @@ -187,6 +193,7 @@ public final double getAndAdd(int i, double delta) { * @param i the index * @param delta the value to add * @return the updated value + * @since 31.1 */ @CanIgnoreReturnValue public double addAndGet(int i, double delta) { @@ -231,7 +238,7 @@ public String toString() { * @serialData The length of the array is emitted (int), followed by all of its elements (each a * {@code double}) in the proper order. */ - private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); // Write out array length @@ -245,8 +252,7 @@ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOExceptio } /** Reconstitutes the instance from a stream (that is, deserializes it). */ - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { + private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); int length = s.readInt(); diff --git a/android/guava/src/com/google/common/util/concurrent/AtomicLongMap.java b/android/guava/src/com/google/common/util/concurrent/AtomicLongMap.java index 7fc58f47fc4f..6a9db7f65cda 100644 --- a/android/guava/src/com/google/common/util/concurrent/AtomicLongMap.java +++ b/android/guava/src/com/google/common/util/concurrent/AtomicLongMap.java @@ -16,11 +16,11 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Function; import com.google.common.collect.Maps; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.io.Serializable; import java.util.Collections; import java.util.Iterator; @@ -28,7 +28,7 @@ import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A map containing {@code long} values that can be atomically updated. While writes to a @@ -43,6 +43,8 @@ *

    Instances of this class may be used by multiple threads concurrently. All operations are * atomic unless otherwise noted. * + *

    Instances of this class are serializable if the keys are serializable. + * *

    Note: If your values are always positive and less than 2^31, you may wish to use a * {@link com.google.common.collect.Multiset} such as {@link * com.google.common.collect.ConcurrentHashMultiset} instead. @@ -63,7 +65,7 @@ private AtomicLongMap(ConcurrentHashMap map) { /** Creates an {@code AtomicLongMap}. */ public static AtomicLongMap create() { - return new AtomicLongMap(new ConcurrentHashMap()); + return new AtomicLongMap<>(new ConcurrentHashMap<>()); } /** Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}. */ @@ -289,7 +291,6 @@ boolean remove(K key, long value) { * * @since 20.0 */ - @Beta @CanIgnoreReturnValue public boolean removeIfZero(K key) { return remove(key, 0); @@ -325,7 +326,7 @@ public long sum() { return sum; } - @NullableDecl private transient Map asMap; + @LazyInit private transient @Nullable Map asMap; /** Returns a live, read-only view of the map backing this {@code AtomicLongMap}. */ public Map asMap() { diff --git a/android/guava/src/com/google/common/util/concurrent/Atomics.java b/android/guava/src/com/google/common/util/concurrent/Atomics.java index f6aafb743120..99098cb96763 100644 --- a/android/guava/src/com/google/common/util/concurrent/Atomics.java +++ b/android/guava/src/com/google/common/util/concurrent/Atomics.java @@ -17,7 +17,7 @@ import com.google.common.annotations.GwtIncompatible; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReferenceArray; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to classes in the {@code java.util.concurrent.atomic} package. @@ -34,8 +34,8 @@ private Atomics() {} * * @return a new {@code AtomicReference} with no initial value */ - public static AtomicReference newReference() { - return new AtomicReference(); + public static AtomicReference<@Nullable V> newReference() { + return new AtomicReference<>(); } /** @@ -44,8 +44,9 @@ public static AtomicReference newReference() { * @param initialValue the initial value * @return a new {@code AtomicReference} with the given initial value */ - public static AtomicReference newReference(@NullableDecl V initialValue) { - return new AtomicReference(initialValue); + public static AtomicReference newReference( + @ParametricNullness V initialValue) { + return new AtomicReference<>(initialValue); } /** @@ -54,8 +55,8 @@ public static AtomicReference newReference(@NullableDecl V initialValue) * @param length the length of the array * @return a new {@code AtomicReferenceArray} with the given length */ - public static AtomicReferenceArray newReferenceArray(int length) { - return new AtomicReferenceArray(length); + public static AtomicReferenceArray<@Nullable E> newReferenceArray(int length) { + return new AtomicReferenceArray<>(length); } /** @@ -65,7 +66,7 @@ public static AtomicReferenceArray newReferenceArray(int length) { * @param array the array to copy elements from * @return a new {@code AtomicReferenceArray} copied from the given array */ - public static AtomicReferenceArray newReferenceArray(E[] array) { - return new AtomicReferenceArray(array); + public static AtomicReferenceArray newReferenceArray(E[] array) { + return new AtomicReferenceArray<>(array); } } diff --git a/android/guava/src/com/google/common/util/concurrent/Callables.java b/android/guava/src/com/google/common/util/concurrent/Callables.java index b6678860cdcf..9523696b4c35 100644 --- a/android/guava/src/com/google/common/util/concurrent/Callables.java +++ b/android/guava/src/com/google/common/util/concurrent/Callables.java @@ -16,12 +16,12 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Supplier; import java.util.concurrent.Callable; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to the {@link Callable} interface. @@ -29,18 +29,13 @@ * @author Isaac Shum * @since 1.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Callables { private Callables() {} /** Creates a {@code Callable} which immediately returns a preset value each time it is called. */ - public static Callable returning(@NullableDecl final T value) { - return new Callable() { - @Override - public T call() { - return value; - } - }; + public static Callable returning(@ParametricNullness T value) { + return () -> value; } /** @@ -51,18 +46,13 @@ public T call() { * * @since 20.0 */ - @Beta + @J2ktIncompatible @GwtIncompatible - public static AsyncCallable asAsyncCallable( - final Callable callable, final ListeningExecutorService listeningExecutorService) { + public static AsyncCallable asAsyncCallable( + Callable callable, ListeningExecutorService listeningExecutorService) { checkNotNull(callable); checkNotNull(listeningExecutorService); - return new AsyncCallable() { - @Override - public ListenableFuture call() throws Exception { - return listeningExecutorService.submit(callable); - } - }; + return () -> listeningExecutorService.submit(callable); } /** @@ -73,23 +63,21 @@ public ListenableFuture call() throws Exception { * @param nameSupplier The supplier of thread names, {@link Supplier#get get} will be called once * for each invocation of the wrapped callable. */ + @J2ktIncompatible @GwtIncompatible // threads - static Callable threadRenaming( - final Callable callable, final Supplier nameSupplier) { + static Callable threadRenaming( + Callable callable, Supplier nameSupplier) { checkNotNull(nameSupplier); checkNotNull(callable); - return new Callable() { - @Override - public T call() throws Exception { - Thread currentThread = Thread.currentThread(); - String oldName = currentThread.getName(); - boolean restoreName = trySetName(nameSupplier.get(), currentThread); - try { - return callable.call(); - } finally { - if (restoreName) { - boolean unused = trySetName(oldName, currentThread); - } + return () -> { + Thread currentThread = Thread.currentThread(); + String oldName = currentThread.getName(); + boolean restoreName = trySetName(nameSupplier.get(), currentThread); + try { + return callable.call(); + } finally { + if (restoreName) { + boolean unused = trySetName(oldName, currentThread); } } }; @@ -103,30 +91,29 @@ public T call() throws Exception { * @param nameSupplier The supplier of thread names, {@link Supplier#get get} will be called once * for each invocation of the wrapped callable. */ + @J2ktIncompatible @GwtIncompatible // threads - static Runnable threadRenaming(final Runnable task, final Supplier nameSupplier) { + static Runnable threadRenaming(Runnable task, Supplier nameSupplier) { checkNotNull(nameSupplier); checkNotNull(task); - return new Runnable() { - @Override - public void run() { - Thread currentThread = Thread.currentThread(); - String oldName = currentThread.getName(); - boolean restoreName = trySetName(nameSupplier.get(), currentThread); - try { - task.run(); - } finally { - if (restoreName) { - boolean unused = trySetName(oldName, currentThread); - } + return () -> { + Thread currentThread = Thread.currentThread(); + String oldName = currentThread.getName(); + boolean restoreName = trySetName(nameSupplier.get(), currentThread); + try { + task.run(); + } finally { + if (restoreName) { + boolean unused = trySetName(oldName, currentThread); } } }; } /** Tries to set name of the given {@link Thread}, returns true if successful. */ + @J2ktIncompatible @GwtIncompatible // threads - private static boolean trySetName(final String threadName, Thread currentThread) { + private static boolean trySetName(String threadName, Thread currentThread) { /* * setName should usually succeed, but the security manager can prohibit it. Is there a way to * see if we have the modifyThread permission without catching an exception? diff --git a/android/guava/src/com/google/common/util/concurrent/ClosingFuture.java b/android/guava/src/com/google/common/util/concurrent/ClosingFuture.java index 5969c4ff7d2e..1b010ec0adab 100644 --- a/android/guava/src/com/google/common/util/concurrent/ClosingFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/ClosingFuture.java @@ -32,13 +32,14 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.Futures.nonCancellationPropagating; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; import static java.util.logging.Level.FINER; import static java.util.logging.Level.SEVERE; import static java.util.logging.Level.WARNING; -import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ClosingFuture.Combiner.AsyncCombiningCallable; @@ -47,8 +48,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotMock; import com.google.j2objc.annotations.RetainedWith; -import java.io.Closeable; -import java.io.IOException; import java.util.IdentityHashMap; import java.util.Map; import java.util.concurrent.Callable; @@ -59,8 +58,7 @@ import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A step in a pipeline of an asynchronous computation. When the last step in the computation is @@ -141,7 +139,7 @@ * to be closed asynchronously after the returned {@code Future} is done: the future * completes before closing starts, rather than once it has finished. * - *

    {@code
    + * {@snippet :
      * FluentFuture userName =
      *     ClosingFuture.submit(
      *             closer -> closer.eventuallyClose(database.newTransaction(), closingExecutor),
    @@ -150,7 +148,7 @@
      *         .transform((closer, result) -> result.get("userName"), directExecutor())
      *         .catching(DBException.class, e -> "no user", directExecutor())
      *         .finishToFuture();
    - * }
    + * } * * In this example, when the {@code userName} {@link Future} is done, the transaction and the query * result cursor will both be closed, even if the operation is cancelled or fails. @@ -161,7 +159,7 @@ * {@link #finishToValueAndCloser(ValueAndCloserConsumer, Executor)} to get an object that holds the * final result. You then call {@link ValueAndCloser#closeAsync()} to close the captured objects. * - *
    {@code
    + * {@snippet :
      *     ClosingFuture.submit(
      *             closer -> closer.eventuallyClose(database.newTransaction(), closingExecutor),
      *             executor)
    @@ -178,7 +176,7 @@
      * } finally {
      *   userNameValueAndCloser.closeAsync();
      * }
    - * }
    + * } * * In this example, when {@code userNameValueAndCloser.closeAsync()} is called, the transaction and * the query result cursor will both be closed, even if the operation is cancelled or fails. @@ -190,12 +188,12 @@ * @since 30.0 */ // TODO(dpb): Consider reusing one CloseableList for the entire pipeline, modulo combinations. -@Beta // @Beta for one release. @DoNotMock("Use ClosingFuture.from(Futures.immediate*Future)") -// TODO(dpb): GWT compatibility. -public final class ClosingFuture { +@J2ktIncompatible +@GwtIncompatible // TODO(dpb): GWT compatibility. +public final class ClosingFuture { - private static final Logger logger = Logger.getLogger(ClosingFuture.class.getName()); + private static final LazyLogger logger = new LazyLogger(ClosingFuture.class); /** * An object that can capture objects to be closed later, when a {@link ClosingFuture} pipeline is @@ -211,31 +209,22 @@ public static final class DeferredCloser { /** * Captures an object to be closed when a {@link ClosingFuture} pipeline is done. * - *

    For users of the {@code -jre} flavor of Guava, the object can be any {@code - * AutoCloseable}. For users of the {@code -android} flavor, the object must be a {@code - * Closeable}. (For more about the flavors, see Adding Guava to your - * build.) - * *

    Be careful when targeting an older SDK than you are building against (most commonly when * building for Android): Ensure that any object you pass implements the interface not just in * your current SDK version but also at the oldest version you support. For example, API Level 16 is the first version - * in which {@code Cursor} is {@code Closeable}. To support older versions, pass a wrapper - * {@code Closeable} with a method reference like {@code cursor::close}. - * - *

    Note that this method is still binary-compatible between flavors because the erasure of - * its parameter type is {@code Object}, not {@code AutoCloseable} or {@code Closeable}. + * href="https://developer.android.com/sdk/api_diff/28/changes/android.media.MediaDrm#android.media.MediaDrm.close_added()">API + * Level 28 is the first version in which {@code MediaDrm} is {@code AutoCloseable}. To + * support older versions, pass a wrapper {@code AutoCloseable} with a method reference like + * {@code mediaDrm::release}. * - * @param closeable the object to be closed (see notes above) + * @param closeable the object to be closed * @param closingExecutor the object will be closed on this executor * @return the first argument */ @CanIgnoreReturnValue - @NullableDecl - // TODO(b/163345357): Widen bound to AutoCloseable once we require API Level 19. - public C eventuallyClose( - @NullableDecl C closeable, Executor closingExecutor) { + @ParametricNullness + public C eventuallyClose( + @ParametricNullness C closeable, Executor closingExecutor) { checkNotNull(closingExecutor); if (closeable != null) { list.add(closeable, closingExecutor); @@ -249,15 +238,15 @@ public C eventuallyClose( * * @param the type of the result */ - public interface ClosingCallable { + public interface ClosingCallable { /** * Computes a result, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, Executor) + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done (but * not before this method completes), even if this method throws or the pipeline is cancelled. */ - @NullableDecl + @ParametricNullness V call(DeferredCloser closer) throws Exception; } @@ -267,11 +256,11 @@ public interface ClosingCallable { * @param the type of the result * @since 30.1 */ - public interface AsyncClosingCallable { + public interface AsyncClosingCallable { /** * Computes a result, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, Executor) + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done (but * not before this method completes), even if this method throws or the pipeline is cancelled. */ @@ -284,17 +273,17 @@ public interface AsyncClosingCallable { * @param the type of the input to the function * @param the type of the result of the function */ - public interface ClosingFunction { + public interface ClosingFunction { /** * Applies this function to an input, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, Executor) + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done (but * not before this method completes), even if this method throws or the pipeline is cancelled. */ - @NullableDecl - U apply(DeferredCloser closer, @NullableDecl T input) throws Exception; + @ParametricNullness + U apply(DeferredCloser closer, @ParametricNullness T input) throws Exception; } /** @@ -303,15 +292,15 @@ public interface ClosingFunction { * @param the type of the input to the function * @param the type of the result of the function */ - public interface AsyncClosingFunction { + public interface AsyncClosingFunction { /** * Applies this function to an input, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, Executor) + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done (but * not before this method completes), even if this method throws or the pipeline is cancelled. */ - ClosingFuture apply(DeferredCloser closer, @NullableDecl T input) throws Exception; + ClosingFuture apply(DeferredCloser closer, @ParametricNullness T input) throws Exception; } /** @@ -324,7 +313,7 @@ public interface AsyncClosingFunction { * @param the type of the value of a successful operation * @see ClosingFuture#finishToValueAndCloser(ValueAndCloserConsumer, Executor) */ - public static final class ValueAndCloser { + public static final class ValueAndCloser { private final ClosingFuture closingFuture; @@ -342,7 +331,7 @@ public static final class ValueAndCloser { * @throws CancellationException if the computation was cancelled * @throws ExecutionException if the computation threw an exception */ - @NullableDecl + @ParametricNullness public V get() throws ExecutionException { return getDone(closingFuture.future); } @@ -350,7 +339,7 @@ public V get() throws ExecutionException { /** * Starts closing all closeable objects captured during the {@link ClosingFuture}'s asynchronous * operation on the {@link Executor}s specified by calls to {@link - * DeferredCloser#eventuallyClose(Closeable, Executor)}. + * DeferredCloser#eventuallyClose(Object, Executor)}. * *

    If any such calls specified {@link MoreExecutors#directExecutor()}, those objects will be * closed synchronously. @@ -369,7 +358,7 @@ public void closeAsync() { * @param the type of the final value of a successful pipeline * @see ClosingFuture#finishToValueAndCloser(ValueAndCloserConsumer, Executor) */ - public interface ValueAndCloserConsumer { + public interface ValueAndCloserConsumer { /** Accepts a {@link ValueAndCloser} for the last step in a {@link ClosingFuture} pipeline. */ void accept(ValueAndCloser valueAndCloser); @@ -381,8 +370,26 @@ public interface ValueAndCloserConsumer { * @throws java.util.concurrent.RejectedExecutionException if the task cannot be scheduled for * execution */ - public static ClosingFuture submit(ClosingCallable callable, Executor executor) { - return new ClosingFuture<>(callable, executor); + public static ClosingFuture submit( + ClosingCallable callable, Executor executor) { + checkNotNull(callable); + CloseableList closeables = new CloseableList(); + TrustedListenableFutureTask task = + TrustedListenableFutureTask.create( + new Callable() { + @Override + @ParametricNullness + public V call() throws Exception { + return callable.call(closeables.closer); + } + + @Override + public String toString() { + return callable.toString(); + } + }); + executor.execute(task); + return new ClosingFuture<>(task, closeables); } /** @@ -392,33 +399,56 @@ public static ClosingFuture submit(ClosingCallable callable, Executor * execution * @since 30.1 */ - public static ClosingFuture submitAsync( + public static ClosingFuture submitAsync( AsyncClosingCallable callable, Executor executor) { - return new ClosingFuture<>(callable, executor); + checkNotNull(callable); + CloseableList closeables = new CloseableList(); + TrustedListenableFutureTask task = + TrustedListenableFutureTask.create( + new AsyncCallable() { + @Override + public ListenableFuture call() throws Exception { + CloseableList newCloseables = new CloseableList(); + try { + ClosingFuture closingFuture = callable.call(newCloseables.closer); + closingFuture.becomeSubsumedInto(closeables); + return closingFuture.future; + } finally { + closeables.add(newCloseables, directExecutor()); + } + } + + @Override + public String toString() { + return callable.toString(); + } + }); + executor.execute(task); + return new ClosingFuture<>(task, closeables); } /** * Starts a {@link ClosingFuture} pipeline with a {@link ListenableFuture}. * *

    {@code future}'s value will not be closed when the pipeline is done even if {@code V} - * implements {@link Closeable}. In order to start a pipeline with a value that will be closed + * implements {@link AutoCloseable}. In order to start a pipeline with a value that will be closed * when the pipeline is done, use {@link #submit(ClosingCallable, Executor)} instead. */ - public static ClosingFuture from(ListenableFuture future) { - return new ClosingFuture(future); + public static ClosingFuture from(ListenableFuture future) { + return new ClosingFuture<>(future); } /** * Starts a {@link ClosingFuture} pipeline with a {@link ListenableFuture}. * - *

    If {@code future} succeeds, its value will be closed (using {@code closingExecutor)} when + *

    If {@code future} succeeds, its value will be closed (using {@code closingExecutor)}) when * the pipeline is done, even if the pipeline is canceled or fails. * *

    Cancelling the pipeline will not cancel {@code future}, so that the pipeline can access its * value in order to close it. * * @param future the future to create the {@code ClosingFuture} from. For discussion of the - * future's result type {@code C}, see {@link DeferredCloser#eventuallyClose(Closeable, + * future's result type {@code C}, see {@link DeferredCloser#eventuallyClose(Object, * Executor)}. * @param closingExecutor the future's result will be closed on this executor * @deprecated Creating {@link Future}s of closeable types is dangerous in general because the @@ -430,16 +460,15 @@ public static ClosingFuture from(ListenableFuture future) { * ClosingFuture#from}. */ @Deprecated - // TODO(b/163345357): Widen bound to AutoCloseable once we require API Level 19. - public static ClosingFuture eventuallyClosing( - ListenableFuture future, final Executor closingExecutor) { + public static + ClosingFuture eventuallyClosing(ListenableFuture future, Executor closingExecutor) { checkNotNull(closingExecutor); - final ClosingFuture closingFuture = new ClosingFuture<>(nonCancellationPropagating(future)); + ClosingFuture closingFuture = new ClosingFuture<>(nonCancellationPropagating(future)); Futures.addCallback( future, - new FutureCallback() { + new FutureCallback<@Nullable AutoCloseable>() { @Override - public void onSuccess(@NullableDecl Closeable result) { + public void onSuccess(@Nullable AutoCloseable result) { closingFuture.closeables.closer.eventuallyClose(result, closingExecutor); } @@ -492,8 +521,8 @@ public static Combiner whenAllSucceed(Iterable> futur * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of * the arguments, or if any has already been {@linkplain #finishToFuture() finished} */ - public static Combiner2 whenAllSucceed( - ClosingFuture future1, ClosingFuture future2) { + public static + Combiner2 whenAllSucceed(ClosingFuture future1, ClosingFuture future2) { return new Combiner2<>(future1, future2); } @@ -507,8 +536,10 @@ public static Combiner2 whenAllSucceed( * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of * the arguments, or if any has already been {@linkplain #finishToFuture() finished} */ - public static Combiner3 whenAllSucceed( - ClosingFuture future1, ClosingFuture future2, ClosingFuture future3) { + public static < + V1 extends @Nullable Object, V2 extends @Nullable Object, V3 extends @Nullable Object> + Combiner3 whenAllSucceed( + ClosingFuture future1, ClosingFuture future2, ClosingFuture future3) { return new Combiner3<>(future1, future2, future3); } @@ -522,11 +553,16 @@ public static Combiner3 whenAllSucceed( * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of * the arguments, or if any has already been {@linkplain #finishToFuture() finished} */ - public static Combiner4 whenAllSucceed( - ClosingFuture future1, - ClosingFuture future2, - ClosingFuture future3, - ClosingFuture future4) { + public static < + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + V4 extends @Nullable Object> + Combiner4 whenAllSucceed( + ClosingFuture future1, + ClosingFuture future2, + ClosingFuture future3, + ClosingFuture future4) { return new Combiner4<>(future1, future2, future3, future4); } @@ -540,12 +576,18 @@ public static Combiner4 whenAllSucceed( * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of * the arguments, or if any has already been {@linkplain #finishToFuture() finished} */ - public static Combiner5 whenAllSucceed( - ClosingFuture future1, - ClosingFuture future2, - ClosingFuture future3, - ClosingFuture future4, - ClosingFuture future5) { + public static < + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + V4 extends @Nullable Object, + V5 extends @Nullable Object> + Combiner5 whenAllSucceed( + ClosingFuture future1, + ClosingFuture future2, + ClosingFuture future3, + ClosingFuture future4, + ClosingFuture future5) { return new Combiner5<>(future1, future2, future3, future4, future5); } @@ -570,56 +612,16 @@ public static Combiner whenAllSucceed( } private final AtomicReference state = new AtomicReference<>(OPEN); - private final CloseableList closeables = new CloseableList(); + private final CloseableList closeables; private final FluentFuture future; private ClosingFuture(ListenableFuture future) { - this.future = FluentFuture.from(future); + this(future, new CloseableList()); } - private ClosingFuture(final ClosingCallable callable, Executor executor) { - checkNotNull(callable); - TrustedListenableFutureTask task = - TrustedListenableFutureTask.create( - new Callable() { - @Override - public V call() throws Exception { - return callable.call(closeables.closer); - } - - @Override - public String toString() { - return callable.toString(); - } - }); - executor.execute(task); - this.future = task; - } - - private ClosingFuture(final AsyncClosingCallable callable, Executor executor) { - checkNotNull(callable); - TrustedListenableFutureTask task = - TrustedListenableFutureTask.create( - new AsyncCallable() { - @Override - public ListenableFuture call() throws Exception { - CloseableList newCloseables = new CloseableList(); - try { - ClosingFuture closingFuture = callable.call(newCloseables.closer); - closingFuture.becomeSubsumedInto(closeables); - return closingFuture.future; - } finally { - closeables.add(newCloseables, directExecutor()); - } - } - - @Override - public String toString() { - return callable.toString(); - } - }); - executor.execute(task); - this.future = task; + private ClosingFuture(ListenableFuture future, CloseableList closeables) { + this.future = FluentFuture.from(future); + this.closeables = closeables; } /** @@ -650,10 +652,10 @@ public ListenableFuture statusFuture() { * *

    Example usage: * - *

    {@code
    +   * {@snippet :
        * ClosingFuture> rowsFuture =
        *     queryFuture.transform((closer, result) -> result.getRows(), executor);
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link ListenableFuture#addListener} documentation. All its warnings @@ -661,7 +663,7 @@ public ListenableFuture statusFuture() { * *

    After calling this method, you may not call {@link #finishToFuture()}, {@link * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, or any other derivation method on - * this {@code ClosingFuture}. + * the original {@code ClosingFuture} instance. * * @param function transforms the value of this step to the value of the derived step * @param executor executor to run the function in @@ -670,8 +672,8 @@ public ListenableFuture statusFuture() { * one, or if this {@code ClosingFuture} has already been {@linkplain #finishToFuture() * finished} */ - public ClosingFuture transform( - final ClosingFunction function, Executor executor) { + public ClosingFuture transform( + ClosingFunction function, Executor executor) { checkNotNull(function); AsyncFunction applyFunction = new AsyncFunction() { @@ -712,7 +714,7 @@ public String toString() { *

  • Use this method only when calling an API that returns a {@link ListenableFuture} or a * {@code ClosingFuture}. If possible, prefer calling {@link #transform(ClosingFunction, * Executor)} instead, with a function that returns the next value directly. - *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) closer.eventuallyClose()} + *
  • Call {@link DeferredCloser#eventuallyClose(Object, Executor) closer.eventuallyClose()} * for every closeable object this step creates in order to capture it for later closing. *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code * ClosingFuture} call {@link #from(ListenableFuture)}. @@ -723,14 +725,14 @@ public String toString() { * *

    Example usage: * - *

    {@code
    +   * {@snippet :
        * // Result.getRowsClosingFuture() returns a ClosingFuture.
        * ClosingFuture> rowsFuture =
        *     queryFuture.transformAsync((closer, result) -> result.getRowsClosingFuture(), executor);
        *
        * // Result.writeRowsToOutputStreamFuture() returns a ListenableFuture that resolves to the
        * // number of written rows. openOutputFile() returns a FileOutputStream (which implements
    -   * // Closeable).
    +   * // AutoCloseable).
        * ClosingFuture rowsFuture2 =
        *     queryFuture.transformAsync(
        *         (closer, result) -> {
    @@ -743,7 +745,7 @@ public String toString() {
        * ClosingFuture> rowsFuture3 =
        *     queryFuture.transformAsync(withoutCloser(Result::getRowsFuture), executor);
        *
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link ListenableFuture#addListener} documentation. All its warnings @@ -754,7 +756,7 @@ public String toString() { * *

    After calling this method, you may not call {@link #finishToFuture()}, {@link * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, or any other derivation method on - * this {@code ClosingFuture}. + * the original {@code ClosingFuture} instance. * * @param function transforms the value of this step to a {@code ClosingFuture} with the value of * the derived step @@ -764,8 +766,8 @@ public String toString() { * one, or if this {@code ClosingFuture} has already been {@linkplain #finishToFuture() * finished} */ - public ClosingFuture transformAsync( - final AsyncClosingFunction function, Executor executor) { + public ClosingFuture transformAsync( + AsyncClosingFunction function, Executor executor) { checkNotNull(function); AsyncFunction applyFunction = new AsyncFunction() { @@ -792,31 +794,26 @@ public String toString() { * meets these conditions: * *

      - *
    • It does not need to capture any {@link Closeable} objects by calling {@link - * DeferredCloser#eventuallyClose(Closeable, Executor)}. + *
    • It does not need to capture any {@link AutoCloseable} objects by calling {@link + * DeferredCloser#eventuallyClose(Object, Executor)}. *
    • It returns a {@link ListenableFuture}. *
    * *

    Example usage: * - *

    {@code
    +   * {@snippet :
        * // Result.getRowsFuture() returns a ListenableFuture.
        * ClosingFuture> rowsFuture =
        *     queryFuture.transformAsync(withoutCloser(Result::getRowsFuture), executor);
    -   * }
    + * } * * @param function transforms the value of a {@code ClosingFuture} step to a {@link * ListenableFuture} with the value of a derived step */ - public static AsyncClosingFunction withoutCloser( - final AsyncFunction function) { + public static + AsyncClosingFunction withoutCloser(AsyncFunction function) { checkNotNull(function); - return new AsyncClosingFunction() { - @Override - public ClosingFuture apply(DeferredCloser closer, V input) throws Exception { - return ClosingFuture.from(function.apply(input)); - } - }; + return (closer, input) -> ClosingFuture.from(function.apply(input)); } /** @@ -833,11 +830,11 @@ public ClosingFuture apply(DeferredCloser closer, V input) throws Exception { * *

    Example usage: * - *

    {@code
    +   * {@snippet :
        * ClosingFuture queryFuture =
        *     queryFuture.catching(
        *         QueryException.class, (closer, x) -> Query.emptyQueryResult(), executor);
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link ListenableFuture#addListener} documentation. All its warnings @@ -845,7 +842,7 @@ public ClosingFuture apply(DeferredCloser closer, V input) throws Exception { * *

    After calling this method, you may not call {@link #finishToFuture()}, {@link * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, or any other derivation method on - * this {@code ClosingFuture}. + * the original {@code ClosingFuture} instance. * * @param exceptionType the exception type that triggers use of {@code fallback}. The exception * type is matched against this step's exception. "This step's exception" means the cause of @@ -867,7 +864,7 @@ public ClosingFuture catching( // Avoids generic type capture inconsistency problems where |? extends V| is incompatible with V. private ClosingFuture catchingMoreGeneric( - Class exceptionType, final ClosingFunction fallback, Executor executor) { + Class exceptionType, ClosingFunction fallback, Executor executor) { checkNotNull(fallback); AsyncFunction applyFallback = new AsyncFunction() { @@ -910,7 +907,7 @@ public String toString() { * {@code ClosingFuture}. If possible, prefer calling {@link #catching(Class, * ClosingFunction, Executor)} instead, with a function that returns the next value * directly. - *

  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) closer.eventuallyClose()} + *
  • Call {@link DeferredCloser#eventuallyClose(Object, Executor) closer.eventuallyClose()} * for every closeable object this step creates in order to capture it for later closing. *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code * ClosingFuture} call {@link #from(ListenableFuture)}. @@ -921,13 +918,12 @@ public String toString() { * *

    Example usage: * - *

    {@code
    +   * {@snippet :
        * // Fall back to a secondary input stream in case of IOException.
        * ClosingFuture inputFuture =
        *     firstInputFuture.catchingAsync(
        *         IOException.class, (closer, x) -> secondaryInputStreamClosingFuture(), executor);
        * }
    -   * }
    * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link ListenableFuture#addListener} documentation. All its warnings @@ -938,7 +934,7 @@ public String toString() { * *

    After calling this method, you may not call {@link #finishToFuture()}, {@link * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, or any other derivation method on - * this {@code ClosingFuture}. + * the original {@code ClosingFuture} instance. * * @param exceptionType the exception type that triggers use of {@code fallback}. The exception * type is matched against this step's exception. "This step's exception" means the cause of @@ -964,9 +960,7 @@ public ClosingFuture catchingAsync( // Avoids generic type capture inconsistency problems where |? extends V| is incompatible with V. private ClosingFuture catchingAsyncMoreGeneric( - Class exceptionType, - final AsyncClosingFunction fallback, - Executor executor) { + Class exceptionType, AsyncClosingFunction fallback, Executor executor) { checkNotNull(fallback); AsyncFunction asyncFunction = new AsyncFunction() { @@ -995,21 +989,18 @@ public String toString() { * *

    After calling this method, you may not call {@link * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, this method, or any other - * derivation method on this {@code ClosingFuture}. + * derivation method on the original {@code ClosingFuture} instance. * * @return a {@link Future} that represents the final value or exception of the pipeline */ public FluentFuture finishToFuture() { if (compareAndUpdateState(OPEN, WILL_CLOSE)) { - logger.log(FINER, "will close {0}", this); + logger.get().log(FINER, "will close {0}", this); future.addListener( - new Runnable() { - @Override - public void run() { - checkAndUpdateState(WILL_CLOSE, CLOSING); - close(); - checkAndUpdateState(CLOSING, CLOSED); - } + () -> { + checkAndUpdateState(WILL_CLOSE, CLOSING); + close(); + checkAndUpdateState(CLOSING, CLOSED); }, directExecutor()); } else { @@ -1040,13 +1031,13 @@ public void run() { * receiver can store the {@link ValueAndCloser} outside the receiver for later synchronous use. * *

    After calling this method, you may not call {@link #finishToFuture()}, this method again, or - * any other derivation method on this {@code ClosingFuture}. + * any other derivation method on the original {@code ClosingFuture} instance. * * @param consumer a callback whose method will be called (using {@code executor}) when this * operation is done */ public void finishToValueAndCloser( - final ValueAndCloserConsumer consumer, Executor executor) { + ValueAndCloserConsumer consumer, Executor executor) { checkNotNull(consumer); if (!compareAndUpdateState(OPEN, WILL_CREATE_VALUE_AND_CLOSER)) { switch (state.get()) { @@ -1068,17 +1059,10 @@ public void finishToValueAndCloser( } throw new AssertionError(state); } - future.addListener( - new Runnable() { - @Override - public void run() { - provideValueAndCloser(consumer, ClosingFuture.this); - } - }, - executor); + future.addListener(() -> provideValueAndCloser(consumer, ClosingFuture.this), executor); } - private static void provideValueAndCloser( + private static void provideValueAndCloser( ValueAndCloserConsumer consumer, ClosingFuture closingFuture) { consumer.accept(new ValueAndCloser(closingFuture)); } @@ -1100,8 +1084,9 @@ private static void provideValueAndCloser( * completed normally; {@code true} otherwise */ @CanIgnoreReturnValue + @SuppressWarnings("Interruption") // We are propagating an interrupt from a caller. public boolean cancel(boolean mayInterruptIfRunning) { - logger.log(FINER, "cancelling {0}", this); + logger.get().log(FINER, "cancelling {0}", this); boolean cancelled = future.cancel(mayInterruptIfRunning); if (cancelled) { close(); @@ -1110,11 +1095,11 @@ public boolean cancel(boolean mayInterruptIfRunning) { } private void close() { - logger.log(FINER, "closing {0}", this); + logger.get().log(FINER, "closing {0}", this); closeables.close(); } - private ClosingFuture derive(FluentFuture future) { + private ClosingFuture derive(FluentFuture future) { ClosingFuture derived = new ClosingFuture<>(future); becomeSubsumedInto(derived.closeables); return derived; @@ -1150,17 +1135,17 @@ private Peeker(ImmutableList> futures) { * CombiningCallable#call(DeferredCloser, Peeker)} or {@link * AsyncCombiningCallable#call(DeferredCloser, Peeker)} */ - @NullableDecl - public final D getDone(ClosingFuture closingFuture) + @ParametricNullness + public final D getDone(ClosingFuture closingFuture) throws ExecutionException { checkState(beingCalled); checkArgument(futures.contains(closingFuture)); return Futures.getDone(closingFuture.future); } - @NullableDecl - private V call(CombiningCallable combiner, CloseableList closeables) - throws Exception { + @ParametricNullness + private V call( + CombiningCallable combiner, CloseableList closeables) throws Exception { beingCalled = true; CloseableList newCloseables = new CloseableList(); try { @@ -1171,7 +1156,7 @@ private V call(CombiningCallable combiner, CloseableList c } } - private FluentFuture callAsync( + private FluentFuture callAsync( AsyncCombiningCallable combiner, CloseableList closeables) throws Exception { beingCalled = true; CloseableList newCloseables = new CloseableList(); @@ -1194,7 +1179,7 @@ private FluentFuture callAsync( * *

    Example: * - *

    {@code
    +   * {@snippet :
        * final ClosingFuture file1ReaderFuture = ...;
        * final ClosingFuture file2ReaderFuture = ...;
        * ListenableFuture numberOfDifferentLines =
    @@ -1207,11 +1192,9 @@ private  FluentFuture callAsync(
        *               },
        *               executor)
        *           .closing(executor);
    -   * }
    + * } */ - // TODO(cpovirk): Use simple name instead of fully qualified after we stop building with JDK 8. - @com.google.errorprone.annotations.DoNotMock( - "Use ClosingFuture.whenAllSucceed() or .whenAllComplete() instead.") + @DoNotMock("Use ClosingFuture.whenAllSucceed() or .whenAllComplete() instead.") public static class Combiner { private final CloseableList closeables = new CloseableList(); @@ -1221,18 +1204,18 @@ public static class Combiner { * * @param the type of the result */ - public interface CombiningCallable { + public interface CombiningCallable { /** * Computes a result, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. * * @param peeker used to get the value of any of the input futures */ - @NullableDecl + @ParametricNullness V call(DeferredCloser closer, Peeker peeker) throws Exception; } @@ -1241,14 +1224,14 @@ public interface CombiningCallable { * * @param the type of the result */ - public interface AsyncCombiningCallable { + public interface AsyncCombiningCallable { /** * Computes a {@link ClosingFuture} result, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. * * @param peeker used to get the value of any of the input futures */ @@ -1280,11 +1263,12 @@ private Combiner(boolean allMustSucceed, Iterable> in *

    If the combiningCallable throws an {@code ExecutionException}, the cause of the thrown * {@code ExecutionException} will be extracted and used as the failure of the derived step. */ - public ClosingFuture call( - final CombiningCallable combiningCallable, Executor executor) { + public ClosingFuture call( + CombiningCallable combiningCallable, Executor executor) { Callable callable = new Callable() { @Override + @ParametricNullness public V call() throws Exception { return new Peeker(inputs).call(combiningCallable, closeables); } @@ -1326,9 +1310,8 @@ public String toString() { *

  • Use this method only when calling an API that returns a {@link ListenableFuture} or a * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, * Executor)} instead, with a function that returns the next value directly. - *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) - * closer.eventuallyClose()} for every closeable object this step creates in order to - * capture it for later closing. + *
  • Call {@link DeferredCloser#eventuallyClose(Object, Executor) closer.eventuallyClose()} + * for every closeable object this step creates in order to capture it for later closing. *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code * ClosingFuture} call {@link #from(ListenableFuture)}. * @@ -1336,8 +1319,8 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( - final AsyncCombiningCallable combiningCallable, Executor executor) { + public ClosingFuture callAsync( + AsyncCombiningCallable combiningCallable, Executor executor) { AsyncCallable asyncCallable = new AsyncCallable() { @Override @@ -1356,22 +1339,16 @@ public String toString() { return derived; } - private FutureCombiner futureCombiner() { + private FutureCombiner<@Nullable Object> futureCombiner() { return allMustSucceed ? Futures.whenAllSucceed(inputFutures()) : Futures.whenAllComplete(inputFutures()); } - private static final Function, FluentFuture> INNER_FUTURE = - new Function, FluentFuture>() { - @Override - public FluentFuture apply(ClosingFuture future) { - return future.future; - } - }; - private ImmutableList> inputFutures() { - return FluentIterable.from(inputs).transform(INNER_FUTURE).toList(); + return FluentIterable.from(inputs) + .>transform(future -> future.future) + .toList(); } } @@ -1383,7 +1360,8 @@ private ImmutableList> inputFutures() { * @param the type returned by the first future * @param the type returned by the second future */ - public static final class Combiner2 extends Combiner { + public static final class Combiner2 + extends Combiner { /** * A function that returns a value when applied to the values of the two futures passed to @@ -1393,18 +1371,19 @@ public static final class Combiner2 extend * @param the type returned by the second future * @param the type returned by the function */ - public interface ClosingFunction2 { + public interface ClosingFunction2< + V1 extends @Nullable Object, V2 extends @Nullable Object, U extends @Nullable Object> { /** * Applies this function to two inputs, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. */ - @NullableDecl - U apply(DeferredCloser closer, @NullableDecl V1 value1, @NullableDecl V2 value2) + @ParametricNullness + U apply(DeferredCloser closer, @ParametricNullness V1 value1, @ParametricNullness V2 value2) throws Exception; } @@ -1416,18 +1395,20 @@ U apply(DeferredCloser closer, @NullableDecl V1 value1, @NullableDecl V2 value2) * @param the type returned by the second future * @param the type returned by the function */ - public interface AsyncClosingFunction2 { + public interface AsyncClosingFunction2< + V1 extends @Nullable Object, V2 extends @Nullable Object, U extends @Nullable Object> { /** * Applies this function to two inputs, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. */ ClosingFuture apply( - DeferredCloser closer, @NullableDecl V1 value1, @NullableDecl V2 value2) throws Exception; + DeferredCloser closer, @ParametricNullness V1 value1, @ParametricNullness V2 value2) + throws Exception; } private final ClosingFuture future1; @@ -1452,12 +1433,12 @@ private Combiner2(ClosingFuture future1, ClosingFuture future2) { *

    If the function throws an {@code ExecutionException}, the cause of the thrown {@code * ExecutionException} will be extracted and used as the failure of the derived step. */ - public ClosingFuture call( - final ClosingFunction2 function, Executor executor) { + public ClosingFuture call( + ClosingFunction2 function, Executor executor) { return call( new CombiningCallable() { @Override - @NullableDecl + @ParametricNullness public U call(DeferredCloser closer, Peeker peeker) throws Exception { return function.apply(closer, peeker.getDone(future1), peeker.getDone(future2)); } @@ -1496,9 +1477,8 @@ public String toString() { *

  • Use this method only when calling an API that returns a {@link ListenableFuture} or a * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, * Executor)} instead, with a function that returns the next value directly. - *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) - * closer.eventuallyClose()} for every closeable object this step creates in order to - * capture it for later closing. + *
  • Call {@link DeferredCloser#eventuallyClose(Object, Executor) closer.eventuallyClose()} + * for every closeable object this step creates in order to capture it for later closing. *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code * ClosingFuture} call {@link #from(ListenableFuture)}. * @@ -1506,8 +1486,8 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( - final AsyncClosingFunction2 function, Executor executor) { + public ClosingFuture callAsync( + AsyncClosingFunction2 function, Executor executor) { return callAsync( new AsyncCombiningCallable() { @Override @@ -1533,7 +1513,8 @@ public String toString() { * @param the type returned by the second future * @param the type returned by the third future */ - public static final class Combiner3 + public static final class Combiner3< + V1 extends @Nullable Object, V2 extends @Nullable Object, V3 extends @Nullable Object> extends Combiner { /** * A function that returns a value when applied to the values of the three futures passed to @@ -1545,21 +1526,24 @@ public static final class Combiner3 the type returned by the function */ public interface ClosingFunction3< - V1 extends Object, V2 extends Object, V3 extends Object, U extends Object> { + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + U extends @Nullable Object> { /** * Applies this function to three inputs, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. */ - @NullableDecl + @ParametricNullness U apply( DeferredCloser closer, - @NullableDecl V1 value1, - @NullableDecl V2 value2, - @NullableDecl V3 v3) + @ParametricNullness V1 value1, + @ParametricNullness V2 value2, + @ParametricNullness V3 value3) throws Exception; } @@ -1573,20 +1557,23 @@ U apply( * @param the type returned by the function */ public interface AsyncClosingFunction3< - V1 extends Object, V2 extends Object, V3 extends Object, U extends Object> { + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + U extends @Nullable Object> { /** * Applies this function to three inputs, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. */ ClosingFuture apply( DeferredCloser closer, - @NullableDecl V1 value1, - @NullableDecl V2 value2, - @NullableDecl V3 value3) + @ParametricNullness V1 value1, + @ParametricNullness V2 value2, + @ParametricNullness V3 value3) throws Exception; } @@ -1615,12 +1602,12 @@ private Combiner3( *

    If the function throws an {@code ExecutionException}, the cause of the thrown {@code * ExecutionException} will be extracted and used as the failure of the derived step. */ - public ClosingFuture call( - final ClosingFunction3 function, Executor executor) { + public ClosingFuture call( + ClosingFunction3 function, Executor executor) { return call( new CombiningCallable() { @Override - @NullableDecl + @ParametricNullness public U call(DeferredCloser closer, Peeker peeker) throws Exception { return function.apply( closer, @@ -1663,9 +1650,8 @@ public String toString() { *

  • Use this method only when calling an API that returns a {@link ListenableFuture} or a * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, * Executor)} instead, with a function that returns the next value directly. - *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) - * closer.eventuallyClose()} for every closeable object this step creates in order to - * capture it for later closing. + *
  • Call {@link DeferredCloser#eventuallyClose(Object, Executor) closer.eventuallyClose()} + * for every closeable object this step creates in order to capture it for later closing. *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code * ClosingFuture} call {@link #from(ListenableFuture)}. * @@ -1673,8 +1659,8 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( - final AsyncClosingFunction3 function, Executor executor) { + public ClosingFuture callAsync( + AsyncClosingFunction3 function, Executor executor) { return callAsync( new AsyncCombiningCallable() { @Override @@ -1706,7 +1692,10 @@ public String toString() { * @param the type returned by the fourth future */ public static final class Combiner4< - V1 extends Object, V2 extends Object, V3 extends Object, V4 extends Object> + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + V4 extends @Nullable Object> extends Combiner { /** * A function that returns a value when applied to the values of the four futures passed to @@ -1719,26 +1708,26 @@ public static final class Combiner4< * @param the type returned by the function */ public interface ClosingFunction4< - V1 extends Object, - V2 extends Object, - V3 extends Object, - V4 extends Object, - U extends Object> { + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + V4 extends @Nullable Object, + U extends @Nullable Object> { /** * Applies this function to four inputs, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. */ - @NullableDecl + @ParametricNullness U apply( DeferredCloser closer, - @NullableDecl V1 value1, - @NullableDecl V2 value2, - @NullableDecl V3 value3, - @NullableDecl V4 value4) + @ParametricNullness V1 value1, + @ParametricNullness V2 value2, + @ParametricNullness V3 value3, + @ParametricNullness V4 value4) throws Exception; } @@ -1754,25 +1743,25 @@ U apply( * @param the type returned by the function */ public interface AsyncClosingFunction4< - V1 extends Object, - V2 extends Object, - V3 extends Object, - V4 extends Object, - U extends Object> { + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + V4 extends @Nullable Object, + U extends @Nullable Object> { /** * Applies this function to four inputs, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. */ ClosingFuture apply( DeferredCloser closer, - @NullableDecl V1 value1, - @NullableDecl V2 value2, - @NullableDecl V3 value3, - @NullableDecl V4 value4) + @ParametricNullness V1 value1, + @ParametricNullness V2 value2, + @ParametricNullness V3 value3, + @ParametricNullness V4 value4) throws Exception; } @@ -1806,12 +1795,12 @@ private Combiner4( *

    If the function throws an {@code ExecutionException}, the cause of the thrown {@code * ExecutionException} will be extracted and used as the failure of the derived step. */ - public ClosingFuture call( - final ClosingFunction4 function, Executor executor) { + public ClosingFuture call( + ClosingFunction4 function, Executor executor) { return call( new CombiningCallable() { @Override - @NullableDecl + @ParametricNullness public U call(DeferredCloser closer, Peeker peeker) throws Exception { return function.apply( closer, @@ -1855,9 +1844,8 @@ public String toString() { *

  • Use this method only when calling an API that returns a {@link ListenableFuture} or a * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, * Executor)} instead, with a function that returns the next value directly. - *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) - * closer.eventuallyClose()} for every closeable object this step creates in order to - * capture it for later closing. + *
  • Call {@link DeferredCloser#eventuallyClose(Object, Executor) closer.eventuallyClose()} + * for every closeable object this step creates in order to capture it for later closing. *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code * ClosingFuture} call {@link #from(ListenableFuture)}. * @@ -1865,8 +1853,8 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( - final AsyncClosingFunction4 function, Executor executor) { + public ClosingFuture callAsync( + AsyncClosingFunction4 function, Executor executor) { return callAsync( new AsyncCombiningCallable() { @Override @@ -1900,11 +1888,11 @@ public String toString() { * @param the type returned by the fifth future */ public static final class Combiner5< - V1 extends Object, - V2 extends Object, - V3 extends Object, - V4 extends Object, - V5 extends Object> + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + V4 extends @Nullable Object, + V5 extends @Nullable Object> extends Combiner { /** * A function that returns a value when applied to the values of the five futures passed to @@ -1919,28 +1907,28 @@ public static final class Combiner5< * @param the type returned by the function */ public interface ClosingFunction5< - V1 extends Object, - V2 extends Object, - V3 extends Object, - V4 extends Object, - V5 extends Object, - U extends Object> { + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + V4 extends @Nullable Object, + V5 extends @Nullable Object, + U extends @Nullable Object> { /** * Applies this function to five inputs, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. */ - @NullableDecl + @ParametricNullness U apply( DeferredCloser closer, - @NullableDecl V1 value1, - @NullableDecl V2 value2, - @NullableDecl V3 value3, - @NullableDecl V4 value4, - @NullableDecl V5 value5) + @ParametricNullness V1 value1, + @ParametricNullness V2 value2, + @ParametricNullness V3 value3, + @ParametricNullness V4 value4, + @ParametricNullness V5 value5) throws Exception; } @@ -1957,27 +1945,27 @@ U apply( * @param the type returned by the function */ public interface AsyncClosingFunction5< - V1 extends Object, - V2 extends Object, - V3 extends Object, - V4 extends Object, - V5 extends Object, - U extends Object> { + V1 extends @Nullable Object, + V2 extends @Nullable Object, + V3 extends @Nullable Object, + V4 extends @Nullable Object, + V5 extends @Nullable Object, + U extends @Nullable Object> { /** * Applies this function to five inputs, or throws an exception if unable to do so. * - *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, - * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline - * is done (but not before this method completes), even if this method throws or the pipeline - * is cancelled. + *

    Any objects that are passed to {@link DeferredCloser#eventuallyClose(Object, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done + * (but not before this method completes), even if this method throws or the pipeline is + * cancelled. */ ClosingFuture apply( DeferredCloser closer, - @NullableDecl V1 value1, - @NullableDecl V2 value2, - @NullableDecl V3 value3, - @NullableDecl V4 value4, - @NullableDecl V5 value5) + @ParametricNullness V1 value1, + @ParametricNullness V2 value2, + @ParametricNullness V3 value3, + @ParametricNullness V4 value4, + @ParametricNullness V5 value5) throws Exception; } @@ -2015,12 +2003,12 @@ private Combiner5( *

    If the function throws an {@code ExecutionException}, the cause of the thrown {@code * ExecutionException} will be extracted and used as the failure of the derived step. */ - public ClosingFuture call( - final ClosingFunction5 function, Executor executor) { + public ClosingFuture call( + ClosingFunction5 function, Executor executor) { return call( new CombiningCallable() { @Override - @NullableDecl + @ParametricNullness public U call(DeferredCloser closer, Peeker peeker) throws Exception { return function.apply( closer, @@ -2066,9 +2054,8 @@ public String toString() { *

  • Use this method only when calling an API that returns a {@link ListenableFuture} or a * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, * Executor)} instead, with a function that returns the next value directly. - *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) - * closer.eventuallyClose()} for every closeable object this step creates in order to - * capture it for later closing. + *
  • Call {@link DeferredCloser#eventuallyClose(Object, Executor) closer.eventuallyClose()} + * for every closeable object this step creates in order to capture it for later closing. *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code * ClosingFuture} call {@link #from(ListenableFuture)}. * @@ -2076,8 +2063,8 @@ public String toString() { *

    The same warnings about doing heavyweight operations within {@link * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. */ - public ClosingFuture callAsync( - final AsyncClosingFunction5 function, Executor executor) { + public ClosingFuture callAsync( + AsyncClosingFunction5 function, Executor executor) { return callAsync( new AsyncCombiningCallable() { @Override @@ -2106,34 +2093,37 @@ public String toString() { return toStringHelper(this).add("state", state.get()).addValue(future).toString(); } + @SuppressWarnings({"removal", "Finalize"}) // b/260137033 @Override protected void finalize() { if (state.get().equals(OPEN)) { - logger.log(SEVERE, "Uh oh! An open ClosingFuture has leaked and will close: {0}", this); + logger.get().log(SEVERE, "Uh oh! An open ClosingFuture has leaked and will close: {0}", this); FluentFuture unused = finishToFuture(); } } - private static void closeQuietly(final Closeable closeable, Executor executor) { + private static void closeQuietly(@Nullable AutoCloseable closeable, Executor executor) { if (closeable == null) { return; } try { executor.execute( - new Runnable() { - @Override - public void run() { - try { - closeable.close(); - } catch (IOException | RuntimeException e) { - logger.log(WARNING, "thrown by close()", e); - } + () -> { + try { + closeable.close(); + } catch (Exception e) { + restoreInterruptIfIsInterruptedException(e); + logger.get().log(WARNING, "thrown by close()", e); } }); } catch (RejectedExecutionException e) { - if (logger.isLoggable(WARNING)) { - logger.log( - WARNING, String.format("while submitting close to %s; will close inline", executor), e); + if (logger.get().isLoggable(WARNING)) { + logger + .get() + .log( + WARNING, + String.format("while submitting close to %s; will close inline", executor), + e); } closeQuietly(closeable, directExecutor()); } @@ -2152,14 +2142,16 @@ private boolean compareAndUpdateState(State oldState, State newState) { } // TODO(dpb): Should we use a pair of ArrayLists instead of an IdentityHashMap? - private static final class CloseableList extends IdentityHashMap - implements Closeable { + private static final class CloseableList extends IdentityHashMap + implements AutoCloseable { private final DeferredCloser closer = new DeferredCloser(this); private volatile boolean closed; - private volatile CountDownLatch whenClosed; + private volatile @Nullable CountDownLatch whenClosed; - ListenableFuture applyClosingFunction( - ClosingFunction transformation, V input) throws Exception { + + ListenableFuture applyClosingFunction( + ClosingFunction transformation, @ParametricNullness V input) + throws Exception { // TODO(dpb): Consider ways to defer closing without creating a separate CloseableList. CloseableList newCloseables = new CloseableList(); try { @@ -2169,8 +2161,10 @@ ListenableFuture applyClosingFunction( } } - FluentFuture applyAsyncClosingFunction( - AsyncClosingFunction transformation, V input) throws Exception { + + FluentFuture applyAsyncClosingFunction( + AsyncClosingFunction transformation, @ParametricNullness V input) + throws Exception { // TODO(dpb): Consider ways to defer closing without creating a separate CloseableList. CloseableList newCloseables = new CloseableList(); try { @@ -2193,7 +2187,7 @@ public void close() { } closed = true; } - for (Map.Entry entry : entrySet()) { + for (Map.Entry entry : entrySet()) { closeQuietly(entry.getKey(), entry.getValue()); } clear(); @@ -2202,7 +2196,7 @@ public void close() { } } - void add(@NullableDecl Closeable closeable, Executor executor) { + void add(@Nullable AutoCloseable closeable, Executor executor) { checkNotNull(executor); if (closeable == null) { return; diff --git a/android/guava/src/com/google/common/util/concurrent/CollectionFuture.java b/android/guava/src/com/google/common/util/concurrent/CollectionFuture.java index b6c752aaecff..49e013a02dc3 100644 --- a/android/guava/src/com/google/common/util/concurrent/CollectionFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/CollectionFuture.java @@ -19,31 +19,35 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.errorprone.annotations.concurrent.LazyInit; +import com.google.j2objc.annotations.RetainedLocalRef; +import java.util.Collections; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** Aggregate future that collects (stores) results of each future. */ -@GwtCompatible(emulated = true) -abstract class CollectionFuture extends AggregateFuture { +@GwtCompatible +abstract class CollectionFuture + extends AggregateFuture { /* * We access this field racily but safely. For discussion of a similar situation, see the comments - * on the fields of TimeoutFuture. This field is slightly different than the fields discussed + * on the fields of TimeoutFuture. This field is slightly different from the fields discussed * there: cancel() never reads this field, only writes to it. That makes the race here completely * harmless, rather than just 99.99% harmless. */ - private List> values; + @LazyInit private @Nullable List<@Nullable Present> values; + @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types CollectionFuture( ImmutableCollection> futures, boolean allMustSucceed) { super(futures, allMustSucceed, true); - List> values = + List<@Nullable Present> values = futures.isEmpty() - ? ImmutableList.>of() - : Lists.>newArrayListWithCapacity(futures.size()); + ? Collections.<@Nullable Present>emptyList() + : Lists.<@Nullable Present>newArrayListWithCapacity(futures.size()); // Populate the results list with null initially. for (int i = 0; i < futures.size(); ++i) { @@ -54,8 +58,8 @@ abstract class CollectionFuture extends AggregateFuture { } @Override - final void collectOneValue(int index, @NullableDecl V returnValue) { - List> localValues = values; + final void collectOneValue(int index, @ParametricNullness V returnValue) { + @RetainedLocalRef List<@Nullable Present> localValues = values; if (localValues != null) { localValues.set(index, new Present<>(returnValue)); } @@ -63,7 +67,7 @@ final void collectOneValue(int index, @NullableDecl V returnValue) { @Override final void handleAllCompleted() { - List> localValues = values; + @RetainedLocalRef List<@Nullable Present> localValues = values; if (localValues != null) { set(combine(localValues)); } @@ -75,10 +79,11 @@ void releaseResources(ReleaseResourcesReason reason) { this.values = null; } - abstract C combine(List> values); + abstract C combine(List<@Nullable Present> values); /** Used for {@link Futures#allAsList} and {@link Futures#successfulAsList}. */ - static final class ListFuture extends CollectionFuture> { + static final class ListFuture + extends CollectionFuture> { ListFuture( ImmutableCollection> futures, boolean allMustSucceed) { @@ -87,8 +92,8 @@ static final class ListFuture extends CollectionFuture> { } @Override - public List combine(List> values) { - List result = newArrayListWithCapacity(values.size()); + public List<@Nullable V> combine(List<@Nullable Present> values) { + List<@Nullable V> result = newArrayListWithCapacity(values.size()); for (Present element : values) { result.add(element != null ? element.value : null); } @@ -97,10 +102,10 @@ public List combine(List> values) { } /** The result of a successful {@code Future}. */ - private static final class Present { - V value; + private static final class Present { + @ParametricNullness final V value; - Present(V value) { + Present(@ParametricNullness V value) { this.value = value; } } diff --git a/android/guava/src/com/google/common/util/concurrent/CombinedFuture.java b/android/guava/src/com/google/common/util/concurrent/CombinedFuture.java index 15a1c07d78af..3820200d730c 100644 --- a/android/guava/src/com/google/common/util/concurrent/CombinedFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/CombinedFuture.java @@ -19,18 +19,21 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableCollection; +import com.google.errorprone.annotations.concurrent.LazyInit; +import com.google.j2objc.annotations.RetainedLocalRef; import com.google.j2objc.annotations.WeakOuter; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** Aggregate future that computes its value by calling a callable. */ @GwtCompatible -final class CombinedFuture extends AggregateFuture { - private CombinedFutureInterruptibleTask task; +final class CombinedFuture + extends AggregateFuture<@Nullable Object, V> { + @LazyInit private @Nullable CombinedFutureInterruptibleTask task; CombinedFuture( ImmutableCollection> futures, @@ -53,11 +56,11 @@ final class CombinedFuture extends AggregateFuture { } @Override - void collectOneValue(int index, @NullableDecl Object returnValue) {} + void collectOneValue(int index, @Nullable Object returnValue) {} @Override void handleAllCompleted() { - CombinedFutureInterruptibleTask localTask = task; + @RetainedLocalRef CombinedFutureInterruptibleTask localTask = task; if (localTask != null) { localTask.execute(); } @@ -80,14 +83,15 @@ void releaseResources(ReleaseResourcesReason reason) { @Override protected void interruptTask() { - CombinedFutureInterruptibleTask localTask = task; + @RetainedLocalRef CombinedFutureInterruptibleTask localTask = task; if (localTask != null) { localTask.interruptTask(); } } @WeakOuter - private abstract class CombinedFutureInterruptibleTask extends InterruptibleTask { + private abstract class CombinedFutureInterruptibleTask + extends InterruptibleTask { private final Executor listenerExecutor; CombinedFutureInterruptibleTask(Executor listenerExecutor) { @@ -108,7 +112,7 @@ final void execute() { } @Override - final void afterRanInterruptibly(T result, Throwable error) { + final void afterRanInterruptiblySuccess(@ParametricNullness T result) { /* * The future no longer needs to interrupt this task, so it no longer needs a reference to it. * @@ -122,20 +126,28 @@ final void afterRanInterruptibly(T result, Throwable error) { */ CombinedFuture.this.task = null; - if (error != null) { - if (error instanceof ExecutionException) { - CombinedFuture.this.setException(error.getCause()); - } else if (error instanceof CancellationException) { - cancel(false); - } else { - CombinedFuture.this.setException(error); - } + setValue(result); + } + + @Override + final void afterRanInterruptiblyFailure(Throwable error) { + // See afterRanInterruptiblySuccess. + CombinedFuture.this.task = null; + + if (error instanceof ExecutionException) { + /* + * Cast to ExecutionException to satisfy our nullness checker, which (unsoundly but + * *usually* safely) assumes that getCause() returns non-null on an ExecutionException. + */ + CombinedFuture.this.setException(((ExecutionException) error).getCause()); + } else if (error instanceof CancellationException) { + cancel(false); } else { - setValue(result); + CombinedFuture.this.setException(error); } } - abstract void setValue(T value); + abstract void setValue(@ParametricNullness T value); } @WeakOuter @@ -179,12 +191,13 @@ private final class CallableInterruptibleTask extends CombinedFutureInterruptibl } @Override + @ParametricNullness V runInterruptibly() throws Exception { return callable.call(); } @Override - void setValue(V value) { + void setValue(@ParametricNullness V value) { CombinedFuture.this.set(value); } diff --git a/android/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java b/android/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java index 2b89549fb646..a59f70959c52 100644 --- a/android/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java +++ b/android/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java @@ -15,9 +15,11 @@ package com.google.common.util.concurrent; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Lists.newArrayListWithCapacity; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; @@ -26,7 +28,6 @@ import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.j2objc.annotations.Weak; import java.util.ArrayList; import java.util.Arrays; @@ -41,8 +42,7 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * The {@code CycleDetectingLockFactory} creates {@link ReentrantLock} instances and {@link @@ -159,8 +159,7 @@ * @author Darick Tong * @since 13.0 */ -@Beta -@CanIgnoreReturnValue // TODO(cpovirk): Consider being more strict. +@J2ktIncompatible @GwtIncompatible public class CycleDetectingLockFactory { @@ -171,7 +170,6 @@ public class CycleDetectingLockFactory { * * @since 13.0 */ - @Beta public interface Policy { /** @@ -191,7 +189,6 @@ public interface Policy { * * @since 13.0 */ - @Beta public enum Policies implements Policy { /** * When potential deadlock is detected, this policy results in the throwing of the {@code @@ -213,7 +210,7 @@ public void handlePotentialDeadlock(PotentialDeadlockException e) { WARN { @Override public void handlePotentialDeadlock(PotentialDeadlockException e) { - logger.log(Level.SEVERE, "Detected potential deadlock", e); + logger.get().log(Level.SEVERE, "Detected potential deadlock", e); } }, @@ -281,7 +278,7 @@ public static > WithExplicitOrdering newInstanceWithExplici checkNotNull(policy); @SuppressWarnings("unchecked") Map lockGraphNodes = (Map) getOrCreateNodes(enumClass); - return new WithExplicitOrdering(policy, lockGraphNodes); + return new WithExplicitOrdering<>(policy, lockGraphNodes); } @SuppressWarnings("unchecked") @@ -306,7 +303,7 @@ private static > Map getOrCreateNo static > Map createNodes(Class clazz) { EnumMap map = Maps.newEnumMap(clazz); E[] keys = clazz.getEnumConstants(); - final int numKeys = keys.length; + int numKeys = keys.length; ArrayList nodes = Lists.newArrayListWithCapacity(numKeys); // Create a LockGraphNode for each enum value. for (E key : keys) { @@ -341,7 +338,7 @@ private static String getLockName(Enum rank) { * corresponding to smaller values of {@link Enum#ordinal()} should only be acquired before locks * with larger ordinals. Example: * - *

    {@code
    +   * {@snippet :
        * enum MyLockOrder {
        *   FIRST, SECOND, THIRD;
        * }
    @@ -356,7 +353,7 @@ private static String getLockName(Enum rank) {
        * lock1.lock();
        * lock3.lock();
        * lock2.lock();  // will throw an IllegalStateException
    -   * }
    + * } * *

    As with all locks created by instances of {@code CycleDetectingLockFactory} explicitly * ordered locks participate in general cycle detection with all other cycle detecting locks, and @@ -368,7 +365,7 @@ private static String getLockName(Enum rank) { * attempting to acquire multiple locks with the same Enum value (within the same thread) will * result in an IllegalStateException regardless of the factory's policy. For example: * - *

    {@code
    +   * {@snippet :
        * CycleDetectingLockFactory.WithExplicitOrdering factory1 =
        *   CycleDetectingLockFactory.newInstanceWithExplicitOrdering(...);
        * CycleDetectingLockFactory.WithExplicitOrdering factory2 =
    @@ -384,7 +381,7 @@ private static String getLockName(Enum rank) {
        * lockC.lock();  // will throw an IllegalStateException
        *
        * lockA.lock();  // reentrant acquisition is okay
    -   * }
    + * } * *

    It is the responsibility of the application to ensure that multiple lock instances with the * same rank are never acquired in the same thread. @@ -392,7 +389,6 @@ private static String getLockName(Enum rank) { * @param The Enum type representing the explicit lock ordering. * @since 13.0 */ - @Beta public static final class WithExplicitOrdering> extends CycleDetectingLockFactory { @@ -420,7 +416,9 @@ public ReentrantLock newReentrantLock(E rank) { public ReentrantLock newReentrantLock(E rank, boolean fair) { return policy == Policies.DISABLED ? new ReentrantLock(fair) - : new CycleDetectingReentrantLock(lockGraphNodes.get(rank), fair); + // requireNonNull is safe because createNodes inserts an entry for every E. + // (If the caller passes `null` for the `rank` parameter, this will throw, but that's OK.) + : new CycleDetectingReentrantLock(requireNonNull(lockGraphNodes.get(rank)), fair); } /** Equivalent to {@code newReentrantReadWriteLock(rank, false)}. */ @@ -439,13 +437,16 @@ public ReentrantReadWriteLock newReentrantReadWriteLock(E rank) { public ReentrantReadWriteLock newReentrantReadWriteLock(E rank, boolean fair) { return policy == Policies.DISABLED ? new ReentrantReadWriteLock(fair) - : new CycleDetectingReentrantReadWriteLock(lockGraphNodes.get(rank), fair); + // requireNonNull is safe because createNodes inserts an entry for every E. + // (If the caller passes `null` for the `rank` parameter, this will throw, but that's OK.) + : new CycleDetectingReentrantReadWriteLock( + requireNonNull(lockGraphNodes.get(rank)), fair); } } //////// Implementation ///////// - private static final Logger logger = Logger.getLogger(CycleDetectingLockFactory.class.getName()); + private static final LazyLogger logger = new LazyLogger(CycleDetectingLockFactory.class); final Policy policy; @@ -459,11 +460,11 @@ private CycleDetectingLockFactory(Policy policy) { */ // This is logically a Set, but an ArrayList is used to minimize the amount // of allocation done on lock()/unlock(). - private static final ThreadLocal> acquiredLocks = - new ThreadLocal>() { + private static final ThreadLocal> acquiredLocks = + new ThreadLocal>() { @Override - protected ArrayList initialValue() { - return Lists.newArrayListWithCapacity(3); + protected List initialValue() { + return newArrayListWithCapacity(3); } }; @@ -527,7 +528,6 @@ private static class ExampleStackTrace extends IllegalStateException { * * @since 13.0 */ - @Beta public static final class PotentialDeadlockException extends ExampleStackTrace { private final ExampleStackTrace conflictingStackTrace; @@ -549,7 +549,8 @@ public ExampleStackTrace getConflictingStackTrace() { */ @Override public String getMessage() { - StringBuilder message = new StringBuilder(super.getMessage()); + // requireNonNull is safe because ExampleStackTrace sets a non-null message. + StringBuilder message = new StringBuilder(requireNonNull(super.getMessage())); for (Throwable t = conflictingStackTrace; t != null; t = t.getCause()) { message.append(", ").append(t.getMessage()); } @@ -563,10 +564,14 @@ public String getMessage() { */ private interface CycleDetectingLock { - /** @return the {@link LockGraphNode} associated with this lock. */ + /** + * @return the {@link LockGraphNode} associated with this lock. + */ LockGraphNode getLockGraphNode(); - /** @return {@code true} if the current thread has acquired this lock. */ + /** + * @return {@code true} if the current thread has acquired this lock. + */ boolean isAcquiredByCurrentThread(); } @@ -574,7 +579,7 @@ private interface CycleDetectingLock { * A {@code LockGraphNode} associated with each lock instance keeps track of the directed edges in * the lock acquisition graph. */ - private static class LockGraphNode { + private static final class LockGraphNode { /** * The map tracking the locks that are known to be acquired before this lock, each associated @@ -677,8 +682,7 @@ void checkAcquiredLock(Policy policy, LockGraphNode acquiredLock) { * @return If a path was found, a chained {@link ExampleStackTrace} illustrating the path to the * {@code lock}, or {@code null} if no path was found. */ - @NullableDecl - private ExampleStackTrace findPathTo(LockGraphNode node, Set seen) { + private @Nullable ExampleStackTrace findPathTo(LockGraphNode node, Set seen) { if (!seen.add(this)) { return null; // Already traversed this node. } @@ -709,7 +713,8 @@ private ExampleStackTrace findPathTo(LockGraphNode node, Set seen */ private void aboutToAcquire(CycleDetectingLock lock) { if (!lock.isAcquiredByCurrentThread()) { - ArrayList acquiredLockList = acquiredLocks.get(); + // requireNonNull accommodates Android's @RecentlyNullable annotation on ThreadLocal.get + List acquiredLockList = requireNonNull(acquiredLocks.get()); LockGraphNode node = lock.getLockGraphNode(); node.checkAcquiredLocks(policy, acquiredLockList); acquiredLockList.add(node); @@ -723,7 +728,8 @@ private void aboutToAcquire(CycleDetectingLock lock) { */ private static void lockStateChanged(CycleDetectingLock lock) { if (!lock.isAcquiredByCurrentThread()) { - ArrayList acquiredLockList = acquiredLocks.get(); + // requireNonNull accommodates Android's @RecentlyNullable annotation on ThreadLocal.get + List acquiredLockList = requireNonNull(acquiredLocks.get()); LockGraphNode node = lock.getLockGraphNode(); // Iterate in reverse because locks are usually locked/unlocked in a // LIFO order. @@ -853,7 +859,7 @@ public boolean isAcquiredByCurrentThread() { } } - private class CycleDetectingReentrantReadLock extends ReentrantReadWriteLock.ReadLock { + private final class CycleDetectingReentrantReadLock extends ReentrantReadWriteLock.ReadLock { @Weak final CycleDetectingReentrantReadWriteLock readWriteLock; @@ -912,7 +918,7 @@ public void unlock() { } } - private class CycleDetectingReentrantWriteLock extends ReentrantReadWriteLock.WriteLock { + private final class CycleDetectingReentrantWriteLock extends ReentrantReadWriteLock.WriteLock { @Weak final CycleDetectingReentrantReadWriteLock readWriteLock; diff --git a/android/guava/src/com/google/common/util/concurrent/DirectExecutorService.java b/android/guava/src/com/google/common/util/concurrent/DirectExecutorService.java new file mode 100644 index 000000000000..12cf90a56915 --- /dev/null +++ b/android/guava/src/com/google/common/util/concurrent/DirectExecutorService.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2024 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.concurrent.GuardedBy; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +/** See newDirectExecutorService javadoc for behavioral notes. */ +@J2ktIncompatible // Emulated +@GwtIncompatible +final class DirectExecutorService extends AbstractListeningExecutorService { + + /** Lock used whenever accessing the state variables (runningTasks, shutdown) of the executor */ + private final Object lock = new Object(); + + /* + * Conceptually, these two variables describe the executor being in + * one of three states: + * - Active: shutdown == false + * - Shutdown: runningTasks > 0 and shutdown == true + * - Terminated: runningTasks == 0 and shutdown == true + */ + @GuardedBy("lock") + private int runningTasks = 0; + + @GuardedBy("lock") + private boolean shutdown = false; + + @Override + public void execute(Runnable command) { + startTask(); + try { + command.run(); + } finally { + endTask(); + } + } + + @Override + public boolean isShutdown() { + synchronized (lock) { + return shutdown; + } + } + + @Override + public void shutdown() { + synchronized (lock) { + shutdown = true; + if (runningTasks == 0) { + lock.notifyAll(); + } + } + } + + // See newDirectExecutorService javadoc for unusual behavior of this method. + @Override + public List shutdownNow() { + shutdown(); + return ImmutableList.of(); + } + + @Override + public boolean isTerminated() { + synchronized (lock) { + return shutdown && runningTasks == 0; + } + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + long nanos = unit.toNanos(timeout); + synchronized (lock) { + while (true) { + if (shutdown && runningTasks == 0) { + return true; + } else if (nanos <= 0) { + return false; + } else { + long now = System.nanoTime(); + NANOSECONDS.timedWait(lock, nanos); + nanos -= System.nanoTime() - now; // subtract the actual time we waited + } + } + } + } + + /** + * Checks if the executor has been shut down and increments the running task count. + * + * @throws RejectedExecutionException if the executor has been previously shutdown + */ + private void startTask() { + synchronized (lock) { + if (shutdown) { + throw new RejectedExecutionException("Executor already shutdown"); + } + runningTasks++; + } + } + + /** Decrements the running task count. */ + private void endTask() { + synchronized (lock) { + int numRunning = --runningTasks; + if (numRunning == 0) { + lock.notifyAll(); + } + } + } +} diff --git a/android/guava/src/com/google/common/util/concurrent/ExecutionError.java b/android/guava/src/com/google/common/util/concurrent/ExecutionError.java index dd25efb13de2..48c20bf81eab 100644 --- a/android/guava/src/com/google/common/util/concurrent/ExecutionError.java +++ b/android/guava/src/com/google/common/util/concurrent/ExecutionError.java @@ -15,7 +15,9 @@ package com.google.common.util.concurrent; import com.google.common.annotations.GwtCompatible; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * {@link Error} variant of {@link java.util.concurrent.ExecutionException}. As with {@code @@ -29,23 +31,60 @@ */ @GwtCompatible public class ExecutionError extends Error { - /** Creates a new instance with {@code null} as its detail message. */ + /* + * Ideally, this class would have exposed only constructors that require a non-null cause. See + * https://github.com/jspecify/jspecify-reference-checker/blob/61aafa4ae52594830cfc2d61c8b113009dbdb045/src/main/java/com/google/jspecify/nullness/NullSpecTransfer.java#L789 + * and https://github.com/jspecify/jspecify/issues/490. + * + * (That would also have ensured that its cause was always an Error, rather than possibly another + * kind of Throwable that was later passed to initCause. Then we could have declared the override + * `public final Error getCause()`.) + */ + + /** + * Creates a new instance with {@code null} as its detail message and no cause. + * + * @deprecated Prefer {@linkplain ExecutionError(Error)} a constructor that accepts a cause: Users + * of this class typically expect for instances to have a non-null cause. At the moment, you + * can usually still preserve behavior by passing an explicit {@code null} cause. Note, + * however, that passing an explicit {@code null} cause prevents anyone from calling {@link + * #initCause} later, so it is not quite equivalent to using a constructor that omits the + * cause. + */ + @Deprecated protected ExecutionError() {} - /** Creates a new instance with the given detail message. */ - protected ExecutionError(@NullableDecl String message) { + /** + * Creates a new instance with the given detail message and no cause. + * + * @deprecated Prefer {@linkplain ExecutionError(String, Error)} a constructor that accepts a + * cause: Users of this class typically expect for instances to have a non-null cause. At the + * moment, you can usually still preserve behavior by passing an explicit {@code null} + * cause. Note, however, that passing an explicit {@code null} cause prevents anyone from + * calling {@link #initCause} later, so it is not quite equivalent to using a constructor that + * omits the cause. + */ + @SuppressWarnings("InlineMeSuggester") // b/387265535 + @Deprecated + protected ExecutionError(@Nullable String message) { super(message); } - /** Creates a new instance with the given detail message and cause. */ - public ExecutionError(@NullableDecl String message, @NullableDecl Error cause) { + /** + * Creates a new instance with the given detail message and cause. Prefer to provide a + * non-nullable {@code cause}, as many users expect to find one. + */ + public ExecutionError(@Nullable String message, @Nullable Error cause) { super(message, cause); } - /** Creates a new instance with the given cause. */ - public ExecutionError(@NullableDecl Error cause) { + /** + * Creates a new instance with {@code null} as its detail message and the given cause. Prefer to + * provide a non-nullable {@code cause}, as many users expect to find one. + */ + public ExecutionError(@Nullable Error cause) { super(cause); } - private static final long serialVersionUID = 0; + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } diff --git a/android/guava/src/com/google/common/util/concurrent/ExecutionList.java b/android/guava/src/com/google/common/util/concurrent/ExecutionList.java index 153f42513794..fbf2e4bcc16e 100644 --- a/android/guava/src/com/google/common/util/concurrent/ExecutionList.java +++ b/android/guava/src/com/google/common/util/concurrent/ExecutionList.java @@ -17,11 +17,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.concurrent.GuardedBy; import java.util.concurrent.Executor; import java.util.logging.Level; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A support class for {@code ListenableFuture} implementations to manage their listeners. An @@ -39,18 +39,18 @@ * @author Sven Mawson * @since 1.0 */ +@J2ktIncompatible @GwtIncompatible public final class ExecutionList { /** Logger to log exceptions caught when running runnables. */ - private static final Logger log = Logger.getLogger(ExecutionList.class.getName()); + private static final LazyLogger log = new LazyLogger(ExecutionList.class); /** * The runnable, executor pairs to execute. This acts as a stack threaded through the {@link * RunnableExecutorPair#next} field. */ @GuardedBy("this") - @NullableDecl - private RunnableExecutorPair runnables; + private @Nullable RunnableExecutorPair runnables; @GuardedBy("this") private boolean executed; @@ -137,26 +137,31 @@ public void execute() { * Submits the given runnable to the given {@link Executor} catching and logging all {@linkplain * RuntimeException runtime exceptions} thrown by the executor. */ + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception private static void executeListener(Runnable runnable, Executor executor) { try { executor.execute(runnable); - } catch (RuntimeException e) { + } catch (Exception e) { // sneaky checked exception // Log it and keep going -- bad runnable and/or executor. Don't punish the other runnables if - // we're given a bad one. We only catch RuntimeException because we want Errors to propagate - // up. - log.log( - Level.SEVERE, - "RuntimeException while executing runnable " + runnable + " with executor " + executor, - e); + // we're given a bad one. We only catch Exception because we want Errors to propagate up. + log.get() + .log( + Level.SEVERE, + "RuntimeException while executing runnable " + + runnable + + " with executor " + + executor, + e); } } private static final class RunnableExecutorPair { final Runnable runnable; final Executor executor; - @NullableDecl RunnableExecutorPair next; + @Nullable RunnableExecutorPair next; - RunnableExecutorPair(Runnable runnable, Executor executor, RunnableExecutorPair next) { + RunnableExecutorPair( + Runnable runnable, Executor executor, @Nullable RunnableExecutorPair next) { this.runnable = runnable; this.executor = executor; this.next = next; diff --git a/android/guava/src/com/google/common/util/concurrent/ExecutionSequencer.java b/android/guava/src/com/google/common/util/concurrent/ExecutionSequencer.java index f589e08b0ac1..557f42fc6868 100644 --- a/android/guava/src/com/google/common/util/concurrent/ExecutionSequencer.java +++ b/android/guava/src/com/google/common/util/concurrent/ExecutionSequencer.java @@ -23,11 +23,16 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.Futures.immediateVoidFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.util.concurrent.Callable; import java.util.concurrent.Executor; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; +import org.jspecify.annotations.Nullable; /** * Serializes execution of tasks, somewhat like an "asynchronous {@code synchronized} block." Each @@ -36,7 +41,14 @@ * until the {@code Future} it returned is {@linkplain Future#isDone done} (successful, failed, or * cancelled). * - *

    This class has limited support for cancellation and other "early completion": + *

    This class serializes execution of submitted tasks but not any listeners of + * those tasks. + * + *

    Submitted tasks have a happens-before order as defined in the Java Language Specification. + * Tasks execute with the same happens-before order that the function calls to {@link #submit} and + * {@link #submitAsync} that submitted those tasks had. + * + *

    This class has limited support for cancellation and other "early completions": * *

      *
    • While calls to {@code submit} and {@code submitAsync} return a {@code Future} that can be @@ -45,7 +57,7 @@ * (However, cancellation can prevent an unstarted task from running.) Therefore, the * next task will wait for any running callable (or pending {@code Future} returned by an * {@code AsyncCallable}) to complete, without interrupting it (and without calling {@code - * cancel} on the {@code Future}). So beware: Even if you cancel every precededing {@code + * cancel} on the {@code Future}). So beware: Even if you cancel every preceding {@code * Future} returned by this class, the next task may still have to wait.. *
    • Once an {@code AsyncCallable} returns a {@code Future}, this class considers that task to * be "done" as soon as that {@code Future} completes in any way. Notably, a {@code @@ -57,9 +69,6 @@ * safe for the next task to start. *
    * - *

    An additional limitation: this class serializes execution of tasks but not any - * listeners of those tasks. - * *

    This class is similar to {@link MoreExecutors#newSequentialExecutor}. This class is different * in a few ways: * @@ -77,7 +86,8 @@ * * @since 26.0 */ -@Beta +@J2ktIncompatible +@GwtIncompatible public final class ExecutionSequencer { private ExecutionSequencer() {} @@ -88,10 +98,10 @@ public static ExecutionSequencer create() { } /** This reference acts as a pointer tracking the head of a linked list of ListenableFutures. */ - private final AtomicReference> ref = + private final AtomicReference> ref = new AtomicReference<>(immediateVoidFuture()); - private ThreadConfinedTaskQueue latestTaskQueue = new ThreadConfinedTaskQueue(); + @LazyInit private ThreadConfinedTaskQueue latestTaskQueue = new ThreadConfinedTaskQueue(); /** * This object is unsafely published, but avoids problematic races by relying exclusively on the @@ -122,11 +132,13 @@ private static final class ThreadConfinedTaskQueue { * All the states where thread != currentThread are identical for our purposes, and so even * though it's racy, we don't care which of those values we get, so no need to synchronize. */ - Thread thread; + @LazyInit @Nullable Thread thread; + /** Only used by the thread associated with this object */ - Runnable nextTask; + @Nullable Runnable nextTask; + /** Only used by the thread associated with this object */ - Executor nextExecutor; + @Nullable Executor nextExecutor; } /** @@ -136,7 +148,8 @@ private static final class ThreadConfinedTaskQueue { * execute, but if the output future is cancelled before {@link Callable#call()} is invoked, * {@link Callable#call()} will not be invoked. */ - public ListenableFuture submit(final Callable callable, Executor executor) { + public ListenableFuture submit( + Callable callable, Executor executor) { checkNotNull(callable); checkNotNull(executor); return submitAsync( @@ -161,12 +174,12 @@ public String toString() { * callable} or a callable that has begun to execute, but if the output future is cancelled before * {@link AsyncCallable#call()} is invoked, {@link AsyncCallable#call()} will not be invoked. */ - public ListenableFuture submitAsync( - final AsyncCallable callable, final Executor executor) { + public ListenableFuture submitAsync( + AsyncCallable callable, Executor executor) { checkNotNull(callable); checkNotNull(executor); - final TaskNonReentrantExecutor taskExecutor = new TaskNonReentrantExecutor(executor, this); - final AsyncCallable task = + TaskNonReentrantExecutor taskExecutor = new TaskNonReentrantExecutor(executor, this); + AsyncCallable task = new AsyncCallable() { @Override public ListenableFuture call() throws Exception { @@ -192,61 +205,58 @@ public String toString() { * have completed - namely after oldFuture is done, and taskFuture has either completed or been * cancelled before the callable started execution. */ - final SettableFuture newFuture = SettableFuture.create(); + SettableFuture<@Nullable Void> newFuture = SettableFuture.create(); - final ListenableFuture oldFuture = ref.getAndSet(newFuture); + ListenableFuture<@Nullable Void> oldFuture = ref.getAndSet(newFuture); // Invoke our task once the previous future completes. - final TrustedListenableFutureTask taskFuture = TrustedListenableFutureTask.create(task); + TrustedListenableFutureTask taskFuture = TrustedListenableFutureTask.create(task); oldFuture.addListener(taskFuture, taskExecutor); - final ListenableFuture outputFuture = Futures.nonCancellationPropagating(taskFuture); + ListenableFuture outputFuture = Futures.nonCancellationPropagating(taskFuture); // newFuture's lifetime is determined by taskFuture, which can't complete before oldFuture // unless taskFuture is cancelled, in which case it falls back to oldFuture. This ensures that // if the future we return is cancelled, we don't begin execution of the next task until after // oldFuture completes. Runnable listener = - new Runnable() { - @Override - public void run() { - if (taskFuture.isDone()) { - // Since the value of oldFuture can only ever be immediateFuture(null) or setFuture of - // a future that eventually came from immediateFuture(null), this doesn't leak - // throwables or completion values. - newFuture.setFuture(oldFuture); - } else if (outputFuture.isCancelled() && taskExecutor.trySetCancelled()) { - // If this CAS succeeds, we know that the provided callable will never be invoked, - // so when oldFuture completes it is safe to allow the next submitted task to - // proceed. Doing this immediately here lets the next task run without waiting for - // the cancelled task's executor to run the noop AsyncCallable. - // - // --- - // - // If the CAS fails, the provided callable already started running (or it is about - // to). Our contract promises: - // - // 1. not to execute a new callable until the old one has returned - // - // If we were to cancel taskFuture, that would let the next task start while the old - // one is still running. - // - // Now, maybe we could tweak our implementation to not start the next task until the - // callable actually completes. (We could detect completion in our wrapper - // `AsyncCallable task`.) However, our contract also promises: - // - // 2. not to cancel any Future the user returned from an AsyncCallable - // - // We promise this because, once we cancel that Future, we would no longer be able to - // tell when any underlying work it is doing is done. Thus, we might start a new task - // while that underlying work is still running. - // - // So that is why we cancel only in the case of CAS success. - taskFuture.cancel(false); - } + () -> { + if (taskFuture.isDone()) { + // Since the value of oldFuture can only ever be immediateVoidFuture() or setFuture of a + // future that eventually came from immediateVoidFuture(), this doesn't leak throwables + // or completion values. + newFuture.setFuture(oldFuture); + } else if (outputFuture.isCancelled() && taskExecutor.trySetCancelled()) { + // If this CAS succeeds, we know that the provided callable will never be invoked, + // so when oldFuture completes it is safe to allow the next submitted task to + // proceed. Doing this immediately here lets the next task run without waiting for + // the cancelled task's executor to run the noop AsyncCallable. + // + // --- + // + // If the CAS fails, the provided callable already started running (or it is about + // to). Our contract promises: + // + // 1. not to execute a new callable until the old one has returned + // + // If we were to cancel taskFuture, that would let the next task start while the old + // one is still running. + // + // Now, maybe we could tweak our implementation to not start the next task until the + // callable actually completes. (We could detect completion in our wrapper + // `AsyncCallable task`.) However, our contract also promises: + // + // 2. not to cancel any Future the user returned from an AsyncCallable + // + // We promise this because, once we cancel that Future, we would no longer be able to + // tell when any underlying work it is doing is done. Thus, we might start a new task + // while that underlying work is still running. + // + // So that is why we cancel only in the case of CAS success. + taskFuture.cancel(false); } }; - // Adding the listener to both futures guarantees that newFuture will aways be set. Adding to + // Adding the listener to both futures guarantees that newFuture will always be set. Adding to // taskFuture guarantees completion if the callable is invoked, and adding to outputFuture // propagates cancellation if the callable has not yet been invoked. outputFuture.addListener(listener, directExecutor()); @@ -279,7 +289,6 @@ enum RunningState { * properties; for example, calling WeakReference.get() on Android will block during an * otherwise-concurrent GC cycle. */ - @SuppressWarnings("ShouldNotSubclass") // Saving an allocation here is worth it private static final class TaskNonReentrantExecutor extends AtomicReference implements Executor, Runnable { @@ -287,22 +296,22 @@ private static final class TaskNonReentrantExecutor extends AtomicReference T newProxy( T target, Class interfaceType, long timeoutDuration, TimeUnit timeoutUnit) { @@ -46,9 +51,11 @@ public T newProxy( return target; // ha ha } + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override - public T callWithTimeout(Callable callable, long timeoutDuration, TimeUnit timeoutUnit) - throws ExecutionException { + @ParametricNullness + public T callWithTimeout( + Callable callable, long timeoutDuration, TimeUnit timeoutUnit) throws ExecutionException { checkNotNull(callable); checkNotNull(timeoutUnit); try { @@ -56,36 +63,32 @@ public T callWithTimeout(Callable callable, long timeoutDuration, TimeUni } catch (RuntimeException e) { throw new UncheckedExecutionException(e); } catch (Exception e) { + restoreInterruptIfIsInterruptedException(e); throw new ExecutionException(e); } catch (Error e) { throw new ExecutionError(e); - } catch (Throwable e) { - // It's a non-Error, non-Exception Throwable. Such classes are usually intended to extend - // Exception, so we'll treat it like an Exception. - throw new ExecutionException(e); } } + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override - public T callUninterruptiblyWithTimeout( + @ParametricNullness + public T callUninterruptiblyWithTimeout( Callable callable, long timeoutDuration, TimeUnit timeoutUnit) throws ExecutionException { return callWithTimeout(callable, timeoutDuration, timeoutUnit); } @Override + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception public void runWithTimeout(Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) { checkNotNull(runnable); checkNotNull(timeoutUnit); try { runnable.run(); - } catch (RuntimeException e) { + } catch (Exception e) { // sneaky checked exception throw new UncheckedExecutionException(e); } catch (Error e) { throw new ExecutionError(e); - } catch (Throwable e) { - // It's a non-Error, non-Exception Throwable. Such classes are usually intended to extend - // Exception, so we'll treat it like a RuntimeException. - throw new UncheckedExecutionException(e); } } diff --git a/android/guava/src/com/google/common/util/concurrent/FluentFuture.java b/android/guava/src/com/google/common/util/concurrent/FluentFuture.java index 070cb157e849..33b12c12b56b 100644 --- a/android/guava/src/com/google/common/util/concurrent/FluentFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/FluentFuture.java @@ -15,29 +15,33 @@ package com.google.common.util.concurrent; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotMock; +import com.google.errorprone.annotations.InlineMe; +import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.Nullable; /** * A {@link ListenableFuture} that supports fluent chains of operations. For example: * - *

    {@code
    + * {@snippet :
      * ListenableFuture adminIsLoggedIn =
      *     FluentFuture.from(usersDatabase.getAdminUser())
      *         .transform(User::getId, directExecutor())
      *         .transform(ActivityService::isLoggedIn, threadPool)
      *         .catching(RpcException.class, e -> false, directExecutor());
    - * }
    + * } * *

    Alternatives

    * @@ -69,25 +73,27 @@ * * @since 23.0 */ -@Beta @DoNotMock("Use FluentFuture.from(Futures.immediate*Future) or SettableFuture") -@GwtCompatible(emulated = true) -public abstract class FluentFuture extends GwtFluentFutureCatchingSpecialization { +@GwtCompatible +public abstract class FluentFuture + extends GwtFluentFutureCatchingSpecialization { /** * A less abstract subclass of AbstractFuture. This can be used to optimize setFuture by ensuring * that {@link #get} calls exactly the implementation of {@link AbstractFuture#get}. */ - abstract static class TrustedFuture extends FluentFuture + abstract static class TrustedFuture extends FluentFuture implements AbstractFuture.Trusted { @CanIgnoreReturnValue @Override + @ParametricNullness public final V get() throws InterruptedException, ExecutionException { return super.get(); } @CanIgnoreReturnValue @Override + @ParametricNullness public final V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return super.get(timeout, unit); @@ -124,7 +130,7 @@ public final boolean cancel(boolean mayInterruptIfRunning) { * directly. If not, it is wrapped in a {@code FluentFuture} that delegates all calls to the * original {@code ListenableFuture}. */ - public static FluentFuture from(ListenableFuture future) { + public static FluentFuture from(ListenableFuture future) { return future instanceof FluentFuture ? (FluentFuture) future : new ForwardingFluentFuture(future); @@ -136,8 +142,11 @@ public static FluentFuture from(ListenableFuture future) { * @deprecated no need to use this * @since 28.0 */ + @InlineMe( + replacement = "checkNotNull(future)", + staticImports = "com.google.common.base.Preconditions.checkNotNull") @Deprecated - public static FluentFuture from(FluentFuture future) { + public static FluentFuture from(FluentFuture future) { return checkNotNull(future); } @@ -150,12 +159,12 @@ public static FluentFuture from(FluentFuture future) { * *

    Usage example: * - *

    {@code
    +   * {@snippet :
        * // Falling back to a zero counter in case an exception happens when processing the RPC to fetch
        * // counters.
        * ListenableFuture faultTolerantFuture =
        *     fetchCounters().catching(FetchException.class, x -> 0, directExecutor());
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight @@ -178,6 +187,7 @@ public static FluentFuture from(FluentFuture future) { * {@code get()} throws a different kind of exception, that exception itself. * @param executor the executor that runs {@code fallback} if the input fails */ + @J2ktIncompatible @Partially.GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class") public final FluentFuture catching( Class exceptionType, Function fallback, Executor executor) { @@ -193,17 +203,17 @@ public final FluentFuture catching( * *

    Usage examples: * - *

    {@code
    +   * {@snippet :
        * // Falling back to a zero counter in case an exception happens when processing the RPC to fetch
        * // counters.
        * ListenableFuture faultTolerantFuture =
        *     fetchCounters().catchingAsync(
        *         FetchException.class, x -> immediateFuture(0), directExecutor());
    -   * }
    + * } * *

    The fallback can also choose to propagate the original exception when desired: * - *

    {@code
    +   * {@snippet :
        * // Falling back to a zero counter only in case the exception was a
        * // TimeoutException.
        * ListenableFuture faultTolerantFuture =
    @@ -216,7 +226,7 @@ public final  FluentFuture catching(
        *           throw e;
        *         },
        *         directExecutor());
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight @@ -242,12 +252,31 @@ public final FluentFuture catching( * {@code get()} throws a different kind of exception, that exception itself. * @param executor the executor that runs {@code fallback} if the input fails */ + @J2ktIncompatible @Partially.GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class") public final FluentFuture catchingAsync( Class exceptionType, AsyncFunction fallback, Executor executor) { return (FluentFuture) Futures.catchingAsync(this, exceptionType, fallback, executor); } + /** + * Returns a future that delegates to this future but will finish early (via a {@link + * TimeoutException} wrapped in an {@link ExecutionException}) if the specified timeout expires. + * If the timeout expires, not only will the output future finish, but also the input future + * ({@code this}) will be cancelled and interrupted. + * + * @param timeout when to time out the future + * @param scheduledExecutor The executor service to enforce the timeout. + * @since 33.4.0 (but since 28.0 in the JRE flavor) + */ + @J2ktIncompatible + @GwtIncompatible // ScheduledExecutorService + @IgnoreJRERequirement // Users will use this only if they're already using Duration. + public final FluentFuture withTimeout( + Duration timeout, ScheduledExecutorService scheduledExecutor) { + return withTimeout(toNanosSaturated(timeout), TimeUnit.NANOSECONDS, scheduledExecutor); + } + /** * Returns a future that delegates to this future but will finish early (via a {@link * TimeoutException} wrapped in an {@link ExecutionException}) if the specified timeout expires. @@ -258,6 +287,7 @@ public final FluentFuture catchingAsync( * @param unit the time unit of the time parameter * @param scheduledExecutor The executor service to enforce the timeout. */ + @J2ktIncompatible @GwtIncompatible // ScheduledExecutorService @SuppressWarnings("GoodTime") // should accept a java.time.Duration public final FluentFuture withTimeout( @@ -274,11 +304,11 @@ public final FluentFuture withTimeout( * by applying the given {@code AsyncFunction} to the result of the original {@code Future}. * Example usage: * - *

    {@code
    +   * {@snippet :
        * FluentFuture rowKeyFuture = FluentFuture.from(indexService.lookUp(query));
        * ListenableFuture queryFuture =
        *     rowKeyFuture.transformAsync(dataService::readFuture, executor);
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight @@ -304,7 +334,7 @@ public final FluentFuture withTimeout( * @return A future that holds result of the function (if the input succeeded) or the original * input's failure (if not) */ - public final FluentFuture transformAsync( + public final FluentFuture transformAsync( AsyncFunction function, Executor executor) { return (FluentFuture) Futures.transformAsync(this, function, executor); } @@ -314,10 +344,10 @@ public final FluentFuture transformAsync( * this input {@code Future} fails, the returned {@code Future} fails with the same exception (and * the function is not invoked). Example usage: * - *

    {@code
    +   * {@snippet :
        * ListenableFuture> rowsFuture =
        *     queryFuture.transform(QueryResult::getRows, executor);
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight @@ -341,7 +371,8 @@ public final FluentFuture transformAsync( * @param executor Executor to run the function in. * @return A future that holds result of the transformation. */ - public final FluentFuture transform(Function function, Executor executor) { + public final FluentFuture transform( + Function function, Executor executor) { return (FluentFuture) Futures.transform(this, function, executor); } @@ -356,7 +387,7 @@ public final FluentFuture transform(Function function, Exec * *

    Example: * - *

    {@code
    +   * {@snippet :
        * future.addCallback(
        *     new FutureCallback() {
        *       public void onSuccess(QueryResult result) {
    @@ -366,7 +397,7 @@ public final  FluentFuture transform(Function function, Exec
        *         reportError(t);
        *       }
        *     }, executor);
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingBlockingDeque.java b/android/guava/src/com/google/common/util/concurrent/ForwardingBlockingDeque.java index 2485966948de..2cdf348d12d8 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingBlockingDeque.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingBlockingDeque.java @@ -17,10 +17,12 @@ package com.google.common.util.concurrent; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ForwardingDeque; import java.util.Collection; import java.util.concurrent.BlockingDeque; import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.Nullable; /** * A {@link BlockingDeque} which forwards all its method calls to another {@code BlockingDeque}. @@ -43,6 +45,7 @@ * @author Emily Soldal * @since 21.0 (since 14.0 as {@link com.google.common.collect.ForwardingBlockingDeque}) */ +@J2ktIncompatible @GwtIncompatible public abstract class ForwardingBlockingDeque extends ForwardingDeque implements BlockingDeque { @@ -89,12 +92,12 @@ public E takeLast() throws InterruptedException { } @Override - public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable E pollFirst(long timeout, TimeUnit unit) throws InterruptedException { return delegate().pollFirst(timeout, unit); } @Override - public E pollLast(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable E pollLast(long timeout, TimeUnit unit) throws InterruptedException { return delegate().pollLast(timeout, unit); } @@ -114,7 +117,7 @@ public E take() throws InterruptedException { } @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable E poll(long timeout, TimeUnit unit) throws InterruptedException { return delegate().poll(timeout, unit); } diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingBlockingQueue.java b/android/guava/src/com/google/common/util/concurrent/ForwardingBlockingQueue.java index f5575a15767c..ae52c9626ddf 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingBlockingQueue.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingBlockingQueue.java @@ -15,11 +15,13 @@ package com.google.common.util.concurrent; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ForwardingQueue; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Collection; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.Nullable; /** * A {@link BlockingQueue} which forwards all its method calls to another {@link BlockingQueue}. @@ -35,7 +37,7 @@ * @param the type of elements held in this collection * @since 4.0 */ -@CanIgnoreReturnValue // TODO(cpovirk): Consider being more strict. +@J2ktIncompatible @GwtIncompatible public abstract class ForwardingBlockingQueue extends ForwardingQueue implements BlockingQueue { @@ -46,23 +48,27 @@ protected ForwardingBlockingQueue() {} @Override protected abstract BlockingQueue delegate(); + @CanIgnoreReturnValue @Override public int drainTo(Collection c, int maxElements) { return delegate().drainTo(c, maxElements); } + @CanIgnoreReturnValue @Override public int drainTo(Collection c) { return delegate().drainTo(c); } + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { return delegate().offer(e, timeout, unit); } + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override - public E poll(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable E poll(long timeout, TimeUnit unit) throws InterruptedException { return delegate().poll(timeout, unit); } @@ -76,6 +82,7 @@ public int remainingCapacity() { return delegate().remainingCapacity(); } + @CanIgnoreReturnValue // TODO(kak): consider removing this @Override public E take() throws InterruptedException { return delegate().take(); diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingCondition.java b/android/guava/src/com/google/common/util/concurrent/ForwardingCondition.java index 62c4d4c37843..97ca427bb3f6 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingCondition.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingCondition.java @@ -14,11 +14,16 @@ package com.google.common.util.concurrent; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; /** Forwarding wrapper around a {@code Condition}. */ +@SuppressWarnings("WaitNotInLoop") // We are just delegating; _our user_ must loop. +@J2ktIncompatible +@GwtIncompatible abstract class ForwardingCondition implements Condition { abstract Condition delegate(); diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingExecutorService.java b/android/guava/src/com/google/common/util/concurrent/ForwardingExecutorService.java index f9da1d495631..92a3a72fa233 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingExecutorService.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingExecutorService.java @@ -15,8 +15,10 @@ package com.google.common.util.concurrent; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ForwardingObject; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.CheckReturnValue; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; @@ -25,16 +27,21 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.Nullable; /** * An executor service which forwards all its method calls to another executor service. Subclasses * should override one or more methods to modify the behavior of the backing executor service as * desired per the decorator pattern. * + *

    {@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingExecutorService}. + * * @author Kurt Alfred Kluever * @since 10.0 */ -@CanIgnoreReturnValue // TODO(cpovirk): Consider being more strict. +@J2ktIncompatible @GwtIncompatible public abstract class ForwardingExecutorService extends ForwardingObject implements ExecutorService { @@ -44,32 +51,34 @@ protected ForwardingExecutorService() {} @Override protected abstract ExecutorService delegate(); + @CheckReturnValue @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return delegate().awaitTermination(timeout, unit); } @Override - public List> invokeAll(Collection> tasks) - throws InterruptedException { + public List> invokeAll( + Collection> tasks) throws InterruptedException { return delegate().invokeAll(tasks); } @Override - public List> invokeAll( + public List> invokeAll( Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { return delegate().invokeAll(tasks, timeout, unit); } @Override - public T invokeAny(Collection> tasks) + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { return delegate().invokeAny(tasks); } @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + public T invokeAny( + Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return delegate().invokeAny(tasks, timeout, unit); } @@ -90,6 +99,7 @@ public void shutdown() { } @Override + @CanIgnoreReturnValue public List shutdownNow() { return delegate().shutdownNow(); } @@ -100,7 +110,7 @@ public void execute(Runnable command) { } @Override - public Future submit(Callable task) { + public Future submit(Callable task) { return delegate().submit(task); } @@ -110,7 +120,8 @@ public Future submit(Runnable task) { } @Override - public Future submit(Runnable task, T result) { + public Future submit( + Runnable task, @ParametricNullness T result) { return delegate().submit(task, result); } } diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingFluentFuture.java b/android/guava/src/com/google/common/util/concurrent/ForwardingFluentFuture.java index 984fd680162d..52fc1b039dfa 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingFluentFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingFluentFuture.java @@ -21,6 +21,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.Nullable; /** * {@link FluentFuture} that forwards all calls to a delegate. @@ -33,7 +34,7 @@ * forwards to that future and adds the desired methods. */ @GwtCompatible -final class ForwardingFluentFuture extends FluentFuture { +final class ForwardingFluentFuture extends FluentFuture { private final ListenableFuture delegate; ForwardingFluentFuture(ListenableFuture delegate) { @@ -61,11 +62,13 @@ public boolean isDone() { } @Override + @ParametricNullness public V get() throws InterruptedException, ExecutionException { return delegate.get(); } @Override + @ParametricNullness public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return delegate.get(timeout, unit); diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingFuture.java b/android/guava/src/com/google/common/util/concurrent/ForwardingFuture.java index 2ca22e09b58d..b8ea27ba845c 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingFuture.java @@ -22,6 +22,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.Nullable; /** * A {@link Future} which forwards all its method calls to another future. Subclasses should @@ -33,10 +34,9 @@ * @author Sven Mawson * @since 1.0 */ -@CanIgnoreReturnValue // TODO(cpovirk): Consider being more strict. @GwtCompatible -@SuppressWarnings("ShouldNotSubclass") -public abstract class ForwardingFuture extends ForwardingObject implements Future { +public abstract class ForwardingFuture extends ForwardingObject + implements Future { /** Constructor for use by subclasses. */ protected ForwardingFuture() {} @@ -44,6 +44,7 @@ protected ForwardingFuture() {} protected abstract Future delegate(); @Override + @CanIgnoreReturnValue public boolean cancel(boolean mayInterruptIfRunning) { return delegate().cancel(mayInterruptIfRunning); } @@ -59,11 +60,15 @@ public boolean isDone() { } @Override + @CanIgnoreReturnValue + @ParametricNullness public V get() throws InterruptedException, ExecutionException { return delegate().get(); } @Override + @CanIgnoreReturnValue + @ParametricNullness public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return delegate().get(timeout, unit); @@ -76,7 +81,8 @@ public V get(long timeout, TimeUnit unit) * * @since 9.0 */ - public abstract static class SimpleForwardingFuture extends ForwardingFuture { + public abstract static class SimpleForwardingFuture + extends ForwardingFuture { private final Future delegate; protected SimpleForwardingFuture(Future delegate) { diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingListenableFuture.java b/android/guava/src/com/google/common/util/concurrent/ForwardingListenableFuture.java index 0186f318280b..d204518bb048 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingListenableFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingListenableFuture.java @@ -16,8 +16,8 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.concurrent.Executor; +import org.jspecify.annotations.Nullable; /** * A {@link ListenableFuture} which forwards all its method calls to another future. Subclasses @@ -29,11 +29,9 @@ * @author Shardul Deo * @since 4.0 */ -@SuppressWarnings("ShouldNotSubclass") -@CanIgnoreReturnValue // TODO(cpovirk): Consider being more strict. @GwtCompatible -public abstract class ForwardingListenableFuture extends ForwardingFuture - implements ListenableFuture { +public abstract class ForwardingListenableFuture + extends ForwardingFuture implements ListenableFuture { /** Constructor for use by subclasses. */ protected ForwardingListenableFuture() {} @@ -53,7 +51,7 @@ public void addListener(Runnable listener, Executor exec) { * * @since 9.0 */ - public abstract static class SimpleForwardingListenableFuture + public abstract static class SimpleForwardingListenableFuture extends ForwardingListenableFuture { private final ListenableFuture delegate; diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingListeningExecutorService.java b/android/guava/src/com/google/common/util/concurrent/ForwardingListeningExecutorService.java index 48a49b89d83c..a362379426e6 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingListeningExecutorService.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingListeningExecutorService.java @@ -15,8 +15,9 @@ package com.google.common.util.concurrent; import com.google.common.annotations.GwtIncompatible; -import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.common.annotations.J2ktIncompatible; import java.util.concurrent.Callable; +import org.jspecify.annotations.Nullable; /** * A listening executor service which forwards all its method calls to another listening executor @@ -24,10 +25,14 @@ * executor service as desired per the decorator pattern. * + *

    {@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingListeningExecutorService}. + * * @author Isaac Shum * @since 10.0 */ -@CanIgnoreReturnValue // TODO(cpovirk): Consider being more strict. +@J2ktIncompatible @GwtIncompatible public abstract class ForwardingListeningExecutorService extends ForwardingExecutorService implements ListeningExecutorService { @@ -38,7 +43,7 @@ protected ForwardingListeningExecutorService() {} protected abstract ListeningExecutorService delegate(); @Override - public ListenableFuture submit(Callable task) { + public ListenableFuture submit(Callable task) { return delegate().submit(task); } @@ -48,7 +53,8 @@ public ListenableFuture submit(Runnable task) { } @Override - public ListenableFuture submit(Runnable task, T result) { + public ListenableFuture submit( + Runnable task, @ParametricNullness T result) { return delegate().submit(task, result); } } diff --git a/android/guava/src/com/google/common/util/concurrent/ForwardingLock.java b/android/guava/src/com/google/common/util/concurrent/ForwardingLock.java index 8c50787ba8b6..56fa7e157f16 100644 --- a/android/guava/src/com/google/common/util/concurrent/ForwardingLock.java +++ b/android/guava/src/com/google/common/util/concurrent/ForwardingLock.java @@ -14,11 +14,15 @@ package com.google.common.util.concurrent; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; /** Forwarding wrapper around a {@code Lock}. */ +@J2ktIncompatible +@GwtIncompatible abstract class ForwardingLock implements Lock { abstract Lock delegate(); diff --git a/android/guava/src/com/google/common/util/concurrent/FutureCallback.java b/android/guava/src/com/google/common/util/concurrent/FutureCallback.java index a10f71bbabae..35033faf8664 100644 --- a/android/guava/src/com/google/common/util/concurrent/FutureCallback.java +++ b/android/guava/src/com/google/common/util/concurrent/FutureCallback.java @@ -17,7 +17,7 @@ import com.google.common.annotations.GwtCompatible; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * A callback for accepting the results of a {@link java.util.concurrent.Future} computation @@ -29,9 +29,9 @@ * @since 10.0 */ @GwtCompatible -public interface FutureCallback { +public interface FutureCallback { /** Invoked with the result of the {@code Future} computation when it is successful. */ - void onSuccess(@NullableDecl V result); + void onSuccess(@ParametricNullness V result); /** * Invoked when a {@code Future} computation fails or is canceled. diff --git a/android/guava/src/com/google/common/util/concurrent/Futures.java b/android/guava/src/com/google/common/util/concurrent/Futures.java index 143f1fcd358b..f91aee9e8459 100644 --- a/android/guava/src/com/google/common/util/concurrent/Futures.java +++ b/android/guava/src/com/google/common/util/concurrent/Futures.java @@ -16,12 +16,14 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static java.util.Objects.requireNonNull; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; @@ -32,6 +34,9 @@ import com.google.common.util.concurrent.internal.InternalFutureFailureAccess; import com.google.common.util.concurrent.internal.InternalFutures; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.concurrent.LazyInit; +import com.google.j2objc.annotations.RetainedLocalRef; +import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; @@ -44,7 +49,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** * Static utility methods pertaining to the {@link Future} interface. @@ -70,7 +75,7 @@ * @author Sven Mawson * @since 1.0 */ -@GwtCompatible(emulated = true) +@GwtCompatible public final class Futures extends GwtFuturesCatchingSpecialization { // A note on memory visibility. @@ -101,7 +106,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization { // (hypothetical) unsafe read by our caller. Note: adding 'volatile' does not fix this issue, // it would just add an edge such that if done() observed non-null, then it would also // definitely observe all earlier writes, but we still have no guarantee that done() would see - // the inital write (just stronger guarantees if it does). + // the initial write (just stronger guarantees if it does). // // See: http://cs.oswego.edu/pipermail/concurrency-interest/2015-January/013800.html // For a (long) discussion about this specific issue and the general futility of life. @@ -125,7 +130,8 @@ private Futures() {} * getters just return the value. This {@code Future} can't be canceled or timed out and its * {@code isDone()} method always returns {@code true}. */ - public static ListenableFuture immediateFuture(@NullableDecl V value) { + public static ListenableFuture immediateFuture( + @ParametricNullness V value) { if (value == null) { // This cast is safe because null is assignable to V for all V (i.e. it is bivariant) @SuppressWarnings("unchecked") @@ -142,8 +148,8 @@ public static ListenableFuture immediateFuture(@NullableDecl V value) { * @since 29.0 */ @SuppressWarnings("unchecked") - public static ListenableFuture immediateVoidFuture() { - return (ListenableFuture) ImmediateFuture.NULL; + public static ListenableFuture<@Nullable Void> immediateVoidFuture() { + return (ListenableFuture<@Nullable Void>) ImmediateFuture.NULL; } /** @@ -153,9 +159,10 @@ public static ListenableFuture immediateVoidFuture() { * returns {@code true}. Calling {@code get()} will immediately throw the provided {@code * Throwable} wrapped in an {@code ExecutionException}. */ - public static ListenableFuture immediateFailedFuture(Throwable throwable) { + public static ListenableFuture immediateFailedFuture( + Throwable throwable) { checkNotNull(throwable); - return new ImmediateFailedFuture(throwable); + return new ImmediateFailedFuture<>(throwable); } /** @@ -164,8 +171,13 @@ public static ListenableFuture immediateFailedFuture(Throwable throwable) * * @since 14.0 */ - public static ListenableFuture immediateCancelledFuture() { - return new ImmediateCancelledFuture(); + @SuppressWarnings("unchecked") // ImmediateCancelledFuture can work with any type + public static ListenableFuture immediateCancelledFuture() { + ListenableFuture instance = ImmediateCancelledFuture.INSTANCE; + if (instance != null) { + return (ListenableFuture) instance; + } + return new ImmediateCancelledFuture<>(); } /** @@ -174,8 +186,8 @@ public static ListenableFuture immediateCancelledFuture() { * @throws RejectedExecutionException if the task cannot be scheduled for execution * @since 28.2 */ - @Beta - public static ListenableFuture submit(Callable callable, Executor executor) { + public static ListenableFuture submit( + Callable callable, Executor executor) { TrustedListenableFutureTask task = TrustedListenableFutureTask.create(callable); executor.execute(task); return task; @@ -188,9 +200,9 @@ public static ListenableFuture submit(Callable callable, Executor exec * @throws RejectedExecutionException if the task cannot be scheduled for execution * @since 28.2 */ - @Beta - public static ListenableFuture submit(Runnable runnable, Executor executor) { - TrustedListenableFutureTask task = TrustedListenableFutureTask.create(runnable, null); + public static ListenableFuture<@Nullable Void> submit(Runnable runnable, Executor executor) { + TrustedListenableFutureTask<@Nullable Void> task = + TrustedListenableFutureTask.create(runnable, null); executor.execute(task); return task; } @@ -201,38 +213,50 @@ public static ListenableFuture submit(Runnable runnable, Executor executor * @throws RejectedExecutionException if the task cannot be scheduled for execution * @since 23.0 */ - @Beta - public static ListenableFuture submitAsync(AsyncCallable callable, Executor executor) { + public static ListenableFuture submitAsync( + AsyncCallable callable, Executor executor) { TrustedListenableFutureTask task = TrustedListenableFutureTask.create(callable); executor.execute(task); return task; } + /** + * Schedules {@code callable} on the specified {@code executor}, returning a {@code Future}. + * + * @throws RejectedExecutionException if the task cannot be scheduled for execution + * @since 33.4.0 (but since 28.0 in the JRE flavor) + */ + @J2ktIncompatible + @GwtIncompatible // java.util.concurrent.ScheduledExecutorService + @IgnoreJRERequirement // Users will use this only if they're already using Duration. + // TODO(cpovirk): Return ListenableScheduledFuture? + public static ListenableFuture scheduleAsync( + AsyncCallable callable, Duration delay, ScheduledExecutorService executorService) { + return scheduleAsync(callable, toNanosSaturated(delay), TimeUnit.NANOSECONDS, executorService); + } + /** * Schedules {@code callable} on the specified {@code executor}, returning a {@code Future}. * * @throws RejectedExecutionException if the task cannot be scheduled for execution * @since 23.0 */ - @Beta + @J2ktIncompatible @GwtIncompatible // java.util.concurrent.ScheduledExecutorService @SuppressWarnings("GoodTime") // should accept a java.time.Duration - public static ListenableFuture scheduleAsync( + // TODO(cpovirk): Return ListenableScheduledFuture? + public static ListenableFuture scheduleAsync( AsyncCallable callable, long delay, TimeUnit timeUnit, ScheduledExecutorService executorService) { TrustedListenableFutureTask task = TrustedListenableFutureTask.create(callable); - final Future scheduled = executorService.schedule(task, delay, timeUnit); - task.addListener( - new Runnable() { - @Override - public void run() { - // Don't want to interrupt twice - scheduled.cancel(false); - } - }, - directExecutor()); + Future scheduled = executorService.schedule(task, delay, timeUnit); + /* + * Even when the user interrupts the task, we pass `false` to `cancel` so that we don't + * interrupt a second time after the interruption performed by TrustedListenableFutureTask. + */ + task.addListener(() -> scheduled.cancel(false), directExecutor()); return task; } @@ -246,14 +270,14 @@ public void run() { * *

    Usage example: * - *

    {@code
    +   * {@snippet :
        * ListenableFuture fetchCounterFuture = ...;
        *
        * // Falling back to a zero counter in case an exception happens when
        * // processing the RPC to fetch counters.
        * ListenableFuture faultTolerantFuture = Futures.catching(
        *     fetchCounterFuture, FetchException.class, x -> 0, directExecutor());
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the warnings the {@link MoreExecutors#directExecutor} documentation. @@ -272,9 +296,9 @@ public void run() { * @param executor the executor that runs {@code fallback} if {@code input} fails * @since 19.0 */ - @Beta + @J2ktIncompatible @Partially.GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class") - public static ListenableFuture catching( + public static ListenableFuture catching( ListenableFuture input, Class exceptionType, Function fallback, @@ -292,18 +316,18 @@ public static ListenableFuture catching( * *

    Usage examples: * - *

    {@code
    +   * {@snippet :
        * ListenableFuture fetchCounterFuture = ...;
        *
        * // Falling back to a zero counter in case an exception happens when
        * // processing the RPC to fetch counters.
        * ListenableFuture faultTolerantFuture = Futures.catchingAsync(
        *     fetchCounterFuture, FetchException.class, x -> immediateFuture(0), directExecutor());
    -   * }
    + * } * *

    The fallback can also choose to propagate the original exception when desired: * - *

    {@code
    +   * {@snippet :
        * ListenableFuture fetchCounterFuture = ...;
        *
        * // Falling back to a zero counter only in case the exception was a
    @@ -318,7 +342,7 @@ public static  ListenableFuture catching(
        *       throw e;
        *     },
        *     directExecutor());
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the warnings the {@link MoreExecutors#directExecutor} documentation. @@ -337,14 +361,33 @@ public static ListenableFuture catching( * @param executor the executor that runs {@code fallback} if {@code input} fails * @since 19.0 (similar functionality in 14.0 as {@code withFallback}) */ - @Beta + @J2ktIncompatible @Partially.GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class") - public static ListenableFuture catchingAsync( + public static ListenableFuture catchingAsync( ListenableFuture input, Class exceptionType, AsyncFunction fallback, Executor executor) { - return AbstractCatchingFuture.create(input, exceptionType, fallback, executor); + return AbstractCatchingFuture.createAsync(input, exceptionType, fallback, executor); + } + + /** + * Returns a future that delegates to another but will finish early (via a {@link + * TimeoutException} wrapped in an {@link ExecutionException}) if the specified duration expires. + * + *

    The delegate future is interrupted and cancelled if it times out. + * + * @param delegate The future to delegate to. + * @param time when to time out the future + * @param scheduledExecutor The executor service to enforce the timeout. + * @since 33.4.0 (but since 28.0 in the JRE flavor) + */ + @J2ktIncompatible + @GwtIncompatible // java.util.concurrent.ScheduledExecutorService + @IgnoreJRERequirement // Users will use this only if they're already using Duration. + public static ListenableFuture withTimeout( + ListenableFuture delegate, Duration time, ScheduledExecutorService scheduledExecutor) { + return withTimeout(delegate, toNanosSaturated(time), TimeUnit.NANOSECONDS, scheduledExecutor); } /** @@ -354,15 +397,15 @@ public static ListenableFuture catchingAsync( *

    The delegate future is interrupted and cancelled if it times out. * * @param delegate The future to delegate to. - * @param time when to timeout the future + * @param time when to time out the future * @param unit the time unit of the time parameter * @param scheduledExecutor The executor service to enforce the timeout. * @since 19.0 */ - @Beta + @J2ktIncompatible @GwtIncompatible // java.util.concurrent.ScheduledExecutorService @SuppressWarnings("GoodTime") // should accept a java.time.Duration - public static ListenableFuture withTimeout( + public static ListenableFuture withTimeout( ListenableFuture delegate, long time, TimeUnit unit, @@ -382,11 +425,11 @@ public static ListenableFuture withTimeout( * by applying the given {@code AsyncFunction} to the result of the original {@code Future}. * Example usage: * - *

    {@code
    +   * {@snippet :
        * ListenableFuture rowKeyFuture = indexService.lookUp(query);
        * ListenableFuture queryFuture =
        *     transformAsync(rowKeyFuture, dataService::readFuture, executor);
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the warnings the {@link MoreExecutors#directExecutor} documentation. @@ -405,12 +448,12 @@ public static ListenableFuture withTimeout( * input's failure (if not) * @since 19.0 (in 11.0 as {@code transform}) */ - @Beta - public static ListenableFuture transformAsync( - ListenableFuture input, - AsyncFunction function, - Executor executor) { - return AbstractTransformFuture.create(input, function, executor); + public static + ListenableFuture transformAsync( + ListenableFuture input, + AsyncFunction function, + Executor executor) { + return AbstractTransformFuture.createAsync(input, function, executor); } /** @@ -418,11 +461,11 @@ public static ListenableFuture transformAsync( * Future}. If {@code input} fails, the returned {@code Future} fails with the same exception (and * the function is not invoked). Example usage: * - *

    {@code
    +   * {@snippet :
        * ListenableFuture queryFuture = ...;
        * ListenableFuture> rowsFuture =
        *     transform(queryFuture, QueryResult::getRows, executor);
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the warnings the {@link MoreExecutors#directExecutor} documentation. @@ -442,9 +485,9 @@ public static ListenableFuture transformAsync( * @return A future that holds result of the transformation. * @since 9.0 (in 2.0 as {@code compose}) */ - @Beta - public static ListenableFuture transform( - ListenableFuture input, Function function, Executor executor) { + public static + ListenableFuture transform( + ListenableFuture input, Function function, Executor executor) { return AbstractTransformFuture.create(input, function, executor); } @@ -468,11 +511,10 @@ public static ListenableFuture transform( * @return A future that returns the result of the transformation. * @since 10.0 */ - @Beta + @J2ktIncompatible @GwtIncompatible // TODO - @SuppressWarnings("ShouldNotSubclass") - public static Future lazyTransform( - final Future input, final Function function) { + public static Future lazyTransform( + Future input, Function function) { checkNotNull(input); checkNotNull(function); return new Future() { @@ -507,6 +549,7 @@ private O applyTransformation(I input) throws ExecutionException { try { return function.apply(input); } catch (Throwable t) { + // Any Exception is either a RuntimeException or sneaky checked exception. throw new ExecutionException(t); } } @@ -529,10 +572,15 @@ private O applyTransformation(I input) throws ExecutionException { * @return a future that provides a list of the results of the component futures * @since 10.0 */ - @Beta @SafeVarargs - public static ListenableFuture> allAsList(ListenableFuture... futures) { - return new ListFuture(ImmutableList.copyOf(futures), true); + public static ListenableFuture> allAsList( + ListenableFuture... futures) { + ListenableFuture> nullable = + new ListFuture(ImmutableList.copyOf(futures), true); + // allAsList ensures that it fills the output list with V instances. + @SuppressWarnings("nullness") + ListenableFuture> nonNull = nullable; + return nonNull; } /** @@ -551,10 +599,14 @@ public static ListenableFuture> allAsList(ListenableFuture ListenableFuture> allAsList( + public static ListenableFuture> allAsList( Iterable> futures) { - return new ListFuture(ImmutableList.copyOf(futures), true); + ListenableFuture> nullable = + new ListFuture(ImmutableList.copyOf(futures), true); + // allAsList ensures that it fills the output list with V instances. + @SuppressWarnings("nullness") + ListenableFuture> nonNull = nullable; + return nonNull; } /** @@ -565,10 +617,10 @@ public static ListenableFuture> allAsList( * * @since 20.0 */ - @Beta @SafeVarargs - public static FutureCombiner whenAllComplete(ListenableFuture... futures) { - return new FutureCombiner(false, ImmutableList.copyOf(futures)); + public static FutureCombiner whenAllComplete( + ListenableFuture... futures) { + return new FutureCombiner<>(false, ImmutableList.copyOf(futures)); } /** @@ -579,10 +631,9 @@ public static FutureCombiner whenAllComplete(ListenableFuture FutureCombiner whenAllComplete( + public static FutureCombiner whenAllComplete( Iterable> futures) { - return new FutureCombiner(false, ImmutableList.copyOf(futures)); + return new FutureCombiner<>(false, ImmutableList.copyOf(futures)); } /** @@ -592,10 +643,10 @@ public static FutureCombiner whenAllComplete( * * @since 20.0 */ - @Beta @SafeVarargs - public static FutureCombiner whenAllSucceed(ListenableFuture... futures) { - return new FutureCombiner(true, ImmutableList.copyOf(futures)); + public static FutureCombiner whenAllSucceed( + ListenableFuture... futures) { + return new FutureCombiner<>(true, ImmutableList.copyOf(futures)); } /** @@ -605,10 +656,9 @@ public static FutureCombiner whenAllSucceed(ListenableFuture * * @since 20.0 */ - @Beta - public static FutureCombiner whenAllSucceed( + public static FutureCombiner whenAllSucceed( Iterable> futures) { - return new FutureCombiner(true, ImmutableList.copyOf(futures)); + return new FutureCombiner<>(true, ImmutableList.copyOf(futures)); } /** @@ -619,7 +669,7 @@ public static FutureCombiner whenAllSucceed( * *

    Example: * - *

    {@code
    +   * {@snippet :
        * final ListenableFuture loginDateFuture =
        *     loginService.findLastLoginDate(username);
        * final ListenableFuture> recentCommandsFuture =
    @@ -633,14 +683,12 @@ public static  FutureCombiner whenAllSucceed(
        *                     Futures.getDone(loginDateFuture),
        *                     Futures.getDone(recentCommandsFuture)),
        *             executor);
    -   * }
    + * } * * @since 20.0 */ - @Beta - @CanIgnoreReturnValue // TODO(cpovirk): Consider removing, especially if we provide run(Runnable) @GwtCompatible - public static final class FutureCombiner { + public static final class FutureCombiner { private final boolean allMustSucceed; private final ImmutableList> futures; @@ -663,9 +711,16 @@ private FutureCombiner( * ExecutionException} that gets thrown by the returned combined future. * *

    Canceling this future will attempt to cancel all the component futures. + * + * @return a future whose result is based on {@code combiner} (or based on the input futures + * passed to {@code whenAllSucceed}, if that is the method you used to create this {@code + * FutureCombiner}). Even if you don't care about the value of the future, you should + * typically check whether it failed: See https://errorprone.info/bugpattern/FutureReturnValueIgnored. */ - public ListenableFuture callAsync(AsyncCallable combiner, Executor executor) { - return new CombinedFuture(futures, allMustSucceed, executor, combiner); + public ListenableFuture callAsync( + AsyncCallable combiner, Executor executor) { + return new CombinedFuture<>(futures, allMustSucceed, executor, combiner); } /** @@ -681,10 +736,16 @@ public ListenableFuture callAsync(AsyncCallable combiner, Executor exe * ExecutionException} that gets thrown by the returned combined future. * *

    Canceling this future will attempt to cancel all the component futures. + * + * @return a future whose result is based on {@code combiner} (or based on the input futures + * passed to {@code whenAllSucceed}, if that is the method you used to create this {@code + * FutureCombiner}). Even if you don't care about the value of the future, you should + * typically check whether it failed: See https://errorprone.info/bugpattern/FutureReturnValueIgnored. */ - @CanIgnoreReturnValue // TODO(cpovirk): Remove this - public ListenableFuture call(Callable combiner, Executor executor) { - return new CombinedFuture(futures, allMustSucceed, executor, combiner); + public ListenableFuture call( + Callable combiner, Executor executor) { + return new CombinedFuture<>(futures, allMustSucceed, executor, combiner); } /** @@ -697,12 +758,17 @@ public ListenableFuture call(Callable combiner, Executor executor) { *

    Canceling this Future will attempt to cancel all the component futures. * * @since 23.6 + * @return a future whose result is based on {@code combiner} (or based on the input futures + * passed to {@code whenAllSucceed}, if that is the method you used to create this {@code + * FutureCombiner}). Even though the future never produces a value other than {@code null}, + * you should typically check whether it failed: See https://errorprone.info/bugpattern/FutureReturnValueIgnored. */ - public ListenableFuture run(final Runnable combiner, Executor executor) { + public ListenableFuture run(Runnable combiner, Executor executor) { return call( - new Callable() { + new Callable<@Nullable Void>() { @Override - public Void call() throws Exception { + public @Nullable Void call() throws Exception { combiner.run(); return null; } @@ -718,8 +784,8 @@ public Void call() throws Exception { * * @since 15.0 */ - @Beta - public static ListenableFuture nonCancellationPropagating(ListenableFuture future) { + public static ListenableFuture nonCancellationPropagating( + ListenableFuture future) { if (future.isDone()) { return future; } @@ -729,11 +795,11 @@ public static ListenableFuture nonCancellationPropagating(ListenableFutur } /** A wrapped future that does not propagate cancellation to its delegate. */ - private static final class NonCancellationPropagatingFuture + private static final class NonCancellationPropagatingFuture extends AbstractFuture.TrustedFuture implements Runnable { - private ListenableFuture delegate; + @LazyInit private @Nullable ListenableFuture delegate; - NonCancellationPropagatingFuture(final ListenableFuture delegate) { + NonCancellationPropagatingFuture(ListenableFuture delegate) { this.delegate = delegate; } @@ -741,15 +807,15 @@ private static final class NonCancellationPropagatingFuture public void run() { // This prevents cancellation from propagating because we don't call setFuture(delegate) until // delegate is already done, so calling cancel() on this future won't affect it. - ListenableFuture localDelegate = delegate; + @RetainedLocalRef ListenableFuture localDelegate = delegate; if (localDelegate != null) { setFuture(localDelegate); } } @Override - protected String pendingToString() { - ListenableFuture localDelegate = delegate; + protected @Nullable String pendingToString() { + @RetainedLocalRef ListenableFuture localDelegate = delegate; if (localDelegate != null) { return "delegate=[" + localDelegate + "]"; } @@ -780,10 +846,21 @@ protected void afterDone() { * @return a future that provides a list of the results of the component futures * @since 10.0 */ - @Beta @SafeVarargs - public static ListenableFuture> successfulAsList( + public static ListenableFuture> successfulAsList( ListenableFuture... futures) { + /* + * Another way to express this signature would be to bound by @NonNull and accept + * LF. That might be better: There's currently no difference between the + * outputs users get when calling this with and calling it with <@Nullable Foo>. The only + * difference is that calling it with won't work when an input Future has a @Nullable + * type. So why even make that error possible by giving callers the choice? + * + * On the other hand, the current signature is consistent with the similar allAsList method. And + * eventually this method may go away entirely in favor of an API like + * whenAllComplete().collectSuccesses(). That API would have a signature more like the current + * one. + */ return new ListFuture(ImmutableList.copyOf(futures), false); } @@ -805,8 +882,7 @@ public static ListenableFuture> successfulAsList( * @return a future that provides a list of the results of the component futures * @since 10.0 */ - @Beta - public static ListenableFuture> successfulAsList( + public static ListenableFuture> successfulAsList( Iterable> futures) { return new ListFuture(ImmutableList.copyOf(futures), false); } @@ -832,28 +908,20 @@ public static ListenableFuture> successfulAsList( * * @since 17.0 */ - @Beta - public static ImmutableList> inCompletionOrder( + public static ImmutableList> inCompletionOrder( Iterable> futures) { ListenableFuture[] copy = gwtCompatibleToArray(futures); - final InCompletionOrderState state = new InCompletionOrderState<>(copy); + InCompletionOrderState state = new InCompletionOrderState<>(copy); ImmutableList.Builder> delegatesBuilder = ImmutableList.builderWithExpectedSize(copy.length); for (int i = 0; i < copy.length; i++) { delegatesBuilder.add(new InCompletionOrderFuture(state)); } - final ImmutableList> delegates = delegatesBuilder.build(); + ImmutableList> delegates = delegatesBuilder.build(); for (int i = 0; i < copy.length; i++) { - final int localI = i; - copy[i].addListener( - new Runnable() { - @Override - public void run() { - state.recordInputCompletion(delegates, localI); - } - }, - directExecutor()); + int localI = i; + copy[i].addListener(() -> state.recordInputCompletion(delegates, localI), directExecutor()); } @SuppressWarnings("unchecked") @@ -863,9 +931,9 @@ public void run() { /** Can't use Iterables.toArray because it's not gwt compatible */ @SuppressWarnings("unchecked") - private static ListenableFuture[] gwtCompatibleToArray( + private static ListenableFuture[] gwtCompatibleToArray( Iterable> futures) { - final Collection> collection; + Collection> collection; if (futures instanceof Collection) { collection = (Collection>) futures; } else { @@ -877,8 +945,9 @@ private static ListenableFuture[] gwtCompatibleToArray( // This can't be a TrustedFuture, because TrustedFuture has clever optimizations that // mean cancel won't be called if this Future is passed into setFuture, and then // cancelled. - private static final class InCompletionOrderFuture extends AbstractFuture { - private InCompletionOrderState state; + private static final class InCompletionOrderFuture + extends AbstractFuture { + private @Nullable InCompletionOrderState state; private InCompletionOrderFuture(InCompletionOrderState state) { this.state = state; @@ -888,7 +957,15 @@ private InCompletionOrderFuture(InCompletionOrderState state) { public boolean cancel(boolean interruptIfRunning) { InCompletionOrderState localState = state; if (super.cancel(interruptIfRunning)) { - localState.recordOutputCancellation(interruptIfRunning); + /* + * requireNonNull is generally safe: If cancel succeeded, then this Future was still + * pending, so its `state` field hasn't been nulled out yet. + * + * OK, it's technically possible for this to fail in the presence of unsafe publishing, as + * discussed in the comments in TimeoutFuture. TODO(cpovirk): Maybe check for null before + * calling recordOutputCancellation? + */ + requireNonNull(localState).recordOutputCancellation(interruptIfRunning); return true; } return false; @@ -900,7 +977,7 @@ protected void afterDone() { } @Override - protected String pendingToString() { + protected @Nullable String pendingToString() { InCompletionOrderState localState = state; if (localState != null) { // Don't print the actual array! We don't want inCompletionOrder(list).toString() to have @@ -915,14 +992,15 @@ protected String pendingToString() { } } - private static final class InCompletionOrderState { + private static final class InCompletionOrderState { // A happens-before edge between the writes of these fields and their reads exists, because // in order to read these fields, the corresponding write to incompleteOutputCount must have // been read. private boolean wasCancelled = false; private boolean shouldInterrupt = true; private final AtomicInteger incompleteOutputCount; - private final ListenableFuture[] inputFutures; + // We set the elements of the array to null as they complete. + private final @Nullable ListenableFuture[] inputFutures; private volatile int delegateIndex = 0; private InCompletionOrderState(ListenableFuture[] inputFutures) { @@ -942,7 +1020,11 @@ private void recordOutputCancellation(boolean interruptIfRunning) { private void recordInputCompletion( ImmutableList> delegates, int inputFutureIndex) { - ListenableFuture inputFuture = inputFutures[inputFutureIndex]; + /* + * requireNonNull is safe because we accepted an Iterable of non-null Future instances, and we + * don't overwrite an element in the array until after reading it. + */ + ListenableFuture inputFuture = requireNonNull(inputFutures[inputFutureIndex]); // Null out our reference to this future, so it can be GCed inputFutures[inputFutureIndex] = null; for (int i = delegateIndex; i < delegates.size(); i++) { @@ -959,9 +1041,10 @@ private void recordInputCompletion( delegateIndex = delegates.size(); } + @SuppressWarnings("Interruption") // We are propagating an interrupt from a caller. private void recordCompletion() { if (incompleteOutputCount.decrementAndGet() == 0 && wasCancelled) { - for (ListenableFuture toCancel : inputFutures) { + for (ListenableFuture toCancel : inputFutures) { if (toCancel != null) { toCancel.cancel(shouldInterrupt); } @@ -986,7 +1069,7 @@ private void recordCompletion() { * *

    Example: * - *

    {@code
    +   * {@snippet :
        * ListenableFuture future = ...;
        * Executor e = ...
        * addCallback(future,
    @@ -998,7 +1081,7 @@ private void recordCompletion() {
        *         reportError(t);
        *       }
        *     }, e);
    -   * }
    + * } * *

    When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See * the warnings the {@link MoreExecutors#directExecutor} documentation. @@ -1011,16 +1094,14 @@ private void recordCompletion() { * @param executor The executor to run {@code callback} when the future completes. * @since 10.0 */ - public static void addCallback( - final ListenableFuture future, - final FutureCallback callback, - Executor executor) { + public static void addCallback( + ListenableFuture future, FutureCallback callback, Executor executor) { Preconditions.checkNotNull(callback); future.addListener(new CallbackListener(future, callback), executor); } /** See {@link #addCallback(ListenableFuture, FutureCallback, Executor)} for behavioral notes. */ - private static final class CallbackListener implements Runnable { + private static final class CallbackListener implements Runnable { final Future future; final FutureCallback callback; @@ -1039,13 +1120,14 @@ public void run() { return; } } - final V value; + V value; try { value = getDone(future); } catch (ExecutionException e) { callback.onFailure(e.getCause()); return; - } catch (RuntimeException | Error e) { + } catch (Throwable e) { + // Any Exception is either a RuntimeException or sneaky checked exception. callback.onFailure(e); return; } @@ -1079,7 +1161,8 @@ public String toString() { */ @CanIgnoreReturnValue // TODO(cpovirk): Consider calling getDone() in our own code. - public static V getDone(Future future) throws ExecutionException { + @ParametricNullness + public static V getDone(Future future) throws ExecutionException { /* * We throw IllegalStateException, since the call could succeed later. Perhaps we "should" throw * IllegalArgumentException, since the call could succeed with a different argument. Those @@ -1120,10 +1203,10 @@ public static V getDone(Future future) throws ExecutionException { * *

    Instances of {@code exceptionClass} are created by choosing an arbitrary public constructor * that accepts zero or more arguments, all of type {@code String} or {@code Throwable} - * (preferring constructors with at least one {@code String}) and calling the constructor via - * reflection. If the exception did not already have a cause, one is set by calling {@link - * Throwable#initCause(Throwable)} on it. If no such constructor exists, an {@code - * IllegalArgumentException} is thrown. + * (preferring constructors with at least one {@code String}, then preferring constructors with at + * least one {@code Throwable}) and calling the constructor via reflection. If the exception did + * not already have a cause, one is set by calling {@link Throwable#initCause(Throwable)} on it. + * If no such constructor exists, an {@code IllegalArgumentException} is thrown. * * @throws X if {@code get} throws any checked exception except for an {@code ExecutionException} * whose cause is not itself a checked exception @@ -1136,14 +1219,68 @@ public static V getDone(Future future) throws ExecutionException { * does not have a suitable constructor * @since 19.0 (in 10.0 as {@code get}) */ - @Beta @CanIgnoreReturnValue + @J2ktIncompatible @GwtIncompatible // reflection - public static V getChecked(Future future, Class exceptionClass) - throws X { + @ParametricNullness + public static V getChecked( + Future future, Class exceptionClass) throws X { return FuturesGetChecked.getChecked(future, exceptionClass); } + /** + * Returns the result of {@link Future#get(long, TimeUnit)}, converting most exceptions to a new + * instance of the given checked exception type. This reduces boilerplate for a common use of + * {@code Future} in which it is unnecessary to programmatically distinguish between exception + * types or to extract other information from the exception instance. + * + *

    Exceptions from {@code Future.get} are treated as follows: + * + *

      + *
    • Any {@link ExecutionException} has its cause wrapped in an {@code X} if the cause + * is a checked exception, an {@link UncheckedExecutionException} if the cause is a {@code + * RuntimeException}, or an {@link ExecutionError} if the cause is an {@code Error}. + *
    • Any {@link InterruptedException} is wrapped in an {@code X} (after restoring the + * interrupt). + *
    • Any {@link TimeoutException} is wrapped in an {@code X}. + *
    • Any {@link CancellationException} is propagated untouched, as is any other {@link + * RuntimeException} (though {@code get} implementations are discouraged from throwing such + * exceptions). + *
    + * + *

    The overall principle is to continue to treat every checked exception as a checked + * exception, every unchecked exception as an unchecked exception, and every error as an error. In + * addition, the cause of any {@code ExecutionException} is wrapped in order to ensure that the + * new stack trace matches that of the current thread. + * + *

    Instances of {@code exceptionClass} are created by choosing an arbitrary public constructor + * that accepts zero or more arguments, all of type {@code String} or {@code Throwable} + * (preferring constructors with at least one {@code String}, then preferring constructors with at + * least one {@code Throwable}) and calling the constructor via reflection. If the exception did + * not already have a cause, one is set by calling {@link Throwable#initCause(Throwable)} on it. + * If no such constructor exists, an {@code IllegalArgumentException} is thrown. + * + * @throws X if {@code get} throws any checked exception except for an {@code ExecutionException} + * whose cause is not itself a checked exception + * @throws UncheckedExecutionException if {@code get} throws an {@code ExecutionException} with a + * {@code RuntimeException} as its cause + * @throws ExecutionError if {@code get} throws an {@code ExecutionException} with an {@code + * Error} as its cause + * @throws CancellationException if {@code get} throws a {@code CancellationException} + * @throws IllegalArgumentException if {@code exceptionClass} extends {@code RuntimeException} or + * does not have a suitable constructor + * @since 33.4.0 (but since 28.0 in the JRE flavor) + */ + @CanIgnoreReturnValue + @J2ktIncompatible + @GwtIncompatible // reflection + @ParametricNullness + @IgnoreJRERequirement // Users will use this only if they're already using Duration. + public static V getChecked( + Future future, Class exceptionClass, Duration timeout) throws X { + return getChecked(future, exceptionClass, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + /** * Returns the result of {@link Future#get(long, TimeUnit)}, converting most exceptions to a new * instance of the given checked exception type. This reduces boilerplate for a common use of @@ -1187,11 +1324,12 @@ public static V getChecked(Future future, Class e * does not have a suitable constructor * @since 19.0 (in 10.0 as {@code get} and with different parameter order) */ - @Beta @CanIgnoreReturnValue + @J2ktIncompatible @GwtIncompatible // reflection @SuppressWarnings("GoodTime") // should accept a java.time.Duration - public static V getChecked( + @ParametricNullness + public static V getChecked( Future future, Class exceptionClass, long timeout, TimeUnit unit) throws X { return FuturesGetChecked.getChecked(future, exceptionClass, timeout, unit); } @@ -1231,26 +1369,22 @@ public static V getChecked( * @since 10.0 */ @CanIgnoreReturnValue - public static V getUnchecked(Future future) { + @ParametricNullness + public static V getUnchecked(Future future) { checkNotNull(future); try { return getUninterruptibly(future); - } catch (ExecutionException e) { - wrapAndThrowUnchecked(e.getCause()); - throw new AssertionError(); - } - } - - private static void wrapAndThrowUnchecked(Throwable cause) { - if (cause instanceof Error) { - throw new ExecutionError((Error) cause); + } catch (ExecutionException wrapper) { + if (wrapper.getCause() instanceof Error) { + throw new ExecutionError((Error) wrapper.getCause()); + } + /* + * It's an Exception. (Or it's a non-Error, non-Exception Throwable. From my survey of such + * classes, I believe that most users intended to extend Exception, so we'll treat it like an + * Exception.) + */ + throw new UncheckedExecutionException(wrapper.getCause()); } - /* - * It's an Exception. (Or it's a non-Error, non-Exception Throwable. From my survey of such - * classes, I believe that most users intended to extend Exception, so we'll treat it like an - * Exception.) - */ - throw new UncheckedExecutionException(cause); } /* diff --git a/android/guava/src/com/google/common/util/concurrent/FuturesGetChecked.java b/android/guava/src/com/google/common/util/concurrent/FuturesGetChecked.java index a5e9d328250c..bac4a9c3d434 100644 --- a/android/guava/src/com/google/common/util/concurrent/FuturesGetChecked.java +++ b/android/guava/src/com/google/common/util/concurrent/FuturesGetChecked.java @@ -19,11 +19,10 @@ import static java.util.Arrays.asList; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; import com.google.common.collect.Ordering; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.j2objc.annotations.J2ObjCIncompatible; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -35,20 +34,24 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** Static methods used to implement {@link Futures#getChecked(Future, Class)}. */ +@J2ktIncompatible @GwtIncompatible final class FuturesGetChecked { @CanIgnoreReturnValue - static V getChecked(Future future, Class exceptionClass) throws X { + @ParametricNullness + static V getChecked( + Future future, Class exceptionClass) throws X { return getChecked(bestGetCheckedTypeValidator(), future, exceptionClass); } /** Implementation of {@link Futures#getChecked(Future, Class)}. */ @CanIgnoreReturnValue @VisibleForTesting - static V getChecked( + @ParametricNullness + static V getChecked( GetCheckedTypeValidator validator, Future future, Class exceptionClass) throws X { validator.validateClass(exceptionClass); try { @@ -64,7 +67,8 @@ static V getChecked( /** Implementation of {@link Futures#getChecked(Future, Class, long, TimeUnit)}. */ @CanIgnoreReturnValue - static V getChecked( + @ParametricNullness + static V getChecked( Future future, Class exceptionClass, long timeout, TimeUnit unit) throws X { // TODO(cpovirk): benchmark a version of this method that accepts a GetCheckedTypeValidator bestGetCheckedTypeValidator().validateClass(exceptionClass); @@ -95,49 +99,15 @@ static GetCheckedTypeValidator weakSetValidator() { return GetCheckedTypeValidatorHolder.WeakSetValidator.INSTANCE; } - @J2ObjCIncompatible // ClassValue - @VisibleForTesting - static GetCheckedTypeValidator classValueValidator() { - return GetCheckedTypeValidatorHolder.ClassValueValidator.INSTANCE; - } - /** * Provides a check of whether an exception type is valid for use with {@link * FuturesGetChecked#getChecked(Future, Class)}, possibly using caching. * *

    Uses reflection to gracefully fall back to when certain implementations aren't available. */ - @VisibleForTesting - static class GetCheckedTypeValidatorHolder { - static final String CLASS_VALUE_VALIDATOR_NAME = - GetCheckedTypeValidatorHolder.class.getName() + "$ClassValueValidator"; - + private static final class GetCheckedTypeValidatorHolder { static final GetCheckedTypeValidator BEST_VALIDATOR = getBestValidator(); - @IgnoreJRERequirement // getChecked falls back to another implementation if necessary - @J2ObjCIncompatible // ClassValue - enum ClassValueValidator implements GetCheckedTypeValidator { - INSTANCE; - - /* - * Static final fields are presumed to be fastest, based on our experience with - * UnsignedBytesBenchmark. TODO(cpovirk): benchmark this - */ - private static final ClassValue isValidClass = - new ClassValue() { - @Override - protected Boolean computeValue(Class type) { - checkExceptionClassValidity(type.asSubclass(Exception.class)); - return true; - } - }; - - @Override - public void validateClass(Class exceptionClass) { - isValidClass.get(exceptionClass); // throws if invalid; returns safely (and caches) if valid - } - } - enum WeakSetValidator implements GetCheckedTypeValidator { INSTANCE; @@ -184,12 +154,7 @@ public void validateClass(Class exceptionClass) { * unable to do so. */ static GetCheckedTypeValidator getBestValidator() { - try { - Class theClass = Class.forName(CLASS_VALUE_VALIDATOR_NAME); - return (GetCheckedTypeValidator) theClass.getEnumConstants()[0]; - } catch (Throwable t) { // ensure we really catch *everything* - return weakSetValidator(); - } + return weakSetValidator(); } } @@ -215,7 +180,7 @@ private static boolean hasConstructorUsableByGetChecked( try { Exception unused = newWithCause(exceptionClass, new Exception()); return true; - } catch (Exception e) { + } catch (Throwable t) { // sneaky checked exception return false; } } @@ -224,8 +189,8 @@ private static X newWithCause(Class exceptionClass, Thr // getConstructors() guarantees this as long as we don't modify the array. @SuppressWarnings({"unchecked", "rawtypes"}) List> constructors = (List) Arrays.asList(exceptionClass.getConstructors()); - for (Constructor constructor : preferringStrings(constructors)) { - @NullableDecl X instance = newFromConstructor(constructor, cause); + for (Constructor constructor : preferringStringsThenThrowables(constructors)) { + X instance = newFromConstructor(constructor, cause); if (instance != null) { if (instance.getCause() == null) { instance.initCause(cause); @@ -240,24 +205,24 @@ private static X newWithCause(Class exceptionClass, Thr cause); } - private static List> preferringStrings( + private static List> preferringStringsThenThrowables( List> constructors) { - return WITH_STRING_PARAM_FIRST.sortedCopy(constructors); + return WITH_STRING_PARAM_THEN_WITH_THROWABLE_PARAM.sortedCopy(constructors); } - private static final Ordering> WITH_STRING_PARAM_FIRST = + // TODO: b/296487962 - Consider defining a total order over constructors. + private static final Ordering>> ORDERING_BY_CONSTRUCTOR_PARAMETER_LIST = Ordering.natural() - .onResultOf( - new Function, Boolean>() { - @Override - public Boolean apply(Constructor input) { - return asList(input.getParameterTypes()).contains(String.class); - } - }) + .onResultOf((List> params) -> params.contains(String.class)) + .compound( + Ordering.natural() + .onResultOf((List> params) -> params.contains(Throwable.class))) .reverse(); + private static final Ordering> WITH_STRING_PARAM_THEN_WITH_THROWABLE_PARAM = + ORDERING_BY_CONSTRUCTOR_PARAMETER_LIST.onResultOf( + constructor -> asList(constructor.getParameterTypes())); - @NullableDecl - private static X newFromConstructor(Constructor constructor, Throwable cause) { + private static @Nullable X newFromConstructor(Constructor constructor, Throwable cause) { Class[] paramTypes = constructor.getParameterTypes(); Object[] params = new Object[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { diff --git a/android/guava/src/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java b/android/guava/src/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java index e8acf625af72..b434056c6099 100644 --- a/android/guava/src/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java +++ b/android/guava/src/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java @@ -15,14 +15,18 @@ package com.google.common.util.concurrent; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.J2ktIncompatible; +import org.jspecify.annotations.Nullable; /** * Hidden superclass of {@link FluentFuture} that provides us a place to declare special GWT * versions of the {@link FluentFuture#catching(Class, com.google.common.base.Function) * FluentFuture.catching} family of methods. Those versions have slightly different signatures. */ -@GwtCompatible(emulated = true) -abstract class GwtFluentFutureCatchingSpecialization extends AbstractFuture { +@GwtCompatible +@J2ktIncompatible // Super-sourced +abstract class GwtFluentFutureCatchingSpecialization + extends AbstractFuture { /* * This server copy of the class is empty. The corresponding GWT copy contains alternative * versions of catching() and catchingAsync() with slightly different signatures from the ones diff --git a/android/guava/src/com/google/common/util/concurrent/GwtFuturesCatchingSpecialization.java b/android/guava/src/com/google/common/util/concurrent/GwtFuturesCatchingSpecialization.java index 4626ce949349..8ec07465a84e 100644 --- a/android/guava/src/com/google/common/util/concurrent/GwtFuturesCatchingSpecialization.java +++ b/android/guava/src/com/google/common/util/concurrent/GwtFuturesCatchingSpecialization.java @@ -15,6 +15,7 @@ package com.google.common.util.concurrent; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.J2ktIncompatible; /** * Hidden superclass of {@link Futures} that provides us a place to declare special GWT versions of @@ -22,7 +23,8 @@ * java.util.concurrent.Executor) Futures.catching} family of methods. Those versions have slightly * different signatures. */ -@GwtCompatible(emulated = true) +@GwtCompatible +@J2ktIncompatible // Super-sourced abstract class GwtFuturesCatchingSpecialization { /* * This server copy of the class is empty. The corresponding GWT copy contains alternative diff --git a/android/guava/src/com/google/common/util/concurrent/IgnoreJRERequirement.java b/android/guava/src/com/google/common/util/concurrent/IgnoreJRERequirement.java index eaaa94f09097..67d2144473d1 100644 --- a/android/guava/src/com/google/common/util/concurrent/IgnoreJRERequirement.java +++ b/android/guava/src/com/google/common/util/concurrent/IgnoreJRERequirement.java @@ -15,10 +15,16 @@ package com.google.common.util.concurrent; import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; import java.lang.annotation.Target; -@Target({METHOD, CONSTRUCTOR, TYPE}) +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

    Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE, FIELD}) @interface IgnoreJRERequirement {} diff --git a/android/guava/src/com/google/common/util/concurrent/ImmediateFuture.java b/android/guava/src/com/google/common/util/concurrent/ImmediateFuture.java index a5d4e38be15a..fd1aaed0f24a 100644 --- a/android/guava/src/com/google/common/util/concurrent/ImmediateFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/ImmediateFuture.java @@ -22,37 +22,40 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; /** Implementation of {@link Futures#immediateFuture}. */ @GwtCompatible // TODO(cpovirk): Make this final (but that may break Mockito spy calls). -@SuppressWarnings("ShouldNotSubclass") -class ImmediateFuture implements ListenableFuture { - static final ListenableFuture NULL = new ImmediateFuture<>(null); +class ImmediateFuture implements ListenableFuture { + static final ListenableFuture NULL = new ImmediateFuture<@Nullable Object>(null); - private static final Logger log = Logger.getLogger(ImmediateFuture.class.getName()); + private static final LazyLogger log = new LazyLogger(ImmediateFuture.class); - @NullableDecl private final V value; + @ParametricNullness private final V value; - ImmediateFuture(@NullableDecl V value) { + ImmediateFuture(@ParametricNullness V value) { this.value = value; } @Override + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception public void addListener(Runnable listener, Executor executor) { checkNotNull(listener, "Runnable was null."); checkNotNull(executor, "Executor was null."); try { executor.execute(listener); - } catch (RuntimeException e) { + } catch (Exception e) { // sneaky checked exception // ListenableFuture's contract is that it will not throw unchecked exceptions, so log the bad // runnable and/or executor and swallow it. - log.log( - Level.SEVERE, - "RuntimeException while executing runnable " + listener + " with executor " + executor, - e); + log.get() + .log( + Level.SEVERE, + "RuntimeException while executing runnable " + + listener + + " with executor " + + executor, + e); } } @@ -63,11 +66,13 @@ public boolean cancel(boolean mayInterruptIfRunning) { // TODO(lukes): Consider throwing InterruptedException when appropriate. @Override + @ParametricNullness public V get() { return value; } @Override + @ParametricNullness public V get(long timeout, TimeUnit unit) throws ExecutionException { checkNotNull(unit); return get(); @@ -89,13 +94,16 @@ public String toString() { return super.toString() + "[status=SUCCESS, result=[" + value + "]]"; } - static final class ImmediateFailedFuture extends TrustedFuture { + static final class ImmediateFailedFuture extends TrustedFuture { ImmediateFailedFuture(Throwable thrown) { setException(thrown); } } - static final class ImmediateCancelledFuture extends TrustedFuture { + static final class ImmediateCancelledFuture extends TrustedFuture { + static final @Nullable ImmediateCancelledFuture INSTANCE = + AbstractFuture.GENERATE_CANCELLATION_CAUSES ? null : new ImmediateCancelledFuture<>(); + ImmediateCancelledFuture() { cancel(false); } diff --git a/android/guava/src/com/google/common/util/concurrent/Internal.java b/android/guava/src/com/google/common/util/concurrent/Internal.java new file mode 100644 index 000000000000..21f62d61c563 --- /dev/null +++ b/android/guava/src/com/google/common/util/concurrent/Internal.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import java.time.Duration; + +/** This class is for {@code com.google.common.util.concurrent} use only! */ +@J2ktIncompatible +@GwtIncompatible // java.time.Duration +@IgnoreJRERequirement // We use this method only from within APIs that require a Duration. +final class Internal { + + /** + * Returns the number of nanoseconds of the given duration without throwing or overflowing. + * + *

    Instead of throwing {@link ArithmeticException}, this method silently saturates to either + * {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE}. This behavior can be useful when decomposing + * a duration in order to call a legacy API which requires a {@code long, TimeUnit} pair. + */ + static long toNanosSaturated(Duration duration) { + // Using a try/catch seems lazy, but the catch block will rarely get invoked (except for + // durations longer than approximately +/- 292 years). + try { + return duration.toNanos(); + } catch (ArithmeticException tooBig) { + return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE; + } + } + + private Internal() {} +} diff --git a/android/guava/src/com/google/common/util/concurrent/InterruptibleTask.java b/android/guava/src/com/google/common/util/concurrent/InterruptibleTask.java index efe43db9f04d..bc17e8c34a07 100644 --- a/android/guava/src/com/google/common/util/concurrent/InterruptibleTask.java +++ b/android/guava/src/com/google/common/util/concurrent/InterruptibleTask.java @@ -14,26 +14,29 @@ package com.google.common.util.concurrent; +import static com.google.common.util.concurrent.NullnessCasts.uncheckedCastNullableTToT; +import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; + import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.VisibleForTesting; import com.google.j2objc.annotations.ReflectionSupport; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.AbstractOwnableSynchronizer; import java.util.concurrent.locks.LockSupport; -import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.jspecify.annotations.Nullable; -@SuppressWarnings("ShouldNotSubclass") -@GwtCompatible(emulated = true) +@GwtCompatible @ReflectionSupport(value = ReflectionSupport.Level.FULL) // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. // Since this class only needs CAS on one field, we can avoid this bug by extending AtomicReference // instead of using an AtomicReferenceFieldUpdater. This reference stores Thread instances // and DONE/INTERRUPTED - they have a common ancestor of Runnable. -abstract class InterruptibleTask extends AtomicReference implements Runnable { +abstract class InterruptibleTask + extends AtomicReference<@Nullable Runnable> implements Runnable { static { // Prevent rare disastrous classloading in first call to LockSupport.park. - // See: https://bugs.openjdk.java.net/browse/JDK-8074773 + // See: https://bugs.openjdk.org/browse/JDK-8074773 @SuppressWarnings("unused") Class ensureLoaded = LockSupport.class; } @@ -42,6 +45,7 @@ private static final class DoNothingRunnable implements Runnable { @Override public void run() {} } + // The thread executing the task publishes itself to the superclass' reference and the thread // interrupting sets DONE when it has finished interrupting. private static final Runnable DONE = new DoNothingRunnable(); @@ -49,7 +53,6 @@ public void run() {} // Why 1000? WHY NOT! private static final int MAX_BUSY_WAIT_SPINS = 1000; - @SuppressWarnings("ThreadPriorityCheck") // The cow told me to @Override public final void run() { /* @@ -71,74 +74,92 @@ public final void run() { result = runInterruptibly(); } } catch (Throwable t) { + restoreInterruptIfIsInterruptedException(t); error = t; } finally { // Attempt to set the task as done so that further attempts to interrupt will fail. if (!compareAndSet(currentThread, DONE)) { - // If we were interrupted, it is possible that the interrupted bit hasn't been set yet. Wait - // for the interrupting thread to set DONE. See interruptTask(). - // We want to wait so that we don't interrupt the _next_ thing run on the thread. - // Note: We don't reset the interrupted bit, just wait for it to be set. - // If this is a thread pool thread, the thread pool will reset it for us. Otherwise, the - // interrupted bit may have been intended for something else, so don't clear it. - boolean restoreInterruptedBit = false; - int spinCount = 0; - // Interrupting Cow Says: - // ______ - // < Spin > - // ------ - // \ ^__^ - // \ (oo)\_______ - // (__)\ )\/\ - // ||----w | - // || || - Runnable state = get(); - Blocker blocker = null; - while (state instanceof Blocker || state == PARKED) { - if (state instanceof Blocker) { - blocker = (Blocker) state; - } - spinCount++; - if (spinCount > MAX_BUSY_WAIT_SPINS) { - // If we have spun a lot just park ourselves. - // This will save CPU while we wait for a slow interrupting thread. In theory - // interruptTask() should be very fast but due to InterruptibleChannel and - // JavaLangAccess.blockedOn(Thread, Interruptible), it isn't predictable what work might - // be done. (e.g. close a file and flush buffers to disk). To protect ourselves from - // this we park ourselves and tell our interrupter that we did so. - if (state == PARKED || compareAndSet(state, PARKED)) { - // Interrupting Cow Says: - // ______ - // < Park > - // ------ - // \ ^__^ - // \ (oo)\_______ - // (__)\ )\/\ - // ||----w | - // || || - // We need to clear the interrupted bit prior to calling park and maintain it in case - // we wake up spuriously. - restoreInterruptedBit = Thread.interrupted() || restoreInterruptedBit; - LockSupport.park(blocker); - } - } else { - Thread.yield(); - } - state = get(); - } - if (restoreInterruptedBit) { - currentThread.interrupt(); + waitForInterrupt(currentThread); + } + if (run) { + if (error == null) { + // The cast is safe because of the `run` and `error` checks. + afterRanInterruptiblySuccess(uncheckedCastNullableTToT(result)); + } else { + afterRanInterruptiblyFailure(error); } + } + } + } + + @SuppressWarnings({ + "Interruption", // We are restoring an interrupt on this thread. + "ThreadPriorityCheck", // TODO: b/175898629 - Consider onSpinWait. + }) + private void waitForInterrupt(Thread currentThread) { + /* + * If someone called cancel(true), it is possible that the interrupted bit hasn't been set yet. + * Wait for the interrupting thread to set DONE. (See interruptTask().) We want to wait so that + * the interrupting thread doesn't interrupt the _next_ thing to run on this thread. + * + * Note: We don't reset the interrupted bit, just wait for it to be set. If this is a thread + * pool thread, the thread pool will reset it for us. Otherwise, the interrupted bit may have + * been intended for something else, so don't clear it. + */ + boolean restoreInterruptedBit = false; + int spinCount = 0; + // Interrupting Cow Says: + // ______ + // < Spin > + // ------ + // \ ^__^ + // \ (oo)\_______ + // (__)\ )\/\ + // ||----w | + // || || + Runnable state = get(); + Blocker blocker = null; + while (state instanceof Blocker || state == PARKED) { + if (state instanceof Blocker) { + blocker = (Blocker) state; + } + spinCount++; + if (spinCount > MAX_BUSY_WAIT_SPINS) { /* - * TODO(cpovirk): Clear interrupt status here? We currently don't, which means that an - * interrupt before, during, or after runInterruptibly() (unless it produced an - * InterruptedException caught above) can linger and affect listeners. + * If we have spun a lot, just park ourselves. This will save CPU while we wait for a slow + * interrupting thread. In theory, interruptTask() should be very fast, but due to + * InterruptibleChannel and JavaLangAccess.blockedOn(Thread, Interruptible), it isn't + * predictable what work might be done. (e.g., close a file and flush buffers to disk). To + * protect ourselves from this, we park ourselves and tell our interrupter that we did so. */ + if (state == PARKED || compareAndSet(state, PARKED)) { + // Interrupting Cow Says: + // ______ + // < Park > + // ------ + // \ ^__^ + // \ (oo)\_______ + // (__)\ )\/\ + // ||----w | + // || || + // We need to clear the interrupted bit prior to calling park and maintain it in case we + // wake up spuriously. + restoreInterruptedBit = Thread.interrupted() || restoreInterruptedBit; + LockSupport.park(blocker); + } + } else { + Thread.yield(); } - if (run) { - afterRanInterruptibly(result, error); - } + state = get(); } + if (restoreInterruptedBit) { + currentThread.interrupt(); + } + /* + * TODO(cpovirk): Clear interrupt status here? We currently don't, which means that an interrupt + * before, during, or after runInterruptibly() (unless it produced an InterruptedException + * caught above) can linger and affect listeners. + */ } /** @@ -151,18 +172,26 @@ public final void run() { * Do interruptible work here - do not complete Futures here, as their listeners could be * interrupted. */ + @ParametricNullness abstract T runInterruptibly() throws Exception; /** * Any interruption that happens as a result of calling interruptTask will arrive before this * method is called. Complete Futures here. */ - abstract void afterRanInterruptibly(@NullableDecl T result, @NullableDecl Throwable error); + abstract void afterRanInterruptiblySuccess(@ParametricNullness T result); + + /** + * Any interruption that happens as a result of calling interruptTask will arrive before this + * method is called. Complete Futures here. + */ + abstract void afterRanInterruptiblyFailure(Throwable error); /** * Interrupts the running task. Because this internally calls {@link Thread#interrupt()} which can * in turn invoke arbitrary code it is not safe to call while holding a lock. */ + @SuppressWarnings("Interruption") // We are implementing a user-requested interrupt. final void interruptTask() { // Since the Thread is replaced by DONE before run() invokes listeners or returns, if we succeed // in this CAS, there's no risk of interrupting the wrong thread or interrupting a thread that @@ -174,7 +203,7 @@ final void interruptTask() { if (compareAndSet(currentRunner, blocker)) { // Thread.interrupt can throw arbitrary exceptions due to the nio InterruptibleChannel API // This will make sure that tasks don't get stuck busy waiting. - // Some of this is fixed in jdk11 (see https://bugs.openjdk.java.net/browse/JDK-8198692) but + // Some of this is fixed in jdk11 (see https://bugs.openjdk.org/browse/JDK-8198692) but // not all. See the test cases for examples on how this can happen. try { ((Thread) currentRunner).interrupt(); @@ -208,6 +237,11 @@ private void setOwner(Thread thread) { super.setExclusiveOwnerThread(thread); } + @VisibleForTesting + @Nullable Thread getOwner() { + return super.getExclusiveOwnerThread(); + } + @Override public String toString() { return task.toString(); @@ -217,7 +251,7 @@ public String toString() { @Override public final String toString() { Runnable state = get(); - final String result; + String result; if (state == DONE) { result = "running=[DONE]"; } else if (state instanceof Blocker) { diff --git a/android/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java b/android/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java index 55d64da20c21..9e744e7ca293 100644 --- a/android/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java +++ b/android/guava/src/com/google/common/util/concurrent/JdkFutureAdapters.java @@ -16,24 +16,28 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static java.util.concurrent.Executors.newCachedThreadPool; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; +import org.jspecify.annotations.Nullable; /** * Utilities necessary for working with libraries that supply plain {@link Future} instances. Note * that, whenever possible, it is strongly preferred to modify those libraries to return {@code * ListenableFuture} directly. * + *

    For interoperability between {@code ListenableFuture} and {@code CompletableFuture}, + * consider Future Converter. + * * @author Sven Mawson * @since 10.0 (replacing {@code Futures.makeListenable}, which existed in 1.0) */ -@Beta +@J2ktIncompatible @GwtIncompatible public final class JdkFutureAdapters { /** @@ -49,11 +53,12 @@ public final class JdkFutureAdapters { * ListenableFutureTask}, {@link AbstractFuture}, and other utilities over creating plain {@code * Future} instances to be upgraded to {@code ListenableFuture} after the fact. */ - public static ListenableFuture listenInPoolThread(Future future) { + public static ListenableFuture listenInPoolThread( + Future future) { if (future instanceof ListenableFuture) { return (ListenableFuture) future; } - return new ListenableFutureAdapter(future); + return new ListenableFutureAdapter<>(future); } /** @@ -76,12 +81,13 @@ public static ListenableFuture listenInPoolThread(Future future) { * * @since 12.0 */ - public static ListenableFuture listenInPoolThread(Future future, Executor executor) { + public static ListenableFuture listenInPoolThread( + Future future, Executor executor) { checkNotNull(executor); if (future instanceof ListenableFuture) { return (ListenableFuture) future; } - return new ListenableFutureAdapter(future, executor); + return new ListenableFutureAdapter<>(future, executor); } /** @@ -93,17 +99,15 @@ public static ListenableFuture listenInPoolThread(Future future, Execu *

    If the delegate future is interrupted or throws an unexpected unchecked exception, the * listeners will not be invoked. */ - @SuppressWarnings("ShouldNotSubclass") - private static class ListenableFutureAdapter extends ForwardingFuture - implements ListenableFuture { + private static final class ListenableFutureAdapter + extends ForwardingFuture implements ListenableFuture { private static final ThreadFactory threadFactory = new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("ListenableFutureAdapter-thread-%d") .build(); - private static final Executor defaultAdapterExecutor = - Executors.newCachedThreadPool(threadFactory); + private static final Executor defaultAdapterExecutor = newCachedThreadPool(threadFactory); private final Executor adapterExecutor; @@ -147,22 +151,21 @@ public void addListener(Runnable listener, Executor exec) { // TODO(lukes): handle RejectedExecutionException adapterExecutor.execute( - new Runnable() { - @Override - public void run() { - try { - /* - * Threads from our private pool are never interrupted. Threads from a - * user-supplied executor might be, but... what can we do? This is another reason - * to return a proper ListenableFuture instead of using listenInPoolThread. - */ - getUninterruptibly(delegate); - } catch (Throwable e) { - // ExecutionException / CancellationException / RuntimeException / Error - // The task is presumably done, run the listeners. - } - executionList.execute(); + () -> { + try { + /* + * Threads from our private pool are never interrupted. Threads from a + * user-supplied executor might be, but... what can we do? This is another reason + * to return a proper ListenableFuture instead of using listenInPoolThread. + */ + getUninterruptibly(delegate); + } catch (Throwable t) { + // (including CancellationException and sneaky checked exception) + // The task is presumably done, run the listeners. + // TODO(cpovirk): Do *something* in case of Error (and maybe + // non-CancellationException, non-ExecutionException exceptions)? } + executionList.execute(); }); } } diff --git a/android/guava/src/com/google/common/util/concurrent/LazyLogger.java b/android/guava/src/com/google/common/util/concurrent/LazyLogger.java new file mode 100644 index 000000000000..0b79ff43d068 --- /dev/null +++ b/android/guava/src/com/google/common/util/concurrent/LazyLogger.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; +import java.util.logging.Logger; +import org.jspecify.annotations.Nullable; + +/** A holder for a {@link Logger} that is initialized only when requested. */ +@GwtCompatible +final class LazyLogger { + private final Object lock = new Object(); + + private final String loggerName; + private volatile @Nullable Logger logger; + + LazyLogger(Class ownerOfLogger) { + this.loggerName = ownerOfLogger.getName(); + } + + Logger get() { + /* + * We use double-checked locking. We could the try racy single-check idiom, but that would + * depend on Logger to not contain mutable state. + * + * We could use Suppliers.memoizingSupplier here, but I micro-optimized to this implementation + * to avoid the extra class for the lambda (and maybe more for memoizingSupplier itself) and the + * indirection. + * + * One thing to *avoid* is a change to make each Logger user use memoizingSupplier directly: + * That may introduce an extra class for each lambda (currently a dozen). + */ + Logger local = logger; + if (local != null) { + return local; + } + synchronized (lock) { + local = logger; + if (local != null) { + return local; + } + return logger = Logger.getLogger(loggerName); + } + } +} diff --git a/android/guava/src/com/google/common/util/concurrent/ListenableFuture.java b/android/guava/src/com/google/common/util/concurrent/ListenableFuture.java index 3d76c6a72bb2..8b78b6b727b6 100644 --- a/android/guava/src/com/google/common/util/concurrent/ListenableFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/ListenableFuture.java @@ -18,6 +18,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * A {@link Future} that accepts completion listeners. Each listener has an associated executor, and @@ -35,10 +37,11 @@ * *

    The main purpose of {@code ListenableFuture} is to help you chain together a graph of * asynchronous operations. You can chain them together manually with calls to methods like {@link - * Futures#transform(ListenableFuture, com.google.common.base.Function, Executor) - * Futures.transform}, but you will often find it easier to use a framework. Frameworks automate the - * process, often adding features like monitoring, debugging, and cancellation. Examples of - * frameworks include: + * Futures#transform(ListenableFuture, com.google.common.base.Function, Executor) Futures.transform} + * (or {@link FluentFuture#transform(com.google.common.base.Function, Executor) + * FluentFuture.transform}), but you will often find it easier to use a framework. Frameworks + * automate the process, often adding features like monitoring, debugging, and cancellation. + * Examples of frameworks include: * *